R/chromote_session.R

Defines functions create_session

#' ChromoteSession class
#'
#' @description
#' This represents one _session_ in a Chromote object. Note that in the Chrome
#' DevTools Protocol a session is a debugging session connected to a _target_,
#' which is a browser window/tab or an iframe.
#'
#' A single target can potentially have more than one session connected to it,
#' but this is not currently supported by chromote.
#'
#' @export
#' @param targetId
#'   [Target](https://chromedevtools.github.io/devtools-protocol/tot/Target/)
#'   ID of an existing target to attach to. When a `targetId` is provided, the
#'   `width` and `height` arguments are ignored. If NULL (the default) a new
#'   target is created and attached to, and the `width` and `height`
#'   arguments determine its viewport size.
ChromoteSession <- R6Class(
  "ChromoteSession",
  lock_objects = FALSE,
  cloneable = FALSE,
  public = list(
    #' @description Create a new `ChromoteSession` object.
    #'
    #' ## Examples
    #'
    #' ```r
    #' # Create a new `ChromoteSession` object.
    #' b <- ChromoteSession$new()
    #'
    #' # Create a ChromoteSession with a specific height,width
    #' b <- ChromoteSession$new(height = 1080, width = 1920)
    #'
    #' # Navigate to page
    #' b$Page$navigate("http://www.r-project.org/")
    #'
    #' # View current chromote session
    #' if (interactive()) b$view()
    #' ```
    #'
    #' @param parent [`Chromote`] object to use; defaults to
    #'   [default_chromote_object()]
    #' @param width,height Width and height of the new window in integer pixel
    #'   values.
    #' @param wait_ If `FALSE`, return a [promises::promise()] of a new
    #'   `ChromoteSession` object. Otherwise, block during initialization, and
    #'   return a `ChromoteSession` object directly.
    #' @param mobile Whether to emulate mobile device. When `TRUE`, Chrome
    #'   updates settings to emulate browsing on a mobile phone; this includes
    #'   viewport meta tag, overlay scrollbars, text autosizing and more. The
    #'   default is `FALSE`.
    #' @param auto_events If `NULL` (the default), use the `auto_events` setting
    #'   from the parent `Chromote` object. If `TRUE`, enable automatic
    #'   event enabling/disabling; if `FALSE`, disable automatic event
    #'   enabling/disabling.
    #' @return A new `ChromoteSession` object.
    initialize = function(
      parent = default_chromote_object(),
      width = 992,
      height = 1323,
      targetId = NULL,
      wait_ = TRUE,
      auto_events = NULL,
      mobile = FALSE
    ) {
      check_number_whole(width)
      check_number_whole(height)
      check_logical(auto_events, allow_null = TRUE)
      check_logical(mobile)
      check_logical(wait_)

      self$parent <- parent
      lockBinding("parent", self) # do not allow `$parent` to be set!

      self$default_timeout <- parent$default_timeout

      # Create a session from the Chromote. Basically the same code as
      # new_session(), but this is synchronous.
      if (is.null(targetId)) {
        # In earlier versions of chromote (< 0.5.0), we set `width` and `height`
        # in `Target.createTarget`. With legacy (old) headless mode, each new
        # session was essentially a tab in a new window. With new headless mode,
        # introduced with Chrome v128, new tabs are created in existing windows.
        # For Chrome v128-v133, `width` and `height` in `Target.createTarget`
        # were ignored completely, and for v134+ they only have an effect when
        # creating a new window, i.e. for the first ChromoteSession. We now use
        # `Emulation.setDeviceMetricsOverride` below to set the viewport
        # dimensions, which works across all versions of Chrome/headless-shell
        # regardless of the parent window size.
        p <- parent$Target$createTarget("about:blank", wait_ = FALSE)$then(
          function(value) {
            private$target_id <- value$targetId
            parent$Target$attachToTarget(
              value$targetId,
              flatten = TRUE,
              wait_ = FALSE
            )
          }
        )
      } else {
        private$target_id <- targetId
        p <- parent$Target$attachToTarget(
          targetId,
          flatten = TRUE,
          wait_ = FALSE
        )
      }

      p <- p$then(function(value) {
        private$session_id <- value$sessionId
        self$parent$register_session(self)
      })

      # Whenever a command method (like x$Page$navigate()) is executed, it calls
      # x$send_command(). This object's send_command() method calls the parent's
      # send_command() method with a sessionId -- that is how the command is
      # scoped to this session.
      self$protocol <- protocol_reassign_envs(
        parent$protocol,
        env = self$.__enclos_env__
      )
      lockBinding("protocol", self)

      # Graft the entries from self$protocol onto self
      list2env(self$protocol, self)

      private$auto_events <- auto_events
      private$event_manager <- EventManager$new(self)
      private$session_is_active <- TRUE
      private$target_is_active <- TRUE

      # Find pixelRatio for screenshots
      p <- p$then(function(value) {
        private$get_pixel_ratio()
      })

      if (is.null(targetId)) {
        # `Emulation.setDeviceMetricsOverride` is equivalent to turning on
        # responsive preview in developer tools and lets us adjust the size of
        # the viewport for the active session. This avoids setting the size of
        # the parent browser window and ensures that the viewport of the current
        # tab has dimensions that exactly match the requested `width` and
        # `height`.
        p <- p$then(function(value) {
          self$Emulation$setDeviceMetricsOverride(
            width = width,
            height = height,
            deviceScaleFactor = private$pixel_ratio,
            mobile = mobile,
            wait_ = FALSE
          )
        })
      }

      # When a target crashes, raise a warning.
      if (!is.null(self$Inspector$targetCrashed)) {
        p <- p$then(function(value) {
          self$Inspector$targetCrashed(
            timeout_ = NULL,
            wait_ = FALSE,
            function(value) {
              warning(
                "Chromote has received a Inspector.targetCrashed event. This means that the ChromoteSession has probably crashed."
              )
              # Even if no targetId nor sessionId is returned by Inspector.targetCashed
              # mark the session as closed. This will close all sessions..
              self$mark_closed(TRUE)
            }
          )
        })
      }

      if (wait_) {
        self$wait_for(p)
      } else {
        # If wait_=FALSE, then we can't use the usual strategy of just
        # returning p, because the call to ChromoteSession$new() always
        # returns the new object. Instead, we'll store it as
        # private$init_promise_, and the user can retrieve it with
        # b$get_init_promise().
        private$init_promise_ <- p$then(function(value) self)
      }
    },

    #' @description Display the current session in the [`Chromote`] browser.
    #'
    #' If a [`Chrome`] browser is being used, this method will open a new tab
    #' using your [`Chrome`] browser. When not using a [`Chrome`] browser, set
    #' `options(browser=)` to change the default behavior of [`browseURL()`].
    #'
    #' ## Examples
    #'
    #' ```r
    #' # Create a new `ChromoteSession` object.
    #' b <- ChromoteSession$new()
    #'
    #' # Navigate to page
    #' b$Page$navigate("http://www.r-project.org/")
    #'
    #' # View current chromote session
    #' if (interactive()) b$view()
    #' ```
    view = function() {
      # A data frame of targets, one row per target.
      info <- fromJSON(self$parent$url("/json"))
      path <- info$devtoolsFrontendUrl[info$id == private$target_id]
      if (length(path) == 0) {
        stop("Target info not found.")
      }

      browse_url(path, self$parent)
    },

    #' @description Close the Chromote session.
    #'
    #' ## Examples
    #'
    #' ```r
    #' # Create a new `ChromoteSession` object.
    #' b <- ChromoteSession$new()
    #'
    #' # Navigate to page
    #' b$Page$navigate("http://www.r-project.org/")
    #'
    #' # Close current chromote session
    #' b$close()
    #' ```
    #'
    #' @param wait_ If `FALSE`, return a [promises::promise()] that will resolve
    #' when the `ChromoteSession` is closed. Otherwise, block until the
    #' `ChromoteSession` has closed.
    close = function(wait_ = TRUE) {
      if (!private$target_is_active) {
        return(invisible())
      }

      # Even if this session calls Target.closeTarget, the response from
      # the browser is sent without a sessionId. In order to wait for the
      # correct browser response, we need to invoke this from the parent's
      # browser-level methods.
      p <- self$parent$protocol$Target$closeTarget(
        private$target_id,
        wait_ = FALSE
      )

      p <- p$then(function(value) {
        if (isTRUE(value$success)) {
          self$mark_closed(TRUE)
        }
        invisible(value$success)
      })

      if (wait_) {
        self$wait_for(p)
      } else {
        p
      }
    },

    #' @description Get the viewport size
    #'
    #' @param wait_ If `FALSE`, return a [promises::promise()] of a new
    #'   `ChromoteSession` object. Otherwise, block during initialization, and
    #'   return a `ChromoteSession` object directly.
    #'
    #' @return Returns a list with values `width`, `height`, `zoom`
    #'   and `mobile`. See `$set_viewport_size()` for more details.
    get_viewport_size = function(wait_ = TRUE) {
      check_bool(wait_)

      p <- self$Page$getLayoutMetrics(wait_ = FALSE)$then(function(value) {
        list(
          width = value$cssVisualViewport$clientWidth,
          height = value$cssVisualViewport$clientHeight
        )
      })$then(function(value) {
        list(
          width = value$width,
          height = value$height,
          zoom = private$pixel_ratio %||% 0,
          mobile = private$is_mobile
        )
      })

      if (wait_) self$wait_for(p) else p
    },

    #' @description Set the viewport size
    #'
    #' Each ChromoteSession is associated with a page that may be one page open
    #' in a browser window among many. Each page can have its own viewport size,
    #' that can be thought of like the window size for that page.
    #'
    #' This function uses the
    #' [Emulation.setDeviceMetricsOverride](https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setDeviceMetricsOverride)
    #' command to set the viewport size. If you need more granular control or
    #' access to additional settings, use
    #' `$Emulation$setDeviceMetricsOverride()`.
    #'
    #' @param width,height Width and height of the new window in integer pixel
    #'   values.
    #' @param zoom The zoom level of displayed content on a device, where a
    #'   value of 1 indicates normal size, greater than 1 indicates zoomed in,
    #'   and less than 1 indicates zoomed out.
    #' @param mobile Whether to emulate mobile device. When `TRUE`, Chrome
    #'   updates settings to emulate browsing on a mobile phone; this includes
    #'   viewport meta tag, overlay scrollbars, text autosizing and more. The
    #'   default is `FALSE`.
    #' @param wait_ If `FALSE`, return a [promises::promise()] of a new
    #'   `ChromoteSession` object. Otherwise, block during initialization, and
    #'   return a `ChromoteSession` object directly.
    #'
    #' @return Invisibly returns the previous viewport dimensions so that you
    #'   can restore the viewport size, if desired.
    set_viewport_size = function(
      width,
      height,
      zoom = NULL,
      mobile = NULL,
      wait_ = TRUE
    ) {
      check_number_whole(width)
      check_number_whole(height)
      check_number_decimal(zoom, allow_null = TRUE)
      check_bool(mobile, allow_null = TRUE)
      check_bool(wait_)

      prev_bounds <- NULL

      p <- self$get_viewport_size(wait_ = FALSE)$then(function(value) {
        prev_bounds <<- value

        self$Emulation$setDeviceMetricsOverride(
          width = width,
          height = height,
          deviceScaleFactor = zoom %||% private$pixel_ratio %||% 0,
          mobile = mobile %||% private$is_mobile %||% FALSE,
          wait_ = FALSE
        )
      })$then(function(value) {
        prev_bounds
      })

      if (wait_) invisible(self$wait_for(p)) else p
    },

    #' @description Take a PNG screenshot
    #'
    #' ## Examples
    #'
    #' ```r
    #' # Create a new `ChromoteSession` object.
    #' b <- ChromoteSession$new()
    #'
    #' # Navigate to page
    #' b$Page$navigate("http://www.r-project.org/")
    #'
    #' # Take screenshot
    #' tmppngfile <- tempfile(fileext = ".png")
    #' is_interactive <- interactive() # Display screenshot if interactive
    #' b$screenshot(tmppngfile, show = is_interactive)
    #'
    #' # Show screenshot file info
    #' unlist(file.info(tmppngfile))
    #'
    #'
    #' # Take screenshot using a selector
    #' sidebar_file <- tempfile(fileext = ".png")
    #' b$screenshot(sidebar_file, selector = ".sidebar", show = is_interactive)
    #'
    #' # ----------------------------
    #' # Take screenshots in parallel
    #'
    #' urls <- c(
    #'   "https://www.r-project.org/",
    #'   "https://github.com/",
    #'   "https://news.ycombinator.com/"
    #' )
    #' # Helper method that:
    #' # 1. Navigates to the given URL
    #' # 2. Waits for the page loaded event to fire
    #' # 3. Takes a screenshot
    #' # 4. Prints a message
    #' # 5. Close the ChromoteSession
    #' screenshot_p <- function(url, filename = NULL) {
    #'   if (is.null(filename)) {
    #'     filename <- gsub("^.*://", "", url)
    #'     filename <- gsub("/", "_", filename)
    #'     filename <- gsub("\\.", "_", filename)
    #'     filename <- sub("_$", "", filename)
    #'     filename <- paste0(filename, ".png")
    #'   }
    #'
    #'   b2 <- b$new_session()
    #'   b2$Page$navigate(url, wait_ = FALSE)
    #'   b2$Page$loadEventFired(wait_ = FALSE)$
    #'     then(function(value) {
    #'       b2$screenshot(filename, wait_ = FALSE)
    #'     })$
    #'     then(function(value) {
    #'       message(filename)
    #'     })$
    #'     finally(function() {
    #'       b2$close()
    #'     })
    #' }
    #'
    #' # Take multiple screenshots simultaneously
    #' ps <- lapply(urls, screenshot_p)
    #' pa <- promises::promise_all(.list = ps)$then(function(value) {
    #'   message("Done!")
    #' })
    #'
    #' # Block the console until the screenshots finish (optional)
    #' b$wait_for(pa)
    #' #> www_r-project_org.png
    #' #> github_com.png
    #' #> news_ycombinator_com.png
    #' #> Done!
    #' ```
    #'
    #' @param filename File path of where to save the screenshot. The format of
    #'   the screenshot is inferred from the file extension; use
    #'   `options = list(format = "jpeg")` to manually choose the format. See
    #'   [`Page.captureScreenshot`](https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-captureScreenshot)
    #'   for supported formats; at the time of this release the format options
    #'   were `"png"` (default), `"jpeg"`, or `"webp"`.
    #' @param selector CSS selector to use for the screenshot.
    #' @param cliprect An unnamed vector or list containing values for `top`,
    #'   `left`, `width`, and `height`, in that order. See
    #' [`Page.Viewport`](https://chromedevtools.github.io/devtools-protocol/tot/Page/#type-Viewport)
    #' for more information. If provided, `selector` and `expand` will be
    #' ignored. To provide a scale, use the `scale` parameter.
    #' @param region CSS region to use for the screenshot.
    #' @param expand Extra pixels to expand the screenshot. May be a single
    #' value or a numeric vector of top, right, bottom, left values.
    #' @param scale Page scale factor
    #' @param show If `TRUE`, the screenshot will be displayed in the viewer.
    #' @param delay The number of seconds to wait before taking the screenshot
    #' after resizing the page. For complicated pages, this may need to be
    #' increased.
    #' @param options Additional options passed to
    #'   [`Page.captureScreenshot`](https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-captureScreenshot).
    #' @param wait_ If `FALSE`, return a [promises::promise()] that will resolve
    #' when the `ChromoteSession` has saved the screenshot. Otherwise, block
    #' until the `ChromoteSession` has saved the screenshot.
    screenshot = function(
      filename = "screenshot.png",
      selector = "html",
      cliprect = NULL,
      region = c("content", "padding", "border", "margin"),
      expand = NULL,
      scale = 1,
      show = FALSE,
      delay = 0.5,
      options = list(),
      wait_ = TRUE
    ) {
      chromote_session_screenshot(
        self,
        private,
        filename = filename,
        selector = selector,
        cliprect = cliprect,
        region = region,
        expand = expand,
        scale = scale,
        show = show,
        delay = delay,
        options = options,
        wait_ = wait_
      )
    },

    #' @description Take a PDF screenshot
    #'
    #' ## Examples
    #'
    #' ```r
    #' # Create a new `ChromoteSession` object.
    #' b <- ChromoteSession$new()
    #'
    #' # Navigate to page
    #' b$Page$navigate("http://www.r-project.org/")
    #'
    #' # Take screenshot
    #' tmppdffile <- tempfile(fileext = ".pdf")
    #' b$screenshot_pdf(tmppdffile)
    #'
    #' # Show PDF file info
    #' unlist(file.info(tmppdffile))
    #' ```
    #'
    #' @param filename File path of where to save the screenshot.
    #' @param pagesize A single character value in the set `"letter"`,
    #' `"legal"`, `"tabloid"`, `"ledger"` and `"a0"` through `"a1"`. Or a
    #' numeric vector `c(width, height)` specifying the page size.
    #' @param margins A numeric vector `c(top, right, bottom, left)` specifying
    #' the page margins.
    #' @param units Page and margin size units. Either `"in"` or `"cm"` for
    #' inches and centimeters respectively.
    #' @param landscape Paper orientation.
    #' @param display_header_footer Display header and footer.
    #' @param print_background Print background graphics.
    #' @param scale Page scale factor.
    #' @param wait_ If `FALSE`, return a [promises::promise()] that will resolve
    #' when the `ChromoteSession` has saved the screenshot. Otherwise, block
    #' until the `ChromoteSession` has saved the screnshot.
    screenshot_pdf = function(
      filename = "screenshot.pdf",
      pagesize = "letter",
      margins = 0.5,
      units = c("in", "cm"),
      landscape = FALSE,
      display_header_footer = FALSE,
      print_background = FALSE,
      scale = 1,
      wait_ = TRUE
    ) {
      chromote_session_screenshot_pdf(
        self,
        private,
        filename = filename,
        pagesize = pagesize,
        margins = margins,
        units = units,
        landscape = landscape,
        display_header_footer = display_header_footer,
        print_background = print_background,
        scale = scale,
        wait_ = wait_
      )
    },

    #' @description Create a new tab / window
    #'
    #' ## Examples
    #'
    #' ```r
    #' b1 <- ChromoteSession$new()
    #' b1$Page$navigate("http://www.google.com")
    #' b2 <- b1$new_session()
    #' b2$Page$navigate("http://www.r-project.org/")
    #' b1$Runtime$evaluate("window.location", returnByValue = TRUE)$result$value$href
    #' #> [1] "https://www.google.com/"
    #' b2$Runtime$evaluate("window.location", returnByValue = TRUE)$result$value$href
    #' #> [1] "https://www.r-project.org/"
    #' ```
    #'
    #' @param width,height Width and height of the new window.
    #' @param wait_ If `FALSE`, return a [promises::promise()] that will resolve
    #' when the `ChromoteSession` has created a new session. Otherwise, block
    #' until the `ChromoteSession` has created a new session.
    new_session = function(
      width = 992,
      height = 1323,
      targetId = NULL,
      wait_ = TRUE
    ) {
      create_session(
        chromote = self$parent,
        width = width,
        height = height,
        targetId = targetId,
        wait_ = wait_
      )
    },

    #' @description
    #' Retrieve the session id
    get_session_id = function() {
      private$session_id
    },

    #' @description
    #' Create a new session that connects to the same target (i.e. page)
    #' as this session. This is useful if the session has been closed but the target still
    #' exists.
    respawn = function() {
      if (!private$target_is_active) {
        stop("Can't respawn session; target has been closed.")
      }

      create_session(
        chromote = self$parent,
        targetId = private$target_id,
        auto_events = private$auto_events
      )
    },

    #' @description
    #' Retrieve the target id
    get_target_id = function() {
      private$target_id
    },

    #' @description
    #' Wait for a Chromote Session to finish. This method will block the R
    #' session until the provided promise resolves. The loop from
    #' `$get_child_loop()` will only advance just far enough for the promise to
    #' resolve.
    #'
    #' ## Examples
    #'
    #' ```r
    #' b <- ChromoteSession$new()
    #'
    #' # Async with promise
    #' p <- b$Browser$getVersion(wait_ = FALSE)
    #' p$then(str)
    #'
    #' # Async with callback
    #' b$Browser$getVersion(wait_ = FALSE, callback_ = str)
    #' ```
    #'
    #' @param p A promise to resolve.
    wait_for = function(p) {
      self$parent$wait_for(p)
    },

    #' @description
    #' Send a debug log message to the parent [Chromote] object
    #'
    #' ## Examples
    #'
    #' ```r
    #' b <- ChromoteSession$new()
    #' b$parent$debug_messages(TRUE)
    #' b$Page$navigate("https://www.r-project.org/")
    #' #> SEND {"method":"Page.navigate","params":{"url":"https://www.r-project.org/"}| __truncated__}
    #' # Turn off debug messages
    #' b$parent$debug_messages(FALSE)
    #' ```
    #'
    #' @param ... Arguments pasted together with `paste0(..., collapse = "")`.
    debug_log = function(...) {
      self$parent$debug_log(...)
    },

    #' @description
    #' \pkg{later} loop.
    #'
    #' For expert async usage only.
    get_child_loop = function() {
      self$parent$get_child_loop()
    },

    #' @description
    #' Send command through Chrome DevTools Protocol.
    #'
    #' For expert use only.
    #' @param msg A JSON-serializable list containing `method`, and `params`.
    #' @param callback Method to run when the command finishes successfully.
    #' @param error Method to run if an error occurs.
    #' @param timeout Number of milliseconds for Chrome DevTools Protocol
    #' execute a method.
    send_command = function(
      msg,
      callback = NULL,
      error = NULL,
      timeout = NULL
    ) {
      self$check_active()
      self$parent$send_command(
        msg,
        callback,
        error,
        timeout,
        sessionId = private$session_id
      )
    },

    #' @description
    #' Resolved `auto_events` value.
    #'
    #' For internal use only.
    get_auto_events = function() {
      if (!is.null(private$auto_events)) {
        private$auto_events
      } else {
        self$parent$get_auto_events()
      }
    },

    #' @description
    #' Set or retrieve the `enable` command arguments for a domain. These
    #' arguments are used for the `enable` command that is called for a domain,
    #' e.g. `Fetch$enable()`, when accessing an event method.
    #'
    #' @examples
    #' if (interactive()) {
    #'   b <- ChromoteSession$new(
    #'     auto_events_enable_args = list(
    #'       Fetch = list(handleAuthRequests = TRUE)
    #'     )
    #'   )
    #'
    #'   # Get current `Fetch.enable` args
    #'   b$auto_events_enable_args("Fetch")
    #'
    #'   # Update the `Fetch.enable` args
    #'   b$auto_events_enable_args("Fetch", handleAuthRequests = FALSE)
    #'
    #'   # Reset `Fetch.enable` args
    #'   b$auto_events_enable_args("Fetch", NULL)
    #' }
    #'
    #' @param domain A command domain, e.g. `"Fetch"`.
    #' @param ... Arguments to use for auto-events for the domain. If not
    #'   provided, returns the argument values currently in place for the
    #'   domain. Use `NULL` to clear the enable arguments for a domain.
    auto_events_enable_args = function(domain, ...) {
      dots <- dots_list(..., .named = TRUE)

      if (length(dots) == 0) {
        return(get_auto_events_enable_args(private, domain, self$parent))
      }

      set_auto_events_enable_args(self, private, domain, dots)
    },

    #' @description
    #' Immediately call all event callback methods.
    #'
    #' For internal use only.
    #' @param event A single event string
    #' @param params A list of parameters to pass to the event callback methods.
    invoke_event_callbacks = function(event, params) {
      private$event_manager$invoke_event_callbacks(event, params)
    },

    #' @description Mark a session, and optionally, the underlying target,
    #'   as closed. For internal use only.
    #' @param target_closed Has the underlying target been closed as well as the
    #'   active debugging session?
    mark_closed = function(target_closed) {
      private$session_is_active <- FALSE
      private$target_is_active <- !target_closed
    },

    #' @description Retrieve active status
    #' Once initialized, the value returned is `TRUE`. If `$close()` has been
    #' called, this value will be `FALSE`.
    is_active = function() {
      private$session_is_active &&
        private$target_is_active &&
        self$parent$is_active()
    },

    #' @description Check that a session is active, erroring if not.
    check_active = function() {
      if (self$is_active()) {
        return()
      }

      if (private$target_is_active) {
        abort(
          c(
            "Session has been closed.",
            i = "Call session$respawn() to create a new session that connects to the same target."
          )
        )
      } else {
        abort("Session and underlying target have been closed.")
      }
    },

    #' @description Initial promise
    #'
    #' For internal use only.
    get_init_promise = function() {
      private$init_promise_
    },

    #' @description Summarise the current state of the object.
    #' @param verbose The print method defaults to a brief summary
    #'   of the most important debugging info; use `verbose = TRUE` tp
    #'   see the complex R6 object.
    #' @param ... Passed on to `format()` when `verbose` = TRUE
    print = function(..., verbose = FALSE) {
      if (verbose) {
        cat(format(self, ...), sep = "\n")
      } else {
        if (self$is_active()) {
          state <- "session + target active"
        } else if (private$target_is_active) {
          state <- "target active"
        } else {
          state <- "closed"
        }

        cat_line("<ChromoteSession> (", state, ")")
        if (self$is_active()) cat_line("  Session ID: ", self$get_session_id())
        if (private$target_is_active)
          cat_line("   Target ID: ", self$get_target_id())

        browser <- self$parent$get_browser()
        if (browser$is_local()) {
          cat_line(
            "  Parent PID: ",
            self$parent$get_browser()$get_process()$get_pid()
          )
        } else {
          cat_line(
            " Remote Host: ",
            sprintf("http://%s:%s", browser$get_host(), browser$get_port())
          )
        }
      }
      invisible(self)
    },

    #' @field parent [`Chromote`] object
    parent = NULL,
    #' @field default_timeout Default timeout in seconds for \pkg{chromote} to
    #' wait for a Chrome DevTools Protocol response.
    default_timeout = NULL,
    #' @field protocol Dynamic protocol implementation. For expert use only!
    protocol = NULL
  ),

  private = list(
    session_id = NULL,
    target_id = NULL,
    session_is_active = NULL,
    target_is_active = NULL,
    event_manager = NULL,
    auto_events = NULL,
    init_promise_ = NULL,

    # Updated when `Emulation.setDeviceMetricsOverride` is called
    is_mobile = NULL,
    pixel_ratio = NULL,

    get_pixel_ratio = function() {
      if (!is.null(private$pixel_ratio)) {
        promise_resolve(private$pixel_ratio)
      } else {
        self$Runtime$evaluate("window.devicePixelRatio", wait_ = FALSE)$then(
          function(value) {
            (private$pixel_ratio <- value$result$value)
          }
        )
      }
    },

    register_event_listener = function(event, callback = NULL, timeout = NULL) {
      self$check_active()
      private$event_manager$register_event_listener(event, callback, timeout)
    }
  )
)

# Wrapper around ChromoteSession$new() that can return a promise
create_session <- function(
  chromote = default_chromote_object(),
  width = 992,
  height = 1323,
  targetId = NULL,
  wait_ = TRUE,
  auto_events = NULL
) {
  session <- ChromoteSession$new(
    parent = chromote,
    width = width,
    height = height,
    targetId,
    auto_events = auto_events,
    wait_ = wait_
  )

  if (wait_) {
    session
  } else {
    # ChromoteSession$new() must return a ChromoteSession object so we need a
    # side-channel to return a promise
    session$get_init_promise()
  }
}

Try the chromote package in your browser

Any scripts or data that you put into this service are public.

chromote documentation built on April 4, 2025, 1:43 a.m.