lib/ferrum/contexts.rb



# frozen_string_literal: true

require "ferrum/context"

module Ferrum
  class Contexts
    include Enumerable

    attr_reader :contexts

    def initialize(client)
      @contexts = Concurrent::Map.new
      @client = client
      subscribe
      auto_attach
      discover
    end

    def default_context
      @default_context ||= create
    end

    def each(&block)
      return enum_for(__method__) unless block_given?

      @contexts.each(&block)
    end

    def [](id)
      @contexts[id]
    end

    def find_by(target_id:)
      context = nil
      @contexts.each_value { |c| context = c if c.target?(target_id) }
      context
    end

    def create(**options)
      response = @client.command("Target.createBrowserContext", **options)
      context_id = response["browserContextId"]
      context = Context.new(@client, self, context_id)
      @contexts[context_id] = context
      context
    end

    def dispose(context_id)
      context = @contexts[context_id]
      context.close_targets_connection
      @client.command("Target.disposeBrowserContext", browserContextId: context.id)
      @contexts.delete(context_id)
      true
    end

    def close_connections
      @contexts.each_value(&:close_targets_connection)
    end

    def reset
      @default_context = nil
      @contexts.each_key { |id| dispose(id) }
    end

    def size
      @contexts.size
    end

    private

    def subscribe
      @client.on("Target.attachedToTarget") do |params|
        info, session_id = params.values_at("targetInfo", "sessionId")
        next unless info["type"] == "page"

        context_id = info["browserContextId"]
        @contexts[context_id]&.add_target(session_id: session_id, params: info)
        if params["waitingForDebugger"]
          @client.session(session_id).command("Runtime.runIfWaitingForDebugger", async: true)
        end
      end

      @client.on("Target.targetCreated") do |params|
        info = params["targetInfo"]
        next unless info["type"] == "page"

        context_id = info["browserContextId"]
        @contexts[context_id]&.add_target(params: info)
      end

      @client.on("Target.targetInfoChanged") do |params|
        info = params["targetInfo"]
        next unless info["type"] == "page"

        context_id, target_id = info.values_at("browserContextId", "targetId")
        @contexts[context_id]&.update_target(target_id, info)
      end

      @client.on("Target.targetDestroyed") do |params|
        context = find_by(target_id: params["targetId"])
        context&.delete_target(params["targetId"])
      end

      @client.on("Target.targetCrashed") do |params|
        context = find_by(target_id: params["targetId"])
        context&.delete_target(params["targetId"])
      end
    end

    def discover
      @client.command("Target.setDiscoverTargets", discover: true)
    end

    def auto_attach
      return unless @client.options.flatten

      @client.command("Target.setAutoAttach", autoAttach: true, waitForDebuggerOnStart: true, flatten: true)
    end
  end
end