From e3c86ed1800fa99d4667107317b27a680feceb12 Mon Sep 17 00:00:00 2001 From: Francis Beaudoin Date: Sat, 21 Oct 2023 08:35:18 -0400 Subject: [PATCH] Handle pending connections losing context on frame navigation --- lib/ferrum/network.rb | 36 ++++++++++++++++++++++++++++++---- lib/ferrum/network/exchange.rb | 14 ++++++++++--- lib/ferrum/network/request.rb | 9 +++++++++ lib/ferrum/page/frames.rb | 6 +++++- 4 files changed, 57 insertions(+), 8 deletions(-) diff --git a/lib/ferrum/network.rb b/lib/ferrum/network.rb index 860c886d..4e0cc13f 100644 --- a/lib/ferrum/network.rb +++ b/lib/ferrum/network.rb @@ -74,11 +74,11 @@ def idle?(connections = 0) end def total_connections - @traffic.size + exchange_connections.count end def finished_connections - @traffic.count(&:finished?) + exchange_connections.count(&:finished?) end def pending_connections @@ -378,7 +378,21 @@ def subscribe_request_will_be_sent exchange.request = request - @exchange = exchange if exchange.navigation_request?(@page.main_frame.id) + if exchange.navigation_request?(@page.main_frame.id) + @exchange = exchange + mark_pending_exchanges_as_unknown(exchange) + end + end + end + + # When the main frame navigates Chrome doesn't send `Network.loadingFailed` + # for pending async requests. Therefore, we mark pending connections as unknown since + # they are not relevant to the current navigation. + def mark_pending_exchanges_as_unknown(navigation_exchange) + @traffic.each do |exchange| + break if exchange.id == navigation_exchange.id + + exchange.unknown = true if exchange.pending? end end @@ -395,7 +409,10 @@ def subscribe_response_received def subscribe_loading_finished @page.on("Network.loadingFinished") do |params| - response = select(params["requestId"]).last&.response + exchange = select(params["requestId"]).last + exchange.unknown = false + + response = exchange&.response if response response.loaded = true @@ -477,5 +494,16 @@ def blacklist? def whitelist? Array(@whitelist).any? end + + def exchange_connections + @traffic.select { |exchange| exchange_connection?(exchange) } + end + + def exchange_connection?(exchange) + return false unless @page.frames.any? { |f| f.id == exchange.request&.frame_id } + + return false unless exchange.request&.frame_id == @exchange.request&.frame_id + return exchange.request&.loader_id == @exchange.request&.loader_id + end end end diff --git a/lib/ferrum/network/exchange.rb b/lib/ferrum/network/exchange.rb index d1e2d5ec..c3462ed4 100644 --- a/lib/ferrum/network/exchange.rb +++ b/lib/ferrum/network/exchange.rb @@ -28,6 +28,12 @@ class Exchange # @return [Error, nil] attr_accessor :error + # Determines if the network exchange is unknown due to + # a lost of its context + # + # @return Boolean + attr_accessor :unknown + # # Initializes the network exchange. # @@ -40,6 +46,7 @@ def initialize(page, id) @page = page @intercepted_request = nil @request = @response = @error = nil + @unknown = false end # @@ -74,12 +81,12 @@ def blocked? # # Determines if the request was blocked, a response was returned, or if an - # error occurred. + # error occurred or the exchange is unknown and cannot be inferred. # # @return [Boolean] # def finished? - blocked? || response&.loaded? || !error.nil? + blocked? || response&.loaded? || !error.nil? || unknown end # @@ -147,7 +154,8 @@ def inspect "@intercepted_request=#{@intercepted_request.inspect} " \ "@request=#{@request.inspect} " \ "@response=#{@response.inspect} " \ - "@error=#{@error.inspect}>" + "@error=#{@error.inspect}>" \ + "@unknown=#{@unknown.inspect}>" end end end diff --git a/lib/ferrum/network/request.rb b/lib/ferrum/network/request.rb index 9981b4fc..33e421d5 100644 --- a/lib/ferrum/network/request.rb +++ b/lib/ferrum/network/request.rb @@ -71,6 +71,15 @@ def frame_id @params["frameId"] end + # + # The loader ID of the request. + # + # @return [String] + # + def loader_id + @params["loaderId"] + end + # # The request timestamp. # diff --git a/lib/ferrum/page/frames.rb b/lib/ferrum/page/frames.rb index d44ae6c2..88bdbe3c 100644 --- a/lib/ferrum/page/frames.rb +++ b/lib/ferrum/page/frames.rb @@ -179,12 +179,16 @@ def subscribe_execution_context_destroyed execution_id = params["executionContextId"] frame = frame_by(execution_id: execution_id) frame&.execution_id = nil + frame&.state = :stopped_loading end end def subscribe_execution_contexts_cleared on("Runtime.executionContextsCleared") do - @frames.each_value { |f| f.execution_id = nil } + @frames.each_value do |f| + f.execution_id = nil + f.state = :stopped_loading + end end end