[
  {
    "path": ".Rbuildignore",
    "content": "^chromote\\.Rproj$\n^\\.Rproj\\.user$\n^temp$\n^chromote\\.sublime-project$\n^\\.github$\n^_pkgdown\\.yml$\n^docs$\n^pkgdown$\n^README\\.Rmd$\n^sidebar.png$\n^revdep$\n^cran-comments\\.md$\n^CRAN-SUBMISSION$\n^_dev$\n^\\.vscode$\n^[\\.]?air\\.toml$\n^LICENSE\\.md$\n"
  },
  {
    "path": ".github/.gitignore",
    "content": "*.html\n"
  },
  {
    "path": ".github/workflows/R-CMD-check.yaml",
    "content": "# Workflow derived from https://github.com/rstudio/shiny-workflows\n#\n# NOTE: This Shiny team GHA workflow is overkill for most R packages.\n# For most R packages it is better to use https://github.com/r-lib/actions\non:\n  push:\n    branches: [main, rc-**]\n  pull_request:\n    branches: [main]\n  schedule:\n    - cron: '0 8 * * 1' # every monday\n\nname: Package checks\n\njobs:\n  website:\n    uses: rstudio/shiny-workflows/.github/workflows/website.yaml@v1\n  routine:\n    uses: rstudio/shiny-workflows/.github/workflows/routine.yaml@v1\n    with:\n      format-r-code: true\n  R-CMD-check:\n    uses: rstudio/shiny-workflows/.github/workflows/R-CMD-check.yaml@v1\n"
  },
  {
    "path": ".gitignore",
    "content": ".Rhistory\n.RData\n.Rproj.user\ntemp\ndocs\nCRAN-SUBMISSION\ninst/doc\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n    \"recommendations\": [\n        \"Posit.air-vscode\"\n    ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n    \"[r]\": {\n        \"editor.formatOnSave\": true,\n        \"editor.defaultFormatter\": \"Posit.air-vscode\"\n    }\n}\n"
  },
  {
    "path": "DESCRIPTION",
    "content": "Package: chromote\nTitle: Headless Chrome Web Browser Interface\nVersion: 0.5.1.9000\nAuthors@R: c(\n    person(\"Garrick\", \"Aden-Buie\", , \"garrick@posit.co\", role = c(\"aut\", \"cre\"),\n           comment = c(ORCID = \"0000-0002-7111-0077\")),\n    person(\"Winston\", \"Chang\", , \"winston@posit.co\", role = \"aut\"),\n    person(\"Barret\", \"Schloerke\", , \"barret@posit.co\", role = \"aut\",\n           comment = c(ORCID = \"0000-0001-9986-114X\")),\n    person(\"Posit Software, PBC\", role = c(\"cph\", \"fnd\"), comment = c(ROR = \"03wc8by49\"))\n  )\nDescription: An implementation of the 'Chrome DevTools Protocol', for\n    controlling a headless Chrome web browser.\nLicense: MIT + file LICENSE\nURL: https://rstudio.github.io/chromote/,\n    https://github.com/rstudio/chromote\nBugReports: https://github.com/rstudio/chromote/issues\nImports:\n    cli,\n    curl,\n    fastmap,\n    jsonlite,\n    later (>= 1.1.0),\n    magrittr,\n    processx,\n    promises (>= 1.1.1),\n    R6,\n    rlang (>= 1.1.0),\n    utils,\n    websocket (>= 1.2.0),\n    withr,\n    zip\nSuggests:\n    knitr,\n    rmarkdown,\n    showimage,\n    testthat (>= 3.0.0)\nVignetteBuilder: \n    knitr\nConfig/Needs/website: r-lib/pkgdown, rstudio/bslib\nConfig/testthat/edition: 3\nConfig/testthat/parallel: FALSE\nConfig/testthat/start-first: chromote_session\nEncoding: UTF-8\nLanguage: en-US\nRoxygen: list(markdown = TRUE)\nRoxygenNote: 7.3.2\nSystemRequirements: Google Chrome or other Chromium-based browser.\n    chromium: chromium (rpm) or chromium-browser (deb)\n"
  },
  {
    "path": "LICENSE",
    "content": "YEAR: 2025\nCOPYRIGHT HOLDER: chromote authors\n"
  },
  {
    "path": "LICENSE.md",
    "content": "# MIT License\n\nCopyright (c) 2025 chromote authors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "NAMESPACE",
    "content": "# Generated by roxygen2: do not edit by hand\n\nS3method(print,chromote_info)\nexport(\"%...!%\")\nexport(\"%...>%\")\nexport(\"%...T!%\")\nexport(\"%...T>%\")\nexport(\"%>%\")\nexport(\"%T>%\")\nexport(Browser)\nexport(Chrome)\nexport(ChromeRemote)\nexport(Chromote)\nexport(ChromoteSession)\nexport(catch)\nexport(chrome_versions_add)\nexport(chrome_versions_list)\nexport(chrome_versions_path)\nexport(chrome_versions_path_cache)\nexport(chrome_versions_remove)\nexport(chromote_info)\nexport(default_chrome_args)\nexport(default_chromote_object)\nexport(finally)\nexport(find_chrome)\nexport(get_chrome_args)\nexport(has_default_chromote_object)\nexport(local_chrome_version)\nexport(local_chromote_chrome)\nexport(promise)\nexport(set_chrome_args)\nexport(set_default_chromote_object)\nexport(then)\nexport(with_chrome_version)\nexport(with_chromote_chrome)\nimport(later)\nimport(promises)\nimport(rlang)\nimportFrom(R6,R6Class)\nimportFrom(fastmap,fastmap)\nimportFrom(jsonlite,fromJSON)\nimportFrom(jsonlite,toJSON)\nimportFrom(magrittr,\"%>%\")\nimportFrom(magrittr,\"%T>%\")\nimportFrom(processx,process)\nimportFrom(promises,\"%...!%\")\nimportFrom(promises,\"%...>%\")\nimportFrom(promises,\"%...T!%\")\nimportFrom(promises,\"%...T>%\")\nimportFrom(promises,catch)\nimportFrom(promises,finally)\nimportFrom(promises,promise)\nimportFrom(promises,then)\nimportFrom(websocket,WebSocket)\n"
  },
  {
    "path": "NEWS.md",
    "content": "# chromote (development version)\n\n# chromote 0.5.1\n\n## New features\n\n* `ChromoteSession` gets a new helper method, `$go_to()`. This is an easier way of reliably waiting for a page load, instead of using `Page$loadEventFired()` and `Page$navigate()` together. (#221)\n\n* `ChromoteSession$view()` now accommodates the new DevTools Frontend URL used by Chrome v135 and later (#225, #226).\n\n# chromote 0.5.0\n\n## New features\n\n* chromote now includes experimental features to download versioned binaries of Chrome and `chrome-headless-shell` for Mac (x64 or arm64), Windows (32- or 64-bit) or Linux (x86-64) from the [Chrome for Testing](https://googlechromelabs.github.io/chrome-for-testing/) service. (#198)\n  * Use `with_chrome_version()` or `local_chrome_version()` to temporarily switch to a specific version of Chrome. The appropriate binary will be downloaded automatically if not yet available locally. \n  * Use `chrome_versions_list()` to list installed or available versions of Chrome. \n  * Or use `chrome_versions_add()` and `chrome_versions_remove()` to manually add or remove a specific version of Chrome from chromote's cache.\n\n* `ChromoteSession` gains two new helper methods: `$set_viewport_size()` and `$get_viewport_size()`. These methods allow you to change the viewport size – effectively the virtual window size for a page – or to get the current viewport size. If you previously relied on `$Emulation$setVisibleSize()` (now a deprecated method in the Chrome DevTools Protocol), `$set_viewport_size()` is a good replacement as it uses [Emulation.setDeviceMetricsOverride](https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setDeviceMetricsOverride) instead. (#206)\n\n## Improvements\n\n* `ChromoteSession$new()` gains a `mobile` argument that can be used to set the device emulation in that session to emulate a mobile browser. The default is `mobile = FALSE`, which matches previous behavior. (#205)\n\n* `Chromote` and `ChromoteSesssion` gain an `$auto_events_enable_args()` method that sets that arguments used by chromote's auto-events feature when calling the `enable` command for a domain, e.g. `Fetch.enable`. (#208)\n\n* The `$view()` method of a `ChromoteSession` will now detect when `chrome-headless-shell` is being used and will use the system browser (via `utils::browseURL()`) rather than the Chrome instance attached to chromote. (#214)\n\n* chromote now has a hex sticker! Thank you to @davidrsch for the inspiration. (#216)\n\n## Bug fixes\n\n* `ChromoteSession$new()` now sets `width` and `height` using [Emulation.setDeviceMetricsOverride](https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setDeviceMetricsOverride), which works for all Chrome binaries and versions. This fixes an issue with `width` and `height` being ignored for Chrome versions 128-133. (#205)\n\n* Fixed a bug in `chromote_info()` on Windows with Powershell when no version info is returned. (#207)\n\n* `Chromote` and `ChromoteSession` once again correctly handles connections to remote Chrome browsers via `ChromeRemote`. Calling `$close()` on a `Chromote` object connected to a remote browser no longer attempts to close the browser, and will now simply close the websocket connection to the browser. For local process, the `Chromote$close()` gains a `wait` argument that sets the number of seconds to wait for Chrome to gracefully shut down before chromote closes the process. (#212)\n\n# chromote 0.4.0\n\n* Chrome v132 and later no longer support [old headless mode](https://developer.chrome.com/blog/removing-headless-old-from-chrome). As such, `chromote` no longer defaults to using `--headless=old` and now uses `--headless` when running Chrome. You can still use the `chromote.headless` option or `CHROMOTE_HEADLESS` environment variable to configure the `--headless` flag if you're using an older version of Chrome. (#187)\n\n* Added `chromote_info()`, a new utility function to print out key information about chromote and Chrome. Useful when debugging chromote or reporting an issue. (#190)\n\n* chromote now uses a consistent prefix for logs, e.g `{tempdir}/chrome-{id}-stdout.log` and `{tempdir}/chrome-{id}-stderr.log`. chromote also now uses `--crash-dumps-dir` to set a session-specific temp directory. (#194)\n\n# chromote 0.3.1\n\n* Fixed a typo that caused `launch_chrome()` to throw an error. (#175)\n\n# chromote 0.3.0\n\n* The headless mode used by Chrome can now be selected with the `chromote.headless` option or `CHROMOTE_HEADLESS` environment variable. \n\n  In Chrome v128, a [new headless mode](https://developer.chrome.com/docs/chromium/new-headless) became the default. The new mode uses the same browser engine as the regular Chrome browser, whereas the old headless mode is built on a separate architecture. The old headless mode may be faster to launch and is still well-suited to many of the tasks for which chromote is used.\n\n  For now, to avoid disruption, chromote defaults to using the old headless mode. In the future, chromote will follow Chrome and default to `\"new\"` headless mode. (And at some point, Chrome intends to remove the old headless mode which is now offered as [a separate binary](https://developer.chrome.com/blog/chrome-headless-shell).) To test the new headless mode, use `options(chromote.headless = \"new\")` or `CHROMOTE_HEADLESS=\"new\"` (in `.Renviron` or via `Sys.setenv()`). (#172)\n\n# chromote 0.2.0\n\n## Breaking changes\n\n* Breaking change: `Chromote$is_active()` method now reports if there is an active connection to the underlying chrome instance, rather than whether or not that instance is alive (#94).\n\n## Improvements and bug fixes\n\n* `Chromote` and `ChromoteSession` gain print methods to give you a snapshot of the most important values (#140).\n\n* `Chromote` gains a new `is_alive()` method equivalent to the old `is_active()` method; i.e. it reports on if there is an active chrome process running in the background (#136).\n\n* `ChromoteSession` now records the `targetId`. This eliminates one round-trip to the browser when viewing or closing a session. You can now call the `$respawn()` method if a session terminates and you want to reconnect to the same target (#94).\n\n* `ChromoteSession$screenshot()` gains an `options` argument that accepts a list of additional options to be passed to the Chrome Devtools Protocol's [`Page.captureScreenshot` method](https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-captureScreenshot) (#129).\n\n* `ChromoteSession$screenshot()` will now infer the image format from the `filename` extension. Alternatively, you can specify the `format` in the list passed to `options` (#130).\n\n* `--disable-gpu` is no longer included in the default Chrome arguments, except on windows where empirically it appears to be necessary (otherwise GHA check runs never terminate) (#142).\n\n# chromote 0.1.2\n\n* Fixed #109: An error would occur when a `Chromote` object's `$close()` method was called. (#110)\n\n* Fixed #99: When the `$view()` method was called, recent versions of Chrome would display `\"Debugging connection was closed. Reason: WebSocket disconnected\"`. (#101)\n\n* Fixed #89, #91: `find_chrome()` now checks more possible binary names for Chrome or Chromium on Linux and Mac. (thanks @brianmsm and @rossellhayes, #117)\n\n* Fixed #22: Added a new `chromote.timeout` global option that can be used to set the timeout (in seconds) for establishing connections with the Chrome session. (#120)\n\n\n# chromote 0.1.1\n\n* Update docs for CRAN (#93)\n\n\n# chromote 0.1.0\n\n* Initial package release\n"
  },
  {
    "path": "R/browser.R",
    "content": "globals <- new.env()\n\n#' Browser base class\n#'\n#' @description\n#' Base class for browsers like Chrome, Chromium, etc. Defines the interface\n#' used by various browser implementations. It can represent a local browser\n#' process or one running remotely.\n#'\n#' @details\n#' The `initialize()` method of an implementation should set `private$host`\n#' and `private$port`. If the process is local, the `initialize()` method\n#' should also set `private$process`.\n#'\n#' @export\nBrowser <- R6Class(\n  \"Browser\",\n  public = list(\n    # Returns TRUE if the browser is running locally, FALSE if it's remote.\n    #' @description Is local browser?\n    #' Returns TRUE if the browser is running locally, FALSE if it's remote.\n    is_local = function() !is.null(private$process),\n\n    #' @description Browser process\n    get_process = function() private$process,\n\n    #' @description Is the process alive?\n    is_alive = function() private$process$is_alive(),\n\n    #' @description Browser Host\n    get_host = function() private$host,\n\n    #' @description Browser port\n    get_port = function() private$port,\n\n    #' @description Close the browser\n    #' @param wait If an integer, waits a number of seconds for the process to\n    #'   exit, killing the process if it takes longer than `wait` seconds to\n    #'   close. Use `wait = TRUE` to wait for 10 seconds.\n    close = function(wait = FALSE) {\n      if (!self$is_local()) return(invisible())\n      if (!private$process$is_alive()) return(invisible())\n\n      if (!isFALSE(wait)) {\n        if (isTRUE(wait)) wait <- 10\n        check_number_whole(wait, min = 0)\n      }\n\n      private$process$signal(tools::SIGTERM)\n\n      if (!isFALSE(wait)) {\n        tryCatch(\n          {\n            private$process$wait(timeout = wait * 1000)\n            if (private$process$is_alive()) {\n              stop(\"shut it down\") # ignored, used to escalate\n            }\n          },\n          error = function(err) {\n            # Still alive after wait...\n            try(private$process$kill(), silent = TRUE)\n          }\n        )\n      }\n    }\n  ),\n  private = list(\n    process = NULL,\n    host = NULL,\n    port = NULL,\n    finalize = function(e) {\n      if (self$is_local()) {\n        self$close()\n      }\n    }\n  )\n)\n"
  },
  {
    "path": "R/callbacks.R",
    "content": "# The data structure for storing callbacks is essentially a queue: items are\n# added to the end, and removed from the front. Occasionally a callback will\n# be manually removed from the middle of the queue. For each callback that's\n# registered, we provide a function that can remove that callback from the\n# queue.\nCallbacks <- R6Class(\n  \"Callbacks\",\n  public = list(\n    initialize = function() {\n      # Use floating point because it has greater range than int while\n      # maintaining precision of 1.0.\n      private$nextId <- 1.0\n      private$callbacks <- fastmap()\n    },\n    add = function(callback) {\n      if (!is.function(callback)) {\n        stop(\"callback must be a function.\")\n      }\n\n      # Keys are formatted like \"0000000000001\", \"0000000000002\", etc., so\n      # that they can be easily sorted by numerical value.\n      id <- sprintf(\"%013.f\", private$nextId)\n      private$nextId <- private$nextId + 1.0\n      private$callbacks$set(id, callback)\n\n      # Return function for unregistering the callback.\n      invisible(function() {\n        if (private$callbacks$has(id)) {\n          private$callbacks$remove(id)\n        }\n      })\n    },\n    invoke = function(..., on_error = NULL) {\n      # Ensure that calls are invoked in the order that they were registered\n      keys <- private$callbacks$keys(sort = TRUE)\n\n      errors <- character()\n      if (is.null(on_error)) {\n        on_error <- function(e) {\n          errors[length(errors) + 1] <<- e$message\n        }\n      }\n\n      for (key in keys) {\n        callback <- private$callbacks$get(key)\n        tryCatch(callback(...), error = on_error)\n      }\n\n      if (length(errors) != 0) {\n        warning(\n          paste0(\n            length(errors),\n            \" errors occurred while executing callbacks:\\n  \",\n            paste(errors, collapse = \"\\n  \")\n          )\n        )\n      }\n    },\n    clear = function() {\n      private$callbacks <- fastmap()\n    },\n    size = function() {\n      private$callbacks$size()\n    }\n  ),\n  private = list(\n    nextId = NULL,\n    callbacks = NULL\n  )\n)\n"
  },
  {
    "path": "R/chrome.R",
    "content": "#' Local Chrome process\n#'\n#' @description\n#' This is a subclass of [`Browser`] that represents a local browser. It extends\n#' the [`Browser`] class with a [`processx::process`] object, which represents\n#' the browser's system process.\n#' @export\nChrome <- R6Class(\n  \"Chrome\",\n  inherit = Browser,\n  public = list(\n    #' @description Create a new Chrome object.\n    #' @param path Location of chrome installation\n    #' @param args A character vector of command-line arguments passed when\n    #'   initializing Chrome. Single on-off arguments are passed as single\n    #'   values (e.g.`\"--disable-gpu\"`), arguments with a value are given with a\n    #'   nested character vector (e.g. `c(\"--force-color-profile\", \"srgb\")`).\n    #'   See\n    #'   [here](https://peter.sh/experiments/chromium-command-line-switches/)\n    #'   for a list of possible arguments. Defaults to [`get_chrome_args()`].\n    #' @return A new `Chrome` object.\n    #' @seealso [`get_chrome_args()`]\n    initialize = function(path = find_chrome(), args = get_chrome_args()) {\n      if (is.null(path)) {\n        stop(\"Invalid path to Chrome\")\n      }\n      res <- launch_chrome(path, args)\n      private$host <- \"127.0.0.1\"\n      private$process <- res$process\n      private$port <- res$port\n      private$path <- path\n    },\n    #' @description Browser application path\n    get_path = function() private$path\n  ),\n  private = list(\n    path = NULL\n  )\n)\n\n#' Remote Chrome process\n#'\n#' @description\n#' Remote Chrome process\n#'\n#' @export\nChromeRemote <- R6Class(\n  \"ChromeRemote\",\n  inherit = Browser,\n  public = list(\n    #' @description Create a new ChromeRemote object.\n    #' @param host A string that is a valid IPv4 or IPv6 address. `\"0.0.0.0\"`\n    #' represents all IPv4 addresses and `\"::/0\"` represents all IPv6 addresses.\n    #' @param port A number or integer that indicates the server port.\n    initialize = function(host, port) {\n      private$host <- host\n      private$port <- port\n    },\n\n    #' @description Is the remote service alive?\n    is_alive = function() {\n      url <- sprintf(\"http://%s:%s/json/version\", private$host, private$port)\n\n      tryCatch(\n        {\n          # If we can read info from the remote host, then it's alive\n          suppressWarnings(fromJSON(url))\n          TRUE\n        },\n        error = function(err) FALSE\n      )\n    },\n\n    #' @description chromote does not manage remote processes, so closing a\n    #'   remote Chrome browser does nothing. You can send a `Browser$close()`\n    #'   command if this is really something you want to do.\n    close = function() {\n      # chromote didn't start this process, so we won't kill it or close it.\n      invisible(TRUE)\n    }\n  )\n)\n\n#' Find path to Chrome or Chromium browser\n#'\n#' @description\n#' \\pkg{chromote} requires a Chrome- or Chromium-based browser with support for\n#' the Chrome DevTools Protocol. There are many such browser variants,\n#' including [Google Chrome](https://www.google.com/chrome/),\n#' [Chromium](https://www.chromium.org/chromium-projects/),\n#' [Microsoft Edge](https://www.microsoft.com/en-us/edge) and others.\n#'\n#' If you want \\pkg{chromote} to use a specific browser, set the\n#' `CHROMOTE_CHROME` environment variable to the full path to the browser's\n#' executable. Note that when `CHROMOTE_CHROME` is set, \\pkg{chromote} will use\n#' the value without any additional checks. On Mac, for example, one could use\n#' Microsoft Edge by setting `CHROMOTE_CHROME` with the following:\n#'\n#' ```r\n#' Sys.setenv(\n#'   CHROMOTE_CHROME = \"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge\"\n#' )\n#' ```\n#'\n#' When `CHROMOTE_CHROME` is not set, `find_chrome()` will perform a limited\n#' search to find a reasonable executable. On Windows, `find_chrome()` consults\n#' the registry to find `chrome.exe`. On Mac, it looks for `Google Chrome` in\n#' the `/Applications` folder (or tries the same checks as on Linux). On Linux,\n#' it searches for several common executable names.\n#'\n#' @examples\n#' find_chrome()\n#'\n#' @returns A character vector with the value of `CHROMOTE_CHROME`, or a path to\n#'   the discovered Chrome executable. If no path to is found, `find_chrome()`\n#'   returns `NULL`.\n#'\n#' @export\nfind_chrome <- function() {\n  if (Sys.getenv(\"CHROMOTE_CHROME\") != \"\") {\n    return(Sys.getenv(\"CHROMOTE_CHROME\"))\n  }\n\n  path <-\n    if (is_mac()) {\n      inform_if_chrome_not_found(find_chrome_mac())\n    } else if (is_windows()) {\n      inform_if_chrome_not_found(find_chrome_windows())\n    } else if (is_linux() || is_openbsd()) {\n      inform_if_chrome_not_found(\n        find_chrome_linux(),\n        searched_for = \"`google-chrome`, `chromium-browser` and `chrome` were\",\n        extra_advice = \"or adding one of these executables to your PATH\"\n      )\n    } else {\n      message(\"Platform currently not supported\")\n      NULL\n    }\n\n  path\n}\n\nchrome_verify <- function(path = find_chrome()) {\n  if (is_windows() && basename(path) != \"chrome-headless-shell.exe\") {\n    return(chrome_verify_windows())\n  }\n\n  processx::run(\n    command = path,\n    args = c(\"--headless\", \"--version\"),\n    timeout = 2,\n    error_on_status = FALSE\n  )\n}\n\nchrome_verify_windows <- function(path = find_chrome()) {\n  # Returns something similar to chrome_verify() for Windows, without actually\n  # launching chrome, since `--version` doesn't work there.\n\n  status <- function(code = 0, stdout = \"\", stderr = \"\") {\n    list(status = code, stdout = stdout, stderr = stderr, timeout = FALSE)\n  }\n\n  path <- normalizePath(path)\n  if (!file.exists(path)) {\n    return(status(-1, stderr = sprintf(\"%s does not exist\", path)))\n  }\n\n  has_powershell <- nzchar(Sys.which(\"powershell\"))\n  has_wmic <- nzchar(Sys.which(\"wmic\"))\n  status_unknown_version <- status(\n    stdout = \"Unknown (please manually verify the Chrome version)\"\n  )\n\n  if (!has_powershell && !has_wmic) {\n    return(status_unknown_version)\n  }\n\n  version <- \"\"\n\n  if (has_powershell) {\n    version <- chrome_windows_version_powershell(path)\n  }\n\n  if (!nzchar(version) && has_wmic) {\n    version <- chrome_windows_version_wmic(path)\n  }\n\n  if (identical(version, \"\")) {\n    return(status_unknown_version)\n  }\n\n  status(stdout = version)\n}\n\nchrome_windows_version_powershell <- function(path) {\n  # Uses PowerShell to get the Chrome version\n  command <- sprintf(\"(Get-Item \\\"%s\\\").VersionInfo.FileVersion\", path)\n  output <- system2(\n    \"powershell\",\n    c(\"-Command\", shQuote(command)),\n    stdout = TRUE\n  )\n\n  if (identical(output, \"\")) {\n    return(\"\")\n  }\n\n  output <- trimws(output)\n  output <- output[nzchar(output)]\n  if (length(output) > 0) output[[1]] else \"\"\n}\n\nchrome_windows_version_wmic <- function(path) {\n  # Uses WMIC to get the Chrome version\n  wmic_cmd <- sprintf(\n    'wmic datafile where \"name=\\'%s\\'\" get version /value',\n    gsub(\"\\\\\\\\\", \"\\\\\\\\\\\\\\\\\", path)\n  )\n\n  output <- tryCatch(\n    system(wmic_cmd, intern = TRUE),\n    error = function(err) \"\"\n  )\n\n  if (identical(output, \"\")) {\n    return(\"\")\n  }\n\n  # Returns possibly several lines, one of which looks like\n  # \"Version=128.0.6613.85\\r\"\n  output <- trimws(output)\n  version <- grep(\"^Version=\", output, value = TRUE)\n  version <- sub(\"Version=\", \"\", version)\n  version <- paste(version, collapse = \", \") # might have more than one line\n\n  return(version)\n}\n\n#' Show information about the chromote package and Chrome browser\n#'\n#' This function gathers information about the operating system, R version,\n#' chromote package version, environment variables, Chrome path, and Chrome\n#' arguments. It also verifies the Chrome installation and retrieves its version.\n#'\n#' @return A list containing the following elements:\n#' \\describe{\n#'   \\item{os}{The operating system platform.}\n#'   \\item{version_r}{The version of R.}\n#'   \\item{version_chromote}{The version of the chromote package.}\n#'   \\item{envvar}{The value of the `CHROMOTE_CHROME` environment variable.}\n#'   \\item{path}{The path to the Chrome browser.}\n#'   \\item{args}{A vector of Chrome arguments.}\n#'   \\item{version}{The version of Chrome (if verification is successful).}\n#'   \\item{error}{The error message (if verification fails).}\n#'   \\item{.check}{A list with the status and output of the Chrome verification.}\n#' }\n#'\n#' @examples\n#' chromote_info()\n#'\n#' @export\nchromote_info <- function() {\n  pkg_version <- as.character(utils::packageVersion(\"chromote\"))\n  pkg_ref <- utils::packageDescription(\"chromote\")$RemotePkgRef\n\n  if (!is.null(pkg_ref) && !identical(\"chromote\", pkg_ref)) {\n    pkg_version <- sprintf(\"%s (%s)\", pkg_version, pkg_ref)\n  }\n\n  info <- structure(\n    list(\n      os = as.character(R.version[\"platform\"]),\n      version_r = R.version.string,\n      version_chromote = pkg_version,\n      envvar = Sys.getenv(\"CHROMOTE_CHROME\", \"\"),\n      path = find_chrome(),\n      args = c(chrome_headless_mode(), get_chrome_args())\n    ),\n    class = c(\"chromote_info\", \"list\")\n  )\n\n  if (is.null(info$path)) {\n    return(info)\n  }\n\n  info$.check <- chrome_verify(info$path)\n\n  if (info$.check$status == 0) {\n    info$version <- trimws(info$.check$stdout)\n  } else {\n    info$error <- info$.check$stderr\n  }\n\n  info\n}\n\n#' @export\nprint.chromote_info <- function(x, ...) {\n  cat0 <- function(...) cat(..., \"\\n\", sep = \"\")\n  wrap <- function(x, nchar = 9) {\n    x <- strwrap(x, width = getOption(\"width\") - nchar, exdent = nchar)\n    paste(x, collapse = \"\\n\")\n  }\n\n  cat0(\"---- {chromote} ----\")\n\n  cat0(\"   System: \", x$os)\n  cat0(\"R version: \", x$version_r)\n  cat0(\" chromote: \", x$version_chromote)\n\n  cat0(\"\\n---- Chrome ----\")\n\n  if (is.null(x$path)) {\n    cat0(\n      \"Path: !! \",\n      wrap(\"Could not find Chrome, is it installed on this system?\")\n    )\n    cat0(\"      !! \", wrap(\"If yes, see `?find_chrome()` for help.\"))\n    return(invisible(x))\n  }\n\n  cat0(\n    \"   Path: \",\n    x$path,\n    if (identical(x$path, x$envvar)) \" (set by CHROMOTE_CHROME envvar)\"\n  )\n  cat0(\"Version: \", x$version %||% \"(unknown)\")\n  cat0(\"   Args: \", wrap(paste(x$args, collapse = \" \")))\n  if (x$.check$timeout) {\n    cat0(\"  Error: Timed out.\")\n    cat0(\"  Error message:\")\n    cat0(x$error)\n  } else if (!is.null(x$error)) {\n    cat0(\"  Error: \", x$error)\n  }\n  invisible(x)\n}\n\nfind_chrome_windows <- function() {\n  tryCatch(\n    {\n      path <- utils::readRegistry(\n        \"SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\App Paths\\\\chrome.exe\\\\\"\n      )\n      path[[\"(Default)\"]]\n    },\n    error = function(e) {\n      NULL\n    }\n  )\n}\n\nfind_chrome_mac <- function() {\n  path_default <- \"/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome\"\n  if (file.exists(path_default)) {\n    return(path_default)\n  }\n\n  find_chrome_linux()\n}\n\nfind_chrome_linux <- function() {\n  possible_names <- c(\n    \"google-chrome\",\n    \"google-chrome-stable\",\n    \"chromium-browser\",\n    \"chromium\",\n    \"google-chrome-beta\",\n    \"google-chrome-unstable\",\n    \"chrome\"\n  )\n\n  for (path in possible_names) {\n    path <- Sys.which(path)\n    if (nzchar(path)) {\n      return(path)\n    }\n  }\n\n  NULL\n}\n\ninform_if_chrome_not_found <- function(\n  path,\n  searched_for = \"Google Chrome was\",\n  extra_advice = \"\"\n) {\n  if (!is.null(path)) return(invisible(path))\n\n  message(\n    searched_for,\n    \" not found. \",\n    \"Try setting the `CHROMOTE_CHROME` environment variable to the executable \",\n    \"of a Chromium-based browser, such as Google Chrome, Chromium or Brave\",\n    if (nzchar(extra_advice)) \" \",\n    extra_advice,\n    \".\"\n  )\n\n  NULL\n}\n\nchrome_headless_mode <- function() {\n  opt <- getOption(\"chromote.headless\", NULL)\n  env <- Sys.getenv(\"CHROMOTE_HEADLESS\", \"\")\n  env <- if (nzchar(env)) env else NULL\n\n  # TODO Chrome v128 changed the default from --headless=old to --headless=new\n  # in 2024-08. Old headless mode was effectively a separate browser render,\n  # and while more performant did not share the same browser implementation as\n  # headful Chrome. New headless mode will likely be useful to some, but in most\n  # chromote use cases -- printing to PDF and testing -- we are not ready to\n  # move to the new mode. Even once removed, the option may be useful if we\n  # add support downloading specific versions of Chrome. (See rstudio/chromote#171)\n  # 2025-01-16: Chrome v132 removed headless mode (rstudio/chromote#187)\n  mode <- opt %||% env\n\n  if (is.null(mode)) {\n    return(\"--headless\")\n  }\n\n  # Just pass headless along directly, Chrome will error if needed\n  sprintf(\"--headless=%s\", mode)\n}\n\nlaunch_chrome <- function(path = find_chrome(), args = get_chrome_args()) {\n  if (is.null(path)) {\n    stop(\"Invalid path to Chrome\")\n  }\n\n  res <- with_random_port(launch_chrome_impl, path = path, args = args)\n  res\n}\n\nlaunch_chrome_impl <- function(path, args, port) {\n  # Create temp locations for logs and crashes, grouped by chromote session\n  tmp_session <- tempfile(\"chrome-\", fileext = \"%s\")\n  path_dir_crash <- sprintf(tmp_session, \"-crashpad\")\n  path_stdout <- sprintf(tmp_session, \"-stdout.log\")\n  path_stderr <- sprintf(tmp_session, \"-stderr.log\")\n\n  p <- process$new(\n    command = path,\n    args = c(\n      chrome_headless_mode(),\n      paste0(\"--remote-debugging-port=\", port),\n      paste0(\"--remote-allow-origins=http://127.0.0.1:\", port),\n      paste0(\"--crash-dumps-dir=\", path_dir_crash),\n      args\n    ),\n    supervise = TRUE,\n    stdout = path_stdout,\n    stderr = path_stderr,\n    echo_cmd = getOption(\"chromote.launch.echo_cmd\", FALSE)\n  )\n\n  connected <- FALSE\n  timeout <- getOption(\"chromote.timeout\", 10)\n  end <- Sys.time() + timeout\n  while (!connected && Sys.time() < end) {\n    if (!p$is_alive()) {\n      error_logs_path <- p$get_error_file()\n      error_logs <- paste(readLines(error_logs_path), collapse = \"\\n\")\n      stdout_file <- p$get_output_file()\n\n      verify <- chrome_verify()\n\n      stop(\n        \"Failed to start chrome. \",\n        if (verify$status == 0) {\n          \"Chrome is available on your system, so this error may be a configuration issue. \"\n        } else {\n          \"Chrome does not appear to be runnable on your system. \"\n        },\n        \"Try `chromote_info()` to check and verify your settings. \",\n        if (nzchar(error_logs)) {\n          sprintf(\n            \"\\nLog file: %s\\nError:\\n%s\",\n            error_logs_path,\n            trimws(error_logs)\n          )\n        } else {\n          \"No error messages were logged.\"\n        },\n        if (file.info(stdout_file)$size > 0) {\n          paste0(\n            \"\\nThe following log file may contain more information:\\n\",\n            stdout_file\n          )\n        }\n      )\n    }\n\n    tryCatch(\n      {\n        # Find port number from output\n        output <- readLines(p$get_error_file())\n        output <- output[grepl(\"^DevTools listening on ws://\", output)]\n        if (length(output) != 1) stop() # Just break out of the tryCatch\n\n        output_port <- sub(\n          \"^DevTools listening on ws://[0-9\\\\.]+:(\\\\d+)/.*\",\n          \"\\\\1\",\n          output\n        )\n        output_port <- as.integer(output_port)\n        if (is.na(output_port) || output_port != port) stop()\n\n        con <- url(paste0(\"http://127.0.0.1:\", port, \"/json/protocol\"), \"rb\")\n        if (!isOpen(con)) break # Failed to connect\n\n        connected <- TRUE\n        close(con)\n      },\n      warning = function(e) {\n      },\n      error = function(e) {\n      }\n    )\n\n    Sys.sleep(0.1)\n  }\n\n  if (!connected) {\n    rlang::abort(\n      paste(\"Chrome debugging port not open after\", timeout, \"seconds.\"),\n      class = \"error_stop_port_search\"\n    )\n  }\n\n  list(\n    process = p,\n    port = port\n  )\n}\n"
  },
  {
    "path": "R/chromote-package.R",
    "content": "#' @keywords internal\n\"_PACKAGE\"\n\n#' chromote Options\n#'\n#' @description\n#' These options and environment variables that are used by chromote. Options\n#' are lowercase and can be set with `options()`. Environment variables are\n#' uppercase and can be set in an `.Renviron` file, with `Sys.setenv()`, or in\n#' the shell or process running R. If both an option or environment variable are\n#' supported, chromote will use the option first.\n#'\n#' * `CHROMOTE_CHROME` \\cr\n#'   Path to the Chrome executable. If not set, chromote will\n#'   attempt to find and use the system installation of Chrome.\n#' * `chromote.headless`, `CHROMOTE_HEADLESS` \\cr\n#'   Headless mode for Chrome. Can be `\"old\"` or `\"new\"`. See\n#'   [Chrome Headless mode](https://developer.chrome.com/docs/chromium/new-headless)\n#'   for more details.\n#' * `chromote.timeout` \\cr\n#'   Timeout (in seconds) for Chrome to launch or connect. Default is `10`.\n#' * `chromote.launch.echo_cmd` \\cr\n#'   Echo the command used to launch Chrome to the console for debugging.\n#'   Default is `FALSE`.\n#'\n#' @name chromote-options\nNULL\n\n## usethis namespace: start\n#' @import promises later rlang\n#' @importFrom fastmap fastmap\n#' @importFrom jsonlite fromJSON toJSON\n#' @importFrom processx process\n#' @importFrom R6 R6Class\n#' @importFrom websocket WebSocket\n## usethis namespace: end\nNULL\n\n# inlined from `lifecycle::badge()` and only supports the experimental badge.\n# Use `usethis::use_lifecycle()` to add additional badges.\nlifecycle_badge <- function(stage) {\n  stage <- rlang::arg_match0(\n    stage,\n    c(\"experimental\") #, \"stable\", \"superseded\", \"deprecated\")\n  )\n  stage_name <- substr(stage, 1, 1) <- toupper(substr(stage, 1, 1))\n\n  url <- paste0(\"https://lifecycle.r-lib.org/articles/stages.html#\", stage)\n\n  html <- sprintf(\n    \"\\\\href{%s}{\\\\figure{%s}{options: alt='[%s]'}}\",\n    url,\n    file.path(tolower(sprintf(\"lifecycle-%s.svg\", stage))),\n    stage_name\n  )\n\n  text <- sprintf(\"\\\\strong{[%s]}\", stage_name)\n  sprintf(\"\\\\ifelse{html}{%s}{%s}\", html, text)\n}\n"
  },
  {
    "path": "R/chromote.R",
    "content": "#' Chromote class\n#'\n#' @description\n#' A `Chromote` object represents the browser as a whole, and it can have\n#' multiple _targets_, which each represent a browser tab. In the Chrome\n#' DevTools Protocol, each target can have one or more debugging _sessions_ to\n#' control it. A `ChromoteSession` object represents a single _session_.\n#'\n#' A `Chromote` object can have any number of `ChromoteSession` objects as\n#' children. It is not necessary to create a `Chromote` object manually. You can\n#' simply call:\n#' ```r\n#' b <- ChromoteSession$new()\n#' ```\n#' and it will automatically create a `Chromote` object if one has not already\n#' been created. The \\pkg{chromote} package will then designate that `Chromote`\n#' object as the _default_ `Chromote` object for the package, so that any future\n#' calls to `ChromoteSession$new()` will automatically use the same `Chromote`.\n#' This is so that it doesn't start a new browser for every `ChromoteSession`\n#' object that is created.\n#' @export\nChromote <- R6Class(\n  \"Chromote\",\n  lock_objects = FALSE,\n  cloneable = FALSE,\n  public = list(\n    #' @param browser A [`Browser`] object\n    #' @param multi_session Should multiple sessions be allowed?\n    #' @param auto_events If `TRUE`, enable automatic event enabling/disabling;\n    #'   if `FALSE`, disable automatic event enabling/disabling.\n    initialize = function(\n      browser = Chrome$new(),\n      multi_session = TRUE,\n      auto_events = TRUE\n    ) {\n      private$browser <- browser\n      private$auto_events <- auto_events\n      private$multi_session <- multi_session\n\n      private$command_callbacks <- fastmap()\n\n      # Use a private event loop to drive the websocket\n      private$child_loop <- create_loop(parent = current_loop())\n\n      p <- self$connect(multi_session = multi_session, wait_ = FALSE)\n\n      # Populate methods while the connection is being established.\n      protocol_spec <- jsonlite::fromJSON(\n        self$url(\"/json/protocol\"),\n        simplifyVector = FALSE\n      )\n      self$protocol <- process_protocol(protocol_spec, self$.__enclos_env__)\n      lockBinding(\"protocol\", self)\n      # self$protocol is a list of domains, each of which is a list of\n      # methods. Graft the entries from self$protocol onto self\n      list2env(self$protocol, self)\n\n      private$event_manager <- EventManager$new(self)\n\n      self$wait_for(p)\n\n      private$register_default_event_listeners()\n    },\n\n    #' @description Re-connect the websocket to the browser. The Chrome browser\n    #'   automatically closes websockets when your computer goes to sleep;\n    #'   you can use this to bring it back to life with a new connection.\n    #' @param multi_session Should multiple sessions be allowed?\n    #' @param wait_ If `FALSE`, return a promise; if `TRUE` wait until\n    #'   connection is complete.\n    connect = function(multi_session = TRUE, wait_ = TRUE) {\n      if (multi_session) {\n        chrome_info <- fromJSON(self$url(\"/json/version\"))\n      } else {\n        chrome_info <- fromJSON(self$url(\"/json\"))\n      }\n\n      with_loop(private$child_loop, {\n        private$ws <- WebSocket$new(\n          chrome_info$webSocketDebuggerUrl,\n          autoConnect = FALSE\n        )\n\n        private$ws$onMessage(private$on_message)\n\n        # Allow up to 10 seconds to connect to browser.\n\n        # TODO: The extra promise_resolve()$then() wrapper is currently\n        # necessary because promise_timeout needs to be run _within_ a\n        # synchronize() call (which $wait_for(), down below, does). If we call\n        # promise_timeout() directly here, then it will error out because\n        # there isn't a current interrupt domain. Hopefully we can remove this\n        # delay and extra wrapper stuff.\n        p <- promise_resolve(TRUE)$then(function(value) {\n          promise_timeout(\n            promise(function(resolve, reject) {\n              private$ws$onOpen(resolve)\n            }),\n            timeout = getOption(\"chromote.timeout\", 10),\n            timeout_message = paste0(\n              \"Chromote: timed out waiting for WebSocket connection to browser. \",\n              \"Use `options(chromote.timeout = \",\n              getOption(\"chromote.timeout\", 10),\n              \")` \",\n              \"to increase the timeout.\"\n            )\n          )\n        })\n\n        private$ws$connect()\n      })\n\n      if (wait_) {\n        invisible(self$wait_for(p))\n      } else {\n        p\n      }\n    },\n\n    #' @description Display the current session in the `browser`\n    #'\n    #' If a [`Chrome`] browser is being used, this method will open a new tab\n    #' using your [`Chrome`] browser. When not using a [`Chrome`] browser, set\n    #' `options(browser=)` to change the default behavior of [`browseURL()`].\n    view = function() {\n      browse_url(path = NULL, self)\n    },\n\n    #' @description\n    #' `auto_events` value.\n    #'\n    #' For internal use only.\n    get_auto_events = function() {\n      private$auto_events\n    },\n\n    #' @description\n    #' Set or retrieve the `enable` command arguments for a domain. These\n    #' arguments are used for the `enable` command that is called for a domain,\n    #' e.g. `Fetch$enable()`, when accessing an event method.\n    #'\n    #' @param domain A command domain, e.g. `\"Fetch\"`.\n    #' @param ... Arguments to use for auto-events for the domain. If not\n    #'   provided, returns the argument values currently in place for the\n    #'   domain. Use `NULL` to clear the enable arguments for a domain.\n    auto_events_enable_args = function(domain, ...) {\n      dots <- dots_list(..., .named = TRUE)\n\n      if (length(dots) == 0) {\n        return(get_auto_events_enable_args(private, domain, self$parent))\n      }\n\n      set_auto_events_enable_args(self, private, domain, dots)\n    },\n\n    # =========================================================================\n    # Event loop, promises, and synchronization\n    # =========================================================================\n\n    #' @description Local \\pkg{later} loop.\n    #'\n    #' For expert async usage only.\n    get_child_loop = function() {\n      private$child_loop\n    },\n\n    # This runs the child loop until the promise is resolved.\n    #' @description Wait until the promise resolves\n    #'\n    #' Blocks the R session until the promise (`p`) is resolved. The loop from\n    #' `$get_child_loop()` will only advance just far enough for the promise to\n    #' resolve.\n    #' @param p A promise to resolve.\n    wait_for = function(p) {\n      if (!is.promise(p)) {\n        stop(\"wait_for requires a promise object.\")\n      }\n\n      synchronize(p, loop = private$child_loop)\n    },\n\n    # =========================================================================\n    # Session management\n    # =========================================================================\n\n    #' @description Create a new tab / window\n    #'\n    #' @param width,height Width and height of the new window.\n    #' @param targetId\n    #'   [Target](https://chromedevtools.github.io/devtools-protocol/tot/Target/)\n    #'   ID of an existing target to attach to. When a `targetId` is provided, the\n    #'   `width` and `height` arguments are ignored. If NULL (the default) a new\n    #'   target is created and attached to, and the `width` and `height`\n    #'   arguments determine its viewport size.\n    #' @param wait_ If `FALSE`, return a [promises::promise()] of a new\n    #'   `ChromoteSession` object. Otherwise, block during initialization, and\n    #'   return a `ChromoteSession` object directly.\n    new_session = function(\n      width = 992,\n      height = 1323,\n      targetId = NULL,\n      wait_ = TRUE\n    ) {\n      self$check_active()\n      create_session(\n        chromote = self,\n        width = width,\n        height = height,\n        targetId = targetId,\n        wait_ = wait_\n      )\n    },\n\n    #' @description Retrieve all [`ChromoteSession`] objects\n    #' @return A list of `ChromoteSession` objects\n    get_sessions = function() {\n      private$sessions\n    },\n\n    #' @description Register [`ChromoteSession`] object\n    #' @param session A `ChromoteSession` object\n    #'\n    #' For internal use only.\n    register_session = function(session) {\n      private$sessions[[session$get_session_id()]] <- session\n    },\n\n    # =========================================================================\n    # Commands and events\n    # =========================================================================\n\n    #' @description\n    #' Send command through Chrome DevTools Protocol.\n    #'\n    #' For expert use only.\n    #' @param msg A JSON-serializable list containing `method`, and `params`.\n    #' @param callback Method to run when the command finishes successfully.\n    #' @param error Method to run if an error occurs.\n    #' @param timeout Number of milliseconds for Chrome DevTools Protocol\n    #' execute a method.\n    #' @param sessionId Determines which [`ChromoteSession`] with the\n    #' corresponding to send the command to.\n    send_command = function(\n      msg,\n      callback = NULL,\n      error = NULL,\n      timeout = NULL,\n      sessionId = NULL\n    ) {\n      self$check_active()\n\n      private$last_msg_id <- private$last_msg_id + 1\n      msg$id <- private$last_msg_id\n\n      if (!is.null(sessionId)) {\n        msg$sessionId <- sessionId\n      }\n\n      p <- promise(function(resolve, reject) {\n        msg_json <- toJSON(msg, auto_unbox = TRUE)\n        private$ws$send(msg_json)\n        self$debug_log(\"SEND \", msg_json)\n        # One of these callbacks will be invoked when a message arrives with a\n        # matching id.\n        private$add_command_callback(msg$id, resolve, reject)\n      })\n\n      p <- p$catch(function(e) {\n        stop(\n          \"code: \",\n          e$code,\n          \"\\n  message: \",\n          e$message,\n          if (!is.null(e$data)) paste0(\"\\n  data: \", e$data)\n        )\n      })\n\n      if (!is.null(timeout) && !is.infinite(timeout)) {\n        p <- promise_timeout(\n          p,\n          timeout,\n          loop = private$child_loop,\n          timeout_message = paste0(\n            \"Chromote: timed out waiting for response to command \",\n            msg$method\n          )\n        )\n      }\n\n      if (!is.null(callback)) {\n        p <- p$then(onFulfilled = callback, onRejected = error)\n      }\n\n      p <- p$finally(function() private$remove_command_callback(msg$id))\n\n      p\n    },\n\n    #' @description\n    #' Immediately call all event callback methods.\n    #'\n    #' For internal use only.\n    #' @param event A single event string\n    #' @param params A list of parameters to pass to the event callback methods.\n    invoke_event_callbacks = function(event, params) {\n      private$event_manager$invoke_event_callbacks(event, params)\n    },\n\n    # =========================================================================\n    # Debugging\n    # =========================================================================\n\n    #' @description Enable or disable message debugging\n    #'\n    #' If enabled, R will print out the\n    # JSON messages that are sent and received. If called with no value, this\n    # method will print out the current debugging state.\n    #' @param value If `TRUE`, enable debugging. If `FALSE`, disable debugging.\n    debug_messages = function(value = NULL) {\n      if (is.null(value)) return(private$debug_messages_)\n\n      if (!(identical(value, TRUE) || identical(value, FALSE)))\n        stop(\"value must be TRUE or FALSE\")\n\n      private$debug_messages_ <- value\n    },\n\n    #' @description\n    #' Submit debug log message\n    #'\n    #' ## Examples\n    #'\n    #' ```r\n    #' b <- ChromoteSession$new()\n    #' b$parent$debug_messages(TRUE)\n    #' b$Page$navigate(\"https://www.r-project.org/\")\n    #' #> SEND {\"method\":\"Page.navigate\",\"params\":{\"url\":\"https://www.r-project.org/\"}| __truncated__}\n    #' # Turn off debug messages\n    #' b$parent$debug_messages(FALSE)\n    #' ```\n    #'\n    #' @param ... Arguments pasted together with `paste0(..., collapse = \"\")`.\n    debug_log = function(...) {\n      txt <- truncate(paste0(..., collapse = \"\"), 1000)\n      if (private$debug_messages_) {\n        message(txt)\n      }\n    },\n\n    # =========================================================================\n    # Misc utility functions\n    # =========================================================================\n\n    #' @description Create url for a given path\n    #' @param path A path string to append to the host and port\n    url = function(path = NULL) {\n      if (!is.null(path) && substr(path, 1, 1) != \"/\") {\n        stop('path must be NULL or a string that starts with \"/\"')\n      }\n      paste0(\n        \"http://\",\n        private$browser$get_host(),\n        \":\",\n        private$browser$get_port(),\n        path\n      )\n    },\n\n    #' @description\n    #' Is there an active websocket connection to the browser process?\n    is_active = function() {\n      self$is_alive() && private$ws$readyState() %in% c(0L, 1L)\n    },\n\n    #' @description\n    #' Is the underlying browser process running?\n    is_alive = function() {\n      private$browser$is_alive()\n    },\n\n    #' @description Check that a chromote instance is active and alive.\n    #'  Will automatically reconnect if browser process is alive, but\n    #'  there's no active web socket connection.\n    check_active = function() {\n      if (!self$is_alive()) {\n        stop(\"Chromote has been closed.\")\n      }\n\n      if (!self$is_active()) {\n        inform(\n          c(\n            \"!\" = \"Reconnecting to chrome process.\",\n            i = \"All active sessions will be need to be respawned.\"\n          )\n        )\n        self$connect()\n\n        # Mark all sessions as closed\n        for (session in private$sessions) {\n          session$mark_closed(FALSE)\n        }\n        private$sessions <- list()\n      }\n      invisible(self)\n    },\n\n    #' @description Retrieve [`Browser`]` object\n    #'\n    get_browser = function() {\n      private$browser\n    },\n\n    #' @description Close the [`Browser`] object\n    #' @param wait If an integer, waits a number of seconds for the process to\n    #'   exit, killing the process if it takes longer than `wait` seconds to\n    #'   close. Use `wait = TRUE` to wait for 10 seconds, or `wait = FALSE` to\n    #'   close the connection without waiting for the process to exit. Only\n    #'   applies when Chromote is connected to a local process.\n    close = function(wait = TRUE) {\n      if (!isFALSE(wait)) {\n        if (isTRUE(wait)) wait <- 10\n        check_number_whole(wait, min = 0)\n      }\n\n      is_local <- private$browser$is_local()\n\n      if (!is_local || !self$is_alive()) {\n        # For remote connections or cases where the process is already closed,\n        # we just close the websocket. Note that we skip $is_active() because it\n        # requires $is_alive().\n        if (private$ws$readyState() %in% c(0L, 1L)) {\n          private$ws$close()\n        }\n        return(invisible())\n      }\n\n      # close the browser nicely, immediately close websocket\n      self$Browser$close(wait_ = FALSE)\n      try(private$ws$close(), silent = TRUE)\n\n      if (!isFALSE(wait)) {\n        # or close it forcefully if it takes too long\n        tryCatch(\n          {\n            private$browser$get_process()$wait(timeout = wait * 1000)\n            if (private$browser$get_process()$is_alive()) {\n              stop(\"shut it down\") # ignored, used to escalate\n            }\n          },\n          error = function(err) {\n            try(private$ws$close(), silent = TRUE)\n            private$browser$close(wait = 1)\n          }\n        )\n      }\n\n      invisible()\n    },\n\n    #' @description Summarise the current state of the object.\n    #' @param verbose The print method defaults to a brief summary\n    #'   of the most important debugging info; use `verbose = TRUE` tp\n    #'   see the complex R6 object.\n    #' @param ... Passed on to `format()` when `verbose` = TRUE\n    print = function(..., verbose = FALSE) {\n      if (verbose) {\n        cat(format(self, ...), sep = \"\\n\")\n      } else {\n        if (self$is_active()) {\n          state <- \"active + alive\"\n        } else if (self$is_alive()) {\n          state <- \"alive\"\n        } else {\n          state <- \"closed\"\n        }\n\n        ps <- self$get_browser()$get_process()\n\n        cat_line(\"<Chromote> (\", state, \")\")\n        if (self$is_alive()) {\n          cat_line(\"  URL:  \", self$url())\n          cat_line(\"  PID:  \", ps$get_pid())\n          cat_line(\"  Path: \", ps$get_cmdline()[[1]])\n        }\n      }\n      invisible(self)\n    },\n\n    #' @field default_timeout Default timeout in seconds for \\pkg{chromote} to\n    #' wait for a Chrome DevTools Protocol response.\n    default_timeout = 10,\n    #' @field protocol Dynamic protocol implementation. For expert use only!\n    protocol = NULL\n  ),\n\n  private = list(\n    browser = NULL,\n    ws = NULL,\n\n    # =========================================================================\n    # Browser commands\n    # =========================================================================\n    last_msg_id = 0,\n    command_callbacks = NULL,\n\n    add_command_callback = function(id, callback, error) {\n      id <- as.character(id)\n      private$command_callbacks$set(\n        id,\n        list(\n          callback = callback,\n          error = error\n        )\n      )\n    },\n\n    # Invoke the callback for a command (using id).\n    invoke_command_callback = function(id, value, error) {\n      id <- as.character(id)\n\n      if (!private$command_callbacks$has(id)) return()\n\n      handlers <- private$command_callbacks$get(id)\n\n      if (!is.null(error)) {\n        handlers$error(error)\n      } else if (!is.null(value)) {\n        handlers$callback(value)\n      }\n    },\n\n    remove_command_callback = function(id) {\n      private$command_callbacks$remove(as.character(id))\n    },\n\n    # =========================================================================\n    # Browser events\n    # =========================================================================\n    event_manager = NULL,\n\n    register_event_listener = function(event, callback = NULL, timeout = NULL) {\n      self$check_active()\n      private$event_manager$register_event_listener(event, callback, timeout)\n    },\n\n    register_default_event_listeners = function() {\n      # When a target is closed, mark the corresponding R session object as\n      # closed and remove it from the list of sessions.\n      self$protocol$Target$detachedFromTarget(function(msg) {\n        sid <- msg$sessionId\n        session <- private$sessions[[sid]]\n        if (is.null(session)) return()\n\n        private$sessions[[sid]] <- NULL\n        session$mark_closed(TRUE)\n      })\n    },\n\n    # =========================================================================\n    # Message handling and dispatch\n    # =========================================================================\n    debug_messages_ = FALSE,\n    debug_message_max_length = 1000,\n\n    on_message = function(msg) {\n      self$debug_log(\"RECV \", msg$data)\n      data <- fromJSON(msg$data, simplifyVector = FALSE)\n\n      if (!is.null(data$method)) {\n        # This is an event notification.\n        #\n        # The reason that the callback is wrapped in later() is to prevent a\n        # possible race when a command response and an event notification arrive\n        # in the same tick. See issue #1.\n        later(function() {\n          if (!is.null(data$sessionId)) {\n            session <- private$sessions[[data$sessionId]]\n          } else {\n            session <- self\n          }\n\n          session$invoke_event_callbacks(data$method, data$params)\n        })\n      } else if (!is.null(data$id)) {\n        # This is a response to a command.\n        private$invoke_command_callback(data$id, data$result, data$error)\n      } else {\n        message(\"Don't know how to handle message: \", msg$data)\n      }\n    },\n\n    # =========================================================================\n    # Sessions\n    # =========================================================================\n    multi_session = NULL,\n    sessions = list(),\n\n    # =========================================================================\n    # Private event loop for the websocket\n    # =========================================================================\n    child_loop = NULL\n  )\n)\n\nglobals$default_chromote <- NULL\n\n#' Default Chromote object\n#'\n#' Returns the Chromote package's default [Chromote] object. If\n#' there is not currently a default `Chromote` object that is active, then\n#' one will be created and set as the default.\n#'\n#' `ChromoteSession$new()` calls this function by default, if the\n#' `parent` is not specified. That means that when\n#' `ChromoteSession$new()` is called and there is not currently an\n#' active default `Chromote` object, then a new `Chromote` object will\n#' be created and set as the default.\n#' @export\ndefault_chromote_object <- function() {\n  if (!has_default_chromote_object()) {\n    set_default_chromote_object(Chromote$new())\n  }\n\n  globals$default_chromote\n}\n\n#' Returns TRUE if there's a default Chromote object and it is active, FALSE\n#' otherwise.\n#' @rdname default_chromote_object\n#' @export\nhas_default_chromote_object <- function() {\n  !is.null(globals$default_chromote) && globals$default_chromote$is_alive()\n}\n\n#' @param x A [Chromote] object.\n#' @rdname default_chromote_object\n#' @export\nset_default_chromote_object <- function(x) {\n  if (!inherits(x, \"Chromote\")) {\n    stop(\"x must be a Chromote object.\")\n  }\n  globals$default_chromote <- x\n}\n\ncache_value <- function(fn) {\n  value <- NULL\n  function() {\n    if (is.null(value)) {\n      value <<- fn()\n    }\n    value\n  }\n}\n# inspired by https://www.npmjs.com/package/is-docker\n# This should not change over time. Cache it\nis_inside_docker <- cache_value(function() {\n  file.exists(\"/.dockerenv\") ||\n    (is_linux() &&\n      file.exists(\"/proc/self/cgroup\") &&\n      any(grepl(\"docker\", readLines(\"/proc/self/cgroup\"), fixed = TRUE)))\n})\n\n# This is a _fast_ function. Do not cache it.\nis_inside_ci <- function() {\n  !identical(Sys.getenv(\"CI\", unset = \"\"), \"\")\n}\n\nis_missing_linux_user <- cache_value(function() {\n  is_linux() &&\n    system(\"id\", ignore.stdout = TRUE) != 0\n})\n\n#' Default Chrome arguments\n#'\n#' A character vector of command-line arguments passed when initializing any new\n#' instance of [`Chrome`]. Single on-off arguments are passed as single values\n#' (e.g.`\"--disable-gpu\"`), arguments with a value are given with a nested\n#' character vector (e.g. `c(\"--force-color-profile\", \"srgb\")`). See\n#' [here](https://peter.sh/experiments/chromium-command-line-switches/) for a\n#' list of possible arguments.\n#'\n#'\n#' @details\n#'\n#' Default chromote arguments are composed of the following values (when\n#' appropriate):\n#'\n#' * [`\"--disable-gpu\"`](https://peter.sh/experiments/chromium-command-line-switches/#disable-gpu)\n#'   * Only added on Windows, as empirically it appears to be needed\n#'     (if not, check runs on GHA never terminate).\n#'   * Disables GPU hardware acceleration. If software renderer is not in place, then the GPU process won't launch.\n#' * [`\"--no-sandbox\"`](https://peter.sh/experiments/chromium-command-line-switches/#no-sandbox)\n#'   * Only added when `CI` system environment variable is set, when the\n#'     user on a Linux system is not set, or when executing inside a Docker container.\n#'   * Disables the sandbox for all process types that are normally sandboxed. Meant to be used as a browser-level switch for testing purposes only\n#' * [`\"--disable-dev-shm-usage\"`](https://peter.sh/experiments/chromium-command-line-switches/#disable-dev-shm-usage)\n#'   * Only added when `CI` system environment variable is set or when inside a docker instance.\n#'   * The `/dev/shm` partition is too small in certain VM environments, causing Chrome to fail or crash.\n#' * [`\"--force-color-profile=srgb\"`](https://peter.sh/experiments/chromium-command-line-switches/#force-color-profile)\n#'   * This means that screenshots taken on a laptop plugged into an external\n#'     monitor will often have subtly different colors than one taken when\n#'     the laptop is using its built-in monitor. This problem will be even\n#'     more likely across machines.\n#'   * Force all monitors to be treated as though they have the specified color profile.\n#' * [`\"--disable-extensions\"`](https://peter.sh/experiments/chromium-command-line-switches/#disable-extensions)\n#'   * Disable extensions.\n#' * [`\"--mute-audio\"`](https://peter.sh/experiments/chromium-command-line-switches/#mute-audio)\n#'   * Mutes audio sent to the audio device so it is not audible during automated testing.\n#'\n#' @return A character vector of default command-line arguments to be used with\n#'   every new [`ChromoteSession`]\n#' @describeIn default_chrome_args Returns a character vector of command-line\n#'   arguments passed when initializing Chrome. See Details for more\n#'   information.\n#' @export\ndefault_chrome_args <- function() {\n  c(\n    # Empirically, appears to be needed for check runs to terminate on GHA\n    if (is_windows()) \"--disable-gpu\",\n\n    # > Note: --no-sandbox is not needed if you properly setup a user in the container.\n    # https://developers.google.com/web/updates/2017/04/headless-chrome\n    if (is_inside_ci() || is_missing_linux_user() || is_inside_docker()) {\n      \"--no-sandbox\"\n    },\n\n    # Until we have hundreds of concurrent usage, let's slow things down by\n    # using `/tmp` disk folder, rather than shared memory folder `/dev/shm`.\n    # This will make things more stable at the cost of accessing disk more often.\n    # Great discussion: https://github.com/puppeteer/puppeteer/issues/1834\n    if (is_inside_ci() || is_inside_docker()) {\n      \"--disable-dev-shm-usage\" # required bc the target easily crashes\n    },\n\n    # Consistent screenshot colors\n    # https://github.com/rstudio/chromote/pull/52\n    \"--force-color-profile=srgb\",\n\n    # Have also seen usage of `--ignore-certificate-errors`\n\n    # Generic options to have consistent output\n    c(\n      '--disable-extensions',\n      '--mute-audio'\n    )\n  )\n}\n\n#' @describeIn default_chrome_args Retrieves the default command-line arguments\n#'   passed to [`Chrome`] during initialization. Returns either `NULL` or a\n#'   character vector.\n#' @export\nget_chrome_args <- function() {\n  if (!exists(\"chrome_args\", envir = globals)) {\n    set_chrome_args(default_chrome_args())\n  }\n\n  globals$chrome_args\n}\nreset_chrome_args <- function() {\n  rm(\"chrome_args\", envir = globals)\n}\n\n#' @describeIn default_chrome_args Sets the default command-line arguments\n#'   passed when initializing. Returns the updated defaults.\n#' @param args A character vector of command-line arguments (or `NULL`) to be\n#'   used with every new [`ChromoteSession`].\n#' @export\n#' @examples\n#' old_chrome_args <- get_chrome_args()\n#'\n#' # Disable the gpu and use of `/dev/shm`\n#' set_chrome_args(c(\"--disable-gpu\", \"--disable-dev-shm-usage\"))\n#'\n#' #... Make new `Chrome` or `ChromoteSession` instance\n#'\n#' # Restore old defaults\n#' set_chrome_args(old_chrome_args)\nset_chrome_args <- function(args) {\n  set_args <- function(args_) {\n    # Using $ to set `NULL` is safe within environments\n    globals$chrome_args <- args_\n    invisible(args_)\n  }\n\n  # Validate\n  default_args <- unique(unlist(args))\n  if (length(default_args) == 0) {\n    return(set_args(NULL))\n  }\n  if (\n    anyNA(default_args) || !any(vapply(default_args, is.character, logical(1)))\n  ) {\n    stop(\"`set_chrome_args()` only accepts a character vector or `NULL`\")\n  }\n\n  # Set\n  return(set_args(default_args))\n}\n"
  },
  {
    "path": "R/chromote_session.R",
    "content": "#' ChromoteSession class\n#'\n#' @description\n#' This represents one _session_ in a Chromote object. Note that in the Chrome\n#' DevTools Protocol a session is a debugging session connected to a _target_,\n#' which is a browser window/tab or an iframe.\n#'\n#' A single target can potentially have more than one session connected to it,\n#' but this is not currently supported by chromote.\n#'\n#' @export\n#' @param targetId\n#'   [Target](https://chromedevtools.github.io/devtools-protocol/tot/Target/)\n#'   ID of an existing target to attach to. When a `targetId` is provided, the\n#'   `width` and `height` arguments are ignored. If NULL (the default) a new\n#'   target is created and attached to, and the `width` and `height`\n#'   arguments determine its viewport size.\nChromoteSession <- R6Class(\n  \"ChromoteSession\",\n  lock_objects = FALSE,\n  cloneable = FALSE,\n  public = list(\n    #' @description Create a new `ChromoteSession` object.\n    #'\n    #' ## Examples\n    #'\n    #' ```r\n    #' # Create a new `ChromoteSession` object.\n    #' b <- ChromoteSession$new()\n    #'\n    #' # Create a ChromoteSession with a specific height,width\n    #' b <- ChromoteSession$new(height = 1080, width = 1920)\n    #'\n    #' # Navigate to page\n    #' b$go_to(\"http://www.r-project.org/\")\n    #'\n    #' # View current chromote session\n    #' if (interactive()) b$view()\n    #' ```\n    #'\n    #' @param parent [`Chromote`] object to use; defaults to\n    #'   [default_chromote_object()]\n    #' @param width,height Width and height of the new window in integer pixel\n    #'   values.\n    #' @param wait_ If `FALSE`, return a [promises::promise()] of a new\n    #'   `ChromoteSession` object. Otherwise, block during initialization, and\n    #'   return a `ChromoteSession` object directly.\n    #' @param mobile Whether to emulate mobile device. When `TRUE`, Chrome\n    #'   updates settings to emulate browsing on a mobile phone; this includes\n    #'   viewport meta tag, overlay scrollbars, text autosizing and more. The\n    #'   default is `FALSE`.\n    #' @param auto_events If `NULL` (the default), use the `auto_events` setting\n    #'   from the parent `Chromote` object. If `TRUE`, enable automatic\n    #'   event enabling/disabling; if `FALSE`, disable automatic event\n    #'   enabling/disabling.\n    #' @return A new `ChromoteSession` object.\n    initialize = function(\n      parent = default_chromote_object(),\n      width = 992,\n      height = 1323,\n      targetId = NULL,\n      wait_ = TRUE,\n      auto_events = NULL,\n      mobile = FALSE\n    ) {\n      check_number_whole(width)\n      check_number_whole(height)\n      check_logical(auto_events, allow_null = TRUE)\n      check_logical(mobile)\n      check_logical(wait_)\n\n      self$parent <- parent\n      lockBinding(\"parent\", self) # do not allow `$parent` to be set!\n\n      self$default_timeout <- parent$default_timeout\n\n      # Create a session from the Chromote. Basically the same code as\n      # new_session(), but this is synchronous.\n      if (is.null(targetId)) {\n        # In earlier versions of chromote (< 0.5.0), we set `width` and `height`\n        # in `Target.createTarget`. With legacy (old) headless mode, each new\n        # session was essentially a tab in a new window. With new headless mode,\n        # introduced with Chrome v128, new tabs are created in existing windows.\n        # For Chrome v128-v133, `width` and `height` in `Target.createTarget`\n        # were ignored completely, and for v134+ they only have an effect when\n        # creating a new window, i.e. for the first ChromoteSession. We now use\n        # `Emulation.setDeviceMetricsOverride` below to set the viewport\n        # dimensions, which works across all versions of Chrome/headless-shell\n        # regardless of the parent window size.\n        p <- parent$Target$createTarget(\"about:blank\", wait_ = FALSE)$then(\n          function(value) {\n            private$target_id <- value$targetId\n            parent$Target$attachToTarget(\n              value$targetId,\n              flatten = TRUE,\n              wait_ = FALSE\n            )\n          }\n        )\n      } else {\n        private$target_id <- targetId\n        p <- parent$Target$attachToTarget(\n          targetId,\n          flatten = TRUE,\n          wait_ = FALSE\n        )\n      }\n\n      p <- p$then(function(value) {\n        private$session_id <- value$sessionId\n        self$parent$register_session(self)\n      })\n\n      # Whenever a command method (like x$Page$navigate()) is executed, it calls\n      # x$send_command(). This object's send_command() method calls the parent's\n      # send_command() method with a sessionId -- that is how the command is\n      # scoped to this session.\n      self$protocol <- protocol_reassign_envs(\n        parent$protocol,\n        env = self$.__enclos_env__\n      )\n      lockBinding(\"protocol\", self)\n\n      # Graft the entries from self$protocol onto self\n      list2env(self$protocol, self)\n\n      private$auto_events <- auto_events\n      private$event_manager <- EventManager$new(self)\n      private$session_is_active <- TRUE\n      private$target_is_active <- TRUE\n\n      # Find pixelRatio for screenshots\n      p <- p$then(function(value) {\n        private$get_pixel_ratio()\n      })\n\n      if (is.null(targetId)) {\n        # `Emulation.setDeviceMetricsOverride` is equivalent to turning on\n        # responsive preview in developer tools and lets us adjust the size of\n        # the viewport for the active session. This avoids setting the size of\n        # the parent browser window and ensures that the viewport of the current\n        # tab has dimensions that exactly match the requested `width` and\n        # `height`.\n        p <- p$then(function(value) {\n          self$Emulation$setDeviceMetricsOverride(\n            width = width,\n            height = height,\n            deviceScaleFactor = private$pixel_ratio,\n            mobile = mobile,\n            wait_ = FALSE\n          )\n        })\n      }\n\n      # When a target crashes, raise a warning.\n      if (!is.null(self$Inspector$targetCrashed)) {\n        p <- p$then(function(value) {\n          self$Inspector$targetCrashed(\n            timeout_ = NULL,\n            wait_ = FALSE,\n            function(value) {\n              warning(\n                \"Chromote has received a Inspector.targetCrashed event. This means that the ChromoteSession has probably crashed.\"\n              )\n              # Even if no targetId nor sessionId is returned by Inspector.targetCashed\n              # mark the session as closed. This will close all sessions..\n              self$mark_closed(TRUE)\n            }\n          )\n        })\n      }\n\n      if (wait_) {\n        self$wait_for(p)\n      } else {\n        # If wait_=FALSE, then we can't use the usual strategy of just\n        # returning p, because the call to ChromoteSession$new() always\n        # returns the new object. Instead, we'll store it as\n        # private$init_promise_, and the user can retrieve it with\n        # b$get_init_promise().\n        private$init_promise_ <- p$then(function(value) self)\n      }\n    },\n\n    #' @description Display the current session in the [`Chromote`] browser.\n    #'\n    #' If a [`Chrome`] browser is being used, this method will open a new tab\n    #' using your [`Chrome`] browser. When not using a [`Chrome`] browser, set\n    #' `options(browser=)` to change the default behavior of [`browseURL()`].\n    #'\n    #' ## Examples\n    #'\n    #' ```r\n    #' # Create a new `ChromoteSession` object.\n    #' b <- ChromoteSession$new()\n    #'\n    #' # Navigate to page\n    #' b$go_to(\"http://www.r-project.org/\")\n    #'\n    #' # View current chromote session\n    #' if (interactive()) b$view()\n    #' ```\n    view = function() {\n      # A data frame of targets, one row per target.\n      info <- fromJSON(self$parent$url(\"/json\"))\n      path <- info$devtoolsFrontendUrl[info$id == private$target_id]\n\n      if (length(path) == 0) {\n        stop(\"Target info not found.\")\n      }\n\n      if (grepl(\"^https://chrome-devtools-frontend\\\\.appspot\\\\.com\", path)) {\n        # Chrome v135+ uses a fully-qualified appspot.com URL because some\n        # flavors of Chrome do not ship with the devtools inspector (iOS,\n        # Android). Using this URL requires also setting\n        # `--remote-allow-origins=https://chrome-devtools-frontend.appspot.com`.\n        # This is cumbersome and not required for desktop Chrome, so we instead\n        # use the legacy path, while trying to guard against future changes.\n        inspector_path <- \"/devtools/inspector.html\"\n        inspector_contents <- tryCatch(\n          readLines(self$parent$url(inspector_path)),\n          error = function(err) character(0)\n        )\n        if (length(inspector_contents) > 0) {\n          ws_url <- info$webSocketDebuggerUrl[info$id == private$target_id]\n          ws_url <- sub(\"ws://\", \"ws=\", ws_url)\n          path <- paste0(inspector_path, \"?\", ws_url)\n        }\n      }\n\n      browse_url(path, self$parent)\n    },\n\n    #' @description Close the Chromote session.\n    #'\n    #' ## Examples\n    #'\n    #' ```r\n    #' # Create a new `ChromoteSession` object.\n    #' b <- ChromoteSession$new()\n    #'\n    #' # Navigate to page\n    #' b$go_to(\"http://www.r-project.org/\")\n    #'\n    #' # Close current chromote session\n    #' b$close()\n    #' ```\n    #'\n    #' @param wait_ If `FALSE`, return a [promises::promise()] that will resolve\n    #' when the `ChromoteSession` is closed. Otherwise, block until the\n    #' `ChromoteSession` has closed.\n    close = function(wait_ = TRUE) {\n      if (!private$target_is_active) {\n        return(invisible())\n      }\n\n      # Even if this session calls Target.closeTarget, the response from\n      # the browser is sent without a sessionId. In order to wait for the\n      # correct browser response, we need to invoke this from the parent's\n      # browser-level methods.\n      p <- self$parent$protocol$Target$closeTarget(\n        private$target_id,\n        wait_ = FALSE\n      )\n\n      p <- p$then(function(value) {\n        if (isTRUE(value$success)) {\n          self$mark_closed(TRUE)\n        }\n        invisible(value$success)\n      })\n\n      if (wait_) {\n        self$wait_for(p)\n      } else {\n        p\n      }\n    },\n\n    #' @description Get the viewport size\n    #'\n    #' @param wait_ If `FALSE`, return a [promises::promise()] of a new\n    #'   `ChromoteSession` object. Otherwise, block during initialization, and\n    #'   return a `ChromoteSession` object directly.\n    #'\n    #' @return Returns a list with values `width`, `height`, `zoom`\n    #'   and `mobile`. See `$set_viewport_size()` for more details.\n    get_viewport_size = function(wait_ = TRUE) {\n      check_bool(wait_)\n\n      p <- self$Page$getLayoutMetrics(wait_ = FALSE)$then(function(value) {\n        list(\n          width = value$cssVisualViewport$clientWidth,\n          height = value$cssVisualViewport$clientHeight\n        )\n      })$then(function(value) {\n        list(\n          width = value$width,\n          height = value$height,\n          zoom = private$pixel_ratio %||% 0,\n          mobile = private$is_mobile\n        )\n      })\n\n      if (wait_) self$wait_for(p) else p\n    },\n\n    #' @description Set the viewport size\n    #'\n    #' Each ChromoteSession is associated with a page that may be one page open\n    #' in a browser window among many. Each page can have its own viewport size,\n    #' that can be thought of like the window size for that page.\n    #'\n    #' This function uses the\n    #' [Emulation.setDeviceMetricsOverride](https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setDeviceMetricsOverride)\n    #' command to set the viewport size. If you need more granular control or\n    #' access to additional settings, use\n    #' `$Emulation$setDeviceMetricsOverride()`.\n    #'\n    #' @param width,height Width and height of the new window in integer pixel\n    #'   values.\n    #' @param zoom The zoom level of displayed content on a device, where a\n    #'   value of 1 indicates normal size, greater than 1 indicates zoomed in,\n    #'   and less than 1 indicates zoomed out.\n    #' @param mobile Whether to emulate mobile device. When `TRUE`, Chrome\n    #'   updates settings to emulate browsing on a mobile phone; this includes\n    #'   viewport meta tag, overlay scrollbars, text autosizing and more. The\n    #'   default is `FALSE`.\n    #' @param wait_ If `FALSE`, return a [promises::promise()] of a new\n    #'   `ChromoteSession` object. Otherwise, block during initialization, and\n    #'   return a `ChromoteSession` object directly.\n    #'\n    #' @return Invisibly returns the previous viewport dimensions so that you\n    #'   can restore the viewport size, if desired.\n    set_viewport_size = function(\n      width,\n      height,\n      zoom = NULL,\n      mobile = NULL,\n      wait_ = TRUE\n    ) {\n      check_number_whole(width)\n      check_number_whole(height)\n      check_number_decimal(zoom, allow_null = TRUE)\n      check_bool(mobile, allow_null = TRUE)\n      check_bool(wait_)\n\n      prev_bounds <- NULL\n\n      p <- self$get_viewport_size(wait_ = FALSE)$then(function(value) {\n        prev_bounds <<- value\n\n        self$Emulation$setDeviceMetricsOverride(\n          width = width,\n          height = height,\n          deviceScaleFactor = zoom %||% private$pixel_ratio %||% 0,\n          mobile = mobile %||% private$is_mobile %||% FALSE,\n          wait_ = FALSE\n        )\n      })$then(function(value) {\n        prev_bounds\n      })\n\n      if (wait_) invisible(self$wait_for(p)) else p\n    },\n\n    #' @description Navigate to a URL and wait for the page to load\n    #'\n    #' This method navigates to a specified URL and waits for the page load\n    #' event to complete. This is a more reliable alternative to directly\n    #' calling `Page$navigate()`, which can return before the page is actually\n    #' loaded. This method also allows for an optional delay after the load\n    #' event has fired, in case the page needs to load additional assets after\n    #' that event.\n    #'\n    #' @param url The URL to navigate to.\n    #' @param ... Additional parameters passed to `Page$navigate()`.\n    #' @param delay Number of seconds to wait after the page load event fires.\n    #' @param callback_ Function to call when the page load event fires.\n    #' @param error_ Function to call if an error occurs during navigation.\n    #' @param timeout_ Maximum time in seconds to wait for the page load event\n    #'   (defaults to session's `default_timeout``).\n    #' @param wait_ If `FALSE`, returns a promise that resolves when navigation\n    #'   is complete. If `TRUE` (default), blocks until navigation is complete.\n    #'\n    #' @return If `wait_` is TRUE, returns invisible(NULL). If wait_ is FALSE,\n    #'   returns a promise that resolves when navigation is complete. The\n    #'   promise resolves with the value from the navigate command.\n    #'\n    #' @examples \\dontrun{\n    #' # Basic navigation\n    #' b$go_to(\"https://www.r-project.org\")\n    #'\n    #' # Navigation with delay\n    #' b$go_to(\"https://www.r-project.org\", delay = 2)\n    #'\n    #' # Asynchronous navigation\n    #' p <- b$go_to(\"https://www.r-project.org\", wait_ = FALSE)\n    #' p$then(function(value) print(\"Navigation complete!\"))\n    #' }\n    go_to = function(\n      url,\n      ...,\n      delay = 0,\n      callback_ = NULL,\n      error_ = NULL,\n      timeout_ = self$default_timeout,\n      wait_ = TRUE\n    ) {\n      p <- self$Page$loadEventFired(\n        callback_ = callback_,\n        timeout_ = timeout_,\n        wait_ = FALSE\n      )\n      result <- self$Page$navigate(url, ..., error_ = error_, wait_ = FALSE)\n\n      if (delay > 0) {\n        # After loadEventFired, wait `delay` seconds.\n        p <- p$then(function(value) {\n          promise(function(resolve, reject) {\n            later(function() resolve(result), delay)\n          })\n        })\n      }\n\n      if (wait_) invisible(self$wait_for(p)) else p\n    },\n\n    #' @description Take a PNG screenshot\n    #'\n    #' ## Examples\n    #'\n    #' ```r\n    #' # Create a new `ChromoteSession` object.\n    #' b <- ChromoteSession$new()\n    #'\n    #' # Navigate to page\n    #' b$go_to(\"http://www.r-project.org/\")\n    #'\n    #' # Take screenshot\n    #' tmppngfile <- tempfile(fileext = \".png\")\n    #' is_interactive <- interactive() # Display screenshot if interactive\n    #' b$screenshot(tmppngfile, show = is_interactive)\n    #'\n    #' # Show screenshot file info\n    #' unlist(file.info(tmppngfile))\n    #'\n    #'\n    #' # Take screenshot using a selector\n    #' sidebar_file <- tempfile(fileext = \".png\")\n    #' b$screenshot(sidebar_file, selector = \".sidebar\", show = is_interactive)\n    #'\n    #' # ----------------------------\n    #' # Take screenshots in parallel\n    #'\n    #' urls <- c(\n    #'   \"https://www.r-project.org/\",\n    #'   \"https://github.com/\",\n    #'   \"https://news.ycombinator.com/\"\n    #' )\n    #' # Helper method that:\n    #' # 1. Navigates to the given URL\n    #' # 2. Waits for the page loaded event to fire\n    #' # 3. Takes a screenshot\n    #' # 4. Prints a message\n    #' # 5. Close the ChromoteSession\n    #' screenshot_p <- function(url, filename = NULL) {\n    #'   if (is.null(filename)) {\n    #'     filename <- gsub(\"^.*://\", \"\", url)\n    #'     filename <- gsub(\"/\", \"_\", filename)\n    #'     filename <- gsub(\"\\\\.\", \"_\", filename)\n    #'     filename <- sub(\"_$\", \"\", filename)\n    #'     filename <- paste0(filename, \".png\")\n    #'   }\n    #'\n    #'   b2 <- b$new_session()\n    #'   b2$go_to(url, wait_ = FALSE)$\n    #'     then(function(value) {\n    #'       b2$screenshot(filename, wait_ = FALSE)\n    #'     })$\n    #'     then(function(value) {\n    #'       message(filename)\n    #'     })$\n    #'     finally(function() {\n    #'       b2$close()\n    #'     })\n    #' }\n    #'\n    #' # Take multiple screenshots simultaneously\n    #' ps <- lapply(urls, screenshot_p)\n    #' pa <- promises::promise_all(.list = ps)$then(function(value) {\n    #'   message(\"Done!\")\n    #' })\n    #'\n    #' # Block the console until the screenshots finish (optional)\n    #' b$wait_for(pa)\n    #' #> www_r-project_org.png\n    #' #> github_com.png\n    #' #> news_ycombinator_com.png\n    #' #> Done!\n    #' ```\n    #'\n    #' @param filename File path of where to save the screenshot. The format of\n    #'   the screenshot is inferred from the file extension; use\n    #'   `options = list(format = \"jpeg\")` to manually choose the format. See\n    #'   [`Page.captureScreenshot`](https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-captureScreenshot)\n    #'   for supported formats; at the time of this release the format options\n    #'   were `\"png\"` (default), `\"jpeg\"`, or `\"webp\"`.\n    #' @param selector CSS selector to use for the screenshot.\n    #' @param cliprect An unnamed vector or list containing values for `top`,\n    #'   `left`, `width`, and `height`, in that order. See\n    #' [`Page.Viewport`](https://chromedevtools.github.io/devtools-protocol/tot/Page/#type-Viewport)\n    #' for more information. If provided, `selector` and `expand` will be\n    #' ignored. To provide a scale, use the `scale` parameter.\n    #' @param region CSS region to use for the screenshot.\n    #' @param expand Extra pixels to expand the screenshot. May be a single\n    #' value or a numeric vector of top, right, bottom, left values.\n    #' @param scale Page scale factor\n    #' @param show If `TRUE`, the screenshot will be displayed in the viewer.\n    #' @param delay The number of seconds to wait before taking the screenshot\n    #' after resizing the page. For complicated pages, this may need to be\n    #' increased.\n    #' @param options Additional options passed to\n    #'   [`Page.captureScreenshot`](https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-captureScreenshot).\n    #' @param wait_ If `FALSE`, return a [promises::promise()] that will resolve\n    #' when the `ChromoteSession` has saved the screenshot. Otherwise, block\n    #' until the `ChromoteSession` has saved the screenshot.\n    screenshot = function(\n      filename = \"screenshot.png\",\n      selector = \"html\",\n      cliprect = NULL,\n      region = c(\"content\", \"padding\", \"border\", \"margin\"),\n      expand = NULL,\n      scale = 1,\n      show = FALSE,\n      delay = 0.5,\n      options = list(),\n      wait_ = TRUE\n    ) {\n      chromote_session_screenshot(\n        self,\n        private,\n        filename = filename,\n        selector = selector,\n        cliprect = cliprect,\n        region = region,\n        expand = expand,\n        scale = scale,\n        show = show,\n        delay = delay,\n        options = options,\n        wait_ = wait_\n      )\n    },\n\n    #' @description Take a PDF screenshot\n    #'\n    #' ## Examples\n    #'\n    #' ```r\n    #' # Create a new `ChromoteSession` object.\n    #' b <- ChromoteSession$new()\n    #'\n    #' # Navigate to page\n    #' b$go_to(\"http://www.r-project.org/\")\n    #'\n    #' # Take screenshot\n    #' tmppdffile <- tempfile(fileext = \".pdf\")\n    #' b$screenshot_pdf(tmppdffile)\n    #'\n    #' # Show PDF file info\n    #' unlist(file.info(tmppdffile))\n    #' ```\n    #'\n    #' @param filename File path of where to save the screenshot.\n    #' @param pagesize A single character value in the set `\"letter\"`,\n    #' `\"legal\"`, `\"tabloid\"`, `\"ledger\"` and `\"a0\"` through `\"a1\"`. Or a\n    #' numeric vector `c(width, height)` specifying the page size.\n    #' @param margins A numeric vector `c(top, right, bottom, left)` specifying\n    #' the page margins.\n    #' @param units Page and margin size units. Either `\"in\"` or `\"cm\"` for\n    #' inches and centimeters respectively.\n    #' @param landscape Paper orientation.\n    #' @param display_header_footer Display header and footer.\n    #' @param print_background Print background graphics.\n    #' @param scale Page scale factor.\n    #' @param wait_ If `FALSE`, return a [promises::promise()] that will resolve\n    #' when the `ChromoteSession` has saved the screenshot. Otherwise, block\n    #' until the `ChromoteSession` has saved the screnshot.\n    screenshot_pdf = function(\n      filename = \"screenshot.pdf\",\n      pagesize = \"letter\",\n      margins = 0.5,\n      units = c(\"in\", \"cm\"),\n      landscape = FALSE,\n      display_header_footer = FALSE,\n      print_background = FALSE,\n      scale = 1,\n      wait_ = TRUE\n    ) {\n      chromote_session_screenshot_pdf(\n        self,\n        private,\n        filename = filename,\n        pagesize = pagesize,\n        margins = margins,\n        units = units,\n        landscape = landscape,\n        display_header_footer = display_header_footer,\n        print_background = print_background,\n        scale = scale,\n        wait_ = wait_\n      )\n    },\n\n    #' @description Create a new tab / window\n    #'\n    #' ## Examples\n    #'\n    #' ```r\n    #' b1 <- ChromoteSession$new()\n    #' b1$go_to(\"http://www.google.com\")\n    #' b2 <- b1$new_session()\n    #' b2$go_to(\"http://www.r-project.org/\")\n    #' b1$Runtime$evaluate(\"window.location\", returnByValue = TRUE)$result$value$href\n    #' #> [1] \"https://www.google.com/\"\n    #' b2$Runtime$evaluate(\"window.location\", returnByValue = TRUE)$result$value$href\n    #' #> [1] \"https://www.r-project.org/\"\n    #' ```\n    #'\n    #' @param width,height Width and height of the new window.\n    #' @param wait_ If `FALSE`, return a [promises::promise()] that will resolve\n    #' when the `ChromoteSession` has created a new session. Otherwise, block\n    #' until the `ChromoteSession` has created a new session.\n    new_session = function(\n      width = 992,\n      height = 1323,\n      targetId = NULL,\n      wait_ = TRUE\n    ) {\n      create_session(\n        chromote = self$parent,\n        width = width,\n        height = height,\n        targetId = targetId,\n        wait_ = wait_\n      )\n    },\n\n    #' @description\n    #' Retrieve the session id\n    get_session_id = function() {\n      private$session_id\n    },\n\n    #' @description\n    #' Create a new session that connects to the same target (i.e. page)\n    #' as this session. This is useful if the session has been closed but the target still\n    #' exists.\n    respawn = function() {\n      if (!private$target_is_active) {\n        stop(\"Can't respawn session; target has been closed.\")\n      }\n\n      create_session(\n        chromote = self$parent,\n        targetId = private$target_id,\n        auto_events = private$auto_events\n      )\n    },\n\n    #' @description\n    #' Retrieve the target id\n    get_target_id = function() {\n      private$target_id\n    },\n\n    #' @description\n    #' Wait for a Chromote Session to finish. This method will block the R\n    #' session until the provided promise resolves. The loop from\n    #' `$get_child_loop()` will only advance just far enough for the promise to\n    #' resolve.\n    #'\n    #' ## Examples\n    #'\n    #' ```r\n    #' b <- ChromoteSession$new()\n    #'\n    #' # Async with promise\n    #' p <- b$Browser$getVersion(wait_ = FALSE)\n    #' p$then(str)\n    #'\n    #' # Async with callback\n    #' b$Browser$getVersion(wait_ = FALSE, callback_ = str)\n    #' ```\n    #'\n    #' @param p A promise to resolve.\n    wait_for = function(p) {\n      self$parent$wait_for(p)\n    },\n\n    #' @description\n    #' Send a debug log message to the parent [Chromote] object\n    #'\n    #' ## Examples\n    #'\n    #' ```r\n    #' b <- ChromoteSession$new()\n    #' b$parent$debug_messages(TRUE)\n    #' b$go_to(\"https://www.r-project.org/\")\n    #' #> SEND {\"method\":\"Page.navigate\",\"params\":{\"url\":\"https://www.r-project.org/\"}| __truncated__}\n    #' # Turn off debug messages\n    #' b$parent$debug_messages(FALSE)\n    #' ```\n    #'\n    #' @param ... Arguments pasted together with `paste0(..., collapse = \"\")`.\n    debug_log = function(...) {\n      self$parent$debug_log(...)\n    },\n\n    #' @description\n    #' \\pkg{later} loop.\n    #'\n    #' For expert async usage only.\n    get_child_loop = function() {\n      self$parent$get_child_loop()\n    },\n\n    #' @description\n    #' Send command through Chrome DevTools Protocol.\n    #'\n    #' For expert use only.\n    #' @param msg A JSON-serializable list containing `method`, and `params`.\n    #' @param callback Method to run when the command finishes successfully.\n    #' @param error Method to run if an error occurs.\n    #' @param timeout Number of milliseconds for Chrome DevTools Protocol\n    #' execute a method.\n    send_command = function(\n      msg,\n      callback = NULL,\n      error = NULL,\n      timeout = NULL\n    ) {\n      self$check_active()\n      self$parent$send_command(\n        msg,\n        callback,\n        error,\n        timeout,\n        sessionId = private$session_id\n      )\n    },\n\n    #' @description\n    #' Resolved `auto_events` value.\n    #'\n    #' For internal use only.\n    get_auto_events = function() {\n      if (!is.null(private$auto_events)) {\n        private$auto_events\n      } else {\n        self$parent$get_auto_events()\n      }\n    },\n\n    #' @description\n    #' Set or retrieve the `enable` command arguments for a domain. These\n    #' arguments are used for the `enable` command that is called for a domain,\n    #' e.g. `Fetch$enable()`, when accessing an event method.\n    #'\n    #' @examples\n    #' if (interactive()) {\n    #'   b <- ChromoteSession$new(\n    #'     auto_events_enable_args = list(\n    #'       Fetch = list(handleAuthRequests = TRUE)\n    #'     )\n    #'   )\n    #'\n    #'   # Get current `Fetch.enable` args\n    #'   b$auto_events_enable_args(\"Fetch\")\n    #'\n    #'   # Update the `Fetch.enable` args\n    #'   b$auto_events_enable_args(\"Fetch\", handleAuthRequests = FALSE)\n    #'\n    #'   # Reset `Fetch.enable` args\n    #'   b$auto_events_enable_args(\"Fetch\", NULL)\n    #' }\n    #'\n    #' @param domain A command domain, e.g. `\"Fetch\"`.\n    #' @param ... Arguments to use for auto-events for the domain. If not\n    #'   provided, returns the argument values currently in place for the\n    #'   domain. Use `NULL` to clear the enable arguments for a domain.\n    auto_events_enable_args = function(domain, ...) {\n      dots <- dots_list(..., .named = TRUE)\n\n      if (length(dots) == 0) {\n        return(get_auto_events_enable_args(private, domain, self$parent))\n      }\n\n      set_auto_events_enable_args(self, private, domain, dots)\n    },\n\n    #' @description\n    #' Immediately call all event callback methods.\n    #'\n    #' For internal use only.\n    #' @param event A single event string\n    #' @param params A list of parameters to pass to the event callback methods.\n    invoke_event_callbacks = function(event, params) {\n      private$event_manager$invoke_event_callbacks(event, params)\n    },\n\n    #' @description Mark a session, and optionally, the underlying target,\n    #'   as closed. For internal use only.\n    #' @param target_closed Has the underlying target been closed as well as the\n    #'   active debugging session?\n    mark_closed = function(target_closed) {\n      private$session_is_active <- FALSE\n      private$target_is_active <- !target_closed\n    },\n\n    #' @description Retrieve active status\n    #' Once initialized, the value returned is `TRUE`. If `$close()` has been\n    #' called, this value will be `FALSE`.\n    is_active = function() {\n      private$session_is_active &&\n        private$target_is_active &&\n        self$parent$is_active()\n    },\n\n    #' @description Check that a session is active, erroring if not.\n    check_active = function() {\n      if (self$is_active()) {\n        return()\n      }\n\n      if (private$target_is_active) {\n        abort(\n          c(\n            \"Session has been closed.\",\n            i = \"Call session$respawn() to create a new session that connects to the same target.\"\n          )\n        )\n      } else {\n        abort(\"Session and underlying target have been closed.\")\n      }\n    },\n\n    #' @description Initial promise\n    #'\n    #' For internal use only.\n    get_init_promise = function() {\n      private$init_promise_\n    },\n\n    #' @description Summarise the current state of the object.\n    #' @param verbose The print method defaults to a brief summary\n    #'   of the most important debugging info; use `verbose = TRUE` tp\n    #'   see the complex R6 object.\n    #' @param ... Passed on to `format()` when `verbose` = TRUE\n    print = function(..., verbose = FALSE) {\n      if (verbose) {\n        cat(format(self, ...), sep = \"\\n\")\n      } else {\n        if (self$is_active()) {\n          state <- \"session + target active\"\n        } else if (private$target_is_active) {\n          state <- \"target active\"\n        } else {\n          state <- \"closed\"\n        }\n\n        cat_line(\"<ChromoteSession> (\", state, \")\")\n        if (self$is_active()) cat_line(\"  Session ID: \", self$get_session_id())\n        if (private$target_is_active)\n          cat_line(\"   Target ID: \", self$get_target_id())\n\n        browser <- self$parent$get_browser()\n        if (browser$is_local()) {\n          cat_line(\n            \"  Parent PID: \",\n            self$parent$get_browser()$get_process()$get_pid()\n          )\n        } else {\n          cat_line(\n            \" Remote Host: \",\n            sprintf(\"http://%s:%s\", browser$get_host(), browser$get_port())\n          )\n        }\n      }\n      invisible(self)\n    },\n\n    #' @field parent [`Chromote`] object\n    parent = NULL,\n    #' @field default_timeout Default timeout in seconds for \\pkg{chromote} to\n    #' wait for a Chrome DevTools Protocol response.\n    default_timeout = NULL,\n    #' @field protocol Dynamic protocol implementation. For expert use only!\n    protocol = NULL\n  ),\n\n  private = list(\n    session_id = NULL,\n    target_id = NULL,\n    session_is_active = NULL,\n    target_is_active = NULL,\n    event_manager = NULL,\n    auto_events = NULL,\n    init_promise_ = NULL,\n\n    # Updated when `Emulation.setDeviceMetricsOverride` is called\n    is_mobile = NULL,\n    pixel_ratio = NULL,\n\n    get_pixel_ratio = function() {\n      if (!is.null(private$pixel_ratio)) {\n        promise_resolve(private$pixel_ratio)\n      } else {\n        self$Runtime$evaluate(\"window.devicePixelRatio\", wait_ = FALSE)$then(\n          function(value) {\n            (private$pixel_ratio <- value$result$value)\n          }\n        )\n      }\n    },\n\n    register_event_listener = function(event, callback = NULL, timeout = NULL) {\n      self$check_active()\n      private$event_manager$register_event_listener(event, callback, timeout)\n    }\n  )\n)\n\n# Wrapper around ChromoteSession$new() that can return a promise\ncreate_session <- function(\n  chromote = default_chromote_object(),\n  width = 992,\n  height = 1323,\n  targetId = NULL,\n  wait_ = TRUE,\n  auto_events = NULL\n) {\n  session <- ChromoteSession$new(\n    parent = chromote,\n    width = width,\n    height = height,\n    targetId,\n    auto_events = auto_events,\n    wait_ = wait_\n  )\n\n  if (wait_) {\n    session\n  } else {\n    # ChromoteSession$new() must return a ChromoteSession object so we need a\n    # side-channel to return a promise\n    session$get_init_promise()\n  }\n}\n"
  },
  {
    "path": "R/event_manager.R",
    "content": "EventManager <- R6Class(\n  \"EventManager\",\n  public = list(\n    initialize = function(session) {\n      private$session <- session\n\n      if (length(session$protocol) == 0) {\n        stop(\"Session object must have non-empty protocol field.\")\n      }\n\n      # Find out which domains require the <domain>.enable command to enable\n      # event notifications.\n      private$event_enable_domains <- lapply(\n        session$protocol,\n        function(domain) {\n          is.function(domain$enable)\n        }\n      )\n\n      private$event_callbacks <- fastmap()\n    },\n\n    register_event_listener = function(event, callback = NULL, timeout = NULL) {\n      domain <- find_domain(event)\n\n      # Note: If callback is specified, then timeout is ignored. Also, returns\n      # a function for deregistering the callback, instead of a promise.\n      if (!is.null(callback)) {\n        deregister_callback_fn <- private$add_event_callback(\n          event,\n          callback,\n          once = FALSE\n        )\n        return(invisible(deregister_callback_fn))\n      }\n\n      deregister_callback_fn <- NULL\n      p <- promise(function(resolve, reject) {\n        deregister_callback_fn <<- private$add_event_callback(\n          event,\n          resolve,\n          once = TRUE\n        )\n      })\n\n      if (!is.null(timeout) && !is.infinite(timeout)) {\n        # !!! TODO: Fix loop !!!\n        p <- promise_timeout(\n          p,\n          timeout,\n          loop = private$session$get_child_loop(),\n          timeout_message = paste0(\n            \"Chromote: timed out waiting for event \",\n            event\n          )\n        )\n      }\n\n      p <- p$finally(function() {\n        deregister_callback_fn()\n      })\n      p\n    },\n\n    invoke_event_callbacks = function(event, params) {\n      callbacks <- private$event_callbacks$get(event)\n      if (is.null(callbacks) || callbacks$size() == 0) return()\n\n      callbacks$invoke(params)\n    },\n\n    remove_event_callbacks = function(event) {\n      # Removes ALL callbacks for a given event. In the future it might be\n      # useful to implement finer control.\n      private$event_callbacks$remove(event)\n    }\n  ),\n\n  private = list(\n    # The ChromoteSession or Chromote object that owns this EventManager.\n    session = NULL,\n    event_callbacks = NULL,\n    # For keeping count of the number of callbacks for each domain; if\n    # auto_events is TRUE, then when the count goes from 0 to 1 or 1 to 0 for\n    # a given domain, it will automatically enable or disable events for that\n    # domain.\n    event_callback_counts = list(),\n\n    # Some domains require a <domain>.event command to enable event\n    # notifications, others do not. (Not really sure why.)\n    event_enable_domains = NULL,\n\n    add_event_callback = function(event, callback, once) {\n      if (!private$event_callbacks$has(event)) {\n        private$event_callbacks$set(event, Callbacks$new())\n      }\n\n      if (once) {\n        orig_callback <- callback\n        callback <- function(...) {\n          tryCatch(\n            orig_callback(...),\n            finally = deregister_and_dec()\n          )\n        }\n      }\n\n      deregister_callback <- private$event_callbacks$get(event)$add(callback)\n\n      domain <- find_domain(event)\n      private$inc_event_callback_count(domain)\n\n      # We'll wrap deregister_callback in another function which also keeps\n      # count to the number of callbacks for the domain.\n      deregister_called <- FALSE\n      deregister_and_dec <- function() {\n        # Make sure that if this is called multiple times that it doesn't keep\n        # having effects.\n        if (deregister_called) return()\n        deregister_called <<- TRUE\n\n        deregister_callback()\n        private$dec_event_callback_count(domain)\n      }\n\n      deregister_and_dec\n    },\n\n    inc_event_callback_count = function(domain) {\n      if (is.null(private$event_callback_counts[[domain]])) {\n        private$event_callback_counts[[domain]] <- 0\n      }\n\n      private$event_callback_counts[[domain]] <-\n        private$event_callback_counts[[domain]] + 1\n\n      private$session$debug_log(\n        \"Callbacks for \",\n        domain,\n        \"++: \",\n        private$event_callback_counts[[domain]]\n      )\n\n      # If we're doing auto events and we're going from 0 to 1, enable events\n      # for this domain. (Some domains do not require or have an .enable\n      # method.)\n      if (\n        private$session$get_auto_events() &&\n          private$event_callback_counts[[domain]] == 1 &&\n          isTRUE(private$event_enable_domains[[domain]])\n      ) {\n        private$session$debug_log(\"Enabling events for \", domain)\n        args <- private$session$auto_events_enable_args(domain)\n        exec(\n          private$session[[domain]]$enable,\n          !!!args\n        )\n      }\n\n      invisible(private$event_callback_counts[[domain]])\n    },\n\n    dec_event_callback_count = function(domain) {\n      private$event_callback_counts[[domain]] <-\n        private$event_callback_counts[[domain]] - 1\n\n      private$session$debug_log(\n        \"Callbacks for \",\n        domain,\n        \"--: \",\n        private$event_callback_counts[[domain]]\n      )\n      # If we're doing auto events and we're going from 1 to 0, disable\n      # enable events for this domain.\n      if (\n        private$session$get_auto_events() &&\n          private$event_callback_counts[[domain]] == 0 &&\n          isTRUE(private$event_enable_domains[[domain]])\n      ) {\n        private$session$debug_log(\"Disabling events for \", domain)\n        private$session[[domain]]$disable()\n      }\n\n      invisible(private$event_callback_counts[[domain]])\n    }\n  )\n)\n\n# These functions power `$auto_events_enable_args()` for both `Chromote` and\n# `ChromoteSession`.\nget_auto_events_enable_args <- function(private, domain, parent = NULL) {\n  session_args <- private$auto_events_enable_args[[domain]]\n  if (!is.null(session_args) || is.null(parent)) {\n    return(session_args)\n  }\n\n  return(parent$auto_events_enable_args(domain))\n}\n\nset_auto_events_enable_args <- function(self, private, domain, dots) {\n  # Set enable args for the domain ----\n  if (identical(dots, list(\"NULL\" = NULL))) {\n    # Unset args with `$auto_events_enable_args(domain, NULL)`\n    dots <- NULL\n  }\n\n  if (!is_function(self[[domain]]$enable)) {\n    cli::cli_abort(\n      \"{.field {domain}} does not have an {.field enable} method.\",\n      call = parent.frame()\n    )\n  }\n\n  known_args <- names(fn_fmls(self[[domain]]$enable))\n  unknown_args <- setdiff(names(dots), known_args)\n  if (length(unknown_args)) {\n    cli::cli_abort(\n      c(\n        \"{.field {domain}.enable} does not have {cli::qty(unknown_args)}argument{?s}: {.arg {unknown_args}}.\",\n        \"i\" = \"Available arguments: {.arg {setdiff(known_args, 'wait_')}}\"\n      ),\n      call = parent.frame()\n    )\n  }\n\n  if (\"wait_\" %in% names(dots)) {\n    cli::cli_warn(\n      \"{.arg wait_} cannot be set for {.field {domain}.enable}, ignoring.\",\n      call = parent.frame()\n    )\n    dots[[\"wait_\"]] <- NULL\n  }\n\n  old <- self$auto_events_enable_args(domain)\n  private$auto_events_enable_args[[domain]] <- dots\n  invisible(old)\n}\n"
  },
  {
    "path": "R/import-standalone-obj-type.R",
    "content": "# Standalone file: do not edit by hand\n# Source: <https://github.com/r-lib/rlang/blob/main/R/standalone-obj-type.R>\n# ----------------------------------------------------------------------\n#\n# ---\n# repo: r-lib/rlang\n# file: standalone-obj-type.R\n# last-updated: 2024-02-14\n# license: https://unlicense.org\n# imports: rlang (>= 1.1.0)\n# ---\n#\n# ## Changelog\n#\n# 2024-02-14:\n# - `obj_type_friendly()` now works for S7 objects.\n#\n# 2023-05-01:\n# - `obj_type_friendly()` now only displays the first class of S3 objects.\n#\n# 2023-03-30:\n# - `stop_input_type()` now handles `I()` input literally in `arg`.\n#\n# 2022-10-04:\n# - `obj_type_friendly(value = TRUE)` now shows numeric scalars\n#   literally.\n# - `stop_friendly_type()` now takes `show_value`, passed to\n#   `obj_type_friendly()` as the `value` argument.\n#\n# 2022-10-03:\n# - Added `allow_na` and `allow_null` arguments.\n# - `NULL` is now backticked.\n# - Better friendly type for infinities and `NaN`.\n#\n# 2022-09-16:\n# - Unprefixed usage of rlang functions with `rlang::` to\n#   avoid onLoad issues when called from rlang (#1482).\n#\n# 2022-08-11:\n# - Prefixed usage of rlang functions with `rlang::`.\n#\n# 2022-06-22:\n# - `friendly_type_of()` is now `obj_type_friendly()`.\n# - Added `obj_type_oo()`.\n#\n# 2021-12-20:\n# - Added support for scalar values and empty vectors.\n# - Added `stop_input_type()`\n#\n# 2021-06-30:\n# - Added support for missing arguments.\n#\n# 2021-04-19:\n# - Added support for matrices and arrays (#141).\n# - Added documentation.\n# - Added changelog.\n#\n# nocov start\n\n#' Return English-friendly type\n#' @param x Any R object.\n#' @param value Whether to describe the value of `x`. Special values\n#'   like `NA` or `\"\"` are always described.\n#' @param length Whether to mention the length of vectors and lists.\n#' @return A string describing the type. Starts with an indefinite\n#'   article, e.g. \"an integer vector\".\n#' @noRd\nobj_type_friendly <- function(x, value = TRUE) {\n  if (is_missing(x)) {\n    return(\"absent\")\n  }\n\n  if (is.object(x)) {\n    if (inherits(x, \"quosure\")) {\n      type <- \"quosure\"\n    } else {\n      type <- class(x)[[1L]]\n    }\n    return(sprintf(\"a <%s> object\", type))\n  }\n\n  if (!is_vector(x)) {\n    return(.rlang_as_friendly_type(typeof(x)))\n  }\n\n  n_dim <- length(dim(x))\n\n  if (!n_dim) {\n    if (!is_list(x) && length(x) == 1) {\n      if (is_na(x)) {\n        return(switch(\n          typeof(x),\n          logical = \"`NA`\",\n          integer = \"an integer `NA`\",\n          double =\n            if (is.nan(x)) {\n              \"`NaN`\"\n            } else {\n              \"a numeric `NA`\"\n            },\n          complex = \"a complex `NA`\",\n          character = \"a character `NA`\",\n          .rlang_stop_unexpected_typeof(x)\n        ))\n      }\n\n      show_infinites <- function(x) {\n        if (x > 0) {\n          \"`Inf`\"\n        } else {\n          \"`-Inf`\"\n        }\n      }\n      str_encode <- function(x, width = 30, ...) {\n        if (nchar(x) > width) {\n          x <- substr(x, 1, width - 3)\n          x <- paste0(x, \"...\")\n        }\n        encodeString(x, ...)\n      }\n\n      if (value) {\n        if (is.numeric(x) && is.infinite(x)) {\n          return(show_infinites(x))\n        }\n\n        if (is.numeric(x) || is.complex(x)) {\n          number <- as.character(round(x, 2))\n          what <- if (is.complex(x)) \"the complex number\" else \"the number\"\n          return(paste(what, number))\n        }\n\n        return(switch(\n          typeof(x),\n          logical = if (x) \"`TRUE`\" else \"`FALSE`\",\n          character = {\n            what <- if (nzchar(x)) \"the string\" else \"the empty string\"\n            paste(what, str_encode(x, quote = \"\\\"\"))\n          },\n          raw = paste(\"the raw value\", as.character(x)),\n          .rlang_stop_unexpected_typeof(x)\n        ))\n      }\n\n      return(switch(\n        typeof(x),\n        logical = \"a logical value\",\n        integer = \"an integer\",\n        double = if (is.infinite(x)) show_infinites(x) else \"a number\",\n        complex = \"a complex number\",\n        character = if (nzchar(x)) \"a string\" else \"\\\"\\\"\",\n        raw = \"a raw value\",\n        .rlang_stop_unexpected_typeof(x)\n      ))\n    }\n\n    if (length(x) == 0) {\n      return(switch(\n        typeof(x),\n        logical = \"an empty logical vector\",\n        integer = \"an empty integer vector\",\n        double = \"an empty numeric vector\",\n        complex = \"an empty complex vector\",\n        character = \"an empty character vector\",\n        raw = \"an empty raw vector\",\n        list = \"an empty list\",\n        .rlang_stop_unexpected_typeof(x)\n      ))\n    }\n  }\n\n  vec_type_friendly(x)\n}\n\nvec_type_friendly <- function(x, length = FALSE) {\n  if (!is_vector(x)) {\n    abort(\"`x` must be a vector.\")\n  }\n  type <- typeof(x)\n  n_dim <- length(dim(x))\n\n  add_length <- function(type) {\n    if (length && !n_dim) {\n      paste0(type, sprintf(\" of length %s\", length(x)))\n    } else {\n      type\n    }\n  }\n\n  if (type == \"list\") {\n    if (n_dim < 2) {\n      return(add_length(\"a list\"))\n    } else if (is.data.frame(x)) {\n      return(\"a data frame\")\n    } else if (n_dim == 2) {\n      return(\"a list matrix\")\n    } else {\n      return(\"a list array\")\n    }\n  }\n\n  type <- switch(\n    type,\n    logical = \"a logical %s\",\n    integer = \"an integer %s\",\n    numeric = ,\n    double = \"a double %s\",\n    complex = \"a complex %s\",\n    character = \"a character %s\",\n    raw = \"a raw %s\",\n    type = paste0(\"a \", type, \" %s\")\n  )\n\n  if (n_dim < 2) {\n    kind <- \"vector\"\n  } else if (n_dim == 2) {\n    kind <- \"matrix\"\n  } else {\n    kind <- \"array\"\n  }\n  out <- sprintf(type, kind)\n\n  if (n_dim >= 2) {\n    out\n  } else {\n    add_length(out)\n  }\n}\n\n.rlang_as_friendly_type <- function(type) {\n  switch(\n    type,\n\n    list = \"a list\",\n\n    NULL = \"`NULL`\",\n    environment = \"an environment\",\n    externalptr = \"a pointer\",\n    weakref = \"a weak reference\",\n    S4 = \"an S4 object\",\n\n    name = ,\n    symbol = \"a symbol\",\n    language = \"a call\",\n    pairlist = \"a pairlist node\",\n    expression = \"an expression vector\",\n\n    char = \"an internal string\",\n    promise = \"an internal promise\",\n    ... = \"an internal dots object\",\n    any = \"an internal `any` object\",\n    bytecode = \"an internal bytecode object\",\n\n    primitive = ,\n    builtin = ,\n    special = \"a primitive function\",\n    closure = \"a function\",\n\n    type\n  )\n}\n\n.rlang_stop_unexpected_typeof <- function(x, call = caller_env()) {\n  abort(\n    sprintf(\"Unexpected type <%s>.\", typeof(x)),\n    call = call\n  )\n}\n\n#' Return OO type\n#' @param x Any R object.\n#' @return One of `\"bare\"` (for non-OO objects), `\"S3\"`, `\"S4\"`,\n#'   `\"R6\"`, or `\"S7\"`.\n#' @noRd\nobj_type_oo <- function(x) {\n  if (!is.object(x)) {\n    return(\"bare\")\n  }\n\n  class <- inherits(x, c(\"R6\", \"S7_object\"), which = TRUE)\n\n  if (class[[1]]) {\n    \"R6\"\n  } else if (class[[2]]) {\n    \"S7\"\n  } else if (isS4(x)) {\n    \"S4\"\n  } else {\n    \"S3\"\n  }\n}\n\n#' @param x The object type which does not conform to `what`. Its\n#'   `obj_type_friendly()` is taken and mentioned in the error message.\n#' @param what The friendly expected type as a string. Can be a\n#'   character vector of expected types, in which case the error\n#'   message mentions all of them in an \"or\" enumeration.\n#' @param show_value Passed to `value` argument of `obj_type_friendly()`.\n#' @param ... Arguments passed to [abort()].\n#' @inheritParams args_error_context\n#' @noRd\nstop_input_type <- function(x,\n                            what,\n                            ...,\n                            allow_na = FALSE,\n                            allow_null = FALSE,\n                            show_value = TRUE,\n                            arg = caller_arg(x),\n                            call = caller_env()) {\n  # From standalone-cli.R\n  cli <- env_get_list(\n    nms = c(\"format_arg\", \"format_code\"),\n    last = topenv(),\n    default = function(x) sprintf(\"`%s`\", x),\n    inherit = TRUE\n  )\n\n  if (allow_na) {\n    what <- c(what, cli$format_code(\"NA\"))\n  }\n  if (allow_null) {\n    what <- c(what, cli$format_code(\"NULL\"))\n  }\n  if (length(what)) {\n    what <- oxford_comma(what)\n  }\n  if (inherits(arg, \"AsIs\")) {\n    format_arg <- identity\n  } else {\n    format_arg <- cli$format_arg\n  }\n\n  message <- sprintf(\n    \"%s must be %s, not %s.\",\n    format_arg(arg),\n    what,\n    obj_type_friendly(x, value = show_value)\n  )\n\n  abort(message, ..., call = call, arg = arg)\n}\n\noxford_comma <- function(chr, sep = \", \", final = \"or\") {\n  n <- length(chr)\n\n  if (n < 2) {\n    return(chr)\n  }\n\n  head <- chr[seq_len(n - 1)]\n  last <- chr[n]\n\n  head <- paste(head, collapse = sep)\n\n  # Write a or b. But a, b, or c.\n  if (n > 2) {\n    paste0(head, sep, final, \" \", last)\n  } else {\n    paste0(head, \" \", final, \" \", last)\n  }\n}\n\n# nocov end\n"
  },
  {
    "path": "R/import-standalone-types-check.R",
    "content": "# Standalone file: do not edit by hand\n# Source: <https://github.com/r-lib/rlang/blob/main/R/standalone-types-check.R>\n# ----------------------------------------------------------------------\n#\n# ---\n# repo: r-lib/rlang\n# file: standalone-types-check.R\n# last-updated: 2023-03-13\n# license: https://unlicense.org\n# dependencies: standalone-obj-type.R\n# imports: rlang (>= 1.1.0)\n# ---\n#\n# ## Changelog\n#\n# 2024-08-15:\n# - `check_character()` gains an `allow_na` argument (@martaalcalde, #1724)\n#\n# 2023-03-13:\n# - Improved error messages of number checkers (@teunbrand)\n# - Added `allow_infinite` argument to `check_number_whole()` (@mgirlich).\n# - Added `check_data_frame()` (@mgirlich).\n#\n# 2023-03-07:\n# - Added dependency on rlang (>= 1.1.0).\n#\n# 2023-02-15:\n# - Added `check_logical()`.\n#\n# - `check_bool()`, `check_number_whole()`, and\n#   `check_number_decimal()` are now implemented in C.\n#\n# - For efficiency, `check_number_whole()` and\n#   `check_number_decimal()` now take a `NULL` default for `min` and\n#   `max`. This makes it possible to bypass unnecessary type-checking\n#   and comparisons in the default case of no bounds checks.\n#\n# 2022-10-07:\n# - `check_number_whole()` and `_decimal()` no longer treat\n#   non-numeric types such as factors or dates as numbers.  Numeric\n#   types are detected with `is.numeric()`.\n#\n# 2022-10-04:\n# - Added `check_name()` that forbids the empty string.\n#   `check_string()` allows the empty string by default.\n#\n# 2022-09-28:\n# - Removed `what` arguments.\n# - Added `allow_na` and `allow_null` arguments.\n# - Added `allow_decimal` and `allow_infinite` arguments.\n# - Improved errors with absent arguments.\n#\n#\n# 2022-09-16:\n# - Unprefixed usage of rlang functions with `rlang::` to\n#   avoid onLoad issues when called from rlang (#1482).\n#\n# 2022-08-11:\n# - Added changelog.\n#\n# nocov start\n\n# Scalars -----------------------------------------------------------------\n\n.standalone_types_check_dot_call <- .Call\n\ncheck_bool <- function(x,\n                       ...,\n                       allow_na = FALSE,\n                       allow_null = FALSE,\n                       arg = caller_arg(x),\n                       call = caller_env()) {\n  if (!missing(x) && .standalone_types_check_dot_call(ffi_standalone_is_bool_1.0.7, x, allow_na, allow_null)) {\n    return(invisible(NULL))\n  }\n\n  stop_input_type(\n    x,\n    c(\"`TRUE`\", \"`FALSE`\"),\n    ...,\n    allow_na = allow_na,\n    allow_null = allow_null,\n    arg = arg,\n    call = call\n  )\n}\n\ncheck_string <- function(x,\n                         ...,\n                         allow_empty = TRUE,\n                         allow_na = FALSE,\n                         allow_null = FALSE,\n                         arg = caller_arg(x),\n                         call = caller_env()) {\n  if (!missing(x)) {\n    is_string <- .rlang_check_is_string(\n      x,\n      allow_empty = allow_empty,\n      allow_na = allow_na,\n      allow_null = allow_null\n    )\n    if (is_string) {\n      return(invisible(NULL))\n    }\n  }\n\n  stop_input_type(\n    x,\n    \"a single string\",\n    ...,\n    allow_na = allow_na,\n    allow_null = allow_null,\n    arg = arg,\n    call = call\n  )\n}\n\n.rlang_check_is_string <- function(x,\n                                   allow_empty,\n                                   allow_na,\n                                   allow_null) {\n  if (is_string(x)) {\n    if (allow_empty || !is_string(x, \"\")) {\n      return(TRUE)\n    }\n  }\n\n  if (allow_null && is_null(x)) {\n    return(TRUE)\n  }\n\n  if (allow_na && (identical(x, NA) || identical(x, na_chr))) {\n    return(TRUE)\n  }\n\n  FALSE\n}\n\ncheck_name <- function(x,\n                       ...,\n                       allow_null = FALSE,\n                       arg = caller_arg(x),\n                       call = caller_env()) {\n  if (!missing(x)) {\n    is_string <- .rlang_check_is_string(\n      x,\n      allow_empty = FALSE,\n      allow_na = FALSE,\n      allow_null = allow_null\n    )\n    if (is_string) {\n      return(invisible(NULL))\n    }\n  }\n\n  stop_input_type(\n    x,\n    \"a valid name\",\n    ...,\n    allow_na = FALSE,\n    allow_null = allow_null,\n    arg = arg,\n    call = call\n  )\n}\n\nIS_NUMBER_true <- 0\nIS_NUMBER_false <- 1\nIS_NUMBER_oob <- 2\n\ncheck_number_decimal <- function(x,\n                                 ...,\n                                 min = NULL,\n                                 max = NULL,\n                                 allow_infinite = TRUE,\n                                 allow_na = FALSE,\n                                 allow_null = FALSE,\n                                 arg = caller_arg(x),\n                                 call = caller_env()) {\n  if (missing(x)) {\n    exit_code <- IS_NUMBER_false\n  } else if (0 == (exit_code <- .standalone_types_check_dot_call(\n    ffi_standalone_check_number_1.0.7,\n    x,\n    allow_decimal = TRUE,\n    min,\n    max,\n    allow_infinite,\n    allow_na,\n    allow_null\n  ))) {\n    return(invisible(NULL))\n  }\n\n  .stop_not_number(\n    x,\n    ...,\n    exit_code = exit_code,\n    allow_decimal = TRUE,\n    min = min,\n    max = max,\n    allow_na = allow_na,\n    allow_null = allow_null,\n    arg = arg,\n    call = call\n  )\n}\n\ncheck_number_whole <- function(x,\n                               ...,\n                               min = NULL,\n                               max = NULL,\n                               allow_infinite = FALSE,\n                               allow_na = FALSE,\n                               allow_null = FALSE,\n                               arg = caller_arg(x),\n                               call = caller_env()) {\n  if (missing(x)) {\n    exit_code <- IS_NUMBER_false\n  } else if (0 == (exit_code <- .standalone_types_check_dot_call(\n    ffi_standalone_check_number_1.0.7,\n    x,\n    allow_decimal = FALSE,\n    min,\n    max,\n    allow_infinite,\n    allow_na,\n    allow_null\n  ))) {\n    return(invisible(NULL))\n  }\n\n  .stop_not_number(\n    x,\n    ...,\n    exit_code = exit_code,\n    allow_decimal = FALSE,\n    min = min,\n    max = max,\n    allow_na = allow_na,\n    allow_null = allow_null,\n    arg = arg,\n    call = call\n  )\n}\n\n.stop_not_number <- function(x,\n                             ...,\n                             exit_code,\n                             allow_decimal,\n                             min,\n                             max,\n                             allow_na,\n                             allow_null,\n                             arg,\n                             call) {\n  if (allow_decimal) {\n    what <- \"a number\"\n  } else {\n    what <- \"a whole number\"\n  }\n\n  if (exit_code == IS_NUMBER_oob) {\n    min <- min %||% -Inf\n    max <- max %||% Inf\n\n    if (min > -Inf && max < Inf) {\n      what <- sprintf(\"%s between %s and %s\", what, min, max)\n    } else if (x < min) {\n      what <- sprintf(\"%s larger than or equal to %s\", what, min)\n    } else if (x > max) {\n      what <- sprintf(\"%s smaller than or equal to %s\", what, max)\n    } else {\n      abort(\"Unexpected state in OOB check\", .internal = TRUE)\n    }\n  }\n\n  stop_input_type(\n    x,\n    what,\n    ...,\n    allow_na = allow_na,\n    allow_null = allow_null,\n    arg = arg,\n    call = call\n  )\n}\n\ncheck_symbol <- function(x,\n                         ...,\n                         allow_null = FALSE,\n                         arg = caller_arg(x),\n                         call = caller_env()) {\n  if (!missing(x)) {\n    if (is_symbol(x)) {\n      return(invisible(NULL))\n    }\n    if (allow_null && is_null(x)) {\n      return(invisible(NULL))\n    }\n  }\n\n  stop_input_type(\n    x,\n    \"a symbol\",\n    ...,\n    allow_na = FALSE,\n    allow_null = allow_null,\n    arg = arg,\n    call = call\n  )\n}\n\ncheck_arg <- function(x,\n                      ...,\n                      allow_null = FALSE,\n                      arg = caller_arg(x),\n                      call = caller_env()) {\n  if (!missing(x)) {\n    if (is_symbol(x)) {\n      return(invisible(NULL))\n    }\n    if (allow_null && is_null(x)) {\n      return(invisible(NULL))\n    }\n  }\n\n  stop_input_type(\n    x,\n    \"an argument name\",\n    ...,\n    allow_na = FALSE,\n    allow_null = allow_null,\n    arg = arg,\n    call = call\n  )\n}\n\ncheck_call <- function(x,\n                       ...,\n                       allow_null = FALSE,\n                       arg = caller_arg(x),\n                       call = caller_env()) {\n  if (!missing(x)) {\n    if (is_call(x)) {\n      return(invisible(NULL))\n    }\n    if (allow_null && is_null(x)) {\n      return(invisible(NULL))\n    }\n  }\n\n  stop_input_type(\n    x,\n    \"a defused call\",\n    ...,\n    allow_na = FALSE,\n    allow_null = allow_null,\n    arg = arg,\n    call = call\n  )\n}\n\ncheck_environment <- function(x,\n                              ...,\n                              allow_null = FALSE,\n                              arg = caller_arg(x),\n                              call = caller_env()) {\n  if (!missing(x)) {\n    if (is_environment(x)) {\n      return(invisible(NULL))\n    }\n    if (allow_null && is_null(x)) {\n      return(invisible(NULL))\n    }\n  }\n\n  stop_input_type(\n    x,\n    \"an environment\",\n    ...,\n    allow_na = FALSE,\n    allow_null = allow_null,\n    arg = arg,\n    call = call\n  )\n}\n\ncheck_function <- function(x,\n                           ...,\n                           allow_null = FALSE,\n                           arg = caller_arg(x),\n                           call = caller_env()) {\n  if (!missing(x)) {\n    if (is_function(x)) {\n      return(invisible(NULL))\n    }\n    if (allow_null && is_null(x)) {\n      return(invisible(NULL))\n    }\n  }\n\n  stop_input_type(\n    x,\n    \"a function\",\n    ...,\n    allow_na = FALSE,\n    allow_null = allow_null,\n    arg = arg,\n    call = call\n  )\n}\n\ncheck_closure <- function(x,\n                          ...,\n                          allow_null = FALSE,\n                          arg = caller_arg(x),\n                          call = caller_env()) {\n  if (!missing(x)) {\n    if (is_closure(x)) {\n      return(invisible(NULL))\n    }\n    if (allow_null && is_null(x)) {\n      return(invisible(NULL))\n    }\n  }\n\n  stop_input_type(\n    x,\n    \"an R function\",\n    ...,\n    allow_na = FALSE,\n    allow_null = allow_null,\n    arg = arg,\n    call = call\n  )\n}\n\ncheck_formula <- function(x,\n                          ...,\n                          allow_null = FALSE,\n                          arg = caller_arg(x),\n                          call = caller_env()) {\n  if (!missing(x)) {\n    if (is_formula(x)) {\n      return(invisible(NULL))\n    }\n    if (allow_null && is_null(x)) {\n      return(invisible(NULL))\n    }\n  }\n\n  stop_input_type(\n    x,\n    \"a formula\",\n    ...,\n    allow_na = FALSE,\n    allow_null = allow_null,\n    arg = arg,\n    call = call\n  )\n}\n\n\n# Vectors -----------------------------------------------------------------\n\n# TODO: Figure out what to do with logical `NA` and `allow_na = TRUE`\n\ncheck_character <- function(x,\n                            ...,\n                            allow_na = TRUE,\n                            allow_null = FALSE,\n                            arg = caller_arg(x),\n                            call = caller_env()) {\n\n  if (!missing(x)) {\n    if (is_character(x)) {\n      if (!allow_na && any(is.na(x))) {\n        abort(\n          sprintf(\"`%s` can't contain NA values.\", arg),\n          arg = arg,\n          call = call\n        )\n      }\n\n      return(invisible(NULL))\n    }\n\n    if (allow_null && is_null(x)) {\n      return(invisible(NULL))\n    }\n  }\n\n  stop_input_type(\n    x,\n    \"a character vector\",\n    ...,\n    allow_null = allow_null,\n    arg = arg,\n    call = call\n  )\n}\n\ncheck_logical <- function(x,\n                          ...,\n                          allow_null = FALSE,\n                          arg = caller_arg(x),\n                          call = caller_env()) {\n  if (!missing(x)) {\n    if (is_logical(x)) {\n      return(invisible(NULL))\n    }\n    if (allow_null && is_null(x)) {\n      return(invisible(NULL))\n    }\n  }\n\n  stop_input_type(\n    x,\n    \"a logical vector\",\n    ...,\n    allow_na = FALSE,\n    allow_null = allow_null,\n    arg = arg,\n    call = call\n  )\n}\n\ncheck_data_frame <- function(x,\n                             ...,\n                             allow_null = FALSE,\n                             arg = caller_arg(x),\n                             call = caller_env()) {\n  if (!missing(x)) {\n    if (is.data.frame(x)) {\n      return(invisible(NULL))\n    }\n    if (allow_null && is_null(x)) {\n      return(invisible(NULL))\n    }\n  }\n\n  stop_input_type(\n    x,\n    \"a data frame\",\n    ...,\n    allow_null = allow_null,\n    arg = arg,\n    call = call\n  )\n}\n\n# nocov end\n"
  },
  {
    "path": "R/manage.R",
    "content": "#' Use a specific version of Chrome or related binaries\n#'\n#' @description\n#' `r lifecycle_badge(\"experimental\")`\n#'\n#' This function downloads and sets up a specific version of Chrome, using the\n#' [Google Chrome for Testing builds](https://googlechromelabs.github.io/chrome-for-testing/)\n#' for `chrome`, `chrome-headless-shell` or `chromedriver` for use with\n#' chromote.\n#'\n#' Managed Chrome installations is an experimental feature introduced in\n#' chromote v0.5.0 and was inspired by similar features in\n#' [playwright](https://playwright.dev/).\n#'\n#' @examplesIf rlang::is_interactive()\n#' # Use the latest version of Chrome\n#' local_chrome_version()\n#'\n#' # Use a specific version of chrome-headless-shell\n#' local_chrome_version(\"114.0.5735.90\", binary = \"chrome-headless-shell\")\n#'\n#' @details This function downloads the specified binary, if not already\n#'   available and configures [find_chrome()] to use the specified binary while\n#'   evaluating `code` or within the local scope. It uses the\n#'   \"known-good-versions\" list from the Google Chrome for Testing versions at\n#'   <https://googlechromelabs.github.io/chrome-for-testing/>.\n#'\n#' @param version A character string specifying the version to use. The default\n#'   value is `\"latest-stable\"` to follow the latest stable release of Chrome.\n#'   For robust results, and to avoid frequently downloading new versions of\n#'   Chrome, use a fully qualified version number, e.g. `\"133.0.6943.141\"`.\n#'\n#'   If you specify a partial version, e.g. `\"133\"`, chromote will find the most\n#'   recent release matching that version, preferring to use the latest\n#'   *installed* release that matches the partially-specified version. chromote\n#'   also supports a few special version names:\n#'\n#'   * `\"latest-installed\"`: The latest version currently installed locally in\n#'     chromote's cache. If you don't have any installed versions of the binary,\n#'     chromote uses `\"latest\"`.\n#'   * `\"latest\"`: The most recent Chrome for Testing release, which may be a\n#'     beta or canary release.\n#'   * `\"latest-stable\"`, `\"latest-beta\"`, `\"latest-extended\"`,\n#'     `\"latest-canary\"` or `\"latest-dev\"`: Installs the latest release from one\n#'     of Chrome's version channels, queried from the\n#'     [VersionHistory API](https://developer.chrome.com/docs/web-platform/versionhistory/reference#platform-identifiers).\n#'     `\"latest-stable\"` is the default value of `with_chrome_version()` and\n#'     `local_chrome_version()`.\n#'   * `\"system\"`: Use the system-wide installation of Chrome.\n#'\n#'   Chromote also supports\n#' @param binary A character string specifying which binary to\n#'   use. Must be one of `\"chrome\"`, `\"chrome-headless-shell\"`, or\n#'   `\"chromedriver\"`. Default is `\"chrome\"`.\n#' @param platform A character string specifying the platform. If `NULL`\n#'   (default), the platform will be automatically detected.\n#' @param quiet Whether to print a message indicating which version and binary\n#'   of Chrome is being used. By default, this message is suppressed for\n#'   [with_chrome_version()] and enabled for [local_chrome_version()].\n#' @inheritParams withr::local_envvar\n#' @param ... Ignored, used to require named arguments and for future feature\n#'   expansion.\n#'\n#' @return Temporarily sets the `CHROMOTE_CHROME` environment variable and\n#'   returns the result of the `code` argument.\n#'\n#' @describeIn with_chrome_version Temporarily use a specific version of Chrome\n#'   during the evaluation of `code`.\n#' @export\nwith_chrome_version <- function(\n  version = \"latest-stable\",\n  code,\n  ...,\n  binary = c(\"chrome\", \"chrome-headless-shell\", \"chromedriver\"),\n  platform = NULL,\n  quiet = TRUE\n) {\n  rlang::check_dots_empty()\n\n  local_chrome_version(\n    version = version,\n    binary = binary,\n    platform = platform,\n    quiet = quiet\n  )\n  force(code)\n}\n\n#' @describeIn with_chrome_version Use a specific version of Chrome within the\n#'   current scope.\n#' @export\nlocal_chrome_version <- function(\n  version = \"latest-stable\",\n  binary = c(\"chrome\", \"chrome-headless-shell\", \"chromedriver\"),\n  platform = NULL,\n  ...,\n  quiet = FALSE,\n  .local_envir = parent.frame()\n) {\n  rlang::check_dots_empty()\n\n  if (identical(version, \"system\")) {\n    if (!quiet)\n      cli::cli_inform(\n        \"chromote will now use {.strong the system-wide installation} of Chrome.\"\n      )\n    return(local_chromote_chrome(\"\", .local_envir = .local_envir))\n  }\n\n  binary <- check_binary(binary)\n\n  resolved <- chrome_versions_ensure(\n    version = version,\n    binary = binary,\n    platform = platform\n  )\n\n  if (!quiet && !identical(version, resolved$version)) {\n    cli::cli_inform(\n      \"chromote will now use version {.field {resolved$version}} of {.code {resolved$binary}} for {resolved$platform}.\"\n    )\n  }\n\n  local_chromote_chrome(resolved$path, .local_envir = .local_envir)\n}\n\n#' @param path A direct path to the Chrome (or Chrome-based) binary. See\n#'   [find_chrome()] for details or [chrome_versions_path()] for paths\n#'   from the chromote-managed cache.\n#' @describeIn with_chrome_version Use a specific Chrome, by path, within the\n#'   current scope.\n#' @export\nlocal_chromote_chrome <- function(path, ..., .local_envir = parent.frame()) {\n  rlang::check_dots_empty()\n\n  old_default_chromote_object <-\n    if (has_default_chromote_object()) default_chromote_object() else NULL\n\n  withr::defer(\n    {\n      if (has_default_chromote_object()) {\n        current <- default_chromote_object()\n        current$close()\n      }\n\n      if (is.null(old_default_chromote_object)) {\n        globals$default_chromote <- NULL\n      } else if (old_default_chromote_object$is_alive()) {\n        set_default_chromote_object(old_default_chromote_object)\n      } else {\n        globals$default_chromote <- NULL\n      }\n    },\n    envir = .local_envir\n  )\n\n  # We always create a *new* Chromote process within `local_chromote_chrome()`\n  # that we completely clean up when the exit handlers run. We do this by\n  # unsetting the current chromote default so that next ChromoteSession uses a\n  # new Chromote obj, side-stepping `set_default_chromote_object()` because that\n  # requires a chromote obj that we don't want to create yet.\n  globals$default_chromote <- NULL\n\n  withr::local_envvar(\n    list(CHROMOTE_CHROME = path),\n    .local_envir = .local_envir,\n    action = \"replace\"\n  )\n}\n\n#' @describeIn with_chrome_version Temporarily use a specific Chrome version, by\n#'   path, for the evaluation of `code`.\n#' @export\nwith_chromote_chrome <- function(path, code, ...) {\n  rlang::check_dots_empty()\n  local_chromote_chrome(path)\n  force(code)\n}\n\n.chrome_versions <- new.env(parent = emptyenv())\n\nchrome_get_versions <- function(update_cached = TRUE) {\n  path_json <- download_json_cached(\n    \"https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json\",\n    update_cached = update_cached\n  )\n\n  if (exists(path_json, envir = .chrome_versions)) {\n    return(get(path_json, envir = .chrome_versions))\n  }\n\n  path_rds <- sub(\"\\\\.json$\", \".rds\", path_json)\n\n  if (file.exists(path_rds)) {\n    # Parsing the chrome versions into a tidy data frame takes a little bit, so\n    # if we've already done the parsing we store the data as RDS. If the cached\n    # object is out-of-date, we re-parse and save the data.\n    if (file.info(path_rds)$mtime == file.info(path_json)$mtime) {\n      return(readRDS(path_rds))\n    }\n  }\n\n  res <- jsonlite::fromJSON(path_json, simplifyDataFrame = FALSE)\n\n  res <- res$versions\n\n  res <- lapply(res, function(v) {\n    version <- data.frame(version = v$version, revision = v$revision)\n\n    all_versions <- data.frame()\n\n    for (binary_type in names(v$downloads)) {\n      binary <- do.call(\n        rbind,\n        lapply(v$downloads[[binary_type]], as.data.frame)\n      )\n      binary <- cbind(data.frame(binary = binary_type), binary)\n      binary <- cbind(version, binary)\n      all_versions <- rbind(all_versions, binary)\n    }\n\n    all_versions\n  })\n\n  res <- do.call(rbind, res)\n  class(res) <- c(\"tbl_df\", \"tbl\", \"data.frame\")\n  assign(path_json, res, envir = .chrome_versions)\n\n  saveRDS(res, path_rds)\n  Sys.setFileTime(path_rds, file.info(path_json)$mtime)\n  res\n}\n\n#' List installed or available Chrome binary versions\n#'\n#' @description\n#' `r lifecycle_badge(\"experimental\")`\n#'\n#' By default lists the installed Chrome versions in the [chrome_versions_path_cache()],\n#' or list all Chrome versions available via Google's\n#' [Chrome for Testing](https://googlechromelabs.github.io/chrome-for-testing/)\n#' service.\n#'\n#' Managed Chrome installations is an experimental feature introduced in\n#' chromote v0.5.0 and was inspired by similar features in\n#' [playwright](https://playwright.dev/).\n#'\n#' @examplesIf rlang::is_interactive()\n#' chrome_versions_list()\n#'\n#' @param which Whether to list `\"installed\"` local binaries or to list `\"all\"`\n#'   chrome versions available from online sources.\n#' @param binary A character string specifying which binary to list. Defaults to\n#'   `\"all\"` to show all binaries, or can be one or more of of `\"chrome\"`,\n#'   `\"chrome-headless-shell\"`, or `\"chromedriver\"`.\n#' @param platform A character string specifying the platform(s) to list. If\n#'   `NULL` (default), the platform will be automatically detected, or if\n#'   `\"all\"`, then binaries for all platforms will be listed.\n#'\n#' @returns Returns a [data.frame()] of Chrome for Testing versions with\n#'   columns: `version`, `revision`, `binary`, `platform`, `url` (where the\n#'   binary can be downloaded), and--if `which = \"installed\"`--the local path to\n#'   the binary in the [chrome_versions_path_cache()].\n#'\n#' @export\nchrome_versions_list <- function(\n  which = c(\"installed\", \"all\"),\n  binary = c(\"all\", \"chrome\", \"chrome-headless-shell\", \"chromedriver\"),\n  platform = NULL\n) {\n  which <- rlang::arg_match(which)\n  binary <- check_binary(binary, multiple = TRUE, allow_all = TRUE)\n  platform <- check_platform(platform, multiple = TRUE, allow_all = TRUE)\n\n  versions <- chrome_get_versions(update_cached = which == \"all\")\n  versions <- versions[versions$binary %in% binary, ]\n  versions <- versions[versions$platform %in% platform, ]\n  versions <- versions[\n    order(numeric_version(versions$version), decreasing = TRUE),\n  ]\n\n  if (which == \"all\") {\n    return(versions)\n  }\n\n  installed <- dir(chrome_versions_path_cache(), include.dirs = TRUE)\n  installed <- intersect(installed, unique(versions$version))\n\n  versions <- versions[versions$version %in% installed, ]\n  versions$path <- chrome_versions_path_cache(\n    versions$version,\n    Map(\n      chrome_relative_exe,\n      binary = versions$binary,\n      platform = versions$platform\n    )\n  )\n\n  versions[file.exists(versions$path), ]\n}\n\n#' Chrome versions cache helpers\n#'\n#' @description\n#' `r lifecycle_badge(\"experimental\")`\n#'\n#' These functions help interact with the cache used by \\pkg{chromote}'s for\n#' storing versioned Chrome for Testing binaries:\n#'\n#' * `chrome_versions_path()`: Returns a path or paths to specific Chrome\n#'   binaries in the cache.\n#' * `chrome_versions_add()`: Add a specific version to the Chrome versions\n#'   cache.\n#' * `chrome_versions_remove()`: Remove specific versions and binaries from the\n#'   Chrome cache. The `version`, `binary` and `platform` arguments can each\n#'   take `\"all\"` to remove all installed copies of that version, binary or\n#'   platform.\n#' * `chrome_versions_path_cache()`: Returns the path to the cache directory\n#'   used for Chrome binaries.\n#'\n#' Managed Chrome installations is an experimental feature introduced in\n#' chromote v0.5.0 and was inspired by similar features in\n#' [playwright](https://playwright.dev/).\n#'\n#' @seealso [chrome_versions_list()]\n#'\n#' @param ... Additional path parts.\n#' @param version A character string specifying the version to list, add or\n#'   remove.\n#' @inheritParams chrome_versions_list\n#' @inheritParams with_chrome_version\n#'\n#' @return A character vector of Chrome binary paths.\n#' @name chrome_versions\nNULL\n\n#' @rdname chrome_versions\n#' @export\nchrome_versions_path_cache <- function(...) {\n  chromote_cache_path(\"chrome\", ...)\n}\n\n# Not exported\nchromote_cache_path <- function(...) {\n  cache_base <- normalizePath(\n    tools::R_user_dir(\"chromote\", which = \"cache\"),\n    mustWork = FALSE,\n    winslash = \"/\"\n  )\n  file.path(cache_base, ...)\n}\n\n#' @rdname chrome_versions\n#' @export\nchrome_versions_path <- function(\n  version = \"latest\",\n  binary = \"chrome\",\n  platform = NULL\n) {\n  platform <- check_platform(platform)\n  binary <- check_binary(binary)\n\n  versions <- chrome_versions_list(\n    which = \"installed\",\n    binary = binary,\n    platform = platform\n  )\n\n  version_og <- version\n  version <- match_version(version, versions$version)\n\n  if (is.null(version)) {\n    cli::cli_abort(\n      c(\n        \"Version {.field {version_og}} of {.code {binary}} for {platform} is not installed.\",\n        \"i\" = 'Use {.run chromote::chrome_versions_add(\"{version_og}\", \"{binary}\", \"{platform}\")} to install, or {.run chromote::chrome_versions_list()} to list locally cached versions.'\n      )\n    )\n  }\n\n  versions[versions$version == version, ]$path\n}\n\n#' @rdname chrome_versions\n#' @export\nchrome_versions_add <- function(version, binary, platform = NULL) {\n  res <- chrome_versions_ensure(version, binary, platform)\n\n  res[[\"path\"]]\n}\n\n#' @param ask Whether to ask before removing files.\n#'\n#' @rdname chrome_versions\n#' @export\nchrome_versions_remove <- function(\n  version,\n  binary,\n  platform = NULL,\n  ask = TRUE\n) {\n  force(version)\n  binary <- check_binary(binary, multiple = TRUE, allow_all = TRUE)\n  platform <- check_platform(platform, multiple = TRUE, allow_all = TRUE)\n\n  if (grepl(\"latest|system\", version)) {\n    cli::cli_abort(c(\n      \"{.fn chrome_versions_remove} does not support deleting versions by keyword.\",\n      \"i\" = \"Please use {.run chromote::chrome_versions_list()} to list installed versions.\"\n    ))\n  }\n\n  versions <- chrome_versions_list(\n    \"installed\",\n    binary = binary,\n    platform = platform\n  )\n\n  version <-\n    if (identical(version, \"all\")) {\n      versions$version\n    } else {\n      match_version(version, available_versions = versions$version)\n    }\n\n  # versions is already filtered by binary + platform\n  to_delete <- versions[versions$version %in% version, ]\n\n  dirs_delete <- chrome_versions_path_cache(\n    to_delete$version,\n    paste0(to_delete$binary, \"-\", to_delete$platform)\n  )\n\n  if (length(dirs_delete) == 0) {\n    cli::cli_inform(\"No cached binaries to remove.\")\n    return(invisible())\n  }\n\n  if (!identical(ask, FALSE)) {\n    cli::cli_inform(\n      \"Will remove {length(dirs_delete)} cached version{?s} of chrome:\"\n    )\n    cli::cli_bullets(sprintf(\"{.path %s}\", dirs_delete))\n\n    cli::cli_inform(\"Delete from cache?\")\n    do_delete <- utils::menu(gettext(c(\"Yes\", \"No\", \"Cancel\")))\n    if (do_delete != 1L) {\n      cli::cli_inform(\"Canceled.\")\n      return(invisible(dirs_delete))\n    }\n  }\n\n  for (path_dir in dirs_delete) {\n    path_parent <- dirname(path_dir)\n    if (identical(dir(path_parent, full.names = TRUE), path_dir)) {\n      # This version contains only the binary being removed...\n      unlink(path_parent, recursive = TRUE)\n    } else {\n      unlink(path_dir, recursive = TRUE)\n    }\n  }\n\n  invisible(dirs_delete)\n}\n\nchrome_versions_ensure <- function(\n  version = \"latest\",\n  binary = \"chrome\",\n  platform = NULL,\n  prefer_installed = TRUE\n) {\n  platform <- check_platform(platform)\n  binary <- check_binary(binary)\n  if (length(version) != 1) {\n    cli::cli_abort(\n      \"`version` must be a single string or integer value, not {.val {version}}.\"\n    )\n  }\n\n  requested_latest_installed <- identical(version, \"latest-installed\")\n\n  if (requested_latest_installed) {\n    prefer_installed <- TRUE\n    version <- \"latest\"\n  } else if (identical(version, \"latest\")) {\n    prefer_installed <- FALSE\n  } else if (grepl(\"^latest-\", version)) {\n    version <- chrome_resolve_latest_channel(version, platform)\n    prefer_installed <- TRUE\n  }\n\n  versions <- if (prefer_installed) {\n    chrome_versions_list(\"installed\", binary = binary, platform = platform)\n  } else {\n    chrome_versions_list(\"all\", binary = binary, platform = platform)\n  }\n\n  versions <- versions[\n    versions$binary == binary & versions$platform == platform,\n  ]\n\n  version_og <- version\n  version <- match_version(version, available_versions = versions$version)\n\n  if (is.null(version)) {\n    if (prefer_installed) {\n      return(\n        chrome_versions_ensure(\n          if (requested_latest_installed) \"latest-stable\" else version_og,\n          binary = binary,\n          platform = platform,\n          prefer_installed = FALSE\n        )\n      )\n    }\n    cli::cli_abort(\n      c(\n        \"Version {.field {version_og}} is not a known {.code {binary}} version.\",\n        \"i\" = \"Use {.run [chrome_versions_list()](chromote::chrome_versions_list())} to show all available versions.\"\n      )\n    )\n  }\n\n  url <- versions[versions$version == version, ]$url\n\n  stopifnot(length(url) == 1)\n\n  cache_path <- chrome_versions_path_cache(version)\n  binary_path <- file.path(cache_path, chrome_relative_exe(binary, platform))\n\n  resolved <- list(\n    path = binary_path,\n    version = version,\n    binary = binary,\n    platform = platform\n  )\n\n  if (file.exists(binary_path)) {\n    return(resolved)\n  }\n\n  old <- options(timeout = max(300, getOption(\"timeout\")))\n  on.exit(options(old), add = TRUE)\n\n  cli::cli_progress_step(\n    \"Downloading {.code {binary}} version {.field {version}} for {platform}\"\n  )\n\n  dir.create(cache_path, recursive = TRUE, showWarnings = FALSE)\n  zip_path <- chrome_versions_path_cache(\"chrome.zip\")\n  withr::with_options(list(timeout = max(20 * 60, getOption(\"timeout\"))), {\n    utils::download.file(url, zip_path, mode = \"wb\")\n  })\n\n  zip::unzip(zip_path, exdir = cache_path)\n\n  cli::cli_progress_done()\n\n  if (!file.exists(binary_path)) {\n    cli::cli_abort(\n      c(\n        \"The Chrome binary was not found at the expected path.\",\n        \"x\" = \"Expected {.path {binary_path}}\",\n        \"i\" = \"The downloaded zip was not deleted: {.path {zip_path}}\",\n        \"i\" = \"If the problem persists, please report this issue to {.href [rstudio/chromote](https://github.com/rstudio/chromote/issues/new)}.\"\n      )\n    )\n  }\n\n  if (!ensure_user_exec(binary_path)) {\n    cli::cli_abort(\n      c(\n        \"Extracted {.code {binary}} binary does not have execution permissions.\",\n        \"i\" = \"You may need to manually adjust the permissions of {.path {binary_path}}.\"\n      )\n    )\n  }\n\n  if (binary == \"chrome\" && platform %in% c(\"win32\", \"win64\")) {\n    chrome_install_windows_run_setup(binary_path)\n  }\n\n  unlink(zip_path)\n  resolved\n}\n\nchrome_relative_exe <- function(binary, platform) {\n  check_binary(binary)\n\n  switch(\n    binary,\n    chrome = chrome_relative_exe_chrome(platform),\n    chromedriver = chrome_relative_exe_chromedriver(platform),\n    \"chrome-headless-shell\" = chrome_relative_exe_chrome_headless_shell(\n      platform\n    )\n  )\n}\n\nchrome_relative_exe_chrome_headless_shell <- function(platform) {\n  # chrome-headless-shell: https://github.com/puppeteer/puppeteer/blob/main/packages/browsers/src/browser-data/chrome-headless-shell.ts\n  check_platform(platform)\n  dir_binary <- paste0(\"chrome-headless-shell-\", platform)\n\n  switch(\n    platform,\n    \"mac-x64\" = ,\n    \"mac-arm64\" = ,\n    linux64 = file.path(dir_binary, \"chrome-headless-shell\"),\n    win64 = ,\n    win32 = file.path(dir_binary, \"chrome-headless-shell.exe\")\n  )\n}\n\nchrome_relative_exe_chrome <- function(platform) {\n  # chrome: https://github.com/puppeteer/puppeteer/blob/main/packages/browsers/src/browser-data/chrome.ts\n  check_platform(platform)\n  dir_binary <- paste0(\"chrome-\", platform)\n\n  switch(\n    platform,\n    \"mac-x64\" = ,\n    \"mac-arm64\" = {\n      file.path(\n        dir_binary,\n        \"Google Chrome for Testing.app\",\n        \"Contents\",\n        \"MacOS\",\n        \"Google Chrome for Testing\"\n      )\n    },\n    linux64 = file.path(dir_binary, \"chrome\"),\n    win32 = ,\n    win64 = file.path(dir_binary, \"chrome.exe\")\n  )\n}\n\nchrome_relative_exe_chromedriver <- function(platform) {\n  # chromedriver: https://github.com/puppeteer/puppeteer/blob/main/packages/browsers/src/browser-data/chromedriver.ts\n  check_platform(platform)\n  dir_binary <- paste0(\"chromedriver-\", platform)\n\n  switch(\n    platform,\n    \"mac-x64\" = ,\n    \"mac-arm64\" = ,\n    linux64 = file.path(dir_binary, \"chromedriver\"),\n    win32 = ,\n    win64 = file.path(dir_binary, \"chromedriver.exe\")\n  )\n}\n\nchrome_platforms <- c(\"mac-arm64\", \"mac-x64\", \"linux64\", \"win32\", \"win64\")\nchrome_binaries <- c(\"chrome\", \"chrome-headless-shell\", \"chromedriver\")\n\ncheck_platform <- function(\n  platform = NULL,\n  multiple = FALSE,\n  allow_all = FALSE\n) {\n  if (is.null(platform)) {\n    return(guess_platform())\n  }\n\n  if (allow_all && \"all\" %in% platform) {\n    return(chrome_platforms)\n  }\n\n  rlang::arg_match(platform, chrome_platforms, multiple = multiple)\n}\n\nguess_platform <- function() {\n  os <- Sys.info()[\"sysname\"]\n  arch <- Sys.info()[\"machine\"]\n\n  is_arch_x86_64 <- grepl(\"^x86[_-]64$\", arch)\n\n  if (os == \"Linux\" && is_arch_x86_64) {\n    return(\"linux64\")\n  } else if (os == \"Darwin\") {\n    if (arch == \"arm64\") {\n      return(\"mac-arm64\")\n    } else if (is_arch_x86_64) {\n      return(\"mac-x64\")\n    }\n  } else if (os == \"Windows\") {\n    if (is_arch_x86_64) {\n      return(\"win64\")\n    } else if (arch == \"x86\") {\n      return(\"win32\")\n    }\n  }\n\n  cli::cli_abort(\n    \"Chrome is not available for {.val {os}} (OS) and {.val {arch}} (arch).\"\n  )\n}\n\ncheck_binary <- function(binary, multiple = FALSE, allow_all = FALSE) {\n  if (allow_all && \"all\" %in% binary) {\n    return(chrome_binaries)\n  }\n\n  rlang::arg_match(binary, chrome_binaries, multiple = multiple)\n}\n\nmatch_version <- function(version, available_versions = NULL) {\n  stopifnot(length(version) == 1)\n\n  if (!is.character(version)) {\n    if (as.integer(version) != version) {\n      rlang::abort(\n        \"`version` must be an character version number or an integer.\"\n      )\n    }\n    version <- as.character(version)\n  }\n\n  if (length(available_versions) == 0) {\n    return(NULL)\n  }\n\n  if (is.null(available_versions)) {\n    available_versions <- unique(chrome_get_versions()$version)\n  }\n\n  if (identical(version, \"latest\")) {\n    return(max(numeric_version(available_versions)))\n  }\n\n  available_versions <- numeric_version(unique(available_versions))\n\n  version_parts <- unclass(numeric_version(version))[[1]]\n\n  max_version <- rep(99999, 4)\n  max_version[seq_along(version_parts)] <- version_parts\n  max_version <- numeric_version(paste(max_version, collapse = \".\"))\n\n  min_version <- rep(0, 4)\n  min_version[seq_along(version_parts)] <- version_parts\n  min_version <- numeric_version(paste(min_version, collapse = \".\"))\n\n  available_versions <- available_versions[\n    available_versions <= max_version &\n      available_versions >= min_version\n  ]\n\n  if (length(available_versions) == 0) {\n    return(NULL)\n  }\n\n  max(available_versions)\n}\n\ncurl_fetch_headers <- function(url) {\n  h <- curl::new_handle()\n  curl::handle_setopt(h, nobody = TRUE)\n  req <- curl::curl_fetch_memory(url, handle = h)\n  req_parse_headers(req)\n}\n\nreq_parse_headers <- function(req) {\n  headers <- rawToChar(req$headers)\n  parsed_headers <- strsplit(headers, \"\\r\\n\")[[1]]\n  parsed_headers <- parsed_headers[parsed_headers != \"\"]\n  parsed_headers <- strsplit(parsed_headers, \": \")\n  parsed_headers <- rlang::set_names(\n    lapply(parsed_headers, `[`, 2),\n    sapply(parsed_headers, `[`, 1)\n  )\n\n  parsed_headers\n}\n\nreq_headers_last_modified <- function(headers) {\n  names(headers) <- tolower(names(headers))\n\n  if (!\"last-modified\" %in% names(headers)) {\n    return(NULL)\n  }\n\n  withr::with_locale(new = c(\"LC_TIME\" = \"C\"), {\n    last_modified <- as.POSIXct(\n      headers[[\"last-modified\"]],\n      format = \"%a, %d %b %Y %H:%M:%S GMT\",\n      tz = \"GMT\"\n    )\n    last_modified\n  })\n}\n\nchrome_resolve_latest_channel <- function(\n  channel,\n  platform = guess_platform()\n) {\n  channel <- sub(\"latest-\", \"\", channel)\n\n  path_json <- download_json_cached(\n    chrome_version_history_url(channel, platform),\n    filename = sprintf(\"chrome-version-history_%s_%s.json\", platform, channel)\n  )\n\n  res <- jsonlite::fromJSON(path_json)$versions\n\n  testing_versions <- chrome_versions_list(\"all\", \"chrome\", platform)\n\n  available_versions <- intersect(res$version, testing_versions$version)\n\n  as.character(match_version(\"latest\", available_versions))\n}\n\nchrome_version_history_url <- function(\n  channel = c(\"stable\", \"beta\", \"extended\", \"dev\", \"canary\"),\n  platform = guess_platform()\n) {\n  channel <- rlang::arg_match(channel)\n  platform <- check_platform(platform)\n\n  platform <- switch(\n    platform,\n    win32 = \"win\",\n    win64 = \"win64\",\n    \"mac-x64\" = \"mac\",\n    \"mac-arm64\" = \"mac_arm64\",\n    \"linux64\" = \"linux\"\n  )\n\n  sprintf(\n    \"https://versionhistory.googleapis.com/v1/chrome/platforms/%s/channels/%s/versions\",\n    platform,\n    channel\n  )\n}\n\ndownload_json_cached <- function(url, update_cached = TRUE, filename = NULL) {\n  path_cache <- chromote_cache_path()\n  dir.create(path_cache, showWarnings = FALSE, recursive = TRUE)\n\n  path_local <- file.path(path_cache, filename %||% basename(url))\n\n  # Check if local file exists and get its modified time\n  if (file.exists(path_local)) {\n    if (!update_cached) {\n      return(path_local)\n    }\n\n    local_mtime <- file.info(path_local)$mtime\n\n    is_local_stale <- tryCatch(\n      {\n        # Fetch headers from the server\n        headers <- curl_fetch_headers(url)\n        server_last_modified <- req_headers_last_modified(headers)\n\n        if (!is.null(server_last_modified)) {\n          length(server_last_modified) == 1 &&\n            local_mtime < server_last_modified\n        } else {\n          # otherwise cache for 8 hours\n          (local_mtime + 60 * 60 * 8) < Sys.time()\n        }\n      },\n      error = function(err) {\n        rlang::inform(\n          \"Could not reach Chrome for Testing to update available versions.\",\n          parent = err\n        )\n        FALSE\n      }\n    )\n\n    # Compare local file time with server's last-modified\n    if (!is_local_stale) {\n      # message(\"Source URL not modified, using cached version\")\n      return(path_local)\n    }\n  }\n\n  req <- curl::curl_fetch_memory(url)\n\n  if (!req$status_code == 200) {\n    cli::cli_abort(\n      \"Could not download {.url {url}}. Status code: {.field {req$status_code}}\",\n      status = req$status_code,\n      request = req\n    )\n  }\n\n  # message(\"Source URL was updated, downloading new content\")\n  json_content <- rawToChar(req$content)\n  writeLines(json_content, path_local)\n\n  # Set the local file's modified time to the last-modified\n  last_modified <- req_headers_last_modified(req_parse_headers(req))\n  if (!is.null(last_modified)) {\n    Sys.setFileTime(path_local, last_modified)\n  }\n\n  path_local\n}\n\nensure_user_exec <- function(path) {\n  current_mode <- file.info(path)$mode\n  user_perm <- as.numeric(as.character(current_mode))\n\n  # If user permissions is even, the file is not executable\n  !((user_perm %/% 100) %% 2 == 0)\n}\n\nchrome_install_windows_run_setup <- function(path) {\n  path_setup <- file.path(dirname(path), \"setup.exe\")\n  if (!file.exists(path_setup)) {\n    return()\n  }\n\n  tryCatch(\n    {\n      processx::run(\n        path_setup,\n        args = sprintf(\"--configure-browser-in-directory=%s\", dirname(path))\n      )\n    },\n    error = function(err) {\n      cli::cli_warn(\n        \"Running Chrome's {.field setup.exe} failed, which may not mean anything or it may mean that you need to manually resolve permissions errors.\",\n        parent = err\n      )\n      return()\n    }\n  )\n}\n"
  },
  {
    "path": "R/promises.R",
    "content": "#' @importFrom promises %...>%\n#' @export\npromises::\"%...>%\"\n\n#' @importFrom promises %...!%\n#' @export\npromises::\"%...!%\"\n\n#' @importFrom promises %...T>%\n#' @export\npromises::\"%...T>%\"\n\n#' @importFrom promises %...T!%\n#' @export\npromises::\"%...T!%\"\n\n#' @importFrom magrittr %>%\n#' @export\nmagrittr::\"%>%\"\n\n#' @importFrom magrittr %T>%\n#' @export\nmagrittr::\"%T>%\"\n\n#' @importFrom promises promise\n#' @export\npromises::promise\n\n#' @importFrom promises then\n#' @export\npromises::then\n\n#' @importFrom promises catch\n#' @export\npromises::catch\n\n#' @importFrom promises finally\n#' @export\npromises::finally\n\npromise_timeout <- function(\n  p,\n  timeout,\n  loop = current_loop(),\n  timeout_message = NULL\n) {\n  promise(function(resolve, reject) {\n    cancel_timer <- later_with_interrupt(\n      function() {\n        if (is.null(timeout_message)) {\n          timeout_message <- \"Promise timed out\"\n        }\n\n        reject(timeout_message)\n      },\n      timeout,\n      loop = loop,\n      on_interrupt = function() {\n        reject(\"interrupted\")\n      }\n    )\n\n    p$then(\n      onFulfilled = function(value) {\n        # Timer is no longer needed, so we'll cancel it to free memory.\n        cancel_timer()\n        resolve(value)\n      },\n      onRejected = function(err) {\n        cancel_timer()\n        reject(err)\n      }\n    )\n  })\n}\n"
  },
  {
    "path": "R/protocol.R",
    "content": "#' @import rlang\n\nutils::globalVariables(\n  c(\"self\", \"private\", \"callback_\", \"error_\", \"timeout\", \"timeout_\", \"wait_\")\n)\n\n# Given a protocol spec (essentially, the Chrome DevTools Protocol JSON\n# converted to an R object), returns a list of domains of the DevTools\n# Protocol (like Browser, Page, Runtime). Each domain has a function for each\n# command and event (like Browser$getVersion, Page$navigate, etc). The\n# `protocol` input is the protocol object from the browser, translated from\n# JSON to an R object, and the `env` is the desired environment that is\n# assigned to the the generated functions -- it should be the Chromote\n# object's enclosing environment so that the functions can find `self` and\n# `private`.\nprocess_protocol <- function(protocol, env) {\n  domains <- protocol$domains\n  names(domains) <- vapply(domains, function(d) d$domain, \"\")\n\n  domains <- lapply(domains, function(domain) {\n    commands <- get_items(domain, \"commands\")\n    commands <- lapply(\n      commands,\n      command_to_function,\n      domain_name = domain$domain,\n      env = env\n    )\n\n    events <- get_items(domain, \"events\")\n    events <- lapply(\n      events,\n      event_to_function,\n      domain_name = domain$domain,\n      env = env\n    )\n\n    c(commands, events)\n  })\n\n  domains\n}\n\n# Returns commands or events for a given domain\nget_items <- function(domain, type = c(\"commands\", \"events\")) {\n  type <- match.arg(type)\n  methods <- domain[[type]]\n  if (is.null(methods)) {\n    return(list())\n  } else {\n    names(methods) <- fetch_key_c(methods, \"name\")\n    methods\n  }\n}\n\ncommand_to_function <- function(command, domain_name, env) {\n  new_function(\n    args = gen_command_args(command$parameters),\n    body = gen_command_body(\n      paste0(domain_name, \".\", command$name),\n      command$parameters\n    ),\n    env = env\n  )\n  # TODO:\n  # * Add type-checking\n  # * Cross-reference types for type checking\n}\n\ngen_command_args <- function(params) {\n  args <- lapply(params, function(param) {\n    if (!isTRUE(param$optional)) {\n      missing_arg()\n    } else {\n      NULL\n    }\n  })\n\n  names(args) <- fetch_key_c(params, \"name\")\n  args <- c(\n    args,\n    callback_ = list(NULL),\n    error_ = list(NULL),\n    timeout_ = if (\"timeout\" %in% names(args)) {\n      expr(missing_arg())\n    } else {\n      expr(self$default_timeout)\n    },\n    wait_ = TRUE\n  )\n  args\n}\n\n# Returns a function body for a command.\n# method_name is something like \"Browser.getVersion\"\ngen_command_body <- function(method_name, params) {\n  # Construct expressions for checking missing args\n  required_params <- params[!fetch_key_l(params, \"optional\", default = FALSE)]\n  check_missing_exprs <- lapply(required_params, function(param) {\n    name <- as.symbol(param$name)\n    check_missing <- expr(\n      if (missing(!!name))\n        stop(\"Missing required argument \", !!(expr_text(name)))\n    )\n  })\n\n  timeout_default_expr <-\n    if (\"timeout\" %in% lapply(params, `[[`, \"name\")) {\n      # Set the wall time of chromote to twice that of the execution time.\n      expr({\n        if (is_missing(timeout_)) {\n          timeout_ <-\n            if (is.null(timeout)) {\n              self$default_timeout\n            } else {\n              2 * timeout / 1000\n            }\n        }\n      })\n    } else {\n      expr({\n      })\n    }\n\n  # As of 2025-02-07, it's not possible to query CDP to determine if the value\n  # of `mobile` in the device metrics override, so we need to track its value\n  # through any calls to `Emulation.setDeviceMetricsOverride`.\n  track_device_override_mobile <-\n    if (identical(method_name, \"Emulation.setDeviceMetricsOverride\")) {\n      expr({\n        if (!!sym(\"deviceScaleFactor\") > 0) {\n          private$pixel_ratio <- !!sym(\"deviceScaleFactor\")\n        } else {\n          private$pixel_ratio <- NULL\n        }\n        private$is_mobile <- !!sym(\"mobile\")\n      })\n    } else {\n      expr({}) # fmt: skip\n    }\n\n  # Construct parameters for message\n  param_list <- lapply(params, function(param) {\n    as.symbol(param$name)\n  })\n  names(param_list) <- fetch_key_c(params, \"name\")\n\n  expr({\n    if (!is.null(callback_) && !is.function(callback_))\n      stop(\"`callback_` must be a function or NULL.\")\n\n    if (!is.null(error_) && !is.function(error_))\n      stop(\"`error_` must be a function or NULL.\")\n\n    !!!timeout_default_expr\n    if (!is.null(timeout_) && !is.numeric(timeout_))\n      stop(\"`timeout_` must be a number or NULL.\")\n\n    if (!identical(wait_, TRUE) && !identical(wait_, FALSE))\n      stop(\"`wait_` must be TRUE or FALSE.\")\n\n    # Check for missing non-optional args\n    !!!check_missing_exprs\n\n    !!!track_device_override_mobile\n\n    msg <- list(\n      method = !!method_name,\n      params = drop_nulls(list(!!!param_list))\n    )\n    p <- self$send_command(\n      msg,\n      callback = callback_,\n      error = error_,\n      timeout = timeout_\n    )\n\n    if (wait_) {\n      self$wait_for(p)\n    } else {\n      p\n    }\n  })\n}\n\nevent_to_function <- function(event, domain_name, env) {\n  new_function(\n    args = list(\n      callback_ = NULL,\n      timeout_ = expr(self$default_timeout),\n      wait_ = TRUE\n    ),\n    body = gen_event_body(paste0(domain_name, \".\", event$name)),\n    env = env\n  )\n}\n\n# Returns a function body for registering an event callback.\n# method_name is something like \"Page.loadEventFired\".\ngen_event_body <- function(method_name) {\n  expr({\n    if (!is.null(callback_) && !is.function(callback_))\n      stop(\"`callback_` must be a function or NULL.\")\n\n    if (!is.null(timeout_) && !is.numeric(timeout_))\n      stop(\"`timeout_` must be a number or NULL.\")\n\n    if (!identical(wait_, TRUE) && !identical(wait_, FALSE))\n      stop(\"`wait_` must be TRUE or FALSE.\")\n\n    p <- private$register_event_listener(!!method_name, callback_, timeout_)\n\n    # If callback_ was a function, then because the callback can fire multiple\n    # times, p is not a promise; it is a function for deregistering the\n    # callback.\n    if (!is.null(callback_)) {\n      return(invisible(p))\n    }\n\n    if (wait_) {\n      self$wait_for(p)\n    } else {\n      p\n    }\n  })\n}\n\n# Given a protocol object, reassign the environment for all functions.\nprotocol_reassign_envs <- function(protocol, env) {\n  lapply(protocol, function(domain) {\n    lapply(domain, function(method) {\n      environment(method) <- env\n      method\n    })\n  })\n}\n"
  },
  {
    "path": "R/screenshot.R",
    "content": "chromote_session_screenshot <- function(\n  self,\n  private,\n  filename = \"screenshot.png\",\n  selector = \"html\",\n  cliprect = NULL,\n  region = c(\"content\", \"padding\", \"border\", \"margin\"),\n  expand = NULL,\n  scale = 1,\n  show = FALSE,\n  delay = 0.5,\n  options = list(),\n  wait_ = TRUE\n) {\n  force(filename)\n  force(selector)\n  force(cliprect)\n  force(region)\n  force(expand)\n  force(scale)\n  force(show)\n  force(wait_)\n\n  region = match.arg(region)\n  if (length(filename) == 0 && !show) {\n    stop(\"Cannot have empty filename and show=FALSE\")\n  }\n\n  if (!is.null(cliprect) && !(is.numeric(cliprect) && length(cliprect) == 4)) {\n    stop(\n      \"`cliprect` must be NULL or a numeric vector with 4 elements (for left, top, width, and height).\"\n    )\n  }\n\n  if (is.null(expand)) {\n    expand <- 0\n  }\n  if (\n    !is.numeric(expand) ||\n      !(length(expand) == 1 || length(expand) == 4)\n  ) {\n    stop(\n      \"`expand` must be NULL, or a numeric vector with 1 or 4 elements (for top, right, bottom, left)\"\n    )\n  }\n  if (length(expand) == 1) {\n    expand <- rep(expand, 4)\n  }\n\n  stopifnot(\n    \"`options` must be a list\" = rlang::is_list(options),\n    \"`options` must be named\" = rlang::is_named2(options)\n  )\n  # Set up arg list from defaults & user options to pass to `Page$captureScreenshot`\n  screenshot_arg_defaults <- list(\n    fromSurface = TRUE,\n    captureBeyondViewport = TRUE\n  )\n  screenshot_args <- utils::modifyList(screenshot_arg_defaults, options)\n  if (is.null(screenshot_args$format)) {\n    screenshot_args$format <- screenshot_format(filename)\n  }\n\n  # These vars are used to store information gathered from one step to use\n  # in a later step.\n  image_data <- NULL\n  overall_width <- NULL\n  overall_height <- NULL\n  root_node_id <- NULL\n  pixel_ratio <- NULL\n\n  # Setup stuff for both selector and cliprect code paths.\n  p <- self$Emulation$setScrollbarsHidden(\n    hidden = TRUE,\n    wait_ = FALSE\n  )$then(function(value) {\n    # Get device pixel ratio if unknown\n    private$get_pixel_ratio()\n  })$then(function(value) {\n    pixel_ratio <<- value\n  })$then(function(value) {\n    # Get overall height and width of the <html> root node\n    self$DOM$getDocument(wait_ = FALSE)\n  })$then(function(value) {\n    root_node_id <<- value$root$nodeId\n    self$DOM$querySelector(value$root$nodeId, \"html\", wait_ = FALSE)\n  })$then(function(value) {\n    self$DOM$getBoxModel(value$nodeId, wait_ = FALSE)\n  })$then(function(value) {\n    overall_width <<- value$model$width\n    overall_height <<- value$model$height\n\n    promise(function(resolve, reject) {\n      # Wait `delay` seconds for resize to complete. For complicated apps this may need to be longer.\n      ## TODO: Can we wait for an event instead?\n      later(function() resolve(TRUE), delay)\n    })\n  })\n\n  if (is.null(cliprect)) {\n    # This code path uses the selector instead of cliprect.\n    p <- p$then(function(value) {\n      find_selectors_bounds(self, root_node_id, selector, region)\n    })$then(function(value) {\n      # Note: `expand` values are top, right, bottom, left.\n      xmin <- value$xmin - expand[4]\n      xmax <- value$xmax + expand[2]\n      ymin <- value$ymin - expand[1]\n      ymax <- value$ymax + expand[3]\n\n      # We need to make sure that we don't go beyond the bounds of the\n      # page.\n      xmin <- max(xmin, 0)\n      xmax <- min(xmax, overall_width)\n      ymin <- max(ymin, 0)\n      ymax <- min(ymax, overall_height)\n\n      screenshot_args$clip <- list(\n        x = xmin,\n        y = ymin,\n        width = xmax - xmin,\n        height = ymax - ymin,\n        scale = scale / pixel_ratio\n      )\n      screenshot_args$wait_ <- FALSE\n\n      do.call(self$Page$captureScreenshot, screenshot_args)\n    })$then(function(value) {\n      image_data <<- value\n    })\n  } else {\n    # If cliprect was provided, use it instead of selector\n    p <- p$then(function(value) {\n      screenshot_args$clip <- list(\n        x = cliprect[[1]],\n        y = cliprect[[2]],\n        width = cliprect[[3]],\n        height = cliprect[[4]],\n        scale = scale / pixel_ratio\n      )\n      screenshot_args$wait_ <- FALSE\n\n      do.call(self$Page$captureScreenshot, screenshot_args)\n    })$then(function(value) {\n      image_data <<- value\n    })\n  }\n\n  p <- p$then(function(value) {\n    # Un-hide scrollbars\n    self$Emulation$setScrollbarsHidden(hidden = FALSE, wait_ = FALSE)\n  })$then(function(value) {\n    temp_output <- FALSE\n    if (is.null(filename)) {\n      temp_output <- TRUE\n      filename <- tempfile(\"chromote-screenshot-\", fileext = \".png\")\n      on.exit(unlink(filename))\n    }\n\n    writeBin(jsonlite::base64_dec(image_data$data), filename)\n    if (show) {\n      showimage::show_image(filename)\n    }\n\n    if (temp_output) {\n      invisible()\n    } else {\n      invisible(filename)\n    }\n  })$catch(function(err) {\n    warning(\"An error occurred: \", err)\n  })\n\n  if (wait_) {\n    self$wait_for(p)\n  } else {\n    p\n  }\n}\n\nscreenshot_format <- function(filename) {\n  ext <- strsplit(filename, \".\", fixed = TRUE)[[1]]\n  if (length(ext) < 2) ext <- \"no_ext\"\n  ext <- ext[length(ext)]\n\n  switch(\n    tolower(ext),\n    png = \"png\",\n    jpg = ,\n    jpeg = \"jpeg\",\n    webp = \"webp\",\n    pdf = rlang::abort(\n      \"Use the `screenshot_pdf()` method to capture a PDF screenshot.\"\n    ),\n    no_ext = rlang::abort(\n      sprintf(\n        'Could not guess screenshot format from filename \"%s\". Does the name include a file extension?',\n        filename\n      )\n    ),\n    rlang::abort(\n      sprintf('\"%s\" is not a supported screenshot format.', ext)\n    )\n  )\n}\n\nchromote_session_screenshot_pdf <- function(\n  self,\n  private,\n  filename = \"screenshot.pdf\",\n  pagesize = \"letter\",\n  margins = 0.5,\n  units = c(\"in\", \"cm\"),\n  landscape = FALSE,\n  display_header_footer = FALSE,\n  print_background = FALSE,\n  scale = 1,\n  wait_ = TRUE\n) {\n  force(filename)\n  force(pagesize)\n  force(margins)\n  force(units)\n  force(landscape)\n  force(display_header_footer)\n  force(print_background)\n  force(scale)\n  force(wait_)\n\n  page_sizes <- list(\n    letter = c(8.5, 11),\n    legal = c(8.5, 14),\n    tabloid = c(11, 17),\n    ledger = c(17, 11),\n    a0 = c(33.1, 46.8),\n    a1 = c(23.4, 33.1),\n    a2 = c(16.54, 23.4),\n    a3 = c(11.7, 16.54),\n    a4 = c(8.27, 11.7),\n    a5 = c(5.83, 8.27),\n    a6 = c(4.13, 5.83)\n  )\n\n  units <- match.arg(units)\n\n  if (units == \"cm\") {\n    margins <- margins / 2.54\n  }\n\n  if (is.character(pagesize)) {\n    pagesize <- tolower(pagesize)\n    pagesize <- match.arg(pagesize, names(page_sizes))\n    pagesize <- page_sizes[[pagesize]]\n  } else if (is.numeric(pagesize) && length(pagesize) == 2) {\n    # User has passed in width and height values\n    if (units == \"cm\") {\n      pagesize <- pagesize / 2.54\n    }\n  } else {\n    stop(\n      '`pagesize` must be one of \"',\n      paste(names(page_sizes), collapse = '\", \"'),\n      '\", or a two-element vector of width and height.'\n    )\n  }\n\n  if (length(margins) == 1) {\n    margins <- rep(margins, 4)\n  }\n  if (length(margins) != 4) {\n    stop(\n      '`margins` must be a single number, or a four-element numeric vector representing',\n      ' the margins for top, right, bottom, and left, respectively.'\n    )\n  }\n\n  p <- self$Page$printToPDF(\n    landscape = landscape,\n    displayHeaderFooter = display_header_footer,\n    printBackground = print_background,\n    scale = scale,\n    paperWidth = pagesize[[1]],\n    paperHeight = pagesize[[2]],\n    marginTop = margins[[1]],\n    marginBottom = margins[[3]],\n    marginLeft = margins[[4]],\n    marginRight = margins[[2]],\n    wait_ = FALSE\n  )$then(function(value) {\n    writeBin(jsonlite::base64_dec(value$data), filename)\n    filename\n  })\n\n  if (wait_) {\n    invisible(self$wait_for(p))\n  } else {\n    p\n  }\n}\n\n# Find a bounding box that contains the elements selected by any number of\n# selectors. Note that a selector can pick out more than one element.\nfind_selectors_bounds <- function(\n  cm,\n  root_node_id,\n  selectors,\n  region = \"content\"\n) {\n  ps <- lapply(selectors, function(selector) {\n    cm$DOM$querySelectorAll(root_node_id, selector, wait_ = FALSE)$then(\n      function(value) {\n        # There can be multiple nodes for a given selector, so we need to\n        # process all of them.\n        ps <- lapply(value$nodeIds, function(nodeId) {\n          cm$DOM$getBoxModel(nodeId, wait_ = FALSE)$catch(function(value) {\n            # Can get an error, \"Could not compute box model\", if the element\n            # is not visible. Just return NULL in this case.\n            NULL\n          })\n        })\n\n        promise_all(.list = ps)\n      }\n    )$then(function(values) {\n      # Could have gotten emtpy list for non-visible elements; remove them.\n      values <- drop_nulls(values)\n\n      lapply(values, function(value) {\n        list(\n          xmin = value$model[[region]][[1]],\n          xmax = value$model[[region]][[3]],\n          ymin = value$model[[region]][[2]],\n          ymax = value$model[[region]][[6]]\n        )\n      })\n    })\n  })\n\n  promise_all(.list = ps)$then(function(value) {\n    value <- unlist(value, recursive = FALSE)\n    if (length(value) == 0) {\n      stop(\"Unable to find any visible elements for selectors.\")\n    }\n\n    list(\n      xmin = min(fetch_key_n(value, \"xmin\")),\n      xmax = max(fetch_key_n(value, \"xmax\")),\n      ymin = min(fetch_key_n(value, \"ymin\")),\n      ymax = max(fetch_key_n(value, \"ymax\"))\n    )\n  })\n}\n"
  },
  {
    "path": "R/synchronize.R",
    "content": "promise_globals <- new.env(parent = emptyenv())\npromise_globals$interrupt_domains <- list()\n\npush_interrupt_domain <- function(domain) {\n  n_domains <- length(promise_globals$interrupt_domains)\n  promise_globals$interrupt_domains[[n_domains + 1]] <- domain\n}\n\npop_interrupt_domain <- function() {\n  n_domains <- length(promise_globals$interrupt_domains)\n  if (length(n_domains) == 0) return(NULL)\n\n  domain <- promise_globals$interrupt_domains[[n_domains]]\n  promise_globals$interrupt_domains[[n_domains]] <- NULL\n\n  domain\n}\n\ncurrent_interrupt_domain <- function() {\n  if (length(promise_globals$interrupt_domains) == 0) {\n    return(NULL)\n  }\n\n  promise_globals$interrupt_domains[[length(promise_globals$interrupt_domains)]]\n}\n\ncreate_interrupt_domain <- function() {\n  domain <- new_promise_domain(\n    wrapOnFulfilled = function(onFulfilled) {\n      function(...) {\n        push_interrupt_domain(domain)\n        on.exit(pop_interrupt_domain(), add = TRUE)\n\n        if (domain$interrupted) {\n          stop(\"Operation was interrupted 1\")\n        }\n        tryCatch(\n          {\n            onFulfilled(...)\n          },\n          interrupt = function(e) {\n            # message(\"wrapOnFulfilled inner caught interrupt\")\n            # Call function here that returns current interrupt\n            domain$interrupted <- TRUE\n            stop(\"Operation was interrupted 2\")\n          }\n        )\n      }\n    },\n    wrapOnRejected = function(onRejected) {\n      function(...) {\n        push_interrupt_domain(domain)\n        on.exit(pop_interrupt_domain(), add = TRUE)\n\n        if (domain$interrupted) {\n          stop(\"Operation was interrupted 3\")\n        }\n        tryCatch(\n          onRejected(...),\n          interrupt = function(e) {\n            domain$interrupted <- TRUE\n            stop(\"Operation was interrupted 4\")\n          }\n        )\n      }\n    },\n    wrapOnFinally = function(onFinally) {\n      function(...) {\n        push_interrupt_domain(domain)\n        on.exit(pop_interrupt_domain(), add = TRUE)\n\n        tryCatch(\n          onFinally(...),\n          interrupt = function(e) {\n            domain$interrupted <- TRUE\n            stop(\"Operation was interrupted 5\")\n          }\n        )\n      }\n    },\n    wrapSync = function(expr) {\n      push_interrupt_domain(domain)\n      on.exit(pop_interrupt_domain(), add = TRUE)\n\n      # Counting is currently not used\n      if (is.null(promise_globals$synchronized)) {\n        promise_globals$synchronized <- 0L\n      }\n      promise_globals$synchronized <- promise_globals$synchronized + 1L\n      on.exit(\n        promise_globals$synchronized <- promise_globals$synchronized - 1L,\n        add = TRUE\n      )\n\n      force(expr)\n    },\n    interrupted = FALSE\n  )\n\n  domain\n}\n\n# This function takes a promise and blocks until it is resolved. It runs the\n# promise's callbacks in the provided event loop. If the promise is\n# interrupted then this function tries to catch the interrupt, then runs the\n# loop until it's empty; then it throws a new interrupt. If the promise throws\n# an error, then also throws an error.\nsynchronize <- function(expr, loop) {\n  domain <- create_interrupt_domain()\n\n  with_promise_domain(domain, {\n    tryCatch(\n      {\n        result <- force(expr)\n\n        if (is.promising(result)) {\n          value <- NULL\n          type <- NULL\n          result$then(function(val) {\n            value <<- val\n            type <<- \"success\"\n          })$catch(function(reason) {\n            value <<- reason\n            type <<- \"error\"\n          })\n\n          while (is.null(type) && !domain$interrupted) {\n            run_now(loop = loop)\n          }\n\n          if (is.null(type)) {\n            generateInterrupt()\n          } else if (type == \"success\") {\n            value\n          } else if (type == \"error\") {\n            stop(value)\n          }\n        }\n      },\n      interrupt = function(e) {\n        domain$interrupted <<- TRUE\n        message(\n          \"Attempting to interrupt gracefully; press Esc/Ctrl+C to force interrupt\"\n        )\n        while (!loop_empty(loop = loop)) {\n          run_now(loop = loop)\n        }\n        generateInterrupt()\n      }\n    )\n  })\n}\n\n# A wrapper for later() which polls for interrupts. If an interrupt has\n# occurred either while running another callback, or when run_now() is\n# waiting, the interrupt will be detected and (1) the scheduled `func` will be\n# cancelled, and (2) the `on_interrupt` callback will be invoked.\nlater_with_interrupt <- function(\n  func,\n  delay = 0,\n  loop = current_loop(),\n  on_interrupt = function() {\n  },\n  interrupt_domain = current_interrupt_domain(),\n  poll_interval = 0.1\n) {\n  force(func)\n  force(loop)\n  force(interrupt_domain)\n  force(on_interrupt)\n  force(poll_interval)\n\n  if (is.null(interrupt_domain)) {\n    return(later(func, delay, loop))\n  }\n\n  end_time <- as.numeric(Sys.time()) + delay\n  cancel <- NULL\n\n  nextTurn <- function(init = FALSE) {\n    if (isTRUE(interrupt_domain$interrupted)) {\n      on_interrupt()\n      return()\n    }\n\n    this_delay <- min(poll_interval, end_time - as.numeric(Sys.time()))\n    if (this_delay <= 0) {\n      # Time has expired. If we've never progressed to the next tick (i.e.\n      # init == TRUE) then don't invoke the callback yet, wait until the next\n      # tick. Otherwise, do invoke the callback.\n      if (!init) {\n        func()\n        return()\n      }\n      this_delay <- 0\n    }\n    cancel <<- later::later(nextTurn, this_delay, loop)\n  }\n  nextTurn(init = TRUE)\n\n  function() {\n    cancel()\n  }\n}\n# TODO\n#\n# The notion of cancellability should go into later package. Instead of this\n# function taking `interrupt_domain`, which is a promise-level object, we could\n# do something like the following:\n#\n# Add on_interrupt option to later(); if FALSE/NULL (the default) then interrupts\n#   don't affect scheduled callback. If TRUE, then interrupt cancels the later().\n#   If a function, then interrupt cancels the later() and calls the on_interrupt\n#   function.\n# Add later::interrupt() function, so that later can know that an interrupt\n#   happened.\n# Add option to later() to make the callback uninterruptable.\n\ngenerateInterrupt <- function() {\n  tools::pskill(Sys.getpid(), tools::SIGINT)\n  Sys.sleep(1)\n}\n"
  },
  {
    "path": "R/utils.R",
    "content": "cat_line <- function(...) {\n  cat(paste0(..., \"\\n\", collapse = \"\"))\n}\n\n# =============================================================================\n# System\n# =============================================================================\n\nis_windows <- function() .Platform$OS.type == \"windows\"\n\nis_mac <- function() Sys.info()[['sysname']] == 'Darwin'\n\nis_linux <- function() Sys.info()[['sysname']] == 'Linux'\n\nis_openbsd <- function() Sys.info()[['sysname']] == \"OpenBSD\"\n\n# =============================================================================\n# Vectors\n# =============================================================================\n\nget_key <- function(x, key, default = stop(\"Key not present\")) {\n  if (key %in% names(x)) {\n    x[[key]]\n  } else {\n    default\n  }\n}\n\nfetch_key_list <- function(x, key, default = stop(\"Key not present\")) {\n  lapply(x, get_key, key, default = default)\n}\n\nfetch_key_c <- function(x, key, default = stop(\"Key not present\")) {\n  vapply(x, get_key, key, default = default, FUN.VALUE = \"\")\n}\n\nfetch_key_n <- function(x, key, default = stop(\"Key not present\")) {\n  vapply(x, get_key, key, default = default, FUN.VALUE = 0.0)\n}\n\nfetch_key_i <- function(x, key, default = stop(\"Key not present\")) {\n  vapply(x, get_key, key, default = default, FUN.VALUE = 0L)\n}\n\nfetch_key_l <- function(x, key, default = stop(\"Key not present\")) {\n  vapply(x, get_key, key, default = default, FUN.VALUE = FALSE)\n}\n\ndrop_nulls <- function(x) {\n  x[!vapply(x, is.null, TRUE)]\n}\n\n# =============================================================================\n# Text\n# =============================================================================\n\ntruncate <- function(x, n = 1000, message = \"[truncated]\") {\n  if (length(x) != 1) {\n    stop(\"Input must be a single string\")\n  }\n  if (nchar(x) > n) {\n    x <- paste0(substr(x, 1, n - nchar(message)), message)\n  }\n  x\n}\n\n# =============================================================================\n# Protocol-related stuff\n# =============================================================================\n\n# Given an event name, return the domain: \"Page.loadEventFired\" -> \"Page\"\nfind_domain <- function(event) {\n  sub(\"\\\\.[^.]+\", \"\", event)\n}\n\n# =============================================================================\n# Browser\n# =============================================================================\n\n# Force url to be opened by Chromium browser\nbrowse_url <- function(path, chromote) {\n  if (grepl(\"^[a-zA-Z][a-zA-Z0-9+.-]*://\", path)) {\n    # `path` is already a full URL\n    url <- path\n  } else {\n    url <- chromote$url(path)\n  }\n\n  browser <- chromote$get_browser()\n  if (inherits(browser, \"Chrome\")) {\n    # If locally available, use the local browser\n    browser_path <- browser$get_path()\n    product <- chromote$Browser$getVersion(wait_ = TRUE)$product\n\n    # And if not chrome-headless-shell (which doesn't have a UI we can use)\n    if (grepl(\"HeadlessChrome\", product, fixed = TRUE)) {\n      cli::cli_warn(\n        \"Cannot open a browser window with {.field chrome-headless-shell}, using your default browser instead.\"\n      )\n    } else {\n      # Quote the path if using a non-windows machine\n      if (!is_windows()) browser_path <- shQuote(browser_path)\n      utils::browseURL(url, browser_path)\n      return(invisible(url))\n    }\n  }\n\n  # Otherwise pray opening the url works as expected\n  # Users can set `options(browser=)` to override default behavior\n  utils::browseURL(url)\n  invisible(url)\n}\n\n# =============================================================================\n# Random Ports\n# =============================================================================\n#\n# Borrowed from https://github.com/rstudio/httpuv/blob/main/R/random_port.R\n\n#' Startup a service that requires a random port\n#'\n#' `with_random_port()` provides `startup()` with a random port value and runs\n#' the function:\n#'\n#' 1. `startup()` always returns a value if successful.\n#' 2. If `startup()` fails with a generic error, we assume the port is occupied\n#'    and try the next random port.\n#' 3. If `startup()` fails with an error classed with errors in `stop_on`,\n#'    (`error_stop_port_search` or `system_command_error` by default), we stop\n#'    the port search and rethrow the fatal error.\n#' 4. If we try `n` random ports, the port search stops with an informative\n#'    error that includes the last port attempt error.\n#'\n#' @param startup A function that takes a `port` argument, where `port` will be\n#'   randomly selected. When successful, `startup()` should return a non-NULL\n#'   value that will also be returned from `with_random_port()`. Generic errors\n#'   emitted by this function are silently ignored: when `startup()` fails, we\n#'   assume the port was unavailable and we try with a new port. Errors with the\n#'   classes in `stop_on` fail immediately.\n#' @param ... Additional arguments passed to `startup()`.\n#' @param min,max Port range\n#' @param n Maximum number of ports to try\n#' @param stop_on Error classes that signal the port search should be stopped\n#'\n#' @return The result of `startup()`, or an error if `startup()` fails.\n#' @noRd\nwith_random_port <- function(\n  startup,\n  ...,\n  min = 1024L,\n  max = 49151L,\n  n = 10,\n  stop_on = c(\"error_stop_port_search\", \"system_command_error\")\n) {\n  stopifnot(is.function(startup))\n  valid_ports <- setdiff(seq.int(min, max), unsafe_ports)\n\n  # Try up to n ports\n  n <- min(n, length(valid_ports))\n  ports <- sample(valid_ports, n)\n  err_port <- NULL\n\n  for (port in ports) {\n    success <- FALSE\n    res <- NULL\n    err_fatal <- NULL\n\n    # Try to run `startup` with the random port\n    tryCatch(\n      {\n        res <- startup(port = port, ...)\n        success <- TRUE\n      },\n      error = function(cnd) {\n        if (rlang::cnd_inherits(cnd, stop_on)) {\n          # Non generic errors that signal we should stop trying new ports\n          err_fatal <<- cnd\n          return()\n        }\n        # For other errors, they are probably because the port is already in\n        # use. Don't do anything; we'll just continue in the loop, but we save\n        # the last port retry error to throw in case it's informative.\n        err_port <<- cnd\n        NULL\n      }\n    )\n\n    if (!is.null(err_fatal)) {\n      rlang::cnd_signal(err_fatal)\n    }\n\n    if (isTRUE(success)) {\n      return(res)\n    }\n  }\n\n  rlang::abort(\n    \"Cannot find an available port. Please try again.\",\n    class = \"error_no_available_port\",\n    parent = err_port\n  )\n}\n\n# Ports that are considered unsafe by Chrome\n# http://superuser.com/questions/188058/which-ports-are-considered-unsafe-on-chrome\n# https://github.com/rstudio/shiny/issues/1784\nunsafe_ports <- c(\n  1,\n  7,\n  9,\n  11,\n  13,\n  15,\n  17,\n  19,\n  20,\n  21,\n  22,\n  23,\n  25,\n  37,\n  42,\n  43,\n  53,\n  77,\n  79,\n  87,\n  95,\n  101,\n  102,\n  103,\n  104,\n  109,\n  110,\n  111,\n  113,\n  115,\n  117,\n  119,\n  123,\n  135,\n  139,\n  143,\n  179,\n  389,\n  427,\n  465,\n  512,\n  513,\n  514,\n  515,\n  526,\n  530,\n  531,\n  532,\n  540,\n  548,\n  556,\n  563,\n  587,\n  601,\n  636,\n  993,\n  995,\n  2049,\n  3659,\n  4045,\n  6000,\n  6665,\n  6666,\n  6667,\n  6668,\n  6669,\n  6697\n)\n"
  },
  {
    "path": "R/zzz.R",
    "content": "`_dummy_` <- function() {\n  # Make a dummy curl call to make R CMD check happy\n  # {jsonlite} only suggests {curl}, but is needed for standard {chromote} usage\n  # https://github.com/rstudio/chromote/issues/37\n  curl::curl\n\n  invisible()\n}\n"
  },
  {
    "path": "README.Rmd",
    "content": "---\noutput: github_document\n---\n\n<!-- README.md is generated from README.Rmd. Please edit that file -->\n<!-- Do not run R chunks that print any session information.\n     This produces unstable output.\n     Instead, copy output from a local execution\n     Still use README.Rmd to get special UTF-8 chars from pandoc -->\n\n```{r, include = FALSE}\nknitr::opts_chunk$set(\n  collapse = TRUE,\n  comment = \"#>\",\n  fig.path = \"man/figures/README-\",\n  out.width = \"100%\"\n)\n```\n\n# chromote <a href=\"https://rstudio.github.io/chromote/\"><img src=\"man/figures/logo.png\" align=\"right\" height=\"138\" alt=\"chromote website\" /></a>\n\n<!-- badges: start -->\n[![R-CMD-check](https://github.com/rstudio/chromote/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/rstudio/chromote/actions)\n[![CRAN status](https://www.r-pkg.org/badges/version/chromote)](https://CRAN.R-project.org/package=chromote)\n[![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental)\n<!-- badges: end -->\n\n```{r child=\"man/fragments/features.Rmd\"}\n```\n\n## Learn More\n\nLearn more about using and programming with chromote:\n\n* [Get started](https://rstudio.github.io/chromote/articles/chromote.html)\n* [Commands and events](https://rstudio.github.io/chromote/articles/commands-and-events.html)\n* [Synchronous vs. asynchronous usage](https://rstudio.github.io/chromote/articles/sync-async.html)\n* [Choosing which Chrome-based browser to use](https://rstudio.github.io/chromote/articles/which-chrome.html)\n\n```{r child=\"man/fragments/install.Rmd\"}\n```\n\n```{r child=\"man/fragments/basic-usage.Rmd\"}\n```\n"
  },
  {
    "path": "README.md",
    "content": "\n<!-- README.md is generated from README.Rmd. Please edit that file -->\n<!-- Do not run R chunks that print any session information.\n     This produces unstable output.\n     Instead, copy output from a local execution\n     Still use README.Rmd to get special UTF-8 chars from pandoc -->\n\n# chromote <a href=\"https://rstudio.github.io/chromote/\"><img src=\"man/figures/logo.png\" align=\"right\" height=\"138\" alt=\"chromote website\" /></a>\n\n<!-- badges: start -->\n\n[![R-CMD-check](https://github.com/rstudio/chromote/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/rstudio/chromote/actions)\n[![CRAN\nstatus](https://www.r-pkg.org/badges/version/chromote)](https://CRAN.R-project.org/package=chromote)\n[![Lifecycle:\nexperimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental)\n<!-- badges: end -->\n\nChromote is an R implementation of the [Chrome DevTools\nProtocol](https://chromedevtools.github.io/devtools-protocol/). It works\nwith Chrome, Chromium, Opera, Vivaldi, and other browsers based on\n[Chromium](https://www.chromium.org/). By default it uses Google Chrome\n(which must already be installed on the system). To use a different\nbrowser, see `vignette(\"which-chrome\")`.\n\nChromote is not the only R package that implements the Chrome DevTools\nProtocol. Here are some others:\n\n- [crrri](https://github.com/RLesur/crrri) by Romain Lesur and\n  Christophe Dervieux\n- [decapitated](https://github.com/hrbrmstr/decapitated/) by Bob Rudis\n- [chradle](https://github.com/milesmcbain/chradle) by Miles McBain\n\nThe interface to Chromote is similar to\n[chrome-remote-interface](https://github.com/cyrus-and/chrome-remote-interface)\nfor node.js.\n\n## Features\n\n- Install and use specific versions of Chrome from the [Chrome for\n  Testing](https://googlechromelabs.github.io/chrome-for-testing/)\n  service.\n\n- Offers a synchronous API for ease of use and an asynchronous API for\n  more sophisticated tasks.\n\n- Full support for the Chrome DevTools Protocol for any version of\n  Chrome or any Chrome-based browser.\n\n- Includes convenience methods, like `$screenshot()` and\n  `$set_viewport_size()`, for common tasks.\n\n- Automatically reconnects to previous sessions if the connection from R\n  to Chrome is lost, for example when restarting from sleep state.\n\n- Powers many higher-level packages and functions, like `{shinytest2}`\n  and `rvest::read_html_live()`.\n\n## Learn More\n\nLearn more about using and programming with chromote:\n\n- [Get\n  started](https://rstudio.github.io/chromote/articles/chromote.html)\n- [Commands and\n  events](https://rstudio.github.io/chromote/articles/commands-and-events.html)\n- [Synchronous vs. asynchronous\n  usage](https://rstudio.github.io/chromote/articles/sync-async.html)\n- [Choosing which Chrome-based browser to\n  use](https://rstudio.github.io/chromote/articles/which-chrome.html)\n\n## Installation\n\nInstall the released version of chromote from CRAN:\n\n``` r\ninstall.packages(\"chromote\")\n```\n\nOr install the development version from GitHub with:\n\n``` r\n# install.packages(\"pak\")\npak::pak(\"rstudio/chromote\")\n```\n\n## Basic usage\n\nThis will start a headless browser and open an interactive viewer for it\nin a normal browser, so that you can see what the headless browser is\ndoing.\n\n``` r\nlibrary(chromote)\n\nb <- ChromoteSession$new()\n\n# In a web browser, open a viewer for the headless browser. Works best with\n# Chromium-based browsers.\nb$view()\n```\n\nThe browser can be given *commands*, as specified by the [Chrome\nDevTools Protocol](https://chromedevtools.github.io/devtools-protocol/).\nFor example, `$Browser$getVersion()` (which corresponds to the\n[Browser.getVersion](https://chromedevtools.github.io/devtools-protocol/tot/Browser/#method-getVersion)\nin the API docs) will query the browser for version information:\n\n``` r\nb$Browser$getVersion()\n#> $protocolVersion\n#> [1] \"1.3\"\n#>\n#> $product\n#> [1] \"HeadlessChrome/98.0.4758.102\"\n#>\n#> $revision\n#> [1] \"@273bf7ac8c909cde36982d27f66f3c70846a3718\"\n#>\n#> $userAgent\n#> [1] \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/98.0.4758.102 Safari/537.36\"\n#>\n#> $jsVersion\n#> [1] \"9.8.177.11\"\n```\n\nIf you have the viewer open and run the following, you’ll see the web\npage load in the viewer[^1]:\n\n``` r\nb$go_to(\"https://www.r-project.org/\")\n```\n\nIn addition to full support of the Chrome Devtools Protocol,\n`ChromoteSession` objects also have some convenience methods, like\n`$go_to()` and `$screenshot()`. (See the Examples section below for more\ninformation about screenshots.)\n\n``` r\n# Saves to screenshot.png\nb$screenshot()\n\n# Takes a screenshot of elements picked out by CSS selector\nb$screenshot(\"sidebar.png\", selector = \".sidebar\")\n```\n\n<figure>\n<img src=\"man/figures/sidebar.png\"\nalt=\"A screenshot of the sidebar of r-rproject.org, circa 2023.\" />\n<figcaption aria-hidden=\"true\">A screenshot of the sidebar of\nr-rproject.org, circa 2023.</figcaption>\n</figure>\n\n[^1]: This simple example works interactively, but if you’re using\n    chromote to programmatically take screenshots you’ll want to read\n    `vignette(\"example-loading-page\")` for a consistent and reliable\n    approach.\n"
  },
  {
    "path": "chromote.Rproj",
    "content": "Version: 1.0\n\nRestoreWorkspace: No\nSaveWorkspace: No\nAlwaysSaveHistory: Default\n\nEnableCodeIndexing: Yes\nUseSpacesForTab: Yes\nNumSpacesForTab: 2\nEncoding: UTF-8\n\nRnwWeave: Sweave\nLaTeX: pdfLaTeX\n\nAutoAppendNewline: Yes\nStripTrailingWhitespace: Yes\n\nBuildType: Package\nPackageUseDevtools: Yes\nPackageInstallArgs: --no-multiarch --with-keep.source\nPackageRoxygenize: rd,collate,namespace\n"
  },
  {
    "path": "cran-comments.md",
    "content": "## R CMD check results\n\n0 errors | 0 warnings | 0 notes\n\n\n## revdepcheck results\n\nWe checked 25 reverse dependencies (24 from CRAN + 1 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package.\n\n * We saw 0 new problems\n * We failed to check 0 packages\n\n"
  },
  {
    "path": "man/Browser.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/browser.R\n\\name{Browser}\n\\alias{Browser}\n\\title{Browser base class}\n\\description{\nBase class for browsers like Chrome, Chromium, etc. Defines the interface\nused by various browser implementations. It can represent a local browser\nprocess or one running remotely.\n}\n\\details{\nThe \\code{initialize()} method of an implementation should set \\code{private$host}\nand \\code{private$port}. If the process is local, the \\code{initialize()} method\nshould also set \\code{private$process}.\n}\n\\section{Methods}{\n\\subsection{Public methods}{\n\\itemize{\n\\item \\href{#method-Browser-is_local}{\\code{Browser$is_local()}}\n\\item \\href{#method-Browser-get_process}{\\code{Browser$get_process()}}\n\\item \\href{#method-Browser-is_alive}{\\code{Browser$is_alive()}}\n\\item \\href{#method-Browser-get_host}{\\code{Browser$get_host()}}\n\\item \\href{#method-Browser-get_port}{\\code{Browser$get_port()}}\n\\item \\href{#method-Browser-close}{\\code{Browser$close()}}\n\\item \\href{#method-Browser-clone}{\\code{Browser$clone()}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Browser-is_local\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Browser-is_local}{}}}\n\\subsection{Method \\code{is_local()}}{\nIs local browser?\nReturns TRUE if the browser is running locally, FALSE if it's remote.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Browser$is_local()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Browser-get_process\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Browser-get_process}{}}}\n\\subsection{Method \\code{get_process()}}{\nBrowser process\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Browser$get_process()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Browser-is_alive\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Browser-is_alive}{}}}\n\\subsection{Method \\code{is_alive()}}{\nIs the process alive?\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Browser$is_alive()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Browser-get_host\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Browser-get_host}{}}}\n\\subsection{Method \\code{get_host()}}{\nBrowser Host\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Browser$get_host()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Browser-get_port\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Browser-get_port}{}}}\n\\subsection{Method \\code{get_port()}}{\nBrowser port\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Browser$get_port()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Browser-close\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Browser-close}{}}}\n\\subsection{Method \\code{close()}}{\nClose the browser\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Browser$close(wait = FALSE)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{wait}}{If an integer, waits a number of seconds for the process to\nexit, killing the process if it takes longer than \\code{wait} seconds to\nclose. Use \\code{wait = TRUE} to wait for 10 seconds.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Browser-clone\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Browser-clone}{}}}\n\\subsection{Method \\code{clone()}}{\nThe objects of this class are cloneable with this method.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Browser$clone(deep = FALSE)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{deep}}{Whether to make a deep clone.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n}\n"
  },
  {
    "path": "man/Chrome.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/chrome.R\n\\name{Chrome}\n\\alias{Chrome}\n\\title{Local Chrome process}\n\\description{\nThis is a subclass of \\code{\\link{Browser}} that represents a local browser. It extends\nthe \\code{\\link{Browser}} class with a \\code{\\link[processx:process]{processx::process}} object, which represents\nthe browser's system process.\n}\n\\seealso{\n\\code{\\link[=get_chrome_args]{get_chrome_args()}}\n}\n\\section{Super class}{\n\\code{\\link[chromote:Browser]{chromote::Browser}} -> \\code{Chrome}\n}\n\\section{Methods}{\n\\subsection{Public methods}{\n\\itemize{\n\\item \\href{#method-Chrome-new}{\\code{Chrome$new()}}\n\\item \\href{#method-Chrome-get_path}{\\code{Chrome$get_path()}}\n\\item \\href{#method-Chrome-clone}{\\code{Chrome$clone()}}\n}\n}\n\\if{html}{\\out{\n<details><summary>Inherited methods</summary>\n<ul>\n<li><span class=\"pkg-link\" data-pkg=\"chromote\" data-topic=\"Browser\" data-id=\"close\"><a href='../../chromote/html/Browser.html#method-Browser-close'><code>chromote::Browser$close()</code></a></span></li>\n<li><span class=\"pkg-link\" data-pkg=\"chromote\" data-topic=\"Browser\" data-id=\"get_host\"><a href='../../chromote/html/Browser.html#method-Browser-get_host'><code>chromote::Browser$get_host()</code></a></span></li>\n<li><span class=\"pkg-link\" data-pkg=\"chromote\" data-topic=\"Browser\" data-id=\"get_port\"><a href='../../chromote/html/Browser.html#method-Browser-get_port'><code>chromote::Browser$get_port()</code></a></span></li>\n<li><span class=\"pkg-link\" data-pkg=\"chromote\" data-topic=\"Browser\" data-id=\"get_process\"><a href='../../chromote/html/Browser.html#method-Browser-get_process'><code>chromote::Browser$get_process()</code></a></span></li>\n<li><span class=\"pkg-link\" data-pkg=\"chromote\" data-topic=\"Browser\" data-id=\"is_alive\"><a href='../../chromote/html/Browser.html#method-Browser-is_alive'><code>chromote::Browser$is_alive()</code></a></span></li>\n<li><span class=\"pkg-link\" data-pkg=\"chromote\" data-topic=\"Browser\" data-id=\"is_local\"><a href='../../chromote/html/Browser.html#method-Browser-is_local'><code>chromote::Browser$is_local()</code></a></span></li>\n</ul>\n</details>\n}}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chrome-new\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chrome-new}{}}}\n\\subsection{Method \\code{new()}}{\nCreate a new Chrome object.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chrome$new(path = find_chrome(), args = get_chrome_args())}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{path}}{Location of chrome installation}\n\n\\item{\\code{args}}{A character vector of command-line arguments passed when\ninitializing Chrome. Single on-off arguments are passed as single\nvalues (e.g.\\code{\"--disable-gpu\"}), arguments with a value are given with a\nnested character vector (e.g. \\code{c(\"--force-color-profile\", \"srgb\")}).\nSee\n\\href{https://peter.sh/experiments/chromium-command-line-switches/}{here}\nfor a list of possible arguments. Defaults to \\code{\\link[=get_chrome_args]{get_chrome_args()}}.}\n}\n\\if{html}{\\out{</div>}}\n}\n\\subsection{Returns}{\nA new \\code{Chrome} object.\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chrome-get_path\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chrome-get_path}{}}}\n\\subsection{Method \\code{get_path()}}{\nBrowser application path\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chrome$get_path()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chrome-clone\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chrome-clone}{}}}\n\\subsection{Method \\code{clone()}}{\nThe objects of this class are cloneable with this method.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chrome$clone(deep = FALSE)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{deep}}{Whether to make a deep clone.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n}\n"
  },
  {
    "path": "man/ChromeRemote.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/chrome.R\n\\name{ChromeRemote}\n\\alias{ChromeRemote}\n\\title{Remote Chrome process}\n\\description{\nRemote Chrome process\n}\n\\section{Super class}{\n\\code{\\link[chromote:Browser]{chromote::Browser}} -> \\code{ChromeRemote}\n}\n\\section{Methods}{\n\\subsection{Public methods}{\n\\itemize{\n\\item \\href{#method-ChromeRemote-new}{\\code{ChromeRemote$new()}}\n\\item \\href{#method-ChromeRemote-is_alive}{\\code{ChromeRemote$is_alive()}}\n\\item \\href{#method-ChromeRemote-close}{\\code{ChromeRemote$close()}}\n\\item \\href{#method-ChromeRemote-clone}{\\code{ChromeRemote$clone()}}\n}\n}\n\\if{html}{\\out{\n<details open><summary>Inherited methods</summary>\n<ul>\n<li><span class=\"pkg-link\" data-pkg=\"chromote\" data-topic=\"Browser\" data-id=\"get_host\"><a href='../../chromote/html/Browser.html#method-Browser-get_host'><code>chromote::Browser$get_host()</code></a></span></li>\n<li><span class=\"pkg-link\" data-pkg=\"chromote\" data-topic=\"Browser\" data-id=\"get_port\"><a href='../../chromote/html/Browser.html#method-Browser-get_port'><code>chromote::Browser$get_port()</code></a></span></li>\n<li><span class=\"pkg-link\" data-pkg=\"chromote\" data-topic=\"Browser\" data-id=\"get_process\"><a href='../../chromote/html/Browser.html#method-Browser-get_process'><code>chromote::Browser$get_process()</code></a></span></li>\n<li><span class=\"pkg-link\" data-pkg=\"chromote\" data-topic=\"Browser\" data-id=\"is_local\"><a href='../../chromote/html/Browser.html#method-Browser-is_local'><code>chromote::Browser$is_local()</code></a></span></li>\n</ul>\n</details>\n}}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromeRemote-new\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromeRemote-new}{}}}\n\\subsection{Method \\code{new()}}{\nCreate a new ChromeRemote object.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromeRemote$new(host, port)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{host}}{A string that is a valid IPv4 or IPv6 address. \\code{\"0.0.0.0\"}\nrepresents all IPv4 addresses and \\code{\"::/0\"} represents all IPv6 addresses.}\n\n\\item{\\code{port}}{A number or integer that indicates the server port.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromeRemote-is_alive\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromeRemote-is_alive}{}}}\n\\subsection{Method \\code{is_alive()}}{\nIs the remote service alive?\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromeRemote$is_alive()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromeRemote-close\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromeRemote-close}{}}}\n\\subsection{Method \\code{close()}}{\nchromote does not manage remote processes, so closing a\nremote Chrome browser does nothing. You can send a \\code{Browser$close()}\ncommand if this is really something you want to do.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromeRemote$close()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromeRemote-clone\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromeRemote-clone}{}}}\n\\subsection{Method \\code{clone()}}{\nThe objects of this class are cloneable with this method.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromeRemote$clone(deep = FALSE)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{deep}}{Whether to make a deep clone.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n}\n"
  },
  {
    "path": "man/Chromote.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/chromote.R\n\\name{Chromote}\n\\alias{Chromote}\n\\title{Chromote class}\n\\description{\nA \\code{Chromote} object represents the browser as a whole, and it can have\nmultiple \\emph{targets}, which each represent a browser tab. In the Chrome\nDevTools Protocol, each target can have one or more debugging \\emph{sessions} to\ncontrol it. A \\code{ChromoteSession} object represents a single \\emph{session}.\n\nA \\code{Chromote} object can have any number of \\code{ChromoteSession} objects as\nchildren. It is not necessary to create a \\code{Chromote} object manually. You can\nsimply call:\n\n\\if{html}{\\out{<div class=\"sourceCode r\">}}\\preformatted{b <- ChromoteSession$new()\n}\\if{html}{\\out{</div>}}\n\nand it will automatically create a \\code{Chromote} object if one has not already\nbeen created. The \\pkg{chromote} package will then designate that \\code{Chromote}\nobject as the \\emph{default} \\code{Chromote} object for the package, so that any future\ncalls to \\code{ChromoteSession$new()} will automatically use the same \\code{Chromote}.\nThis is so that it doesn't start a new browser for every \\code{ChromoteSession}\nobject that is created.\n}\n\\section{Public fields}{\n\\if{html}{\\out{<div class=\"r6-fields\">}}\n\\describe{\n\\item{\\code{default_timeout}}{Default timeout in seconds for \\pkg{chromote} to\nwait for a Chrome DevTools Protocol response.}\n\n\\item{\\code{protocol}}{Dynamic protocol implementation. For expert use only!}\n}\n\\if{html}{\\out{</div>}}\n}\n\\section{Methods}{\n\\subsection{Public methods}{\n\\itemize{\n\\item \\href{#method-Chromote-new}{\\code{Chromote$new()}}\n\\item \\href{#method-Chromote-connect}{\\code{Chromote$connect()}}\n\\item \\href{#method-Chromote-view}{\\code{Chromote$view()}}\n\\item \\href{#method-Chromote-get_auto_events}{\\code{Chromote$get_auto_events()}}\n\\item \\href{#method-Chromote-auto_events_enable_args}{\\code{Chromote$auto_events_enable_args()}}\n\\item \\href{#method-Chromote-get_child_loop}{\\code{Chromote$get_child_loop()}}\n\\item \\href{#method-Chromote-wait_for}{\\code{Chromote$wait_for()}}\n\\item \\href{#method-Chromote-new_session}{\\code{Chromote$new_session()}}\n\\item \\href{#method-Chromote-get_sessions}{\\code{Chromote$get_sessions()}}\n\\item \\href{#method-Chromote-register_session}{\\code{Chromote$register_session()}}\n\\item \\href{#method-Chromote-send_command}{\\code{Chromote$send_command()}}\n\\item \\href{#method-Chromote-invoke_event_callbacks}{\\code{Chromote$invoke_event_callbacks()}}\n\\item \\href{#method-Chromote-debug_messages}{\\code{Chromote$debug_messages()}}\n\\item \\href{#method-Chromote-debug_log}{\\code{Chromote$debug_log()}}\n\\item \\href{#method-Chromote-url}{\\code{Chromote$url()}}\n\\item \\href{#method-Chromote-is_active}{\\code{Chromote$is_active()}}\n\\item \\href{#method-Chromote-is_alive}{\\code{Chromote$is_alive()}}\n\\item \\href{#method-Chromote-check_active}{\\code{Chromote$check_active()}}\n\\item \\href{#method-Chromote-get_browser}{\\code{Chromote$get_browser()}}\n\\item \\href{#method-Chromote-close}{\\code{Chromote$close()}}\n\\item \\href{#method-Chromote-print}{\\code{Chromote$print()}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chromote-new\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chromote-new}{}}}\n\\subsection{Method \\code{new()}}{\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chromote$new(browser = Chrome$new(), multi_session = TRUE, auto_events = TRUE)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{browser}}{A \\code{\\link{Browser}} object}\n\n\\item{\\code{multi_session}}{Should multiple sessions be allowed?}\n\n\\item{\\code{auto_events}}{If \\code{TRUE}, enable automatic event enabling/disabling;\nif \\code{FALSE}, disable automatic event enabling/disabling.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chromote-connect\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chromote-connect}{}}}\n\\subsection{Method \\code{connect()}}{\nRe-connect the websocket to the browser. The Chrome browser\nautomatically closes websockets when your computer goes to sleep;\nyou can use this to bring it back to life with a new connection.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chromote$connect(multi_session = TRUE, wait_ = TRUE)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{multi_session}}{Should multiple sessions be allowed?}\n\n\\item{\\code{wait_}}{If \\code{FALSE}, return a promise; if \\code{TRUE} wait until\nconnection is complete.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chromote-view\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chromote-view}{}}}\n\\subsection{Method \\code{view()}}{\nDisplay the current session in the \\code{browser}\n\nIf a \\code{\\link{Chrome}} browser is being used, this method will open a new tab\nusing your \\code{\\link{Chrome}} browser. When not using a \\code{\\link{Chrome}} browser, set\n\\code{options(browser=)} to change the default behavior of \\code{\\link[=browseURL]{browseURL()}}.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chromote$view()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chromote-get_auto_events\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chromote-get_auto_events}{}}}\n\\subsection{Method \\code{get_auto_events()}}{\n\\code{auto_events} value.\n\nFor internal use only.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chromote$get_auto_events()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chromote-auto_events_enable_args\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chromote-auto_events_enable_args}{}}}\n\\subsection{Method \\code{auto_events_enable_args()}}{\nSet or retrieve the \\code{enable} command arguments for a domain. These\narguments are used for the \\code{enable} command that is called for a domain,\ne.g. \\code{Fetch$enable()}, when accessing an event method.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chromote$auto_events_enable_args(domain, ...)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{domain}}{A command domain, e.g. \\code{\"Fetch\"}.}\n\n\\item{\\code{...}}{Arguments to use for auto-events for the domain. If not\nprovided, returns the argument values currently in place for the\ndomain. Use \\code{NULL} to clear the enable arguments for a domain.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chromote-get_child_loop\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chromote-get_child_loop}{}}}\n\\subsection{Method \\code{get_child_loop()}}{\nLocal \\pkg{later} loop.\n\nFor expert async usage only.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chromote$get_child_loop()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chromote-wait_for\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chromote-wait_for}{}}}\n\\subsection{Method \\code{wait_for()}}{\nWait until the promise resolves\n\nBlocks the R session until the promise (\\code{p}) is resolved. The loop from\n\\verb{$get_child_loop()} will only advance just far enough for the promise to\nresolve.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chromote$wait_for(p)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{p}}{A promise to resolve.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chromote-new_session\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chromote-new_session}{}}}\n\\subsection{Method \\code{new_session()}}{\nCreate a new tab / window\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chromote$new_session(width = 992, height = 1323, targetId = NULL, wait_ = TRUE)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{width, height}}{Width and height of the new window.}\n\n\\item{\\code{targetId}}{\\href{https://chromedevtools.github.io/devtools-protocol/tot/Target/}{Target}\nID of an existing target to attach to. When a \\code{targetId} is provided, the\n\\code{width} and \\code{height} arguments are ignored. If NULL (the default) a new\ntarget is created and attached to, and the \\code{width} and \\code{height}\narguments determine its viewport size.}\n\n\\item{\\code{wait_}}{If \\code{FALSE}, return a \\code{\\link[promises:promise]{promises::promise()}} of a new\n\\code{ChromoteSession} object. Otherwise, block during initialization, and\nreturn a \\code{ChromoteSession} object directly.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chromote-get_sessions\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chromote-get_sessions}{}}}\n\\subsection{Method \\code{get_sessions()}}{\nRetrieve all \\code{\\link{ChromoteSession}} objects\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chromote$get_sessions()}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Returns}{\nA list of \\code{ChromoteSession} objects\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chromote-register_session\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chromote-register_session}{}}}\n\\subsection{Method \\code{register_session()}}{\nRegister \\code{\\link{ChromoteSession}} object\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chromote$register_session(session)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{session}}{A \\code{ChromoteSession} object\n\nFor internal use only.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chromote-send_command\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chromote-send_command}{}}}\n\\subsection{Method \\code{send_command()}}{\nSend command through Chrome DevTools Protocol.\n\nFor expert use only.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chromote$send_command(\n  msg,\n  callback = NULL,\n  error = NULL,\n  timeout = NULL,\n  sessionId = NULL\n)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{msg}}{A JSON-serializable list containing \\code{method}, and \\code{params}.}\n\n\\item{\\code{callback}}{Method to run when the command finishes successfully.}\n\n\\item{\\code{error}}{Method to run if an error occurs.}\n\n\\item{\\code{timeout}}{Number of milliseconds for Chrome DevTools Protocol\nexecute a method.}\n\n\\item{\\code{sessionId}}{Determines which \\code{\\link{ChromoteSession}} with the\ncorresponding to send the command to.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chromote-invoke_event_callbacks\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chromote-invoke_event_callbacks}{}}}\n\\subsection{Method \\code{invoke_event_callbacks()}}{\nImmediately call all event callback methods.\n\nFor internal use only.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chromote$invoke_event_callbacks(event, params)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{event}}{A single event string}\n\n\\item{\\code{params}}{A list of parameters to pass to the event callback methods.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chromote-debug_messages\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chromote-debug_messages}{}}}\n\\subsection{Method \\code{debug_messages()}}{\nEnable or disable message debugging\n\nIf enabled, R will print out the\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chromote$debug_messages(value = NULL)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{value}}{If \\code{TRUE}, enable debugging. If \\code{FALSE}, disable debugging.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chromote-debug_log\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chromote-debug_log}{}}}\n\\subsection{Method \\code{debug_log()}}{\nSubmit debug log message\n\\subsection{Examples}{\n\n\\if{html}{\\out{<div class=\"sourceCode r\">}}\\preformatted{b <- ChromoteSession$new()\nb$parent$debug_messages(TRUE)\nb$Page$navigate(\"https://www.r-project.org/\")\n#> SEND \\{\"method\":\"Page.navigate\",\"params\":\\{\"url\":\"https://www.r-project.org/\"\\}| __truncated__\\}\n# Turn off debug messages\nb$parent$debug_messages(FALSE)\n}\\if{html}{\\out{</div>}}\n}\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chromote$debug_log(...)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{...}}{Arguments pasted together with \\code{paste0(..., collapse = \"\")}.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chromote-url\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chromote-url}{}}}\n\\subsection{Method \\code{url()}}{\nCreate url for a given path\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chromote$url(path = NULL)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{path}}{A path string to append to the host and port}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chromote-is_active\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chromote-is_active}{}}}\n\\subsection{Method \\code{is_active()}}{\nIs there an active websocket connection to the browser process?\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chromote$is_active()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chromote-is_alive\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chromote-is_alive}{}}}\n\\subsection{Method \\code{is_alive()}}{\nIs the underlying browser process running?\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chromote$is_alive()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chromote-check_active\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chromote-check_active}{}}}\n\\subsection{Method \\code{check_active()}}{\nCheck that a chromote instance is active and alive.\nWill automatically reconnect if browser process is alive, but\nthere's no active web socket connection.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chromote$check_active()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chromote-get_browser\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chromote-get_browser}{}}}\n\\subsection{Method \\code{get_browser()}}{\nRetrieve \\code{\\link{Browser}}` object\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chromote$get_browser()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chromote-close\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chromote-close}{}}}\n\\subsection{Method \\code{close()}}{\nClose the \\code{\\link{Browser}} object\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chromote$close(wait = TRUE)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{wait}}{If an integer, waits a number of seconds for the process to\nexit, killing the process if it takes longer than \\code{wait} seconds to\nclose. Use \\code{wait = TRUE} to wait for 10 seconds, or \\code{wait = FALSE} to\nclose the connection without waiting for the process to exit. Only\napplies when Chromote is connected to a local process.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-Chromote-print\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-Chromote-print}{}}}\n\\subsection{Method \\code{print()}}{\nSummarise the current state of the object.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{Chromote$print(..., verbose = FALSE)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{...}}{Passed on to \\code{format()} when \\code{verbose} = TRUE}\n\n\\item{\\code{verbose}}{The print method defaults to a brief summary\nof the most important debugging info; use \\code{verbose = TRUE} tp\nsee the complex R6 object.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n}\n"
  },
  {
    "path": "man/ChromoteSession.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/chromote_session.R\n\\name{ChromoteSession}\n\\alias{ChromoteSession}\n\\title{ChromoteSession class}\n\\description{\nThis represents one \\emph{session} in a Chromote object. Note that in the Chrome\nDevTools Protocol a session is a debugging session connected to a \\emph{target},\nwhich is a browser window/tab or an iframe.\n\nA single target can potentially have more than one session connected to it,\nbut this is not currently supported by chromote.\n}\n\\examples{\n\n## ------------------------------------------------\n## Method `ChromoteSession$go_to`\n## ------------------------------------------------\n\n\\dontrun{\n# Basic navigation\nb$go_to(\"https://www.r-project.org\")\n\n# Navigation with delay\nb$go_to(\"https://www.r-project.org\", delay = 2)\n\n# Asynchronous navigation\np <- b$go_to(\"https://www.r-project.org\", wait_ = FALSE)\np$then(function(value) print(\"Navigation complete!\"))\n}\n\n## ------------------------------------------------\n## Method `ChromoteSession$auto_events_enable_args`\n## ------------------------------------------------\n\nif (interactive()) {\n  b <- ChromoteSession$new(\n    auto_events_enable_args = list(\n      Fetch = list(handleAuthRequests = TRUE)\n    )\n  )\n\n  # Get current `Fetch.enable` args\n  b$auto_events_enable_args(\"Fetch\")\n\n  # Update the `Fetch.enable` args\n  b$auto_events_enable_args(\"Fetch\", handleAuthRequests = FALSE)\n\n  # Reset `Fetch.enable` args\n  b$auto_events_enable_args(\"Fetch\", NULL)\n}\n\n}\n\\section{Public fields}{\n\\if{html}{\\out{<div class=\"r6-fields\">}}\n\\describe{\n\\item{\\code{parent}}{\\code{\\link{Chromote}} object}\n\n\\item{\\code{default_timeout}}{Default timeout in seconds for \\pkg{chromote} to\nwait for a Chrome DevTools Protocol response.}\n\n\\item{\\code{protocol}}{Dynamic protocol implementation. For expert use only!}\n}\n\\if{html}{\\out{</div>}}\n}\n\\section{Methods}{\n\\subsection{Public methods}{\n\\itemize{\n\\item \\href{#method-ChromoteSession-new}{\\code{ChromoteSession$new()}}\n\\item \\href{#method-ChromoteSession-view}{\\code{ChromoteSession$view()}}\n\\item \\href{#method-ChromoteSession-close}{\\code{ChromoteSession$close()}}\n\\item \\href{#method-ChromoteSession-get_viewport_size}{\\code{ChromoteSession$get_viewport_size()}}\n\\item \\href{#method-ChromoteSession-set_viewport_size}{\\code{ChromoteSession$set_viewport_size()}}\n\\item \\href{#method-ChromoteSession-go_to}{\\code{ChromoteSession$go_to()}}\n\\item \\href{#method-ChromoteSession-screenshot}{\\code{ChromoteSession$screenshot()}}\n\\item \\href{#method-ChromoteSession-screenshot_pdf}{\\code{ChromoteSession$screenshot_pdf()}}\n\\item \\href{#method-ChromoteSession-new_session}{\\code{ChromoteSession$new_session()}}\n\\item \\href{#method-ChromoteSession-get_session_id}{\\code{ChromoteSession$get_session_id()}}\n\\item \\href{#method-ChromoteSession-respawn}{\\code{ChromoteSession$respawn()}}\n\\item \\href{#method-ChromoteSession-get_target_id}{\\code{ChromoteSession$get_target_id()}}\n\\item \\href{#method-ChromoteSession-wait_for}{\\code{ChromoteSession$wait_for()}}\n\\item \\href{#method-ChromoteSession-debug_log}{\\code{ChromoteSession$debug_log()}}\n\\item \\href{#method-ChromoteSession-get_child_loop}{\\code{ChromoteSession$get_child_loop()}}\n\\item \\href{#method-ChromoteSession-send_command}{\\code{ChromoteSession$send_command()}}\n\\item \\href{#method-ChromoteSession-get_auto_events}{\\code{ChromoteSession$get_auto_events()}}\n\\item \\href{#method-ChromoteSession-auto_events_enable_args}{\\code{ChromoteSession$auto_events_enable_args()}}\n\\item \\href{#method-ChromoteSession-invoke_event_callbacks}{\\code{ChromoteSession$invoke_event_callbacks()}}\n\\item \\href{#method-ChromoteSession-mark_closed}{\\code{ChromoteSession$mark_closed()}}\n\\item \\href{#method-ChromoteSession-is_active}{\\code{ChromoteSession$is_active()}}\n\\item \\href{#method-ChromoteSession-check_active}{\\code{ChromoteSession$check_active()}}\n\\item \\href{#method-ChromoteSession-get_init_promise}{\\code{ChromoteSession$get_init_promise()}}\n\\item \\href{#method-ChromoteSession-print}{\\code{ChromoteSession$print()}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-new\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-new}{}}}\n\\subsection{Method \\code{new()}}{\nCreate a new \\code{ChromoteSession} object.\n\\subsection{Examples}{\n\n\\if{html}{\\out{<div class=\"sourceCode r\">}}\\preformatted{# Create a new `ChromoteSession` object.\nb <- ChromoteSession$new()\n\n# Create a ChromoteSession with a specific height,width\nb <- ChromoteSession$new(height = 1080, width = 1920)\n\n# Navigate to page\nb$go_to(\"http://www.r-project.org/\")\n\n# View current chromote session\nif (interactive()) b$view()\n}\\if{html}{\\out{</div>}}\n}\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$new(\n  parent = default_chromote_object(),\n  width = 992,\n  height = 1323,\n  targetId = NULL,\n  wait_ = TRUE,\n  auto_events = NULL,\n  mobile = FALSE\n)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{parent}}{\\code{\\link{Chromote}} object to use; defaults to\n\\code{\\link[=default_chromote_object]{default_chromote_object()}}}\n\n\\item{\\code{width, height}}{Width and height of the new window in integer pixel\nvalues.}\n\n\\item{\\code{targetId}}{\\href{https://chromedevtools.github.io/devtools-protocol/tot/Target/}{Target}\nID of an existing target to attach to. When a \\code{targetId} is provided, the\n\\code{width} and \\code{height} arguments are ignored. If NULL (the default) a new\ntarget is created and attached to, and the \\code{width} and \\code{height}\narguments determine its viewport size.}\n\n\\item{\\code{wait_}}{If \\code{FALSE}, return a \\code{\\link[promises:promise]{promises::promise()}} of a new\n\\code{ChromoteSession} object. Otherwise, block during initialization, and\nreturn a \\code{ChromoteSession} object directly.}\n\n\\item{\\code{auto_events}}{If \\code{NULL} (the default), use the \\code{auto_events} setting\nfrom the parent \\code{Chromote} object. If \\code{TRUE}, enable automatic\nevent enabling/disabling; if \\code{FALSE}, disable automatic event\nenabling/disabling.}\n\n\\item{\\code{mobile}}{Whether to emulate mobile device. When \\code{TRUE}, Chrome\nupdates settings to emulate browsing on a mobile phone; this includes\nviewport meta tag, overlay scrollbars, text autosizing and more. The\ndefault is \\code{FALSE}.}\n}\n\\if{html}{\\out{</div>}}\n}\n\\subsection{Returns}{\nA new \\code{ChromoteSession} object.\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-view\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-view}{}}}\n\\subsection{Method \\code{view()}}{\nDisplay the current session in the \\code{\\link{Chromote}} browser.\n\nIf a \\code{\\link{Chrome}} browser is being used, this method will open a new tab\nusing your \\code{\\link{Chrome}} browser. When not using a \\code{\\link{Chrome}} browser, set\n\\code{options(browser=)} to change the default behavior of \\code{\\link[=browseURL]{browseURL()}}.\n\\subsection{Examples}{\n\n\\if{html}{\\out{<div class=\"sourceCode r\">}}\\preformatted{# Create a new `ChromoteSession` object.\nb <- ChromoteSession$new()\n\n# Navigate to page\nb$go_to(\"http://www.r-project.org/\")\n\n# View current chromote session\nif (interactive()) b$view()\n}\\if{html}{\\out{</div>}}\n}\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$view()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-close\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-close}{}}}\n\\subsection{Method \\code{close()}}{\nClose the Chromote session.\n\\subsection{Examples}{\n\n\\if{html}{\\out{<div class=\"sourceCode r\">}}\\preformatted{# Create a new `ChromoteSession` object.\nb <- ChromoteSession$new()\n\n# Navigate to page\nb$go_to(\"http://www.r-project.org/\")\n\n# Close current chromote session\nb$close()\n}\\if{html}{\\out{</div>}}\n}\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$close(wait_ = TRUE)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{wait_}}{If \\code{FALSE}, return a \\code{\\link[promises:promise]{promises::promise()}} that will resolve\nwhen the \\code{ChromoteSession} is closed. Otherwise, block until the\n\\code{ChromoteSession} has closed.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-get_viewport_size\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-get_viewport_size}{}}}\n\\subsection{Method \\code{get_viewport_size()}}{\nGet the viewport size\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$get_viewport_size(wait_ = TRUE)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{wait_}}{If \\code{FALSE}, return a \\code{\\link[promises:promise]{promises::promise()}} of a new\n\\code{ChromoteSession} object. Otherwise, block during initialization, and\nreturn a \\code{ChromoteSession} object directly.}\n}\n\\if{html}{\\out{</div>}}\n}\n\\subsection{Returns}{\nReturns a list with values \\code{width}, \\code{height}, \\code{zoom}\nand \\code{mobile}. See \\verb{$set_viewport_size()} for more details.\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-set_viewport_size\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-set_viewport_size}{}}}\n\\subsection{Method \\code{set_viewport_size()}}{\nSet the viewport size\n\nEach ChromoteSession is associated with a page that may be one page open\nin a browser window among many. Each page can have its own viewport size,\nthat can be thought of like the window size for that page.\n\nThis function uses the\n\\href{https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setDeviceMetricsOverride}{Emulation.setDeviceMetricsOverride}\ncommand to set the viewport size. If you need more granular control or\naccess to additional settings, use\n\\verb{$Emulation$setDeviceMetricsOverride()}.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$set_viewport_size(\n  width,\n  height,\n  zoom = NULL,\n  mobile = NULL,\n  wait_ = TRUE\n)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{width, height}}{Width and height of the new window in integer pixel\nvalues.}\n\n\\item{\\code{zoom}}{The zoom level of displayed content on a device, where a\nvalue of 1 indicates normal size, greater than 1 indicates zoomed in,\nand less than 1 indicates zoomed out.}\n\n\\item{\\code{mobile}}{Whether to emulate mobile device. When \\code{TRUE}, Chrome\nupdates settings to emulate browsing on a mobile phone; this includes\nviewport meta tag, overlay scrollbars, text autosizing and more. The\ndefault is \\code{FALSE}.}\n\n\\item{\\code{wait_}}{If \\code{FALSE}, return a \\code{\\link[promises:promise]{promises::promise()}} of a new\n\\code{ChromoteSession} object. Otherwise, block during initialization, and\nreturn a \\code{ChromoteSession} object directly.}\n}\n\\if{html}{\\out{</div>}}\n}\n\\subsection{Returns}{\nInvisibly returns the previous viewport dimensions so that you\ncan restore the viewport size, if desired.\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-go_to\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-go_to}{}}}\n\\subsection{Method \\code{go_to()}}{\nNavigate to a URL and wait for the page to load\n\nThis method navigates to a specified URL and waits for the page load\nevent to complete. This is a more reliable alternative to directly\ncalling \\code{Page$navigate()}, which can return before the page is actually\nloaded. This method also allows for an optional delay after the load\nevent has fired, in case the page needs to load additional assets after\nthat event.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$go_to(\n  url,\n  ...,\n  delay = 0,\n  callback_ = NULL,\n  error_ = NULL,\n  timeout_ = self$default_timeout,\n  wait_ = TRUE\n)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{url}}{The URL to navigate to.}\n\n\\item{\\code{...}}{Additional parameters passed to \\code{Page$navigate()}.}\n\n\\item{\\code{delay}}{Number of seconds to wait after the page load event fires.}\n\n\\item{\\code{callback_}}{Function to call when the page load event fires.}\n\n\\item{\\code{error_}}{Function to call if an error occurs during navigation.}\n\n\\item{\\code{timeout_}}{Maximum time in seconds to wait for the page load event\n(defaults to session's `default_timeout``).}\n\n\\item{\\code{wait_}}{If \\code{FALSE}, returns a promise that resolves when navigation\nis complete. If \\code{TRUE} (default), blocks until navigation is complete.}\n}\n\\if{html}{\\out{</div>}}\n}\n\\subsection{Returns}{\nIf \\code{wait_} is TRUE, returns invisible(NULL). If wait_ is FALSE,\nreturns a promise that resolves when navigation is complete. The\npromise resolves with the value from the navigate command.\n}\n\\subsection{Examples}{\n\\if{html}{\\out{<div class=\"r example copy\">}}\n\\preformatted{\\dontrun{\n# Basic navigation\nb$go_to(\"https://www.r-project.org\")\n\n# Navigation with delay\nb$go_to(\"https://www.r-project.org\", delay = 2)\n\n# Asynchronous navigation\np <- b$go_to(\"https://www.r-project.org\", wait_ = FALSE)\np$then(function(value) print(\"Navigation complete!\"))\n}\n}\n\\if{html}{\\out{</div>}}\n\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-screenshot\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-screenshot}{}}}\n\\subsection{Method \\code{screenshot()}}{\nTake a PNG screenshot\n\\subsection{Examples}{\n\n\\if{html}{\\out{<div class=\"sourceCode r\">}}\\preformatted{# Create a new `ChromoteSession` object.\nb <- ChromoteSession$new()\n\n# Navigate to page\nb$go_to(\"http://www.r-project.org/\")\n\n# Take screenshot\ntmppngfile <- tempfile(fileext = \".png\")\nis_interactive <- interactive() # Display screenshot if interactive\nb$screenshot(tmppngfile, show = is_interactive)\n\n# Show screenshot file info\nunlist(file.info(tmppngfile))\n\n\n# Take screenshot using a selector\nsidebar_file <- tempfile(fileext = \".png\")\nb$screenshot(sidebar_file, selector = \".sidebar\", show = is_interactive)\n\n# ----------------------------\n# Take screenshots in parallel\n\nurls <- c(\n  \"https://www.r-project.org/\",\n  \"https://github.com/\",\n  \"https://news.ycombinator.com/\"\n)\n# Helper method that:\n# 1. Navigates to the given URL\n# 2. Waits for the page loaded event to fire\n# 3. Takes a screenshot\n# 4. Prints a message\n# 5. Close the ChromoteSession\nscreenshot_p <- function(url, filename = NULL) \\{\n  if (is.null(filename)) \\{\n    filename <- gsub(\"^.*://\", \"\", url)\n    filename <- gsub(\"/\", \"_\", filename)\n    filename <- gsub(\"\\\\\\\\.\", \"_\", filename)\n    filename <- sub(\"_$\", \"\", filename)\n    filename <- paste0(filename, \".png\")\n  \\}\n\n  b2 <- b$new_session()\n  b2$go_to(url, wait_ = FALSE)$\n    then(function(value) \\{\n      b2$screenshot(filename, wait_ = FALSE)\n    \\})$\n    then(function(value) \\{\n      message(filename)\n    \\})$\n    finally(function() \\{\n      b2$close()\n    \\})\n\\}\n\n# Take multiple screenshots simultaneously\nps <- lapply(urls, screenshot_p)\npa <- promises::promise_all(.list = ps)$then(function(value) \\{\n  message(\"Done!\")\n\\})\n\n# Block the console until the screenshots finish (optional)\nb$wait_for(pa)\n#> www_r-project_org.png\n#> github_com.png\n#> news_ycombinator_com.png\n#> Done!\n}\\if{html}{\\out{</div>}}\n}\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$screenshot(\n  filename = \"screenshot.png\",\n  selector = \"html\",\n  cliprect = NULL,\n  region = c(\"content\", \"padding\", \"border\", \"margin\"),\n  expand = NULL,\n  scale = 1,\n  show = FALSE,\n  delay = 0.5,\n  options = list(),\n  wait_ = TRUE\n)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{filename}}{File path of where to save the screenshot. The format of\nthe screenshot is inferred from the file extension; use\n\\code{options = list(format = \"jpeg\")} to manually choose the format. See\n\\href{https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-captureScreenshot}{\\code{Page.captureScreenshot}}\nfor supported formats; at the time of this release the format options\nwere \\code{\"png\"} (default), \\code{\"jpeg\"}, or \\code{\"webp\"}.}\n\n\\item{\\code{selector}}{CSS selector to use for the screenshot.}\n\n\\item{\\code{cliprect}}{An unnamed vector or list containing values for \\code{top},\n\\code{left}, \\code{width}, and \\code{height}, in that order. See\n\\href{https://chromedevtools.github.io/devtools-protocol/tot/Page/#type-Viewport}{\\code{Page.Viewport}}\nfor more information. If provided, \\code{selector} and \\code{expand} will be\nignored. To provide a scale, use the \\code{scale} parameter.}\n\n\\item{\\code{region}}{CSS region to use for the screenshot.}\n\n\\item{\\code{expand}}{Extra pixels to expand the screenshot. May be a single\nvalue or a numeric vector of top, right, bottom, left values.}\n\n\\item{\\code{scale}}{Page scale factor}\n\n\\item{\\code{show}}{If \\code{TRUE}, the screenshot will be displayed in the viewer.}\n\n\\item{\\code{delay}}{The number of seconds to wait before taking the screenshot\nafter resizing the page. For complicated pages, this may need to be\nincreased.}\n\n\\item{\\code{options}}{Additional options passed to\n\\href{https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-captureScreenshot}{\\code{Page.captureScreenshot}}.}\n\n\\item{\\code{wait_}}{If \\code{FALSE}, return a \\code{\\link[promises:promise]{promises::promise()}} that will resolve\nwhen the \\code{ChromoteSession} has saved the screenshot. Otherwise, block\nuntil the \\code{ChromoteSession} has saved the screenshot.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-screenshot_pdf\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-screenshot_pdf}{}}}\n\\subsection{Method \\code{screenshot_pdf()}}{\nTake a PDF screenshot\n\\subsection{Examples}{\n\n\\if{html}{\\out{<div class=\"sourceCode r\">}}\\preformatted{# Create a new `ChromoteSession` object.\nb <- ChromoteSession$new()\n\n# Navigate to page\nb$go_to(\"http://www.r-project.org/\")\n\n# Take screenshot\ntmppdffile <- tempfile(fileext = \".pdf\")\nb$screenshot_pdf(tmppdffile)\n\n# Show PDF file info\nunlist(file.info(tmppdffile))\n}\\if{html}{\\out{</div>}}\n}\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$screenshot_pdf(\n  filename = \"screenshot.pdf\",\n  pagesize = \"letter\",\n  margins = 0.5,\n  units = c(\"in\", \"cm\"),\n  landscape = FALSE,\n  display_header_footer = FALSE,\n  print_background = FALSE,\n  scale = 1,\n  wait_ = TRUE\n)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{filename}}{File path of where to save the screenshot.}\n\n\\item{\\code{pagesize}}{A single character value in the set \\code{\"letter\"},\n\\code{\"legal\"}, \\code{\"tabloid\"}, \\code{\"ledger\"} and \\code{\"a0\"} through \\code{\"a1\"}. Or a\nnumeric vector \\code{c(width, height)} specifying the page size.}\n\n\\item{\\code{margins}}{A numeric vector \\code{c(top, right, bottom, left)} specifying\nthe page margins.}\n\n\\item{\\code{units}}{Page and margin size units. Either \\code{\"in\"} or \\code{\"cm\"} for\ninches and centimeters respectively.}\n\n\\item{\\code{landscape}}{Paper orientation.}\n\n\\item{\\code{display_header_footer}}{Display header and footer.}\n\n\\item{\\code{print_background}}{Print background graphics.}\n\n\\item{\\code{scale}}{Page scale factor.}\n\n\\item{\\code{wait_}}{If \\code{FALSE}, return a \\code{\\link[promises:promise]{promises::promise()}} that will resolve\nwhen the \\code{ChromoteSession} has saved the screenshot. Otherwise, block\nuntil the \\code{ChromoteSession} has saved the screnshot.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-new_session\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-new_session}{}}}\n\\subsection{Method \\code{new_session()}}{\nCreate a new tab / window\n\\subsection{Examples}{\n\n\\if{html}{\\out{<div class=\"sourceCode r\">}}\\preformatted{b1 <- ChromoteSession$new()\nb1$go_to(\"http://www.google.com\")\nb2 <- b1$new_session()\nb2$go_to(\"http://www.r-project.org/\")\nb1$Runtime$evaluate(\"window.location\", returnByValue = TRUE)$result$value$href\n#> [1] \"https://www.google.com/\"\nb2$Runtime$evaluate(\"window.location\", returnByValue = TRUE)$result$value$href\n#> [1] \"https://www.r-project.org/\"\n}\\if{html}{\\out{</div>}}\n}\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$new_session(\n  width = 992,\n  height = 1323,\n  targetId = NULL,\n  wait_ = TRUE\n)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{width, height}}{Width and height of the new window.}\n\n\\item{\\code{targetId}}{\\href{https://chromedevtools.github.io/devtools-protocol/tot/Target/}{Target}\nID of an existing target to attach to. When a \\code{targetId} is provided, the\n\\code{width} and \\code{height} arguments are ignored. If NULL (the default) a new\ntarget is created and attached to, and the \\code{width} and \\code{height}\narguments determine its viewport size.}\n\n\\item{\\code{wait_}}{If \\code{FALSE}, return a \\code{\\link[promises:promise]{promises::promise()}} that will resolve\nwhen the \\code{ChromoteSession} has created a new session. Otherwise, block\nuntil the \\code{ChromoteSession} has created a new session.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-get_session_id\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-get_session_id}{}}}\n\\subsection{Method \\code{get_session_id()}}{\nRetrieve the session id\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$get_session_id()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-respawn\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-respawn}{}}}\n\\subsection{Method \\code{respawn()}}{\nCreate a new session that connects to the same target (i.e. page)\nas this session. This is useful if the session has been closed but the target still\nexists.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$respawn()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-get_target_id\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-get_target_id}{}}}\n\\subsection{Method \\code{get_target_id()}}{\nRetrieve the target id\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$get_target_id()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-wait_for\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-wait_for}{}}}\n\\subsection{Method \\code{wait_for()}}{\nWait for a Chromote Session to finish. This method will block the R\nsession until the provided promise resolves. The loop from\n\\verb{$get_child_loop()} will only advance just far enough for the promise to\nresolve.\n\\subsection{Examples}{\n\n\\if{html}{\\out{<div class=\"sourceCode r\">}}\\preformatted{b <- ChromoteSession$new()\n\n# Async with promise\np <- b$Browser$getVersion(wait_ = FALSE)\np$then(str)\n\n# Async with callback\nb$Browser$getVersion(wait_ = FALSE, callback_ = str)\n}\\if{html}{\\out{</div>}}\n}\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$wait_for(p)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{p}}{A promise to resolve.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-debug_log\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-debug_log}{}}}\n\\subsection{Method \\code{debug_log()}}{\nSend a debug log message to the parent \\link{Chromote} object\n\\subsection{Examples}{\n\n\\if{html}{\\out{<div class=\"sourceCode r\">}}\\preformatted{b <- ChromoteSession$new()\nb$parent$debug_messages(TRUE)\nb$go_to(\"https://www.r-project.org/\")\n#> SEND \\{\"method\":\"Page.navigate\",\"params\":\\{\"url\":\"https://www.r-project.org/\"\\}| __truncated__\\}\n# Turn off debug messages\nb$parent$debug_messages(FALSE)\n}\\if{html}{\\out{</div>}}\n}\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$debug_log(...)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{...}}{Arguments pasted together with \\code{paste0(..., collapse = \"\")}.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-get_child_loop\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-get_child_loop}{}}}\n\\subsection{Method \\code{get_child_loop()}}{\n\\pkg{later} loop.\n\nFor expert async usage only.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$get_child_loop()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-send_command\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-send_command}{}}}\n\\subsection{Method \\code{send_command()}}{\nSend command through Chrome DevTools Protocol.\n\nFor expert use only.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$send_command(\n  msg,\n  callback = NULL,\n  error = NULL,\n  timeout = NULL\n)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{msg}}{A JSON-serializable list containing \\code{method}, and \\code{params}.}\n\n\\item{\\code{callback}}{Method to run when the command finishes successfully.}\n\n\\item{\\code{error}}{Method to run if an error occurs.}\n\n\\item{\\code{timeout}}{Number of milliseconds for Chrome DevTools Protocol\nexecute a method.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-get_auto_events\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-get_auto_events}{}}}\n\\subsection{Method \\code{get_auto_events()}}{\nResolved \\code{auto_events} value.\n\nFor internal use only.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$get_auto_events()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-auto_events_enable_args\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-auto_events_enable_args}{}}}\n\\subsection{Method \\code{auto_events_enable_args()}}{\nSet or retrieve the \\code{enable} command arguments for a domain. These\narguments are used for the \\code{enable} command that is called for a domain,\ne.g. \\code{Fetch$enable()}, when accessing an event method.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$auto_events_enable_args(domain, ...)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{domain}}{A command domain, e.g. \\code{\"Fetch\"}.}\n\n\\item{\\code{...}}{Arguments to use for auto-events for the domain. If not\nprovided, returns the argument values currently in place for the\ndomain. Use \\code{NULL} to clear the enable arguments for a domain.}\n}\n\\if{html}{\\out{</div>}}\n}\n\\subsection{Examples}{\n\\if{html}{\\out{<div class=\"r example copy\">}}\n\\preformatted{if (interactive()) {\n  b <- ChromoteSession$new(\n    auto_events_enable_args = list(\n      Fetch = list(handleAuthRequests = TRUE)\n    )\n  )\n\n  # Get current `Fetch.enable` args\n  b$auto_events_enable_args(\"Fetch\")\n\n  # Update the `Fetch.enable` args\n  b$auto_events_enable_args(\"Fetch\", handleAuthRequests = FALSE)\n\n  # Reset `Fetch.enable` args\n  b$auto_events_enable_args(\"Fetch\", NULL)\n}\n\n}\n\\if{html}{\\out{</div>}}\n\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-invoke_event_callbacks\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-invoke_event_callbacks}{}}}\n\\subsection{Method \\code{invoke_event_callbacks()}}{\nImmediately call all event callback methods.\n\nFor internal use only.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$invoke_event_callbacks(event, params)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{event}}{A single event string}\n\n\\item{\\code{params}}{A list of parameters to pass to the event callback methods.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-mark_closed\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-mark_closed}{}}}\n\\subsection{Method \\code{mark_closed()}}{\nMark a session, and optionally, the underlying target,\nas closed. For internal use only.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$mark_closed(target_closed)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{target_closed}}{Has the underlying target been closed as well as the\nactive debugging session?}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-is_active\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-is_active}{}}}\n\\subsection{Method \\code{is_active()}}{\nRetrieve active status\nOnce initialized, the value returned is \\code{TRUE}. If \\verb{$close()} has been\ncalled, this value will be \\code{FALSE}.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$is_active()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-check_active\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-check_active}{}}}\n\\subsection{Method \\code{check_active()}}{\nCheck that a session is active, erroring if not.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$check_active()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-get_init_promise\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-get_init_promise}{}}}\n\\subsection{Method \\code{get_init_promise()}}{\nInitial promise\n\nFor internal use only.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$get_init_promise()}\\if{html}{\\out{</div>}}\n}\n\n}\n\\if{html}{\\out{<hr>}}\n\\if{html}{\\out{<a id=\"method-ChromoteSession-print\"></a>}}\n\\if{latex}{\\out{\\hypertarget{method-ChromoteSession-print}{}}}\n\\subsection{Method \\code{print()}}{\nSummarise the current state of the object.\n\\subsection{Usage}{\n\\if{html}{\\out{<div class=\"r\">}}\\preformatted{ChromoteSession$print(..., verbose = FALSE)}\\if{html}{\\out{</div>}}\n}\n\n\\subsection{Arguments}{\n\\if{html}{\\out{<div class=\"arguments\">}}\n\\describe{\n\\item{\\code{...}}{Passed on to \\code{format()} when \\code{verbose} = TRUE}\n\n\\item{\\code{verbose}}{The print method defaults to a brief summary\nof the most important debugging info; use \\code{verbose = TRUE} tp\nsee the complex R6 object.}\n}\n\\if{html}{\\out{</div>}}\n}\n}\n}\n"
  },
  {
    "path": "man/chrome_versions.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/manage.R\n\\name{chrome_versions}\n\\alias{chrome_versions}\n\\alias{chrome_versions_path_cache}\n\\alias{chrome_versions_path}\n\\alias{chrome_versions_add}\n\\alias{chrome_versions_remove}\n\\title{Chrome versions cache helpers}\n\\usage{\nchrome_versions_path_cache(...)\n\nchrome_versions_path(version = \"latest\", binary = \"chrome\", platform = NULL)\n\nchrome_versions_add(version, binary, platform = NULL)\n\nchrome_versions_remove(version, binary, platform = NULL, ask = TRUE)\n}\n\\arguments{\n\\item{...}{Additional path parts.}\n\n\\item{version}{A character string specifying the version to list, add or\nremove.}\n\n\\item{binary}{A character string specifying which binary to list. Defaults to\n\\code{\"all\"} to show all binaries, or can be one or more of of \\code{\"chrome\"},\n\\code{\"chrome-headless-shell\"}, or \\code{\"chromedriver\"}.}\n\n\\item{platform}{A character string specifying the platform(s) to list. If\n\\code{NULL} (default), the platform will be automatically detected, or if\n\\code{\"all\"}, then binaries for all platforms will be listed.}\n\n\\item{ask}{Whether to ask before removing files.}\n}\n\\value{\nA character vector of Chrome binary paths.\n}\n\\description{\n\\ifelse{html}{\\href{https://lifecycle.r-lib.org/articles/stages.html#Experimental}{\\figure{lifecycle-experimental.svg}{options: alt='[E]'}}}{\\strong{[E]}}\n\nThese functions help interact with the cache used by \\pkg{chromote}'s for\nstoring versioned Chrome for Testing binaries:\n\\itemize{\n\\item \\code{chrome_versions_path()}: Returns a path or paths to specific Chrome\nbinaries in the cache.\n\\item \\code{chrome_versions_add()}: Add a specific version to the Chrome versions\ncache.\n\\item \\code{chrome_versions_remove()}: Remove specific versions and binaries from the\nChrome cache. The \\code{version}, \\code{binary} and \\code{platform} arguments can each\ntake \\code{\"all\"} to remove all installed copies of that version, binary or\nplatform.\n\\item \\code{chrome_versions_path_cache()}: Returns the path to the cache directory\nused for Chrome binaries.\n}\n\nManaged Chrome installations is an experimental feature introduced in\nchromote v0.5.0 and was inspired by similar features in\n\\href{https://playwright.dev/}{playwright}.\n}\n\\seealso{\n\\code{\\link[=chrome_versions_list]{chrome_versions_list()}}\n}\n"
  },
  {
    "path": "man/chrome_versions_list.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/manage.R\n\\name{chrome_versions_list}\n\\alias{chrome_versions_list}\n\\title{List installed or available Chrome binary versions}\n\\usage{\nchrome_versions_list(\n  which = c(\"installed\", \"all\"),\n  binary = c(\"all\", \"chrome\", \"chrome-headless-shell\", \"chromedriver\"),\n  platform = NULL\n)\n}\n\\arguments{\n\\item{which}{Whether to list \\code{\"installed\"} local binaries or to list \\code{\"all\"}\nchrome versions available from online sources.}\n\n\\item{binary}{A character string specifying which binary to list. Defaults to\n\\code{\"all\"} to show all binaries, or can be one or more of of \\code{\"chrome\"},\n\\code{\"chrome-headless-shell\"}, or \\code{\"chromedriver\"}.}\n\n\\item{platform}{A character string specifying the platform(s) to list. If\n\\code{NULL} (default), the platform will be automatically detected, or if\n\\code{\"all\"}, then binaries for all platforms will be listed.}\n}\n\\value{\nReturns a \\code{\\link[=data.frame]{data.frame()}} of Chrome for Testing versions with\ncolumns: \\code{version}, \\code{revision}, \\code{binary}, \\code{platform}, \\code{url} (where the\nbinary can be downloaded), and--if \\code{which = \"installed\"}--the local path to\nthe binary in the \\code{\\link[=chrome_versions_path_cache]{chrome_versions_path_cache()}}.\n}\n\\description{\n\\ifelse{html}{\\href{https://lifecycle.r-lib.org/articles/stages.html#Experimental}{\\figure{lifecycle-experimental.svg}{options: alt='[E]'}}}{\\strong{[E]}}\n\nBy default lists the installed Chrome versions in the \\code{\\link[=chrome_versions_path_cache]{chrome_versions_path_cache()}},\nor list all Chrome versions available via Google's\n\\href{https://googlechromelabs.github.io/chrome-for-testing/}{Chrome for Testing}\nservice.\n\nManaged Chrome installations is an experimental feature introduced in\nchromote v0.5.0 and was inspired by similar features in\n\\href{https://playwright.dev/}{playwright}.\n}\n\\examples{\n\\dontshow{if (rlang::is_interactive()) (if (getRversion() >= \"3.4\") withAutoprint else force)(\\{ # examplesIf}\nchrome_versions_list()\n\\dontshow{\\}) # examplesIf}\n}\n"
  },
  {
    "path": "man/chromote-options.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/chromote-package.R\n\\name{chromote-options}\n\\alias{chromote-options}\n\\title{chromote Options}\n\\description{\nThese options and environment variables that are used by chromote. Options\nare lowercase and can be set with \\code{options()}. Environment variables are\nuppercase and can be set in an \\code{.Renviron} file, with \\code{Sys.setenv()}, or in\nthe shell or process running R. If both an option or environment variable are\nsupported, chromote will use the option first.\n\\itemize{\n\\item \\code{CHROMOTE_CHROME} \\cr\nPath to the Chrome executable. If not set, chromote will\nattempt to find and use the system installation of Chrome.\n\\item \\code{chromote.headless}, \\code{CHROMOTE_HEADLESS} \\cr\nHeadless mode for Chrome. Can be \\code{\"old\"} or \\code{\"new\"}. See\n\\href{https://developer.chrome.com/docs/chromium/new-headless}{Chrome Headless mode}\nfor more details.\n\\item \\code{chromote.timeout} \\cr\nTimeout (in seconds) for Chrome to launch or connect. Default is \\code{10}.\n\\item \\code{chromote.launch.echo_cmd} \\cr\nEcho the command used to launch Chrome to the console for debugging.\nDefault is \\code{FALSE}.\n}\n}\n"
  },
  {
    "path": "man/chromote-package.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/chromote-package.R\n\\docType{package}\n\\name{chromote-package}\n\\alias{chromote}\n\\alias{chromote-package}\n\\title{chromote: Headless Chrome Web Browser Interface}\n\\description{\n\\if{html}{\\figure{logo.png}{options: style='float: right' alt='logo' width='120'}}\n\nAn implementation of the 'Chrome DevTools Protocol', for controlling a headless Chrome web browser.\n}\n\\seealso{\nUseful links:\n\\itemize{\n  \\item \\url{https://rstudio.github.io/chromote/}\n  \\item \\url{https://github.com/rstudio/chromote}\n  \\item Report bugs at \\url{https://github.com/rstudio/chromote/issues}\n}\n\n}\n\\author{\n\\strong{Maintainer}: Garrick Aden-Buie \\email{garrick@posit.co} (\\href{https://orcid.org/0000-0002-7111-0077}{ORCID})\n\nAuthors:\n\\itemize{\n  \\item Winston Chang \\email{winston@posit.co}\n  \\item Barret Schloerke \\email{barret@posit.co} (\\href{https://orcid.org/0000-0001-9986-114X}{ORCID})\n}\n\nOther contributors:\n\\itemize{\n  \\item Posit Software, PBC (03wc8by49) [copyright holder, funder]\n}\n\n}\n\\keyword{internal}\n"
  },
  {
    "path": "man/chromote_info.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/chrome.R\n\\name{chromote_info}\n\\alias{chromote_info}\n\\title{Show information about the chromote package and Chrome browser}\n\\usage{\nchromote_info()\n}\n\\value{\nA list containing the following elements:\n\\describe{\n\\item{os}{The operating system platform.}\n\\item{version_r}{The version of R.}\n\\item{version_chromote}{The version of the chromote package.}\n\\item{envvar}{The value of the \\code{CHROMOTE_CHROME} environment variable.}\n\\item{path}{The path to the Chrome browser.}\n\\item{args}{A vector of Chrome arguments.}\n\\item{version}{The version of Chrome (if verification is successful).}\n\\item{error}{The error message (if verification fails).}\n\\item{.check}{A list with the status and output of the Chrome verification.}\n}\n}\n\\description{\nThis function gathers information about the operating system, R version,\nchromote package version, environment variables, Chrome path, and Chrome\narguments. It also verifies the Chrome installation and retrieves its version.\n}\n\\examples{\nchromote_info()\n\n}\n"
  },
  {
    "path": "man/default_chrome_args.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/chromote.R\n\\name{default_chrome_args}\n\\alias{default_chrome_args}\n\\alias{get_chrome_args}\n\\alias{set_chrome_args}\n\\title{Default Chrome arguments}\n\\usage{\ndefault_chrome_args()\n\nget_chrome_args()\n\nset_chrome_args(args)\n}\n\\arguments{\n\\item{args}{A character vector of command-line arguments (or \\code{NULL}) to be\nused with every new \\code{\\link{ChromoteSession}}.}\n}\n\\value{\nA character vector of default command-line arguments to be used with\nevery new \\code{\\link{ChromoteSession}}\n}\n\\description{\nA character vector of command-line arguments passed when initializing any new\ninstance of \\code{\\link{Chrome}}. Single on-off arguments are passed as single values\n(e.g.\\code{\"--disable-gpu\"}), arguments with a value are given with a nested\ncharacter vector (e.g. \\code{c(\"--force-color-profile\", \"srgb\")}). See\n\\href{https://peter.sh/experiments/chromium-command-line-switches/}{here} for a\nlist of possible arguments.\n}\n\\details{\nDefault chromote arguments are composed of the following values (when\nappropriate):\n\\itemize{\n\\item \\href{https://peter.sh/experiments/chromium-command-line-switches/#disable-gpu}{\\code{\"--disable-gpu\"}}\n\\itemize{\n\\item Only added on Windows, as empirically it appears to be needed\n(if not, check runs on GHA never terminate).\n\\item Disables GPU hardware acceleration. If software renderer is not in place, then the GPU process won't launch.\n}\n\\item \\href{https://peter.sh/experiments/chromium-command-line-switches/#no-sandbox}{\\code{\"--no-sandbox\"}}\n\\itemize{\n\\item Only added when \\code{CI} system environment variable is set, when the\nuser on a Linux system is not set, or when executing inside a Docker container.\n\\item Disables the sandbox for all process types that are normally sandboxed. Meant to be used as a browser-level switch for testing purposes only\n}\n\\item \\href{https://peter.sh/experiments/chromium-command-line-switches/#disable-dev-shm-usage}{\\code{\"--disable-dev-shm-usage\"}}\n\\itemize{\n\\item Only added when \\code{CI} system environment variable is set or when inside a docker instance.\n\\item The \\verb{/dev/shm} partition is too small in certain VM environments, causing Chrome to fail or crash.\n}\n\\item \\href{https://peter.sh/experiments/chromium-command-line-switches/#force-color-profile}{\\code{\"--force-color-profile=srgb\"}}\n\\itemize{\n\\item This means that screenshots taken on a laptop plugged into an external\nmonitor will often have subtly different colors than one taken when\nthe laptop is using its built-in monitor. This problem will be even\nmore likely across machines.\n\\item Force all monitors to be treated as though they have the specified color profile.\n}\n\\item \\href{https://peter.sh/experiments/chromium-command-line-switches/#disable-extensions}{\\code{\"--disable-extensions\"}}\n\\itemize{\n\\item Disable extensions.\n}\n\\item \\href{https://peter.sh/experiments/chromium-command-line-switches/#mute-audio}{\\code{\"--mute-audio\"}}\n\\itemize{\n\\item Mutes audio sent to the audio device so it is not audible during automated testing.\n}\n}\n}\n\\section{Functions}{\n\\itemize{\n\\item \\code{default_chrome_args()}: Returns a character vector of command-line\narguments passed when initializing Chrome. See Details for more\ninformation.\n\n\\item \\code{get_chrome_args()}: Retrieves the default command-line arguments\npassed to \\code{\\link{Chrome}} during initialization. Returns either \\code{NULL} or a\ncharacter vector.\n\n\\item \\code{set_chrome_args()}: Sets the default command-line arguments\npassed when initializing. Returns the updated defaults.\n\n}}\n\\examples{\nold_chrome_args <- get_chrome_args()\n\n# Disable the gpu and use of `/dev/shm`\nset_chrome_args(c(\"--disable-gpu\", \"--disable-dev-shm-usage\"))\n\n#... Make new `Chrome` or `ChromoteSession` instance\n\n# Restore old defaults\nset_chrome_args(old_chrome_args)\n}\n"
  },
  {
    "path": "man/default_chromote_object.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/chromote.R\n\\name{default_chromote_object}\n\\alias{default_chromote_object}\n\\alias{has_default_chromote_object}\n\\alias{set_default_chromote_object}\n\\title{Default Chromote object}\n\\usage{\ndefault_chromote_object()\n\nhas_default_chromote_object()\n\nset_default_chromote_object(x)\n}\n\\arguments{\n\\item{x}{A \\link{Chromote} object.}\n}\n\\description{\nReturns the Chromote package's default \\link{Chromote} object. If\nthere is not currently a default \\code{Chromote} object that is active, then\none will be created and set as the default.\n}\n\\details{\n\\code{ChromoteSession$new()} calls this function by default, if the\n\\code{parent} is not specified. That means that when\n\\code{ChromoteSession$new()} is called and there is not currently an\nactive default \\code{Chromote} object, then a new \\code{Chromote} object will\nbe created and set as the default.\n}\n"
  },
  {
    "path": "man/find_chrome.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/chrome.R\n\\name{find_chrome}\n\\alias{find_chrome}\n\\title{Find path to Chrome or Chromium browser}\n\\usage{\nfind_chrome()\n}\n\\value{\nA character vector with the value of \\code{CHROMOTE_CHROME}, or a path to\nthe discovered Chrome executable. If no path to is found, \\code{find_chrome()}\nreturns \\code{NULL}.\n}\n\\description{\n\\pkg{chromote} requires a Chrome- or Chromium-based browser with support for\nthe Chrome DevTools Protocol. There are many such browser variants,\nincluding \\href{https://www.google.com/chrome/}{Google Chrome},\n\\href{https://www.chromium.org/chromium-projects/}{Chromium},\n\\href{https://www.microsoft.com/en-us/edge}{Microsoft Edge} and others.\n\nIf you want \\pkg{chromote} to use a specific browser, set the\n\\code{CHROMOTE_CHROME} environment variable to the full path to the browser's\nexecutable. Note that when \\code{CHROMOTE_CHROME} is set, \\pkg{chromote} will use\nthe value without any additional checks. On Mac, for example, one could use\nMicrosoft Edge by setting \\code{CHROMOTE_CHROME} with the following:\n\n\\if{html}{\\out{<div class=\"sourceCode r\">}}\\preformatted{Sys.setenv(\n  CHROMOTE_CHROME = \"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge\"\n)\n}\\if{html}{\\out{</div>}}\n\nWhen \\code{CHROMOTE_CHROME} is not set, \\code{find_chrome()} will perform a limited\nsearch to find a reasonable executable. On Windows, \\code{find_chrome()} consults\nthe registry to find \\code{chrome.exe}. On Mac, it looks for \\verb{Google Chrome} in\nthe \\verb{/Applications} folder (or tries the same checks as on Linux). On Linux,\nit searches for several common executable names.\n}\n\\examples{\nfind_chrome()\n\n}\n"
  },
  {
    "path": "man/fragments/basic-usage.Rmd",
    "content": "```{r}\n#| echo: false\nif (!exists(\"MAN_PATH\")) MAN_PATH <- \"man\"\n```\n\n## Basic usage\n\nThis will start a headless browser and open an interactive viewer for it in a normal browser, so that you can see what the headless browser is doing.\n\n```R\nlibrary(chromote)\n\nb <- ChromoteSession$new()\n\n# In a web browser, open a viewer for the headless browser. Works best with\n# Chromium-based browsers.\nb$view()\n```\n\nThe browser can be given _commands_, as specified by the [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/). For example, `$Browser$getVersion()` (which corresponds to the [Browser.getVersion](https://chromedevtools.github.io/devtools-protocol/tot/Browser/#method-getVersion) in the API docs) will query the browser for version information:\n\n\n```R\nb$Browser$getVersion()\n#> $protocolVersion\n#> [1] \"1.3\"\n#>\n#> $product\n#> [1] \"HeadlessChrome/98.0.4758.102\"\n#>\n#> $revision\n#> [1] \"@273bf7ac8c909cde36982d27f66f3c70846a3718\"\n#>\n#> $userAgent\n#> [1] \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/98.0.4758.102 Safari/537.36\"\n#>\n#> $jsVersion\n#> [1] \"9.8.177.11\"\n```\n\n\nIf you have the viewer open and run the following, you'll see the web page load in the viewer[^interactive]:\n\n```R\nb$go_to(\"https://www.r-project.org/\")\n```\n\nIn addition to full support of the Chrome Devtools Protocol, `ChromoteSession` objects also have some convenience methods, like `$go_to()` and `$screenshot()`. (See the Examples section below for more information about screenshots.)\n\n```R\n# Saves to screenshot.png\nb$screenshot()\n\n# Takes a screenshot of elements picked out by CSS selector\nb$screenshot(\"sidebar.png\", selector = \".sidebar\")\n```\n\n![A screenshot of the sidebar of r-rproject.org, circa 2023.](`r MAN_PATH`/figures/sidebar.png)\n\n[^interactive]: This simple example works interactively, but if you're using chromote to programmatically take screenshots you'll want to read `vignette(\"example-loading-page\")` for a consistent and reliable approach.\n"
  },
  {
    "path": "man/fragments/features.Rmd",
    "content": "Chromote is an R implementation of the [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/). It works with Chrome, Chromium, Opera, Vivaldi, and other browsers based on [Chromium](https://www.chromium.org/). By default it uses Google Chrome (which must already be installed on the system). To use a different browser, see `vignette(\"which-chrome\")`.\n\nChromote is not the only R package that implements the Chrome DevTools Protocol. Here are some others:\n\n* [crrri](https://github.com/RLesur/crrri) by Romain Lesur and Christophe Dervieux\n* [decapitated](https://github.com/hrbrmstr/decapitated/) by Bob Rudis\n* [chradle](https://github.com/milesmcbain/chradle) by Miles McBain\n\nThe interface to Chromote is similar to [chrome-remote-interface](https://github.com/cyrus-and/chrome-remote-interface) for node.js.\n\n## Features\n\n* Install and use specific versions of Chrome from the [Chrome for Testing](https://googlechromelabs.github.io/chrome-for-testing/) service.\n\n* Offers a synchronous API for ease of use and an asynchronous API for more sophisticated tasks.\n\n* Full support for the Chrome DevTools Protocol for any version of Chrome or any Chrome-based browser.\n\n* Includes convenience methods, like `$screenshot()` and `$set_viewport_size()`, for common tasks.\n\n* Automatically reconnects to previous sessions if the connection from R to Chrome is lost, for example when restarting from sleep state.\n\n* Powers many higher-level packages and functions, like `{shinytest2}` and `rvest::read_html_live()`.\n"
  },
  {
    "path": "man/fragments/install.Rmd",
    "content": "## Installation\n\nInstall the released version of chromote from CRAN:\n\n```{r, eval = FALSE}\ninstall.packages(\"chromote\")\n```\n\nOr install the development version from GitHub with:\n\n```{r, eval = FALSE}\n# install.packages(\"pak\")\npak::pak(\"rstudio/chromote\")\n```\n"
  },
  {
    "path": "man/reexports.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/promises.R\n\\docType{import}\n\\name{reexports}\n\\alias{reexports}\n\\alias{\\%...>\\%}\n\\alias{\\%...!\\%}\n\\alias{\\%...T>\\%}\n\\alias{\\%...T!\\%}\n\\alias{\\%>\\%}\n\\alias{\\%T>\\%}\n\\alias{promise}\n\\alias{then}\n\\alias{catch}\n\\alias{finally}\n\\title{Objects exported from other packages}\n\\keyword{internal}\n\\description{\nThese objects are imported from other packages. Follow the links\nbelow to see their documentation.\n\n\\describe{\n  \\item{magrittr}{\\code{\\link[magrittr:pipe]{\\%>\\%}}, \\code{\\link[magrittr:tee]{\\%T>\\%}}}\n\n  \\item{promises}{\\code{\\link[promises:pipes]{\\%...!\\%}}, \\code{\\link[promises:pipes]{\\%...>\\%}}, \\code{\\link[promises:pipes]{\\%...T!\\%}}, \\code{\\link[promises:pipes]{\\%...T>\\%}}, \\code{\\link[promises:then]{catch}}, \\code{\\link[promises:then]{finally}}, \\code{\\link[promises]{promise}}, \\code{\\link[promises]{then}}}\n}}\n\n"
  },
  {
    "path": "man/with_chrome_version.Rd",
    "content": "% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/manage.R\n\\name{with_chrome_version}\n\\alias{with_chrome_version}\n\\alias{local_chrome_version}\n\\alias{local_chromote_chrome}\n\\alias{with_chromote_chrome}\n\\title{Use a specific version of Chrome or related binaries}\n\\usage{\nwith_chrome_version(\n  version = \"latest-stable\",\n  code,\n  ...,\n  binary = c(\"chrome\", \"chrome-headless-shell\", \"chromedriver\"),\n  platform = NULL,\n  quiet = TRUE\n)\n\nlocal_chrome_version(\n  version = \"latest-stable\",\n  binary = c(\"chrome\", \"chrome-headless-shell\", \"chromedriver\"),\n  platform = NULL,\n  ...,\n  quiet = FALSE,\n  .local_envir = parent.frame()\n)\n\nlocal_chromote_chrome(path, ..., .local_envir = parent.frame())\n\nwith_chromote_chrome(path, code, ...)\n}\n\\arguments{\n\\item{version}{A character string specifying the version to use. The default\nvalue is \\code{\"latest-stable\"} to follow the latest stable release of Chrome.\nFor robust results, and to avoid frequently downloading new versions of\nChrome, use a fully qualified version number, e.g. \\code{\"133.0.6943.141\"}.\n\nIf you specify a partial version, e.g. \\code{\"133\"}, chromote will find the most\nrecent release matching that version, preferring to use the latest\n\\emph{installed} release that matches the partially-specified version. chromote\nalso supports a few special version names:\n\\itemize{\n\\item \\code{\"latest-installed\"}: The latest version currently installed locally in\nchromote's cache. If you don't have any installed versions of the binary,\nchromote uses \\code{\"latest\"}.\n\\item \\code{\"latest\"}: The most recent Chrome for Testing release, which may be a\nbeta or canary release.\n\\item \\code{\"latest-stable\"}, \\code{\"latest-beta\"}, \\code{\"latest-extended\"},\n\\code{\"latest-canary\"} or \\code{\"latest-dev\"}: Installs the latest release from one\nof Chrome's version channels, queried from the\n\\href{https://developer.chrome.com/docs/web-platform/versionhistory/reference#platform-identifiers}{VersionHistory API}.\n\\code{\"latest-stable\"} is the default value of \\code{with_chrome_version()} and\n\\code{local_chrome_version()}.\n\\item \\code{\"system\"}: Use the system-wide installation of Chrome.\n}\n\nChromote also supports}\n\n\\item{code}{\\code{[any]}\\cr Code to execute in the temporary environment}\n\n\\item{...}{Ignored, used to require named arguments and for future feature\nexpansion.}\n\n\\item{binary}{A character string specifying which binary to\nuse. Must be one of \\code{\"chrome\"}, \\code{\"chrome-headless-shell\"}, or\n\\code{\"chromedriver\"}. Default is \\code{\"chrome\"}.}\n\n\\item{platform}{A character string specifying the platform. If \\code{NULL}\n(default), the platform will be automatically detected.}\n\n\\item{quiet}{Whether to print a message indicating which version and binary\nof Chrome is being used. By default, this message is suppressed for\n\\code{\\link[=with_chrome_version]{with_chrome_version()}} and enabled for \\code{\\link[=local_chrome_version]{local_chrome_version()}}.}\n\n\\item{.local_envir}{\\verb{[environment]}\\cr The environment to use for scoping.}\n\n\\item{path}{A direct path to the Chrome (or Chrome-based) binary. See\n\\code{\\link[=find_chrome]{find_chrome()}} for details or \\code{\\link[=chrome_versions_path]{chrome_versions_path()}} for paths\nfrom the chromote-managed cache.}\n}\n\\value{\nTemporarily sets the \\code{CHROMOTE_CHROME} environment variable and\nreturns the result of the \\code{code} argument.\n}\n\\description{\n\\ifelse{html}{\\href{https://lifecycle.r-lib.org/articles/stages.html#Experimental}{\\figure{lifecycle-experimental.svg}{options: alt='[E]'}}}{\\strong{[E]}}\n\nThis function downloads and sets up a specific version of Chrome, using the\n\\href{https://googlechromelabs.github.io/chrome-for-testing/}{Google Chrome for Testing builds}\nfor \\code{chrome}, \\code{chrome-headless-shell} or \\code{chromedriver} for use with\nchromote.\n\nManaged Chrome installations is an experimental feature introduced in\nchromote v0.5.0 and was inspired by similar features in\n\\href{https://playwright.dev/}{playwright}.\n}\n\\details{\nThis function downloads the specified binary, if not already\navailable and configures \\code{\\link[=find_chrome]{find_chrome()}} to use the specified binary while\nevaluating \\code{code} or within the local scope. It uses the\n\"known-good-versions\" list from the Google Chrome for Testing versions at\n\\url{https://googlechromelabs.github.io/chrome-for-testing/}.\n}\n\\section{Functions}{\n\\itemize{\n\\item \\code{with_chrome_version()}: Temporarily use a specific version of Chrome\nduring the evaluation of \\code{code}.\n\n\\item \\code{local_chrome_version()}: Use a specific version of Chrome within the\ncurrent scope.\n\n\\item \\code{local_chromote_chrome()}: Use a specific Chrome, by path, within the\ncurrent scope.\n\n\\item \\code{with_chromote_chrome()}: Temporarily use a specific Chrome version, by\npath, for the evaluation of \\code{code}.\n\n}}\n\\examples{\n\\dontshow{if (rlang::is_interactive()) (if (getRversion() >= \"3.4\") withAutoprint else force)(\\{ # examplesIf}\n# Use the latest version of Chrome\nlocal_chrome_version()\n\n# Use a specific version of chrome-headless-shell\nlocal_chrome_version(\"114.0.5735.90\", binary = \"chrome-headless-shell\")\n\\dontshow{\\}) # examplesIf}\n}\n"
  },
  {
    "path": "pkgdown/_brand.yml",
    "content": "color:\n  palette:\n    blue: \"#007bc2\"\n    indigo: \"#4b00c1\"\n    purple: \"#74149c\"\n    pink: \"#bf007f\"\n    red: \"#c10000\"\n    orange: \"#f45100\"\n    yellow: \"#f9b928\"\n    green: \"#00891a\"\n    teal: \"#00bf7f\"\n    cyan: \"#03c7e8\"\n    white: \"#ffffff\"\n    black: \"#1D1F21\"\n\n  foreground: black\n  background: white\n  primary: blue\n  secondary: gray\n  success: green\n  info: cyan\n  warning: yellow\n  danger: red\n  light: \"#f8f8f8\"\n  dark: \"#212529\"\n\ntypography:\n  fonts:\n    - family: Open Sans\n      source: bunny\n    - family: Source Code Pro\n      source: bunny\n\n  headings:\n    family: Open Sans\n    weight: 300\n  monospace: Source Code Pro\n  monospace-inline:\n    color: pink\n    background-color: transparent\n    size: 1em\n\ndefaults:\n  bootstrap:\n    defaults:\n      navbar-bg: $brand-blue\n      code-color-dark: \"#fa88d4\"\n"
  },
  {
    "path": "pkgdown/_pkgdown.yml",
    "content": "url: https://rstudio.github.io/chromote\n\nauthors:\n  Posit Software, PBC:\n    href: https://www.posit.co\n    html: >-\n      <img src='https://www.tidyverse.org/posit-logo.svg' alt='Posit' height='16' width='62' style=\"margin-bottom: 3px;\" />\n  Barret Schloerke:\n    href: http://schloerke.com\n  Garrick Aden-Buie:\n    href: https://garrickadenbuie.com\n  Winston Chang:\n    href: https://github.com/wch\n\ntemplate:\n  bootstrap: 5\n  light-switch: true\n  theme: github-light\n  theme-dark: github-dark\n  bslib:\n    brand: pkgdown/_brand.yml\n\ndevelopment:\n  mode: auto\n\narticles:\n  - title: Learn chromote\n    navbar: ~\n    contents:\n      - chromote\n      - commands-and-events\n      - sync-async\n      - which-chrome\n  - title: Examples\n    navbar: Examples\n    contents:\n      - example-loading-page\n      - example-screenshot\n      - example-extract-text\n      - starts_with(\"example-\")\n\nreference:\n- title: Chromote Sessions\n  # desc:  ~\n  contents:\n  - ChromoteSession\n  - Chromote\n\n- title: Default settings\n  # desc:  ~\n  contents:\n  - chromote-options\n  - chromote_info\n  - default_chrome_args\n  - default_chromote_object\n\n- title: Browsers\n  # desc: ~\n  contents:\n  - find_chrome\n  - Browser\n  - Chrome\n  - ChromeRemote\n\n- title: Manage and install Chrome for Testing\n  desc: |\n    Download and use any version of Chrome or `chrome-headless-shell` available\n    via the [Chrome for Testing](https://googlechromelabs.github.io/chrome-for-testing/)\n    service.\n  contents:\n  - with_chrome_version\n  - chrome_versions_list\n  - chrome_versions\n\nnews:\n  releases:\n  - text: \"v0.5.0\"\n    href: https://shiny.posit.co/blog/posts/chromote-0.5.0/\n"
  },
  {
    "path": "pkgdown/extra.scss",
    "content": "html[data-bs-theme=\"dark\"] code {\n  background-color: transparent;\n}\n\n.navbar-brand+.nav-text {\n  color: var(--bs-navbar-color) !important;\n}\n\ncode a:any-link {\n  text-decoration-color: currentColor !important;\n}"
  },
  {
    "path": "pkgdown/favicon/site.webmanifest",
    "content": "{\n  \"name\": \"\",\n  \"short_name\": \"\",\n  \"icons\": [\n    {\n      \"src\": \"/web-app-manifest-192x192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\",\n      \"purpose\": \"maskable\"\n    },\n    {\n      \"src\": \"/web-app-manifest-512x512.png\",\n      \"sizes\": \"512x512\",\n      \"type\": \"image/png\",\n      \"purpose\": \"maskable\"\n    }\n  ],\n  \"theme_color\": \"#ffffff\",\n  \"background_color\": \"#ffffff\",\n  \"display\": \"standalone\"\n}"
  },
  {
    "path": "revdep/.gitignore",
    "content": "checks\nlibrary\nchecks.noindex\nlibrary.noindex\ncloud.noindex\ndata.sqlite\n*.html\n"
  },
  {
    "path": "revdep/README.md",
    "content": "# Revdeps\n\n## Failed to check (1)\n\n|package    |version |error |warning |note |\n|:----------|:-------|:-----|:-------|:----|\n|renderthis |?       |      |        |     |\n\n"
  },
  {
    "path": "revdep/cran.md",
    "content": "## revdepcheck results\n\nWe checked 25 reverse dependencies (24 from CRAN + 1 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package.\n\n * We saw 0 new problems\n * We failed to check 0 packages\n\n"
  },
  {
    "path": "revdep/failures.md",
    "content": "# renderthis\n\n<details>\n\n* Version: NA\n* GitHub: NA\n* Source code: https://github.com/cran/renderthis\n* Number of recursive dependencies: 77\n\nRun `revdepcheck::cloud_details(, \"renderthis\")` for more info\n\n</details>\n\n## Error before installation\n\n### Devel\n\n```\n\n\n\n\n\n\n```\n### CRAN\n\n```\n\n\n\n\n\n\n```\n"
  },
  {
    "path": "revdep/problems.md",
    "content": "*Wow, no problems at all. :)*"
  },
  {
    "path": "tests/testthat/_snaps/chromote_session.md",
    "content": "# ChromoteSession auto_events_enable_args errors\n\n    Code\n      chromote_session$auto_events_enable_args(\"Browser\", no_enable = TRUE)\n    Condition\n      Error in `chromote_session$auto_events_enable_args()`:\n      ! Browser does not have an enable method.\n\n---\n\n    Code\n      chromote_session$auto_events_enable_args(\"Animation\", bad = TRUE)\n    Condition\n      Error in `chromote_session$auto_events_enable_args()`:\n      ! Animation.enable does not have argument: `bad`.\n      i Available arguments: `callback_`, `error_`, and `timeout_`\n\n"
  },
  {
    "path": "tests/testthat/_snaps/linux64/manage.md",
    "content": "# with_chrome_version() works\n\n    Code\n      with_chrome_version(\"128.0.6612.0\", with_retries(try_chromote_info))\n    Output\n      $path\n      [1] \"~/.cache/R/chromote/chrome/128.0.6612.0/chrome-linux64/chrome\"\n      \n      $version\n      [1] \"Google Chrome for Testing 128.0.6612.0\"\n      \n\n"
  },
  {
    "path": "tests/testthat/_snaps/mac-arm64/manage.md",
    "content": "# with_chrome_version() works\n\n    Code\n      with_chrome_version(\"128.0.6612.0\", with_retries(try_chromote_info))\n    Output\n      $path\n      [1] \"~/Library/Caches/org.R-project.R/R/chromote/chrome/128.0.6612.0/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing\"\n      \n      $version\n      [1] \"Google Chrome for Testing 128.0.6612.0\"\n      \n\n"
  },
  {
    "path": "tests/testthat/_snaps/win64/manage.md",
    "content": "# with_chrome_version() works\n\n    Code\n      with_chrome_version(\"128.0.6612.0\", with_retries(try_chromote_info))\n    Output\n      $path\n      [1] \"C:/Users/runneradmin/AppData/Local/R/cache/R/chromote/chrome/128.0.6612.0/chrome-win64/chrome.exe\"\n      \n      $version\n      [1] \"128.0.6612.0\"\n      \n\n"
  },
  {
    "path": "tests/testthat/helper.R",
    "content": "skip_if_no_chromote <- function() {\n  skip_on_cran()\n  skip_if(lacks_chromote(), \"chromote not available\")\n}\n\nlacks_chromote <- function() {\n  # We try twice because in particular Windows on GHA seems to need it,\n  # but it doesn't otherwise hurt. More details at\n  # https://github.com/rstudio/shinytest2/issues/209\n  env_cache(globals, \"lacks_chromote\", !has_chromote() && !has_chromote())\n}\n\nhas_chromote <- function() {\n  tryCatch(\n    {\n      default <- default_chromote_object()\n      local_bindings(default_timeout = 5, .env = default)\n      startup <- default$new_session(wait_ = FALSE)\n      default$wait_for(startup)\n      TRUE\n    },\n    error = function(cnd) {\n      FALSE\n    }\n  )\n}\n\nwith_retries <- function(fn, max_tries = 3) {\n  trace <- trace_back()\n\n  retry <- function(tried = 0) {\n    tryCatch(\n      {\n        fn()\n      },\n      error = function(err) {\n        tried <- tried + 1\n        if (tried >= max_tries) {\n          rlang::abort(\n            sprintf(\"Failed after %s tries\", tried),\n            parent = err,\n            trace = trace\n          )\n        } else {\n          retry(tried)\n        }\n      }\n    )\n  }\n\n  retry()\n}\n"
  },
  {
    "path": "tests/testthat/setup.R",
    "content": "on_cran <- !isTRUE(as.logical(Sys.getenv(\"NOT_CRAN\", \"false\")))\n\nif (!on_cran) {\n  has_chromote_envvar <- !identical(Sys.getenv(\"CHROMOTE_CHROME\"), \"\")\n\n  if (!has_chromote_envvar) {\n    local_chrome_version(\"latest-stable\", \"chrome\")\n  }\n}\n"
  },
  {
    "path": "tests/testthat/test-chrome.R",
    "content": "expect_true_eventually <- function(expr, max_tries = 50, delay = 0.1) {\n  expr <- enquo(expr)\n\n  expect_true(\n    with_retries(\n      function() {\n        if (!eval_tidy(expr)) {\n          Sys.sleep(delay)\n          stop(expr_text(expr), \" is not yet TRUE\")\n        }\n        TRUE\n      },\n      max_tries = max_tries\n    )\n  )\n}\n\ntest_that(\"chrome with remote hosts\", {\n  skip_if_no_chromote()\n\n  res <- with_random_port(function(port) {\n    args <- c(\n      get_chrome_args(),\n      \"--headless\",\n      \"--remote-debugging-address=0.0.0.0\",\n      sprintf(\"--remote-debugging-port=%s\", port)\n    )\n\n    p <- processx::process$new(find_chrome(), args)\n    list(port = port, process = p)\n  })\n\n  withr::defer(if (!res$process$is_alive()) res$process$kill())\n\n  remote <- ChromeRemote$new(host = \"localhost\", port = res$port)\n\n  expect_true_eventually(remote$is_alive())\n  expect_true(remote$close()) # does nothing but invisibly returns TRUE\n  expect_true(remote$is_alive())\n\n  chromote <- Chromote$new(browser = remote)\n  expect_true(chromote$is_alive())\n  expect_true(chromote$is_active())\n\n  tab <- ChromoteSession$new(parent = chromote)\n  expect_true(tab$is_active())\n\n  # Close the websocket\n  chromote$.__enclos_env__$private$ws$close()\n  expect_true_eventually(!chromote$is_active())\n  expect_true_eventually(!tab$is_active())\n\n  # Reconnect\n  tab2 <- suppressMessages(tab$respawn())\n  expect_true_eventually(chromote$is_active())\n  expect_true_eventually(tab2$is_active())\n\n  tab2$close()\n  expect_false(tab$is_active())\n  tab2$parent$close()\n  expect_true_eventually(!chromote$is_active())\n  expect_true(chromote$is_alive()) # still alive, we haven't killed the process yet\n\n  res$process$kill()\n  expect_true_eventually(!chromote$is_alive())\n})\n"
  },
  {
    "path": "tests/testthat/test-chromote_session.R",
    "content": "test_that(\"respawning preserves targetId and auto_events\", {\n  skip_if_no_chromote()\n\n  sess1 <- create_session(auto_events = FALSE)\n  sess2 <- sess1$respawn()\n\n  expect_equal(sess1$get_target_id(), sess2$get_target_id())\n  expect_equal(sess1$get_auto_events(), sess2$get_auto_events())\n})\n\ntest_that(\"ChromoteSession track metrics from `Emulation.setDeviceMetricsOverride`\", {\n  skip_if_no_chromote()\n\n  page <- ChromoteSession$new(mobile = TRUE)\n  withr::defer(page$close())\n\n  expect_true(page$.__enclos_env__$private$is_mobile)\n\n  page$Emulation$setDeviceMetricsOverride(\n    600,\n    600,\n    deviceScaleFactor = 2,\n    mobile = FALSE\n  )\n  expect_false(page$.__enclos_env__$private$is_mobile)\n  expect_equal(page$.__enclos_env__$private$pixel_ratio, 2)\n})\n\ntest_that(\"ChromoteSession gets and sets viewport size\", {\n  skip_if_no_chromote()\n  skip_if_offline()\n\n  page <- ChromoteSession$new(width = 400, height = 800, mobile = TRUE)\n  withr::defer(page$close())\n\n  # viewport requires an active page\n  p <- page$Page$loadEventFired(wait_ = FALSE)\n  page$Page$navigate(\"https://example.com\", wait_ = TRUE)\n  page$wait_for(p)\n\n  init_size <- list(\n    width = 400,\n    height = 800,\n    zoom = page$.__enclos_env__$private$pixel_ratio,\n    mobile = TRUE\n  )\n\n  expect_equal(\n    page$get_viewport_size(),\n    init_size\n  )\n\n  expect_equal(\n    page$set_viewport_size(500, 900, zoom = 2, mobile = FALSE),\n    init_size # returned invisibly\n  )\n\n  expect_equal(\n    page$get_viewport_size(),\n    list(\n      width = 500,\n      height = 900,\n      zoom = 2,\n      mobile = FALSE\n    )\n  )\n})\n\ntest_that(\"ChromoteSession inherits `auto_events_enable_args` from parent\", {\n  skip_if_no_chromote()\n\n  args <- list(\n    Fetch = list(handleAuthRequests = TRUE),\n    Network = list(maxTotalBufferSize = 1024)\n  )\n\n  parent <- Chromote$new()\n  for (domain in names(args)) {\n    parent$auto_events_enable_args(domain, !!!args[[domain]])\n  }\n  page <- ChromoteSession$new(parent = parent)\n\n  expect_equal(\n    page$auto_events_enable_args(\"Fetch\"),\n    !!args[[\"Fetch\"]]\n  )\n\n  expect_equal(\n    page$auto_events_enable_args(\"Network\"),\n    !!args[[\"Network\"]]\n  )\n\n  page$auto_events_enable_args(\"Fetch\", handleAuthRequests = FALSE)\n  expect_equal(\n    page$auto_events_enable_args(\"Fetch\"),\n    list(handleAuthRequests = FALSE)\n  )\n  expect_equal(\n    parent$auto_events_enable_args(\"Fetch\"),\n    !!args[[\"Fetch\"]]\n  )\n})\n\ntest_that(\"ChromoteSession$new(auto_events_enable_args)\", {\n  skip_if_no_chromote()\n\n  # b <- ChromoteSession$new()\n  # ls(b, pattern = \"^[A-Z]\") |>\n  #   set_names() |>\n  #   lapply(\\(p) if (is_function(b[[p]]$enable)) names(fn_fmls(b[[p]]$enable))) |>\n  #   purrr::compact() |>\n  #   str()\n\n  args_parent <- list(DOM = list(includeWhitespace = FALSE))\n  args_page <- list(DOM = list(includeWhitespace = TRUE))\n\n  parent <- Chromote$new()\n  for (domain in names(args_parent)) {\n    parent$auto_events_enable_args(domain, !!!args_parent[[domain]])\n  }\n\n  page <- ChromoteSession$new(parent = parent)\n  for (domain in names(args_page)) {\n    page$auto_events_enable_args(domain, !!!args_page[[domain]])\n  }\n\n  expect_equal(\n    page$auto_events_enable_args(\"DOM\"),\n    !!args_page[[\"DOM\"]]\n  )\n\n  expect_equal(\n    page$parent$auto_events_enable_args(\"DOM\"),\n    !!args_parent[[\"DOM\"]]\n  )\n\n  # Unset local page-specific auto events args\n  page$auto_events_enable_args(\"DOM\", NULL)\n  expect_equal(\n    page$auto_events_enable_args(\"DOM\"),\n    !!args_parent[[\"DOM\"]]\n  )\n})\n\ntest_that(\"ChromoteSession auto_events_enable_args errors\", {\n  skip_if_no_chromote()\n\n  chromote_session <- ChromoteSession$new()\n\n  expect_snapshot(\n    chromote_session$auto_events_enable_args(\"Browser\", no_enable = TRUE),\n    error = TRUE\n  )\n\n  expect_snapshot(\n    chromote_session$auto_events_enable_args(\"Animation\", bad = TRUE),\n    error = TRUE\n  )\n\n  expect_warning(\n    chromote_session$auto_events_enable_args(\"Animation\", wait_ = TRUE)\n  )\n})\n\ntest_that(\"ChromoteSession with deviceScaleFactor = 0\", {\n  skip_if_no_chromote()\n  skip_if_offline()\n\n  page <- ChromoteSession$new(width = 400, height = 800, mobile = TRUE)\n  withr::defer(page$close())\n\n  # viewport requires an active page\n  p <- page$Page$loadEventFired(wait_ = FALSE)\n  page$Page$navigate(\"https://example.com\", wait_ = TRUE)\n  page$wait_for(p)\n\n  init_size <- list(\n    width = 400,\n    height = 800,\n    zoom = page$.__enclos_env__$private$pixel_ratio,\n    mobile = TRUE\n  )\n\n  expect_equal(\n    page$get_viewport_size(),\n    init_size\n  )\n\n  expect_equal(\n    page$set_viewport_size(500, 900, zoom = 0, mobile = FALSE),\n    init_size # returned invisibly\n  )\n\n  expect_null(page$.__enclos_env__$private$pixel_ratio)\n\n  expect_equal(\n    page$get_viewport_size(),\n    list(\n      width = 500,\n      height = 900,\n      zoom = 0,\n      mobile = FALSE\n    )\n  )\n})\n"
  },
  {
    "path": "tests/testthat/test-default_chromote_args.R",
    "content": "min_chrome_arg_length <- 3 + is_inside_ci() + is_windows()\n\ntest_that(\"default args are retrieved\", {\n  expect_gte(length(default_chrome_args()), min_chrome_arg_length)\n})\n\ntest_that(\"default args can be reset\", {\n  # safety\n  cur_args <- get_chrome_args()\n  on.exit(\n    {\n      set_chrome_args(cur_args)\n    },\n    add = TRUE\n  )\n\n  reset_chrome_args()\n\n  # Exists\n  expect_gte(length(get_chrome_args()), min_chrome_arg_length)\n\n  # Remove\n  set_chrome_args(NULL)\n  expect_equal(length(get_chrome_args()), 0)\n  expect_gte(length(default_chrome_args()), min_chrome_arg_length)\n\n  # Reset\n  reset_chrome_args()\n  expect_gte(length(get_chrome_args()), min_chrome_arg_length)\n\n  # Remove\n  set_chrome_args(character(0))\n  expect_equal(length(get_chrome_args()), 0)\n})\n\ntest_that(\"default args can be overwritten\", {\n  # safety\n  cur_args <- get_chrome_args()\n  on.exit(\n    {\n      set_chrome_args(cur_args)\n    },\n    add = TRUE\n  )\n\n  reset_chrome_args()\n\n  expect_gte(length(get_chrome_args()), min_chrome_arg_length)\n\n  set_chrome_args(c(\"hello\", \"goodbye\"))\n  expect_equal(length(get_chrome_args()), 2)\n})\n\ntest_that(\"type checking\", {\n  # safety\n  cur_args <- get_chrome_args()\n  on.exit(\n    {\n      set_chrome_args(cur_args)\n    },\n    add = TRUE\n  )\n\n  expect_error(set_chrome_args(NA))\n  expect_error(set_chrome_args(NaN))\n  expect_error(set_chrome_args(1:10))\n})\n"
  },
  {
    "path": "tests/testthat/test-manage.R",
    "content": "skip_on_cran()\n\ntest_that(\"with_chrome_version('system') works\", {\n  system_path <- find_chrome()\n  skip_if_not(nzchar(system_path), \"Chrome is not installed on this system.\")\n\n  fake_chromote_path <- tempfile(\"chrome\")\n\n  local_chromote_chrome(fake_chromote_path)\n  expect_equal(find_chrome(), fake_chromote_path)\n\n  expect_equal(\n    with_chrome_version(\"system\", find_chrome(), quiet = TRUE),\n    system_path\n  )\n})\n\ntry_chromote_info <- function() {\n  info <- chromote_info()\n  if (!is.null(info$error)) {\n    rlang::abort(c(\"Could not resolve full `chromote_info()`.\", i = info$error))\n  }\n\n  info$path <- sub(normalizePath(\"~/\"), \"~\", info$path)\n  list(path = info$path, version = info$version)\n}\n\ntest_that(\"with_chrome_version() manages Chromote object\", {\n  chrome_versions_add(\"128.0.6612.0\", \"chrome\")\n  chrome_versions_add(\"129.0.6668.100\", \"chrome-headless-shell\")\n\n  expect_closed <- function(chromote_obj) {\n    max_wait <- Sys.time() + 15\n    while (chromote_obj$is_alive() && Sys.time() < max_wait) {\n      Sys.sleep(0.1)\n    }\n    if (Sys.time() >= max_wait) {\n      warning(\"Waited the full 15 seconds for the process to close\")\n    }\n    expect_false(chromote_obj$is_alive())\n  }\n\n  chromote_128 <- NULL\n\n  # Another copy of chromote 128 that we start globally, should be unaffected\n  chromote_128_global <- Chromote$new(\n    browser = Chrome$new(path = chrome_versions_path(\"128.0.6612.0\", \"chrome\"))\n  )\n\n  with_chrome_version(\"128.0.6612.0\", {\n    expect_equal(find_chrome(), chrome_versions_path(\"128.0.6612.0\"))\n    if (!has_chromote()) {\n      skip(sprintf(\n        \"Skipping because Chrome failed to start (%s)\",\n        find_chrome()\n      ))\n    }\n    chromote_128 <- default_chromote_object()\n    chromote_129 <- NULL\n\n    with_chrome_version(\"129.0.6668.100\", binary = \"chrome-headless-shell\", {\n      expect_equal(\n        find_chrome(),\n        chrome_versions_path(\"129.0.6668.100\", \"chrome-headless-shell\")\n      )\n      if (!has_chromote()) {\n        skip(sprintf(\n          \"Skipping because Chrome failed to start (%s)\",\n          find_chrome()\n        ))\n      }\n      chromote_129 <- default_chromote_object()\n\n      expect_true(chromote_129$is_alive())\n      expect_equal(chromote_129$get_browser()$get_path(), find_chrome())\n      expect_true(!identical(chromote_129, chromote_128))\n    })\n\n    expect_equal(default_chromote_object(), chromote_128)\n\n    expect_closed(chromote_129)\n    expect_true(chromote_128$is_alive())\n  })\n\n  expect_closed(chromote_128)\n\n  # The global chromote 128 process is still running\n  expect_true(chromote_128_global$is_alive())\n  chromote_128_global$close()\n  expect_closed(chromote_128_global)\n})\n\ntest_that(\"with_chrome_version() works\", {\n  chrome_versions_add(\"128.0.6612.0\", \"chrome\")\n\n  expect_snapshot(\n    with_chrome_version(\"128.0.6612.0\", with_retries(try_chromote_info)),\n    variant = guess_platform()\n  )\n\n  with_chrome_version(\"128.0.6612.0\", {\n    if (!has_chromote()) {\n      skip(sprintf(\n        \"Skipping because Chrome failed to start (%s)\",\n        find_chrome()\n      ))\n    }\n\n    b <- ChromoteSession$new()\n\n    expect_match(\n      b$Runtime$evaluate(\"navigator.appVersion\")$result$value,\n      \"HeadlessChrome/128\"\n    )\n  })\n})\n"
  },
  {
    "path": "tests/testthat/test-utils.R",
    "content": "test_that(\"with_random_port() tries expected number of ports in range\", {\n  min <- 2000L\n  max <- 4000L\n  n <- 25\n\n  tried_ports <- c()\n\n  try_unavailable_port <- function(port) {\n    tried_ports <<- c(tried_ports, port)\n    stop(\"Port \", port, \" is unavailable.\")\n  }\n\n  expect_error(\n    with_random_port(\n      try_unavailable_port,\n      min = min,\n      max = max,\n      n = n\n    )\n  )\n\n  expect_length(tried_ports, n)\n  expect_true(all(tried_ports >= min))\n  expect_true(all(tried_ports <= max))\n})\n\ntest_that(\"with_random_port() stops trying for `error_stop_port_search` errors\", {\n  tried_ports <- c()\n\n  try_port_with_fatal_error <- function(port) {\n    tried_ports <<- c(tried_ports, port)\n    rlang::abort(\n      paste0(\"Port \", port, \" is unavailable.\"),\n      class = \"error_stop_port_search\"\n    )\n  }\n\n  expect_error(\n    with_random_port(try_port_with_fatal_error),\n    class = \"error_stop_port_search\"\n  )\n  expect_length(tried_ports, 1)\n})\n\ntest_that(\"with_random_port() returns result of `startup()`\", {\n  tried_ports <- c()\n\n  accept_round_port <- function(port) {\n    if (port %% 5 == 0) {\n      return(port)\n    }\n\n    tried_ports <<- c(tried_ports, port)\n    stop(\"Odd port\")\n  }\n\n  port <- with_random_port(accept_round_port, n = 100)\n\n  expect_true(port %% 5 == 0)\n\n  if (length(tried_ports)) {\n    expect_true(all(tried_ports %% 5 > 0))\n  }\n})\n\ntest_that(\"with_random_port() startup function can return NULL\", {\n  accept_any_port <- function(port) {\n    NULL\n  }\n\n  port <- with_random_port(accept_any_port)\n  expect_null(port)\n})\n"
  },
  {
    "path": "tests/testthat.R",
    "content": "library(testthat)\nlibrary(chromote)\n\ntest_check(\"chromote\")\n"
  },
  {
    "path": "vignettes/.gitignore",
    "content": "*.html\n*.R\n"
  },
  {
    "path": "vignettes/chromote.Rmd",
    "content": "---\ntitle: \"chromote\"\noutput: rmarkdown::html_vignette\nvignette: >\n  %\\VignetteIndexEntry{chromote}\n  %\\VignetteEngine{knitr::rmarkdown}\n  %\\VignetteEncoding{UTF-8}\n\neditor:\n  markdown:\n    wrap: sentence\n---\n\n```{r, include = FALSE}\nknitr::opts_chunk$set(\n  collapse = TRUE,\n  comment = \"#>\"\n)\nMAN_PATH <- \"../man\"\n```\n\n```{r child=\"../man/fragments/features.Rmd\"}\n```\n\n```{r child=\"../man/fragments/install.Rmd\"}\n```\n\n```{r child=\"../man/fragments/basic-usage.Rmd\"}\n```\n\n\n> **Technical Note**\n>\n> All members of `Chromote` and `ChromoteSession` objects which start with a capital letter (like `b$Page`, `b$DOM`, and `b$Browser`) correspond to domains from the Chrome DevTools Protocol, and are documented in the [official CDP site](https://chromedevtools.github.io/devtools-protocol/).\n> All members which start with a lower-case letter (like `b$screenshot` and `b$close`) are not part of the Chrome DevTools Protocol, and are specific to `Chromote` and `ChromoteSession`.\n\nHere is an example of how to use Chromote to find the position of a DOM element using [DOM.getBoxModel](https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-getBoxModel).\n\n``` r\nx <- b$DOM$getDocument()\nx <- b$DOM$querySelector(x$root$nodeId, \".sidebar\")\nx <- b$DOM$getBoxModel(x$nodeId)\nstr(x)\n#> List of 1\n#>  $ model:List of 6\n#>   ..$ content:List of 8\n#>   .. ..$ : num 128\n#>   .. ..$ : int 28\n#>   .. ..$ : num 292\n#>   .. ..$ : int 28\n#>   .. ..$ : num 292\n#>   .. ..$ : num 988\n#>   .. ..$ : num 128\n#>   .. ..$ : num 988\n#>   ..$ padding:List of 8\n#>   .. ..$ : num 112\n#>   .. ..$ : int 28\n#>   .. ..$ : num 308\n#>   .. ..$ : int 28\n#>   .. ..$ : num 308\n#>   .. ..$ : num 988\n#>   .. ..$ : num 112\n#>   .. ..$ : num 988\n#>   ..$ border :List of 8\n#>   .. ..$ : num 112\n#>   .. ..$ : int 28\n#>   .. ..$ : num 308\n#>   .. ..$ : int 28\n#>   .. ..$ : num 308\n#>   .. ..$ : num 988\n#>   .. ..$ : num 112\n#>   .. ..$ : num 988\n#>   ..$ margin :List of 8\n#>   .. ..$ : int 15\n#>   .. ..$ : int 28\n#>   .. ..$ : num 308\n#>   .. ..$ : int 28\n#>   .. ..$ : num 308\n#>   .. ..$ : num 1030\n#>   .. ..$ : int 15\n#>   .. ..$ : num 1030\n#>   ..$ width  : int 195\n#>   ..$ height : int 960\n```\n\n## Creating new tabs and managing the process\n\nTo create a new tab/window:\n\n``` r\nb1 <- b$new_session()\n```\n\nOnce it's created, you can perform operations with the new tab without affecting the first one.\n\n``` r\nb1$view()\nb1$Page$navigate(\"https://github.com/rstudio/chromote\")\n#> $frameId\n#> [1] \"714439EBDD663E597658503C86F77B0B\"\n#>\n#> $loaderId\n#> [1] \"F39339CBA7D1ACB83618FEF40C3C7467\"\n```\n\nTo close a browser tab/window, you can run:\n\n``` r\nb1$close()\n```\n\nThis is different from shutting down the browser process.\nIf you call `b$close()`, the browser process will still be running, even if all tabs have been closed.\nIf all tabs have been closed, you can still create a new tab by calling `b1$new_session()`.\n\nTo shut down the process, call:\n\n``` r\nb1$parent$close()\n```\n\n`b1$parent` is a `Chromote` object (as opposed to `ChromoteSession`), which represents the browser as a whole.\nThis is explained in [The Chromote object model](#the-chromote-object-model).\n\n## Commands and Events\n\nThe Chrome DevTools Protocol has two types of methods: *commands* and *events*.\nThe methods used in the previous examples are commands.\nThat is, they tell the browser to do something; the browser does it, and then sends back some data.\nLearn more in `vignette(\"commands-and-events\")`.\n\n## The Chromote object model {#the-chromote-object-model}\n\nThere are two R6 classes that are used to represent the Chrome browser.\nOne is `Chromote`, and the other is `ChromoteSession`.\nA `Chromote` object represents the browser as a whole, and it can have multiple *targets*, which each represent a browser tab.\nIn the Chrome DevTools Protocol, each target can have one or more debugging *sessions* to control it.\nA `ChromoteSession` object represents a single *session*.\n\nWhen a `ChromoteSession` object is instantiated, a target is created, then a session is attached to that target, and the `ChromoteSession` object represents the session.\n(It is possible, though not very useful, to have multiple `ChromoteSession` objects connected to the same target, each with a different session.)\n\nA `Chromote` object can have any number of `ChromoteSession` objects as children.\nIt is not necessary to create a `Chromote` object manually.\nYou can simply call:\n\n``` r\nb <- ChromoteSession$new()\n```\n\nand it will automatically create a `Chromote` object if one has not already been created.\nThe Chromote package will then designate that `Chromote` object as the *default* Chromote object for the package, so that any future calls to `ChromoteSession$new()` will automatically use the same `Chromote`.\nThis is so that it doesn't start a new browser for every `ChromoteSession` object that is created.\n\nIn the Chrome DevTools Protocol, most commands can be sent to individual sessions using the `ChromoteSession` object, but there are some commands which can only be sent to the overall browser, using the `Chromote` object.\n\nTo access the parent `Chromote` object from a `ChromoteSession`, you can simply use `$parent`:\n\n``` r\nb <- ChromoteSession$new()\nm <- b$parent\n```\n\nWith a `Chromote` object, you can get a list containing all the `ChromoteSession`s, with `$get_sessions()`:\n\n``` r\nm$get_sessions()\n```\n\nNormally, subsequent calls to `ChromoteSession$new()` will use the existing `Chromote` object.\nHowever, if you want to start a new browser process, you can manually create a `Chromote` object, then spawn a session from it; or you can pass the new `Chromote` object to `ChromoteSession$new()`:\n\n``` r\ncm <- Chromote$new()\nb1 <- cm$new_session()\n\n# Or:\nb1 <- ChromoteSession$new(parent = cm)\n```\n\nNote that if you use either of these methods, the new `Chromote` object `cm` will *not* be set as the default that is used by future calls to `ChromoteSesssion$new()`.\nSee `vignette(\"which-chrome\")` for an example showing how you can set the default `Chromote` object.\n\nThere are also the following classes which represent the browser at a lower level:\n\n-   `Browser`: This represents an instance of a browser that supports the Chrome DevTools Protocol. It contains information about how to communicate with the Chrome browser. A `Chromote` object contains one of these.\n-   `Chrome`: This is a subclass of `Browser` that represents a local browser. It extends the `Browser` class with a `processx::process` object, which represents the browser's system process.\n-   `ChromeRemote`: This is a subclass of `Browser` that represents a browser running on a remote system.\n\n## Debugging\n\nCalling `b$debug_messages(TRUE)` will enable the printing of all the JSON messages sent between R and Chrome.\nThis can be very helpful for understanding how the Chrome DevTools Protocol works.\n\n``` r\nb <- ChromoteSession$new()\nb$parent$debug_messages(TRUE)\nb$Page$navigate(\"https://www.r-project.org/\")\n#> SEND {\"method\":\"Page.navigate\",\"params\":{\"url\":\"https://www.r-project.org/\"},\"id\":53,\"sessionId\":\"12CB6B044A379DA0BDCFBBA55318247C\"}\n#> $frameId\n#> [1] \"BAAC175C67E55886207BADE1776E7B1F\"\n#>\n#> $loaderId\n#> [1] \"66DED3DF9403DA4A307444765FDE828E\"\n\n# Disable debug messages\nb$parent$debug_messages(FALSE)\n```\n\n## Resource cleanup and garbage collection\n\nWhen Chromote starts a Chrome process, it calls `Chrome$new()`.\nThis launches the Chrome process it using `processx::process()`, and enables a supervisor for the process.\nThis means that if the R process stops, the supervisor will detect this and shut down any Chrome processes that were registered with the supervisor.\nThis prevents the proliferation of Chrome processes that are no longer needed.\n\nThe Chromote package will, by default, use a single Chrome process and a single `Chromote` object, and each time `ChromoteSession$new()` is called, it will spawn them from the `Chromote` object.\nSee [The Chromote object model](#the-chromote-object-model) for more information."
  },
  {
    "path": "vignettes/commands-and-events.Rmd",
    "content": "---\ntitle: \"Commands and events\"\noutput: rmarkdown::html_vignette\nvignette: >\n  %\\VignetteIndexEntry{Commands and events}\n  %\\VignetteEngine{knitr::rmarkdown}\n  %\\VignetteEncoding{UTF-8}\n\neditor:\n  markdown:\n    wrap: sentence\n---\n\n```{r, include = FALSE}\nknitr::opts_chunk$set(\n  collapse = TRUE,\n  comment = \"#>\"\n)\n```\n\n# Commands and Events\n\nThe Chrome DevTools Protocol has two types of methods: *commands* and *events*.\nMethods like `Page$navigate()` and `DOM$querySelector()` are **commands**.\nThat is, they tell the browser to do something; the browser does it, and then sends back some data.\n\n**Events** are quite different from commands.\nWhen, for example, you run `b$Page$loadEventFired()`, it does not send a message to the browser.\nRather, this method tells the R process to wait until it receives a `Page.loadEventFired` message from the browser.\n\nHere is an example of how that event can be used.\nNote that these two lines of code must be run together, without any delay at all (this can be enforced by wrapping both lines of code in `{ .... }`).\n\n``` r\nlibrary(chromote)\nb <- ChromoteSession$new()\n\n# Send a command to navigate to a page\nb$Page$navigate(\"https://www.r-project.org\")\n#> $frameId\n#> [1] \"0ADE3CFBAF764B0308ADE1ACCC33358B\"\n#>\n#> $loaderId\n#> [1] \"112AF4AC0C13FF4A95BED8173C3F4C7F\"\n\n# Wait for the Page.loadEventFired event\nb$Page$loadEventFired()\n#> $timestamp\n#> [1] 680.7603\n```\n\nAfter running these two lines, the R process will be blocked.\nWhile it's blocked, the browser will load the page, and then send a message to the R process saying that the `Page.loadEventFired` event has occurred.\nThe message looks something like this:\n\n``` json\n{\"method\":\"Page.loadEventFired\",\"params\":{\"timestamp\":699232.345338}}\n```\n\nAfter the R process receives this message, the function returns the value, which looks like this:\n\n```         \n$timestamp\n[1] 699232.3\n```\n\n> **Note:** This sequence of commands, with `Page$navigate()` and then `Page$loadEventFired()` works interactively when you run the commands slowly, but it will not work 100% of the time.\n> In practice you should use `$go_to()` instead for reliable loading.\n> See `vignette(\"example-loading-page\")` for more information.\n\n## Automatic Events\n\nChromote insulates the user from some of the details of how the CDP implements event notifications.\nEvent notifications are not sent from the browser to the R process by default; you must first send a command to enable event notifications for a domain.\nFor example `Page.enable` enables event notifications for the `Page` domain -- the browser will send messages for *all* `Page` events.\n(See the Events section in [this page](https://chromedevtools.github.io/devtools-protocol/tot/Page/)).\nThese notifications will continue to be sent until the browser receives a `Page.disable` command.\n\nBy default, Chromote hides this implementation detail.\nWhen you call `b$Page$loadEventFired()`, Chromote sends a `Page.enable` command automatically, and then waits until it receives the `Page.loadEventFired` event notification.\nThen it sends a `Page.disable` command.\n\nNote that in asynchronous mode, the behavior is slightly more sophisticated: it maintains a counter of how many outstanding events it is waiting for in a given domain.\nWhen that count goes from 0 to 1, it sends the `X.enable` command; when the count goes from 1 to 0, it sends the `X.disable` command.\nFor more information, see `vignette(\"sync-async\")`.\n\nIf you need to customize the arguments used by the automatically-run `enable` command, you can use the `$auto_events_enable_args()` method of a `Chromote` or `ChromoteSession` instance, e.g. `b$auto_events_enable_args(\"Page\", enableFileChooserOpenedEvent = TRUE)`.\n\nIf you do not want automatic event enabling and disabling, then when creating the ChromoteSession object, use `ChromoteSession$new(auto_events = FALSE)`."
  },
  {
    "path": "vignettes/example-attach-existing.Rmd",
    "content": "---\ntitle: \"Attaching to existing tabs\"\noutput: rmarkdown::html_vignette\nvignette: >\n  %\\VignetteIndexEntry{Attaching to existing tabs}\n  %\\VignetteEngine{knitr::rmarkdown}\n  %\\VignetteEncoding{UTF-8}\n\neditor:\n  markdown:\n    wrap: sentence\n---\n\n```{r, include = FALSE}\nknitr::opts_chunk$set(\n  collapse = TRUE,\n  comment = \"#>\"\n)\n```\n\n``` r\nlibrary(chromote)\nr <- Chromote$new()\n```\n\nWhen you use `ChromoteSession$new()` or `b$new_session()`, you're typically connecting to an existing browser, but creating a new tab to attach to.\nIt's also possible to attach to an existing browser *and* and existing tab.\nIn Chrome debugging terminology a tab is called a \"Target\", and there is a command to retrieve the list of current Targets:\n\n``` r\nr$Target$getTargets()\n```\n\nEvery target has a unique identifier string associated with it called the `targetId`; `\"9DAE349A3A533718ED9E17441BA5159B\"` is an example of one.\n\nHere we define a function that retrieves the ID of the first Target (tab) from a Chromote object:\n\n``` r\nfirst_id <- function(r) {\n  ts <- r$Target$getTargets()$targetInfos\n  stopifnot(length(ts) > 0)\n  r$Target$getTargets()$targetInfos[[1]]$targetId\n}\n```\n\nThe following code shows an alert box in the first tab, whatever it is:\n\n``` r\nrc <- ChromeRemote$new(host = \"localhost\", port = 9222)\nr <- Chromote$new(browser = rc)\ntid <- first_id(r)\nb <- r$new_session(targetId = tid)\nb$Runtime$evaluate('alert(\"this is the first tab\")')\n```"
  },
  {
    "path": "vignettes/example-authentication.Rmd",
    "content": "---\ntitle: \"Websites that require authentication\"\noutput: rmarkdown::html_vignette\nvignette: >\n  %\\VignetteIndexEntry{Websites that require authentication}\n  %\\VignetteEngine{knitr::rmarkdown}\n  %\\VignetteEncoding{UTF-8}\n\neditor:\n  markdown:\n    wrap: sentence\n---\n\n```{r, include = FALSE}\nknitr::opts_chunk$set(\n  collapse = TRUE,\n  comment = \"#>\"\n)\n```\n\nFor websites that require authentication, you can use Chromote to get screenshots by doing the following:\n\n1.  Log in interactively and navigate to the page.\n2.  Capture cookies from the page and save them.\n3.  In a later R session, load the cookies.\n4.  Use the cookies in Chromote and navigate to the page.\n5.  Take a screenshot.\n\nThere are two ways to capture the cookies.\n\n## Method 1: Manually interact with the page\n\nThe first method uses the headless browser's viewer.\nThis can be a bit inconvenient because it requires going through the entire login process, even if you have already logged in with a normal browser.\n\nFirst navigate to the page:\n\n``` r\nlibrary(chromote)\nb <- ChromoteSession$new()\nb$view()\nb$go_to(\"https://beta.rstudioconnect.com/content/123456/\")\n```\n\nNext, log in interactively via the viewer.\nOnce that's done, use Chromote to capture the cookies.\n\n``` r\ncookies <- b$Network$getCookies()\nstr(cookies)\nsaveRDS(cookies, \"cookies.rds\")\n```\n\nAfter saving the cookies, you can restart R and navigate to the page, using the cookies.\n\n``` r\nlibrary(chromote)\nb <- ChromoteSession$new()\nb$view()\ncookies <- readRDS(\"cookies.rds\")\nb$Network$setCookies(cookies = cookies$cookies)\n# Navigate to the app that requires a login\nb$go_to(\"https://beta.rstudioconnect.com/content/123456/\")\nb$screenshot()\n```\n\n## Method 2: Capture and re-use cookies\n\nThe second method captures the cookies using a normal browser.\nThis is can be more convenient because, if you are already logged in, you don't need to do it again.\nThis requires a Chromium-based browser, and it requires running DevTools-in-DevTools on that browser.\n\nFirst, navigate to the page in your browser.\nThen press CMD-Option-I (Mac) or Ctrl-Shift-I (Windows/Linux).\nThe developer tools panel will open.\nMake sure to undock the developer tools so that they are in their own window.\nThen press CMD-Option-I or Ctrl-Shift-I again.\nA second developer tools window will open.\n(See [this SO answer](https://stackoverflow.com/questions/12291138/how-do-you-inspect-the-web-inspector-in-chrome/12291163#12291163) for detailed instructions.)\n\nIn the second developer tools window, run the following:\n\n``` js\nvar cookies = await Main.sendOverProtocol('Network.getCookies', {})\nJSON.stringify(cookies)\n```\n\nThis will return a JSON string representing the cookies for that page.\nFor example:\n\n``` json\n[{\"cookies\":[{\"name\":\"AWSALB\",\"value\":\"T3dNdcdnMasdf/cNn0j+JHMVkZ3RI8mitnAggd9AlPsaWJdsfoaje/OowIh0qe3dDPiHc0mSafe5jNH+1Aeinfalsd30AejBZDYwE\",\"domain\":\"beta.rstudioconnect.com\",\"path\":\"/\",\"expires\":1594632233.96943,\"size\":130,\"httpOnly\":false,\"secure\":false,\"session\":false}]}]\n```\n\nCopy that string to the clipboard.\nIn your R session, you can paste it to this code, surrounded by single-quotes:\n\n``` r\ncookie_json <- '[{\"cookies\":[{\"name\":\"AWSALB\",\"value\":\"T3dNdcdnMasdf/cNn0j+JHMVkZ3RI8mitnAggd9AlPsaWJdsfoaje/OowIh0qe3dDPiHc0mSafe5jNH+1Aeinfalsd30AejBZDYwE\",\"domain\":\"beta.rstudioconnect.com\",\"path\":\"/\",\"expires\":1594632233.96943,\"size\":130,\"httpOnly\":false,\"secure\":false,\"session\":false}]}]'\n\ncookies <- jsonlite::fromJSON(cookie_json, simplifyVector = FALSE)[[1]]\n```\n\nThen you can use Chromote to navigate to the page and take a screenshot.\n\n``` r\nlibrary(chromote)\nb <- ChromoteSession$new()\n\nb$Network$setCookies(cookies = cookies$cookies)\nb$go_to(\"https://beta.rstudioconnect.com/content/123456/\")\n\nb$screenshot()\n```"
  },
  {
    "path": "vignettes/example-cran-tests.Rmd",
    "content": "---\ntitle: \"Using chromote in CRAN tests\"\noutput: rmarkdown::html_vignette\nvignette: >\n  %\\VignetteIndexEntry{Using chromote in CRAN tests}\n  %\\VignetteEngine{knitr::rmarkdown}\n  %\\VignetteEncoding{UTF-8}\n---\n\n```{r, include = FALSE}\nknitr::opts_chunk$set(\n  collapse = TRUE,\n  comment = \"#>\"\n)\n```\n\n::: lead\n**We do not recommend using chromote in tests that you run on CRAN.**\n:::\n\nWe **do recommend** that you test your package's integration with chromote, just not on CRAN.\nInstead, use a continuous testing service, like [GitHub Actions](https://usethis.r-lib.org/reference/github_actions.html), and include `testthat::skip_on_cran()` in tests that require Chrome or chromote.\n\nThere are a number of issues with testing package functionality based on chromote on CRAN:\n\n* By default, chromote uses the system installation of Chrome, which can change frequently and without warning.\n\n* chromote's API depends entirely on Chrome, which may change or break between releases.\n\n* There is no 100% reliable way to check or test which system-installed version of Chrome is used on CRAN.\n  While `chromote_info()` can _generally_ provide this information, we use heuristics to gather the Chrome version that do not always work.\n\n* While chromote now provides features to download and use any version of Chrome, **these features should not be used on CRAN**.\n  For one, downloading Chrome unnecessarily consumes CRAN's limited resources.\n  Furthermore, testing against a pinned version of Chrome won't alert you to issues with the latest version.\n\nGiven these challenges, we instead recommend:\n\n1. Using `testthat::skip_on_cran()` for tests that rely on the availability of Chrome.\n\n2. Run tests in a CI environment, ideally on a [weekly or monthly schedule](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#schedule).\n\n3. Use the system version of Chrome provided by the CI environment, or use \n   ```r\n   local_chrome_version(\"latest-stable\")\n   ``` \n   to ensure you're testing against the latest stable version of Chrome.\n   ```r\n   local_chrome_version(\"latest-stable\", binary = \"chrome-headless-shell\")\n   ```\n   is another valid choice.\n   See `vignette(\"which-chrome\")` for details.\n"
  },
  {
    "path": "vignettes/example-custom-headers.Rmd",
    "content": "---\ntitle: \"Setting custom headers\"\noutput: rmarkdown::html_vignette\nvignette: >\n  %\\VignetteIndexEntry{Setting custom headers}\n  %\\VignetteEngine{knitr::rmarkdown}\n  %\\VignetteEncoding{UTF-8}\n\neditor:\n  markdown:\n    wrap: sentence\n---\n\n```{r, include = FALSE}\nknitr::opts_chunk$set(\n  collapse = TRUE,\n  comment = \"#>\"\n)\n```\n\nCurrently setting custom headers requires a little extra work because it requires `Network.enable` be called before using it.\nIn the future we'll streamline things so that it will happen automatically.\n\n``` r\nlibrary(chromote)\nb <- ChromoteSession$new()\n# Currently need to manually enable Network domain notifications. Calling\n# b$Network$enable() would do it, but calling it directly will bypass the\n# callback counting and the notifications could get automatically disabled by a\n# different Network event. We'll enable notifications for the Network domain by\n# listening for a particular event. We'll also store a callback that will\n# decrement the callback counter, so that we can disable notifications after.\ndisable_network_notifications <- b$Network$responseReceived(function (msg) NULL)\nb$Network$setExtraHTTPHeaders(headers = list(\n  foo = \"bar\",\n  header1 = \"value1\"\n))\n\n# Visit a web page that prints out the request headers\nb$go_to(\"http://scooterlabs.com/echo\")\nb$screenshot(show = TRUE)\n\n\n# Unset extra headers. Note that `list(a=1)[0]` creates an empty _named_ list;\n# an empty unnamed list will cause an error because they're converted to JSON\n# differently. A named list becomes \"{}\", but an unnamed list becomes \"[]\".\nb$Network$setExtraHTTPHeaders(headers = list(a=1)[0])\n\n# Request again\nb$go_to(\"http://scooterlabs.com/echo\")\nb$screenshot(show = TRUE)\n\n\n# Disable extra headers entirely, by decrementing Network callback counter,\n# which will disable Network notifications.\ndisable_network_notifications()\n```"
  },
  {
    "path": "vignettes/example-custom-user-agent.Rmd",
    "content": "---\ntitle: \"Setting custom user agent\"\noutput: rmarkdown::html_vignette\nvignette: >\n  %\\VignetteIndexEntry{Setting custom user agent}\n  %\\VignetteEngine{knitr::rmarkdown}\n  %\\VignetteEncoding{UTF-8}\n\neditor:\n  markdown:\n    wrap: sentence\n---\n\n```{r, include = FALSE}\nknitr::opts_chunk$set(\n  collapse = TRUE,\n  comment = \"#>\"\n)\n```\n\nA user agent is a string of text that a browser sends to a web server to identify itself, including details about the browser type, operating system, and device.\n\nIn ⁠chromote, setting the user agent allows you to simulate requests from different browsers or devices.\nThis is useful for testing how websites behave in various environments, scraping data by mimicking real user behavior, accessing mobile versions of sites, or bypassing restrictions some websites place on certain browsers.\n\nYou can see the user agent string provided by your browser, or a list of other user agents strings, by using a site like <https://UserAgentString.com>.\n\n## Synchronous version\n\n``` r\nlibrary(chromote)\nb <- ChromoteSession$new()\n\nb$Network$setUserAgentOverride(userAgent = \"My fake browser\")\n\nb$go_to(\"http://scooterlabs.com/echo\")\nb$screenshot(show = TRUE)\n```\n\n## Asynchronous version\n\n``` r\nlibrary(chromote)\nb <- ChromoteSession$new()\n\nb$Network$setUserAgentOverride(userAgent = \"My fake browser\", wait_ = FALSE)\np <- b$Page$loadEventFired(wait_ = FALSE)\nb$go_to(\"http://scooterlabs.com/echo\", wait_ = FALSE)\np$then(function(value) {\n  b$screenshot(show = TRUE)\n})\n```"
  },
  {
    "path": "vignettes/example-extract-text.Rmd",
    "content": "---\ntitle: \"Extracting text from a web page\"\noutput: rmarkdown::html_vignette\nvignette: >\n  %\\VignetteIndexEntry{Extracting text from a web page}\n  %\\VignetteEngine{knitr::rmarkdown}\n  %\\VignetteEncoding{UTF-8}\n\neditor:\n  markdown:\n    wrap: sentence\n---\n\n```{r, include = FALSE}\nknitr::opts_chunk$set(\n  collapse = TRUE,\n  comment = \"#>\"\n)\n```\n\n## Using JavaScript\n\nOne way to extract text from a page is to tell the browser to run JavaScript code that does it.\n\n### Synchronous version\n\n``` r\nlibrary(chromote)\nb <- ChromoteSession$new()\n\nb$go_to(\"https://www.whatismybrowser.com/\")\n\n# Run JavaScript to extract text from the page\nx <- b$Runtime$evaluate('document.querySelector(\".corset .string-major a\").innerText')\nx$result$value\n#> [1] \"Chrome 75 on macOS (Mojave)\"\n```\n\n### Asynchronous version\n\n``` r\nlibrary(chromote)\nb <- ChromoteSession$new()\n\np <- b$Page$loadEventFired(wait_ = FALSE)\nb$go_to(\"https://www.whatismybrowser.com/\", wait_ = FALSE)\np$then(function(value) {\n  b$Runtime$evaluate(\n    'document.querySelector(\".corset .string-major a\").innerText'\n  )\n})$\nthen(function(value) {\n  print(value$result$value)\n})\n```\n\n## Using Chrome DevTools Protocol commands\n\nAnother way is to use CDP commands to extract content from the DOM.\nThis does not require executing JavaScript in the browser's context, but it is also not as flexible as JavaScript.\n\n### Synchronous version\n\n``` r\nlibrary(chromote)\nb <- ChromoteSession$new()\n\nb$go_to(\"https://www.whatismybrowser.com/\")\nx <- b$DOM$getDocument()\nx <- b$DOM$querySelector(x$root$nodeId, \".corset .string-major a\")\nb$DOM$getOuterHTML(x$nodeId)\n#> $outerHTML\n#> [1] \"<a href=\\\"/detect/what-version-of-chrome-do-i-have\\\">Chrome 75 on macOS (Mojave)</a>\"\n```\n\n### Asynchronous version\n\n``` r\nlibrary(chromote)\nb <- ChromoteSession$new()\n\nb$go_to(\"https://www.whatismybrowser.com/\", wait_ = FALSE)$\nthen(function(value) {\n  b$DOM$getDocument()\n})$\nthen(function(value) {\n  b$DOM$querySelector(value$root$nodeId, \".corset .string-major a\")\n})$\nthen(function(value) {\n  b$DOM$getOuterHTML(value$nodeId)\n})$\nthen(function(value) {\n  print(value)\n})\n```"
  },
  {
    "path": "vignettes/example-loading-page.Rmd",
    "content": "---\ntitle: \"Loading a page reliably\"\noutput: rmarkdown::html_vignette\nvignette: >\n  %\\VignetteIndexEntry{Loading a page reliably}\n  %\\VignetteEngine{knitr::rmarkdown}\n  %\\VignetteEncoding{UTF-8}\n\neditor:\n  markdown:\n    wrap: sentence\n---\n\n```{r, include = FALSE}\nknitr::opts_chunk$set(\n  collapse = TRUE,\n  comment = \"#>\"\n)\n```\n\nThis document explains why you should use the convenience method `$go_to()` instead of the lower-level Chrome Devtools Protocol command `Page$navigate()`.\n\n\n``` r\nlibrary(chromote)\nb <- ChromoteSession$new()\n```\n\nIn many cases, the commands `Page$navigate()` and then `$Page$loadEventFired()` will not reliably block until the page loads.\nFor example:\n\n``` r\n# Not reliable\nb$Page$navigate(\"https://www.r-project.org/\")\nb$Page$loadEventFired()  # Block until page has loaded\n```\n\nThis is because the browser might successfully navigate to the page before it receives the `loadEventFired` command from R.\n\nIn order to navigate to a page reliably, you must issue the `loadEventFired` command first in async mode, then issue the `navigate` command, and then wait for the `loadEventFired` promise to resolve.\n(If it has already resolved at this point, then the code will continue.)\n\n``` r\n# Reliable method 1: for use with synchronous API\np <- b$Page$loadEventFired(wait_ = FALSE)  # Get the promise for the loadEventFired\nb$Page$navigate(\"https://www.r-project.org/\", wait_ = FALSE)\n\n# Block until p resolves\nb$wait_for(p)\n\n# Add more synchronous commands here\nb$screenshot(\"browser.png\")\n```\n\nThe above code uses the async API to do the waiting, but then assumes that you want to write subsequent code with the synchronous API.\n\nIf you want to go fully async, then instead of calling `wait_for(p)`, you would simply chain more promises from `p`, using `$then()`.\n\n``` r\n# Reliable method 2: for use with asynchronous API\np <- b$Page$loadEventFired(wait_ = FALSE)  # Get the promise for the loadEventFired\nb$Page$navigate(\"https://www.r-project.org/\", wait_ = FALSE)\n\n# Chain more async commands after the page has loaded\np$then(function(value) {\n  b$screenshot(\"browser.png\", wait_ = FALSE)\n})\n```\n\nThis method of calling `Page$loadEventFired()` before `Page$navigate()` is essentially what the `$go_to()` convenience method does. It can also operate in synchronous and asynchronous modes.\n\n``` r\n# Synchronous API\nb$go_to(\"https://www.r-project.org/\")\nb$screenshot(\"browser.png\")\n\n# Asynchronous API\nb$go_to(\"https://www.r-project.org/\", wait_ = FALSE)$\nthen(function(value) {\n  b$screenshot(\"browser.png\")\n})\n```\n\n\nThe synchronous and asynchronous APIs are explained in more detail in `vignette(\"sync-async\")`."
  },
  {
    "path": "vignettes/example-remote-hosts.Rmd",
    "content": "---\ntitle: \"Chrome on remote hosts\"\noutput: rmarkdown::html_vignette\nvignette: >\n  %\\VignetteIndexEntry{Chrome on remote hosts}\n  %\\VignetteEngine{knitr::rmarkdown}\n  %\\VignetteEncoding{UTF-8}\n\neditor:\n  markdown:\n    wrap: sentence\n---\n\n```{r, include = FALSE}\nknitr::opts_chunk$set(\n  collapse = TRUE,\n  comment = \"#>\"\n)\n```\n\nChromote can control a browser running on a remote host.\nTo start the browser, open a terminal on the remote host and run one of the following, depending on your platform:\n\n**Warning: Depending on how the remote machine is configured, the Chrome debug server might be accessible to anyone on the Internet. Proceed with caution.**\n\n```         \n# Mac\n\"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome\" --headless \\\n  --remote-debugging-address=0.0.0.0 --remote-debugging-port=9222\n\n# Linux\ngoogle-chrome --headless --remote-debugging-address=0.0.0.0 --remote-debugging-port=9222\n\n# Windows\n\"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe\"  --headless \\\n  --remote-debugging-address=0.0.0.0 --remote-debugging-port=9222\n```\n\nOr you can launch this process in R:\n\n```{r eval=FALSE}\nlibrary(chromote)\n\nargs <- c(\n  get_chrome_args(),\n  \"--headless\",\n  \"--remote-debugging-address=0.0.0.0\",\n  \"--remote-debugging-port=9222\"\n)\n\np <- processx::process$new(find_chrome(), args)\n\n# To (abruptly) stop this process when you're finished with it:\np$kill()\n```\n\nThen, in your local R session, create a Chromote object with the `host` and `port` (you will need to use the correct IP address).\nOnce it's created, you can spawn a session off of it which you can control as normal:\n\n``` r\nlibrary(chromote)\n\nr <- Chromote$new(\n  browser = ChromeRemote$new(host = \"10.0.0.5\", port = 9222)\n)\n\nb <- r$new_session()\n\nb$Browser$getVersion()\nb$view()\nb$go_to(\"https://www.whatismybrowser.com/\")\nb$screenshot(\"browser.png\")\nb$screenshot(\"browser_string.png\", selector = \".string-major\")\n```\n\nWhen you use `$view()` on the remote browser, your local browser may block scripts for security reasons, which means that you won't be able to view the remote browser.\nIf your local browser is Chrome, there will be a shield-shaped icon in the location bar that you can click in order to enable loading the scripts.\n(Note: Some browsers don't seem to work at all with the viewer.)\n"
  },
  {
    "path": "vignettes/example-screenshot.Rmd",
    "content": "---\ntitle: \"Taking a screenshot of a web page\"\noutput: rmarkdown::html_vignette\nvignette: >\n  %\\VignetteIndexEntry{Taking a screenshot of a web page}\n  %\\VignetteEngine{knitr::rmarkdown}\n  %\\VignetteEncoding{UTF-8}\n\neditor:\n  markdown:\n    wrap: sentence\n---\n\n```{r, include = FALSE}\nknitr::opts_chunk$set(\n  collapse = TRUE,\n  comment = \"#>\"\n)\n```\n\n``` r\nlibrary(chromote)\n```\n\n## Taking a screenshot of a web page\n\nTake a screenshot of the viewport and display it using the [showimage](https://github.com/r-lib/showimage#readme) package.\nThis uses Chromote's `$screenshot()` method, which wraps up many calls to the Chrome DevTools Protocol.\n\n``` r\nb <- ChromoteSession$new()\n\n# ==== Synchronous version ====\n# Run the next two lines together, without any delay in between.\nb$go_to(\"https://www.r-project.org/\")\nb$screenshot(show = TRUE)  # Saves to screenshot.png and displays in viewer\n\n# ==== Async version ====\nb$go_to(\"https://www.r-project.org/\", wait_ = FALSE)$\n  then(function(value) {\n    b$screenshot(show = TRUE)\n  })\n```\n\nIt is also possible to use selectors to specify what to screenshot, as well as the region (\"content\", \"border\", \"padding\", or \"margin\").\n\n``` r\n# Using CSS selectors, choosing the region, and using scaling\nb$screenshot(\"s1.png\", selector = \".sidebar\")\nb$screenshot(\"s2.png\", selector = \".sidebar\", region = \"margin\")\nb$screenshot(\"s3.png\", selector = \".page\", region = \"margin\", scale = 2)\n```\n\nIf a vector is passed to `selector`, it will take a screenshot with a rectangle that encompasses all the DOM elements picked out by the selectors.\nSimilarly, if a selector picks out multiple DOM elements, all of them will be in the screenshot region.\n\n## Setting width and height of the viewport (window)\n\nThe default size of a `ChromoteSession` viewport is 992 by 1323 pixels.\nYou can set the width and height when it is created:\n\n``` r\nb <- ChromoteSession$new(width = 390, height = 844)\n\nb$go_to(\"https://www.r-project.org/\")\nb$screenshot(\"narrow.png\")\n```\n\nWith an existing `ChromoteSession`, you can set the size with `b$set_viewport_size()`:\n\n``` r\nb$set_viewport_size(width = 1600, height = 900)\nb$screenshot(\"wide.png\")\n```\n\nYou can take a \"Retina\" (double) resolution screenshot by using `b$screenshot(scale=2)`:\n\n``` r\nb$screenshot(\"wide-2x.png\", scale = 2)\n```\n\n## Taking a screenshot of a web page after interacting with it\n\nHeadless Chrome provides a remote debugging UI which you can use to interact with the web page.\nThe ChromoteSession's `$view()` method opens a regular browser and navigates to the remote debugging UI.\n\n``` r\nb <- ChromoteSession$new()\n\nb$view()\nb$go_to(\"https://www.google.com\") # Or just type the URL in the navigation bar\n```\n\nAt this point, you can interact with the web page by typing in text and clicking on things.\n\nThen take a screenshot:\n\n``` r\nb$screenshot()\n```\n\n## Taking screenshots of web pages in parallel\n\nWith async code, it's possible to navigate to and take screenshots of multiple websites in parallel.\n\n``` r\nlibrary(promises)\nlibrary(chromote)\nurls <- c(\n  \"https://www.r-project.org/\",\n  \"https://github.com/\",\n  \"https://news.ycombinator.com/\"\n)\n\nscreenshot_p <- function(url, filename = NULL) {\n  if (is.null(filename)) {\n    filename <- gsub(\"^.*://\", \"\", url)\n    filename <- gsub(\"/\", \"_\", filename)\n    filename <- gsub(\"\\\\.\", \"_\", filename)\n    filename <- sub(\"_$\", \"\", filename)\n    filename <- paste0(filename, \".png\")\n  }\n\n  b <- ChromoteSession$new()\n  b$go_to(url, wait_ = FALSE)$\n    then(function(value) {\n      b$screenshot(filename, wait_ = FALSE)\n    })$\n    then(function(value) {\n      message(filename)\n    })$\n    finally(function() {\n      b$close()\n    })\n}\n\n# Screenshot multiple simultaneously\nps <- lapply(urls, screenshot_p)\npa <- promise_all(.list = ps)$then(function(value) {\n  message(\"Done!\")\n})\n\n# Block the console until the screenshots finish (optional)\ncm <- default_chromote_object()\ncm$wait_for(pa)\n#> www_r-project_org.png\n#> github_com.png\n#> news_ycombinator_com.png\n#> Done!\n```"
  },
  {
    "path": "vignettes/sync-async.Rmd",
    "content": "---\ntitle: \"Synchronous vs. asynchronous usage\"\noutput: rmarkdown::html_vignette\nvignette: >\n  %\\VignetteIndexEntry{Synchronous vs. asynchronous usage}\n  %\\VignetteEngine{knitr::rmarkdown}\n  %\\VignetteEncoding{UTF-8}\n\neditor:\n  markdown:\n    wrap: sentence\n---\n\n```{r, include = FALSE}\nknitr::opts_chunk$set(\n  collapse = TRUE,\n  comment = \"#>\"\n)\n```\n\nBy default, when you call methods from a `Chromote` or `ChromoteSession` object, it operates in **synchronous** mode.\nFor example, when you call a command function (like `b$Page$navigate()`), a command message is sent to the headless browser, the headless browser executes that command, and it sends a response message back.\nWhen the R process receives the response, it converts it from JSON to an R object and the function returns that value.\nDuring this time, the R process is blocked; no other R code can execute.\n\nThe methods in Chromote/ChromoteSession objects can also be called in **asynchronous** mode.\nIn async mode, a command function fires off a message to the browser, and then the R process continues running other code; when the response comes back at some time in the future, the R process calls another function and passes the response value to it.\n\nThere are two different ways of using async with Chromote.\nThe first is with [promises](https://rstudio.github.io/promises/) (note that these are not the regular R-language promises; these are similar to JavaScript promises for async programming.) The second way is with callbacks: you call methods with a `callback_` argument.\nAlthough callbacks are initially easier to use than promises, once you start writing more complex code, managing callbacks becomes very difficult, especially when error handling is involved.\nFor this reason, this document will focus mostly on promises instead of callback-style programming.\n\nWhen Chromote methods are called in synchronous mode, under the hood, they are implemented with asynchronous functions, and then waiting for the asynchronous functions to resolve.\n\n> **Technical note: About the event loop**\n>\n> When methods are called asynchronously, the R process will run callbacks and promises using an event loop provided by the [later](https://github.com/r-lib/later) package.\n> This event loop is very similar to the one used in JavaScript, which is explained in depth by [Philip Roberts in this video](https://youtu.be/8aGhZQkoFbQ).\n> One important difference between JavaScript's event loop and the one provided by **later**'s is that in JavaScript, the event loop only runs when the call stack is empty (essentially, when the JS runtime is idle); with **later** the event loop similarly runs when the call stack is empty (when the R console is idle), but it can also be run at any point by calling `later::run_now()`.\n>\n> There is another important difference between the JS event loop and the one used by Chromote: Chromote uses *private event loops* provided by [later](https://github.com/r-lib/later).\n> Running the private event loop with `run_now()` will not interfere with the global event loop.\n> This is crucial for being able to run asynchronous code in a way that appears synchronous.\n\n## Why use async?\n\nThe synchronous API is easier to use than the asynchronous one.\nSo why would you want to use the async API?\nHere are some reasons:\n\n-   The async API allows you to send commands to the browser that may take some time for the browser to complete, and they will not block the R process from doing other work while the browser executes the command.\n-   The async API lets you send commands to multiple browser \"tabs\" and let them work in parallel.\n\nOn the other hand, async programming can make it difficult to write code that proceeds in a straightforward, linear manner.\nAsync programming may be difficult to use in, say, an analysis script.\n\nWhen using Chromote interactively at the R console, it's usually best to just call methods synchronously.\nThis fits well with a iterative, interactive data analysis workflow.\n\nWhen you are *programming* with Chromote instead of using it interactively, it is in many cases better to call the methods asynchronously, because it allows for better performance.\nIn a later section, we'll see how to write asynchronous code with Chromote that can be run either synchronously or asynchronously.\nThis provides the best of both worlds.\n\nTo see this in action, we'll first start a new chromote session:\n\n``` r\nlibrary(chromote)\nb <- ChromoteSession$new()\n```\n\n## Async commands\n\nWhen a method is called in synchronous mode, it blocks until the browser sends back a response, and then it returns the value, converted from JSON to an R object.\nFor example:\n\n``` r\n# Synchronous\nstr(b$Browser$getVersion())\n#> List of 5\n#>  $ protocolVersion: chr \"1.3\"\n#>  $ product        : chr \"HeadlessChrome/98.0.4758.102\"\n#>  $ revision       : chr \"@273bf7ac8c909cde36982d27f66f3c70846a3718\"\n#>  $ userAgent      : chr \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/98.0.4758.102 Safari/537.36\"\n#>  $ jsVersion      : chr \"9.8.177.11\"\n```\n\nIn async mode, there are two ways to use the value that the browser sends to the R process.\nOne is to use the `callback_` argument with `wait_=FALSE`.\nThe `wait_=FALSE` tells it to run the command in async mode; instead of returning the value from the browser, it returns a promise.\nFor example:\n\n``` r\n# Async with callback\nb$Browser$getVersion(wait_ = FALSE, callback_ = str)\n#> <Promise [pending]>\n#> List of 5\n#>  $ protocolVersion: chr \"1.3\"\n#>  $ product        : chr \"HeadlessChrome/98.0.4758.102\"\n#>  $ revision       : chr \"@273bf7ac8c909cde36982d27f66f3c70846a3718\"\n#>  $ userAgent      : chr \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/98.0.4758.102 Safari/537.36\"\n#>  $ jsVersion      : chr \"9.8.177.11\"\n```\n\nNotice that the function returned `<Promise [pending]>`, and then it printed out the data.\nWe'll come back to the promise part.\n\n> **Technical note**\n>\n> When you pass a function as `callback_`, that function is used as the first step in the promise chain that is returned.\n\nIf you run the command in a code block (or a function), the entire code block will finish executing before the callback can be executed.\nFor example:\n\n``` r\n{\n  b$Browser$getVersion(wait_ = FALSE, callback_ = str)\n  1+1\n}\n#> [1] 2\n#> List of 5\n#>  $ protocolVersion: chr \"1.3\"\n#>  $ product        : chr \"HeadlessChrome/98.0.4758.102\"\n#>  $ revision       : chr \"@273bf7ac8c909cde36982d27f66f3c70846a3718\"\n#>  $ userAgent      : chr \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/98.0.4758.102 Safari/537.36\"\n#>  $ jsVersion      : chr \"9.8.177.11\"\n```\n\nIn the code above, it executes the `1+1` and returns the value before the `str` callback can be executed on the message from the browser.\n\nIf you want to store the value from the browser, you can write a callback that stores the value like so:\n\n``` r\n# This will extract the product field\nproduct <- NULL\nb$Browser$getVersion(wait_ = FALSE, callback_ = function(msg) {\n  product <<- msg$product\n})\n#> <Promise [pending]>\n# Wait for a moment, then run:\nproduct\n#> [1] \"HeadlessChrome/98.0.4758.102\"\n```\n\nBut to get the value, you need to wait for the callback to execute before you can use the value.\nWaiting for a value is simple when running R interactively -- you can just add a `message(\"message arrived\")` call in the callback and wait for it before running the next line of code -- but waiting for the value is not easy to do using ordinary straight-line coding.\nFortunately, Chromote has a way to wait for async operations, which we'll see later.\n\nThe other way of using the value is to use *promises*.\nIf `wait_=FALSE` and no `callback_` is passed to the command, then it will simply return a promise.\nPromises have many advantages over plain old callbacks: they are easier to chain, and they provide better error-handling capabilities.\nYou can *chain* more steps to the promise: when the promise resolves -- that is, when the message is received from the browser -- it will run the next step in the promise chain.\n\nHere's an example that uses promises to print out the version information.\nNote that the surrounding curly braces are there to indicate that this whole thing must be run as a block without any idle time in between the function calls -- if you were to run the code in the R console line-by-line, the browser would send back the message and the promise would resolve before you called `p$then()`, which is where you tell the promise what to do with the return value.\n(The curly braces aren't strictly necessary -- you could run the code inside the braces in a single paste operation and have the same effect.)\n\n``` r\n{\n  p <- b$Browser$getVersion(wait_ = FALSE)\n  p$then(function(value) {\n    print(value$product)\n  })\n}\n# Wait for a moment, then prints:\n#> [1] \"HeadlessChrome/98.0.4758.102\"\n```\n\nHere are some progressively more concise ways of achieving the same thing.\nAs you work with promises, you will see these various forms of promise chaining.\nFor more information, see the [promises documentation](https://rstudio.github.io/promises/).\n\n``` r\nlibrary(promises)\n\n# Chained method call to $then()  \nb$Browser$getVersion(wait_ = FALSE)$then(function(value) {  \n  print(value$product)  \n})  \n\n# Regular function pipe to promises::then() function \nb$Browser$getVersion(wait_ = FALSE) |> then(function(value) {  \n  print(value$product)  \n})  \n\n\n# Promise-pipe to anonymous function, which must be wrapped in parens\nb$Browser$getVersion(wait_ = FALSE) %...>% (function(value) {\n  print(value$product)\n})\n\n# Promise-pipe to an expression (which gets converted to a function with the first argument `.`)\nb$Browser$getVersion(wait_ = FALSE) %...>% { print(.$product) }\n\n# Promise-pipe to a named function, with parentheses\nprint_product <- function(msg) print(msg$product)\nb$Browser$getVersion(wait_ = FALSE) %...>% print_product()\n\n# Promise-pipe to a named function, without parentheses\nb$Browser$getVersion(wait_ = FALSE) %...>% print_product\n```\n\nThe earlier example where we found the dimensions of a DOM element using CSS selectors was done with the synchronous API and `%>%` pipes.\nThe same can be done in async mode by switching from the regular pipe to the promise-pipe, and calling all the methods with `wait_=FALSE`:\n\n``` r\nb$DOM$getDocument(wait_ = FALSE) %...>%\n  { b$DOM$querySelector(.$root$nodeId, \".sidebar\", wait_ = FALSE) } %...>%\n  { b$DOM$getBoxModel(.$nodeId, wait_ = FALSE) } %...>%\n  str()\n\n\n# Or, more verbosely:\nb$DOM$getDocument(wait_ = FALSE)$\n  then(function(value) {\n    b$DOM$querySelector(value$root$nodeId, \".sidebar\", wait_ = FALSE)\n  })$\n  then(function(value) {\n    b$DOM$getBoxModel(value$nodeId, wait_ = FALSE)\n  })$\n  then(function(value) {\n    str(value)\n  })\n```\n\nEach step in the promise chain uses the value from the previous step, via `.` or `value`.\nNote that not all asynchronous code works in such a linear, straightforward way.\nSometimes it is necessary to save data from intermediate steps in a broader-scoped variable, if it is to be used in a later step in the promise chain.\n\n## Turning asynchronous code into synchronous code\n\nThere may be times, especially when programming with Chromote, where you want to wait for a promise to resolve before continuing.\nTo do this, you can use the Chromote or ChromoteSession's `wait_for()` method.\n\n``` r\n# A promise chain\np <- b$DOM$getDocument(wait_ = FALSE) %...>%\n  { b$DOM$querySelector(.$root$nodeId, \".sidebar\", wait_ = FALSE) } %...>%\n  { b$DOM$getBoxModel(.$nodeId, wait_ = FALSE) } %...>%\n  str()\n\nb$wait_for(p)\n#> List of 1\n#>  $ model:List of 6\n#>   ..$ content:List of 8\n#>   .. ..$ : num 128\n#>   .. ..$ : int 28\n#>   .. ..$ : num 292\n#>   .. ..$ : int 28\n#>   .. ..$ : num 292\n#>   .. ..$ : num 988\n#>   .. ..$ : num 128\n#>   .. ..$ : num 988\n#>   ..$ padding:List of 8\n#>   .. ..$ : num 112\n#>   .. ..$ : int 28\n#>   .. ..$ : num 308\n#>   .. ..$ : int 28\n#>   .. ..$ : num 308\n#>   .. ..$ : num 988\n#>   .. ..$ : num 112\n#>   .. ..$ : num 988\n#>   ..$ border :List of 8\n#>   .. ..$ : num 112\n#>   .. ..$ : int 28\n#>   .. ..$ : num 308\n#>   .. ..$ : int 28\n#>   .. ..$ : num 308\n#>   .. ..$ : num 988\n#>   .. ..$ : num 112\n#>   .. ..$ : num 988\n#>   ..$ margin :List of 8\n#>   .. ..$ : int 15\n#>   .. ..$ : int 28\n#>   .. ..$ : num 308\n#>   .. ..$ : int 28\n#>   .. ..$ : num 308\n#>   .. ..$ : num 1030\n#>   .. ..$ : int 15\n#>   .. ..$ : num 1030\n#>   ..$ width  : int 195\n#>   ..$ height : int 960\n```\n\nThis documentation will refer to this technique as *synchronizing* asynchronous code.\nThe way that `wait_for()` works is that it runs the Chromote object's private event loop until the promise has resolved.\nBecause the event loop is *private*, running it will not interfere with the global event loop, which, for example, may used by Shiny to serve a web application.\n\nThe `$wait_for()` method will return the value from the promise, so instead of putting the `str()` in the chain, you call `str()` on the value returned by `$wait_for()`:\n\n``` r\np <- b$DOM$getDocument(wait_ = FALSE) %...>%\n  { b$DOM$querySelector(.$root$nodeId, \".sidebar\", wait_ = FALSE) } %...>%\n  { b$DOM$getBoxModel(.$nodeId, wait_ = FALSE) }\n\nx <- b$wait_for(p)\nstr(x)\n#> List of 1\n#>  $ model:List of 6\n#>   ..$ content:List of 8\n#>   .. ..$ : num 128\n#>   .. ..$ : int 28\n#>   .. ..$ : num 292\n#>   .. ..$ : int 28\n#>   .. ..$ : num 292\n#>   .. ..$ : num 988\n#>   .. ..$ : num 128\n#>   .. ..$ : num 988\n#>   ..$ padding:List of 8\n#>   .. ..$ : num 112\n#>   .. ..$ : int 28\n#>   .. ..$ : num 308\n#>   .. ..$ : int 28\n#>   .. ..$ : num 308\n#>   .. ..$ : num 988\n#>   .. ..$ : num 112\n#>   .. ..$ : num 988\n#>   ..$ border :List of 8\n#>   .. ..$ : num 112\n#>   .. ..$ : int 28\n#>   .. ..$ : num 308\n#>   .. ..$ : int 28\n#>   .. ..$ : num 308\n#>   .. ..$ : num 988\n#>   .. ..$ : num 112\n#>   .. ..$ : num 988\n#>   ..$ margin :List of 8\n#>   .. ..$ : int 15\n#>   .. ..$ : int 28\n#>   .. ..$ : num 308\n#>   .. ..$ : int 28\n#>   .. ..$ : num 308\n#>   .. ..$ : num 1030\n#>   .. ..$ : int 15\n#>   .. ..$ : num 1030\n#>   ..$ width  : int 195\n#>   ..$ height : int 960\n```\n\nThere are some methods in Chromote and ChromoteSession objects which are written using asynchronous method calls, but conditionally use `wait_for()` so that they can be called either synchronously or asynchronously.\nThe `$screenshot()` method works this way, for example.\nYou can call `b$screenshot(wait_=TRUE)` (which is the default) for synchronous behavior, or `b$screenshot(wait_=FALSE)` for async behavior.\n\nIf you want to write a function that can be called in either sync or async mode, you can use this basic structure: First, construct a promise chain by calling the CDP methods with `wait_=FALSE`.\nThen, at the end, if the user used `wait_=TRUE`, wait for the promise to resolve; otherwise, simply return the promise.\n\n``` r\ngetBoxModel <- function(b, selector = \"html\", wait_ = TRUE) {\n  p <- b$DOM$getDocument(wait_ = FALSE) %...>%\n    { b$DOM$querySelector(.$root$nodeId, selector, wait_ = FALSE) } %...>%\n    { b$DOM$getBoxModel(.$nodeId, wait_ = FALSE) }\n\n  if (wait_) {\n    b$wait_for(p)\n  } else {\n    p\n  }\n}\n\n# Synchronous call\nstr(getBoxModel(b, \".sidebar\"))\n\n# Asynchronous call\ngetBoxModel(b, \".sidebar\", wait_ = FALSE) %...>%\n  str()\n```\n\nBut, you might be wondering, if we want a synchronous API, why not simply write the synchronous code by calling the individual methods synchronously, and using a normal pipe to connect them, as in:\n\n``` r\nb$DOM$getDocument() %>%\n  { b$DOM$querySelector(.$root$nodeId, \".sidebar\") } %>%\n  { b$DOM$getBoxModel(.$nodeId) } %>%\n  str()\n```\n\nThere are two reasons for this.\nThe first is that this would require a duplication of all the code for the sync and async code paths.\nAnother reason is that the internal async code can be written to send multiple independent command chains to the ChromoteSession (or multiple ChromoteSessions), and they will be executed concurrently.\nIf there are multiple promise chains, you can do something like the following to wait for all of them to resolve:\n\n``` r\n# Starting with promises p1, p2, and p3, create a promise that resolves only\n# after they have all been resolved.\np <- promise_all(p1, p2, p3)\nb$wait_for(p)\n```\n\n## Async events\n\nIn addition to *commands* The Chrome DevTools Protocol also has *events*.\nThese are messages that are sent from the browser to the R process when various browser events happen.\n\nAs an example, it can be a bit tricky to find out when to take a screenshot.\nWhen you send the browser a command to navigate to a page, it sends a response immediately, but it may take several more seconds for it to actually finish loading that page.\nWhen it does, the `Page.loadEventFired` event will be fired.\n\n``` r\nb <- ChromoteSession$new()\n\n# Navigate and wait for Page.loadEventFired.\n# Note: these lines are put in a single code block to ensure that there is no\n# idle time in between.\n{\n  b$Page$navigate(\"https://www.r-project.org/\")\n  str(b$Page$loadEventFired())\n}\n#> List of 1\n#>  $ timestamp: num 683\n```\n\nWith the synchronous API, the call to `b$Page$loadEventFired()` will block until Chromote receives a `Page.loadEventFired` message from the browser.\nHowever, with the async promise API, you would write it like this:\n\n``` r\nb$Page$navigate(\"https://www.r-project.org/\", wait_ = FALSE) %...>%\n  { b$Page$loadEventFired(wait_ = FALSE) } %...>%\n  { str(.) }\n\n# Or, more verbosely:\nb$Page$navigate(\"https://www.r-project.org/\", wait_ = FALSE)$\n  then(function(value) {\n    b$Page$loadEventFired(wait_ = FALSE)\n  })$\n  then(function(value) {\n    str(value)\n  })\n```\n\nThere will be a short delay after running the code before the value is printed.\n\nHowever, even this is not perfectly reliable, because in some cases, the browser will navigate to the page before it receives the `loadEventFired` command from Chromote.\nIf that happens, the load even will have already happened before the browser starts waiting for it, and it will hang.\nThe correct way to deal with this is to issue the `loadEventFired` command *before* navigating to the page, and then wait for the `loadEventFired` promise to resolve.\n\n``` r\n# This is the correct way to wait for a page to load with async and then chain more commands\np <- b$Page$loadEventFired(wait_ = FALSE)\nb$Page$navigate(\"https://www.r-project.org/\", wait_ = FALSE)\n\n# A promise chain of more commands after the page has loaded\np$then(function(value) {\n  str(value)\n})\n```\n\nIf you want to block until the page has loaded, you can once again use `wait_for()`.\nFor example:\n\n``` r\np <- b$Page$loadEventFired(wait_ = FALSE)\nb$Page$navigate(\"https://www.r-project.org/\", wait_ = FALSE)\n\n# wait_for returns the last value in the chain, so we can call str() on it\nstr(b$wait_for(p))\n#> List of 1\n#>  $ timestamp: num 683\n```\n\n`b$go_to()` is a convenience method that does things in this order, and in most cases you should use it instead of calling `Page$navigate()` directly.\n\n> **Technical note**\n>\n> The Chrome DevTools Protocol itself does not automatically enable event notifications.\n> Normally, you would have to call the `Page.enable` method to turn on event notifications for the Page domain.\n> However, Chromote saves you from needing to do this step by keeping track of how many callbacks there are for each domain.\n> When the number of event callbacks for a domain goes from 0 to 1, Chromote automatically calls `$enable()` for that domain, and when it goes from 1 to 0, it it calls `$disable()`.\n> See `vignette(\"commands-and-events\")` for more details.\n\nIn addition to async events with promises, they can also be used with regular callbacks.\nFor example:\n\n``` r\nb$Page$loadEventFired(callback_ = str)\n```\n\nThis tells Chromote to call `str()` (which prints to the console) on the message value every single time that a `Page.loadEventFired` event message is received.\nIt will continue doing this indefinitely.\n(Calling an event method this way also increments the event callback counter.)\n\nWhen an event method is called with a callback, the return value is a function which will cancel the callback, so that it will no longer fire.\n(The canceller function also decrements the event callback counter. If you lose the canceller function, there is no way to decrement the callback counter back to 0.)\n\n``` r\ncancel_load_event_callback <- b$Page$loadEventFired(callback_ = str)\n\n# Each of these will cause the callback to fire.\nn1 <- b$Page$navigate(\"https://www.r-project.org/\")\nn2 <- b$Page$navigate(\"https://cran.r-project.org/\")\n\ncancel_load_event_callback()\n\n# No longer causes the callback to fire.\nn3 <- b$Page$navigate(\"https://www.rstudio.com/\")\n```"
  },
  {
    "path": "vignettes/which-chrome.Rmd",
    "content": "---\ntitle: \"Choosing which Chrome-based browser to use\"\noutput: rmarkdown::html_vignette\nvignette: >\n  %\\VignetteIndexEntry{Choosing which Chrome-based browser to use}\n  %\\VignetteEngine{knitr::rmarkdown}\n  %\\VignetteEncoding{UTF-8}\n\neditor:\n  markdown:\n    wrap: sentence\n---\n\n```{r, include = FALSE}\nknitr::opts_chunk$set(\n  collapse = TRUE,\n  comment = \"#>\"\n)\n```\n\n## Use any version of Chrome or `chrome-headless-shell` with chromote\n\nBy default, chromote uses the Chrome browser installed on your system.\nModern browsers automatically and frequently update, which is convenient for you when you're browsing the Internet but can easily introduce breaking and unexpected changes in your automations.\n\nThe chromote package allows you to download and use any version of Chrome or `chrome-headless-shell` available via the [Google Chrome for Testing](https://googlechromelabs.github.io/chrome-for-testing/) service.\n\nTo get started, call `local_chrome_version()` with a specific `version` and `binary` choice at the start of your script, before you create a new `ChromoteSession`:\n\n```r\nlibrary(chromote)\n\nlocal_chrome_version(\"latest-stable\", binary = \"chrome\")\n#> ℹ Downloading `chrome` version 134.0.6998.88 for mac-arm64\n#> trying URL 'https://storage.googleapis.com/chrome-for-testing-public/134.0.6998.88/mac-arm64/chrome-mac-arm64.zip'\n#> Content type 'application/zip' length 158060459 bytes (150.7 MB)\n#> ==================================================\n#> downloaded 150.7 MB\n#> \n#> ✔ Downloading `chrome` version 134.0.6998.88 for mac-arm64 [5.3s]\n#> chromote will now use version 134.0.6998.88 of `chrome` for mac-arm64.\n\nb <- ChromoteSession$new()\n```\n\nBy default, `local_chrome_version()` uses the latest stable version of Chrome, matching the arguments shown in the code example above.\n\nFor scripts with a longer life span and to ensure reproducibility, you can specify a specific version of Chrome or `chrome-headless-shell`:\n\n```r\nlocal_chrome_version(\"134.0.6998.88\", binary = \"chrome-headless-shell\")\n#> chromote will now use version 134.0.6998.88 of `chrome-headless-shell` for mac-arm64.\n```\n\nIf you don't already have a copy of the requested version of the binary, `local_chrome_version()` will download it for you so you'll only need to download the binary once.\nYou can list all of the versions and binaries you've installed with `chrome_versions_list()`, or all available versions and binaries with `chrome_versions_list(\"all\")`.\n\n```r\nchrome_versions_list()\n#> # A tibble: 2 × 6\n#>   version       revision binary                platform  url                        path \n#>   <chr>         <chr>    <chr>                 <chr>     <chr>                      <chr>\n#> 1 134.0.6998.88 1415337  chrome                mac-arm64 https://storage.googleapi… /Use…\n#> 2 134.0.6998.88 1415337  chrome-headless-shell mac-arm64 https://storage.googleapi… /Use…\n```\n\n> **Technincal Note: chrome-headless-shell**\n>\n> chromote runs Chrome in \"headless mode\", i.e. without a visual interface.\n> Between versions 120 and 132 of Chrome, there were, essentially, two flavors of headless mode.\n>\n> `chrome-headless-shell` is the version of Chrome's headless mode that is designed and best suited for automated testing, screenshots and printing, typically referred to as \"old headless\" mode.\n>\n> In most uses of chromote, `chrome-headless-shell` is an appropriate choice.\n> It will generally load faster and run more quickly than the alternative headless mode which uses the same version of Chrome you use when browsing, but without the UI.\n> After v132, old headless mode is no longer included in the Chrome binary, but the `chrome-headless-shell` binary is available from v120+.\n\n`local_chrome_version()` sets the version of Chrome for the current session or within the context of a function.\nFor small tasks where you want to use a specific version of Chrome for a few lines of code, chromote provides a `with_chrome_version()` variant:\n\n\n```r\nwith_chrome_version(\"132\", {\n  # Take a screenshot with Chrome v132\n  webshot2::webshot(\"https://r-project.org\")\n})\n```\n\nFinally, you can manage Chrome binaries directly with three helper functions:\n\n1. `chrome_versions_add()` can be used to add a new Chrome version to the cache, without explicitly configuring chromote to use that version.\n\n2. `chrome_versions_path()` returns the path to the Chrome binary for a given version and binary type.\n\n3. `chrome_versions_remove()` can be used to delete copies of Chrome from the local cache.\n\n> **Note for Windows users**\n>\n> Chrome for Windows includes a `setup.exe` file that chromote runs when it extracts the Chrome zipfile.\n> This file is provided by Chrome and is used to set the correct permissions on the `chrome.exe` executable file.\n> Sometimes, running `setup.exe` returns an error, even if it works correctly.\n>\n> If you do encounter errors using a downloaded version of Chrome, use `chrome_versions_path()` to get the path to the problematic executable.\n> Then, try running this executable yourself with the **Run** command (from the Start menu).\n> This typically resolves any lingering permissions issues.\n\n\n## Using a Chrome-based browser that you installed\n\nChromote will look in specific places for the Chrome web browser, depending on platform.\nThis is done by the `chromote:::find_chrome()` function.\n\nIf you wish to use a different browser from the default, you can set the `CHROMOTE_CHROME` environment variable, either with `Sys.setenv(CHROMOTE_CHROME=\"/path/to/browser\")`.\n\n``` r\nlibrary(chromote)\nSys.setenv(CHROMOTE_CHROME = \"/Applications/Chromium.app/Contents/MacOS/Chromium\")\n\nb <- ChromoteSession$new()\nb$view()\nb$go_to(\"https://www.whatismybrowser.com/\")\n```\n\nAnother way is create a `Chromote` object and explicitly specify the browser, then spawn `ChromoteSession`s from it.\n\n``` r\nm <- Chromote$new(\n  browser = Chrome$new(path = \"/Applications/Chromium.app/Contents/MacOS/Chromium\")\n)\n\n# Spawn a ChromoteSession from the Chromote object\nb <- m$new_session()\nb$go_to(\"https://www.whatismybrowser.com/\")\n```\n\nYet another way is to create a `Chromote` object with a specified browser, then set it as the default Chromote object.\n\n``` r\nm <- Chromote$new(\n  browser = Chrome$new(path = \"/Applications/Chromium.app/Contents/MacOS/Chromium\")\n)\n\n# Set this Chromote object as the default. Then any\n# ChromoteSession$new() will be spawned from it.\nset_default_chromote_object(m)\nb <- ChromoteSession$new()\nb$view()\nb$Page$navigate(\"https://www.whatismybrowser.com/\")\n```\n"
  }
]