Do not send PRIORITY_UPDATE frame if it does not change priority.

gfe-relnote: n/a, change to QUIC v99-only code.  Protected by existing disabled gfe2_reloadable_flag_quic_enable_version_99.
PiperOrigin-RevId: 292388791
Change-Id: I8e146591be22f57026f89231f847080299506ef6
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index d69299e..b393ee3 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -202,7 +202,8 @@
       decoder_(http_decoder_visitor_.get()),
       sequencer_offset_(0),
       is_decoder_processing_input_(false),
-      ack_listener_(nullptr) {
+      ack_listener_(nullptr),
+      last_sent_urgency_(kDefaultUrgency) {
   DCHECK_EQ(session()->connection(), spdy_session->connection());
   DCHECK_EQ(transport_version(), spdy_session->transport_version());
   DCHECK(!QuicUtils::IsCryptoStreamId(transport_version(), id));
@@ -238,7 +239,8 @@
       decoder_(http_decoder_visitor_.get()),
       sequencer_offset_(sequencer()->NumBytesConsumed()),
       is_decoder_processing_input_(false),
-      ack_listener_(nullptr) {
+      ack_listener_(nullptr),
+      last_sent_urgency_(kDefaultUrgency) {
   DCHECK_EQ(session()->connection(), spdy_session->connection());
   DCHECK_EQ(transport_version(), spdy_session->transport_version());
   DCHECK(!QuicUtils::IsCryptoStreamId(transport_version(), id()));
@@ -591,6 +593,10 @@
 
   // Value between 0 and 7, inclusive.  Lower value means higher priority.
   int urgency = precedence().spdy3_priority();
+  if (last_sent_urgency_ == urgency) {
+    return;
+  }
+  last_sent_urgency_ = urgency;
 
   PriorityUpdateFrame priority_update;
   priority_update.prioritized_element_type = REQUEST_STREAM;
diff --git a/quic/core/http/quic_spdy_stream.h b/quic/core/http/quic_spdy_stream.h
index e6dbb50..437e0bf 100644
--- a/quic/core/http/quic_spdy_stream.h
+++ b/quic/core/http/quic_spdy_stream.h
@@ -43,6 +43,12 @@
     : public QuicStream,
       public QpackDecodedHeadersAccumulator::Visitor {
  public:
+  // The default value of urgency when using the priority extension defined at
+  // https://httpwg.org/http-extensions/draft-ietf-httpbis-priority.html#default.
+  // This is not the default priority of a stream, rather the default priority a
+  // server thinks a stream has until it receives a PRIORITY_UPDATE frame.
+  static const int kDefaultUrgency = 1;
+
   // Visitor receives callbacks from the stream.
   class QUIC_EXPORT_PRIVATE Visitor {
    public:
@@ -334,6 +340,10 @@
 
   // Offset of unacked frame headers.
   QuicIntervalSet<QuicStreamOffset> unacked_frame_headers_offsets_;
+
+  // Urgency value sent in the last PRIORITY_UPDATE frame, or default urgency
+  // defined by the spec if no PRIORITY_UPDATE frame has been sent.
+  int last_sent_urgency_;
 };
 
 }  // namespace quic
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc
index 1e9bbff..decba06 100644
--- a/quic/core/http/quic_spdy_stream_test.cc
+++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -1339,6 +1339,39 @@
   EXPECT_TRUE(stream_->fin_sent());
 }
 
+TEST_P(QuicSpdyStreamTest, DoNotSendPriorityUpdateWithDefaultUrgency) {
+  if (!UsesHttp3()) {
+    return;
+  }
+
+  InitializeWithPerspective(kShouldProcessData, Perspective::IS_CLIENT);
+
+  // Four writes on the request stream: HEADERS frame header and payload both
+  // for headers and trailers.
+  EXPECT_CALL(*session_, WritevData(stream_, stream_->id(), _, _, _)).Times(4);
+
+  stream_->SetPriority(
+      spdy::SpdyStreamPrecedence(QuicSpdyStream::kDefaultUrgency));
+
+  // No PRIORITY_UPDATE frames on the control stream.
+  auto send_control_stream =
+      QuicSpdySessionPeer::GetSendControlStream(session_.get());
+  EXPECT_CALL(*session_, WritevData(send_control_stream,
+                                    send_control_stream->id(), _, _, _))
+      .Times(0);
+
+  // Write the initial headers, without a FIN.
+  EXPECT_CALL(*stream_, WriteHeadersMock(false));
+  stream_->WriteHeaders(SpdyHeaderBlock(), /*fin=*/false, nullptr);
+
+  // Writing trailers implicitly sends a FIN.
+  SpdyHeaderBlock trailers;
+  trailers["trailer key"] = "trailer value";
+  EXPECT_CALL(*stream_, WriteHeadersMock(true));
+  stream_->WriteTrailers(std::move(trailers), nullptr);
+  EXPECT_TRUE(stream_->fin_sent());
+}
+
 // Test that when writing trailers, the trailers that are actually sent to the
 // peer contain the final offset field indicating last byte of data.
 TEST_P(QuicSpdyStreamTest, WritingTrailersFinalOffset) {