From 7924c18aa0fb0751ccd90ac0d4ec35849529ebff Mon Sep 17 00:00:00 2001 From: Carlos IL Date: Tue, 14 Dec 2021 23:42:23 +0000 Subject: [PATCH 01/77] Implement CT policy changes This implements the proposed CT policy changes behind a default off feature flag (CertificateTransparencyUpdatedPolicy), and also adds a feature flag (CertificateTransparencyUpdatedPolicyAllCerts) that causes changes to apply to all certificates (instead of only to those issued after February 1, 2022). Changes are: -One Google log requirement is removed. -A log operator diversity requirement is added (each certificate needs SCTs from at least 2 logs with different operators). -Embedded SCTs requirement is increased to 3 for certificates with a lifetime between 180 days and 15 months. -Lifetime calculations are now done directly in days instead of months. Bug: 1267104 Change-Id: I7b4a46de421437b0dd621b221fc7e62fa7efeb9c Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3306682 Reviewed-by: David Benjamin Commit-Queue: Carlos IL Cr-Commit-Position: refs/heads/main@{#951724} --- .../chrome_ct_policy_enforcer.cc | 172 ++++++---- .../chrome_ct_policy_enforcer.h | 7 +- .../chrome_ct_policy_enforcer_unittest.cc | 308 ++++++++++++++++++ .../certificate_transparency/ct_features.cc | 7 + .../certificate_transparency/ct_features.h | 13 + 5 files changed, 451 insertions(+), 56 deletions(-) diff --git a/components/certificate_transparency/chrome_ct_policy_enforcer.cc b/components/certificate_transparency/chrome_ct_policy_enforcer.cc index 6a70a0d46b1a71..e8e0ccffe21e1f 100644 --- a/components/certificate_transparency/chrome_ct_policy_enforcer.cc +++ b/components/certificate_transparency/chrome_ct_policy_enforcer.cc @@ -12,6 +12,7 @@ #include "base/bind.h" #include "base/callback_helpers.h" +#include "base/feature_list.h" #include "base/metrics/field_trial.h" #include "base/metrics/histogram_macros.h" #include "base/numerics/safe_conversions.h" @@ -20,6 +21,7 @@ #include "base/time/time.h" #include "base/values.h" #include "base/version.h" +#include "components/certificate_transparency/ct_features.h" #include "components/certificate_transparency/ct_known_logs.h" #include "crypto/sha2.h" #include "net/cert/ct_policy_status.h" @@ -215,13 +217,33 @@ CTPolicyCompliance ChromeCTPolicyEnforcer::CheckCTPolicyCompliance( issuance_date = std::min(sct->timestamp, issuance_date); } + // Certificates issued after this date (February 1, 2022, OO:OO:OO GMT) + // will be subject to the new CT policy, which: + // -Removes the One Google log requirement. + // -Introduces a log operator diversity (at least 2 SCTs that come from + // different operators are required). + // -Uses days for certificate lifetime calculations instead of rounding to + // months. + // Increases the SCT requirements for certificates with a lifetime between + // 180 days and 15 months, from 2 to 3. + const base::Time kPolicyUpdateDate = + base::Time::UnixEpoch() + base::Seconds(1643673600); + bool use_2022_policy = + base::FeatureList::IsEnabled( + features::kCertificateTransparency2022PolicyAllCerts) || + (base::FeatureList::IsEnabled( + features::kCertificateTransparency2022Policy) && + issuance_date >= kPolicyUpdateDate); + bool has_valid_google_sct = false; bool has_valid_nongoogle_sct = false; bool has_valid_embedded_sct = false; bool has_valid_nonembedded_sct = false; bool has_embedded_google_sct = false; bool has_embedded_nongoogle_sct = false; + bool has_diverse_log_operators = false; std::vector embedded_log_ids; + std::string first_seen_operator; for (const auto& sct : verified_scts) { base::Time disqualification_date; bool is_disqualified = @@ -233,14 +255,16 @@ CTPolicyCompliance ChromeCTPolicyEnforcer::CheckCTPolicyCompliance( continue; } - if (IsLogOperatedByGoogle(sct->log_id)) { - has_valid_google_sct |= !is_disqualified; - if (sct->origin == net::ct::SignedCertificateTimestamp::SCT_EMBEDDED) - has_embedded_google_sct = true; - } else { - has_valid_nongoogle_sct |= !is_disqualified; - if (sct->origin == net::ct::SignedCertificateTimestamp::SCT_EMBEDDED) - has_embedded_nongoogle_sct = true; + if (!use_2022_policy) { + if (IsLogOperatedByGoogle(sct->log_id)) { + has_valid_google_sct |= !is_disqualified; + if (sct->origin == net::ct::SignedCertificateTimestamp::SCT_EMBEDDED) + has_embedded_google_sct = true; + } else { + has_valid_nongoogle_sct |= !is_disqualified; + if (sct->origin == net::ct::SignedCertificateTimestamp::SCT_EMBEDDED) + has_embedded_nongoogle_sct = true; + } } if (sct->origin != net::ct::SignedCertificateTimestamp::SCT_EMBEDDED) { has_valid_nonembedded_sct = true; @@ -254,22 +278,42 @@ CTPolicyCompliance ChromeCTPolicyEnforcer::CheckCTPolicyCompliance( embedded_log_ids.push_back(sct->log_id); } } + + if (use_2022_policy && !has_diverse_log_operators) { + std::string sct_operator = GetOperatorForLog(sct->log_id, sct->timestamp); + if (first_seen_operator.empty()) { + first_seen_operator = sct_operator; + } else { + has_diverse_log_operators |= first_seen_operator != sct_operator; + } + } } // Option 1: // An SCT presented via the TLS extension OR embedded within a stapled OCSP // response is from a log qualified at time of check; - // AND there is at least one SCT from a Google Log that is qualified at - // time of check, presented via any method; - // AND there is at least one SCT from a non-Google Log that is qualified - // at the time of check, presented via any method. + // With previous policy: + // AND there is at least one SCT from a Google Log that is qualified at + // time of check, presented via any method; + // AND there is at least one SCT from a non-Google Log that is qualified + // at the time of check, presented via any method. + // With new policy: + // AND there are at least two SCTs from logs with different operators, + // presented by any method. // // Note: Because SCTs embedded via TLS or OCSP can be updated on the fly, // the issuance date is irrelevant, as any policy changes can be - // accomodated. - if (has_valid_nonembedded_sct && has_valid_google_sct && - has_valid_nongoogle_sct) { - return CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS; + // accommodated. + if (has_valid_nonembedded_sct) { + if (use_2022_policy) { + if (has_diverse_log_operators) { + return CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS; + } + } else { + if (has_valid_google_sct && has_valid_nongoogle_sct) { + return CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS; + } + } } // Note: If has_valid_nonembedded_sct was true, but Option 2 isn't met, // then the result will be that there weren't diverse enough SCTs, as that @@ -289,44 +333,62 @@ CTPolicyCompliance ChromeCTPolicyEnforcer::CheckCTPolicyCompliance( : CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS; } - // ... AND there is at least one embedded SCT from a Google Log once or - // currently qualified; - // AND there is at least one embedded SCT from a non-Google Log once or - // currently qualified; - // ... - // - // Note: This policy language is only enforced after the below issuance - // date, as that's when the diversity policy first came into effect for - // SCTs embedded in certificates. - // The date when diverse SCTs requirement is effective from. - // 2015-07-01 00:00:00 UTC. - const base::Time kDiverseSCTRequirementStartDate = - base::Time::UnixEpoch() + base::Seconds(1435708800); - if (issuance_date >= kDiverseSCTRequirementStartDate && - !(has_embedded_google_sct && has_embedded_nongoogle_sct)) { - // Note: This also covers the case for non-embedded SCTs, as it's only - // possible to reach here if both sets are not diverse enough. - return CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS; - } - - size_t lifetime_in_months = 0; - bool has_partial_month = false; - RoundedDownMonthDifference(cert.valid_start(), cert.valid_expiry(), - &lifetime_in_months, &has_partial_month); - - // ... AND the certificate embeds SCTs from AT LEAST the number of logs - // once or currently qualified shown in Table 1 of the CT Policy. size_t num_required_embedded_scts = 5; - if (lifetime_in_months > 39 || - (lifetime_in_months == 39 && has_partial_month)) { - num_required_embedded_scts = 5; - } else if (lifetime_in_months > 27 || - (lifetime_in_months == 27 && has_partial_month)) { - num_required_embedded_scts = 4; - } else if (lifetime_in_months >= 15) { - num_required_embedded_scts = 3; + if (use_2022_policy) { + // ... AND there are at least two SCTs from logs with different + // operators ... + if (!has_diverse_log_operators) { + return CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS; + } + // ... AND the certificate embeds SCTs from AT LEAST the number of logs + // once or currently qualified shown in Table 1 of the CT Policy. + base::TimeDelta lifetime = cert.valid_expiry() - cert.valid_start(); + if (lifetime >= base::Days(180)) { + num_required_embedded_scts = 3; + } else { + num_required_embedded_scts = 2; + } } else { - num_required_embedded_scts = 2; + // ... AND there is at least one embedded SCT from a Google Log once or + // currently qualified; + // AND there is at least one embedded SCT from a non-Google Log once or + // currently qualified; + // ... + // + // Note: This policy language is only enforced after the below issuance + // date, as that's when the diversity policy first came into effect for + // SCTs embedded in certificates. + // The date when diverse SCTs requirement is effective from. + // 2015-07-01 00:00:00 UTC. + // TODO(carlosil): There are no more valid certificates from before this + // date, so this date and the associated logic should be cleaned up. + const base::Time kDiverseSCTRequirementStartDate = + base::Time::UnixEpoch() + base::Seconds(1435708800); + if (issuance_date >= kDiverseSCTRequirementStartDate && + !(has_embedded_google_sct && has_embedded_nongoogle_sct)) { + // Note: This also covers the case for non-embedded SCTs, as it's only + // possible to reach here if both sets are not diverse enough. + return CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS; + } + + size_t lifetime_in_months = 0; + bool has_partial_month = false; + RoundedDownMonthDifference(cert.valid_start(), cert.valid_expiry(), + &lifetime_in_months, &has_partial_month); + + // ... AND the certificate embeds SCTs from AT LEAST the number of logs + // once or currently qualified shown in Table 1 of the CT Policy. + if (lifetime_in_months > 39 || + (lifetime_in_months == 39 && has_partial_month)) { + num_required_embedded_scts = 5; + } else if (lifetime_in_months > 27 || + (lifetime_in_months == 27 && has_partial_month)) { + num_required_embedded_scts = 4; + } else if (lifetime_in_months >= 15) { + num_required_embedded_scts = 3; + } else { + num_required_embedded_scts = 2; + } } // Sort the embedded log IDs and remove duplicates, so that only a single @@ -352,11 +414,11 @@ CTPolicyCompliance ChromeCTPolicyEnforcer::CheckCTPolicyCompliance( std::string ChromeCTPolicyEnforcer::GetOperatorForLog( std::string log_id, - base::TimeDelta timestamp) { + base::Time timestamp) const { DCHECK(log_operator_history_.find(log_id) != log_operator_history_.end()); - OperatorHistoryEntry log_history = log_operator_history_[log_id]; + OperatorHistoryEntry log_history = log_operator_history_.at(log_id); for (auto operator_entry : log_history.previous_operators_) { - if (timestamp < operator_entry.second) + if (timestamp - base::Time::UnixEpoch() < operator_entry.second) return operator_entry.first; } // Either the log has only ever had one operator, or the timestamp is after diff --git a/components/certificate_transparency/chrome_ct_policy_enforcer.h b/components/certificate_transparency/chrome_ct_policy_enforcer.h index 234910e9edc041..807578c8c61fab 100644 --- a/components/certificate_transparency/chrome_ct_policy_enforcer.h +++ b/components/certificate_transparency/chrome_ct_policy_enforcer.h @@ -87,6 +87,11 @@ class ChromeCTPolicyEnforcer : public net::CTPolicyEnforcer { return log_operator_history_; } + void SetOperatorHistoryForTesting( + std::map log_operator_history) { + log_operator_history_ = std::move(log_operator_history); + } + private: // Returns true if the log identified by |log_id| (the SHA-256 hash of the // log's DER-encoded SPKI) has been disqualified, and sets @@ -107,7 +112,7 @@ class ChromeCTPolicyEnforcer : public net::CTPolicyEnforcer { const net::X509Certificate& cert, const net::ct::SCTList& verified_scts) const; - std::string GetOperatorForLog(std::string log_id, base::TimeDelta timestamp); + std::string GetOperatorForLog(std::string log_id, base::Time timestamp) const; // Map of SHA-256(SPKI) to log disqualification date. std::vector> disqualified_logs_; diff --git a/components/certificate_transparency/chrome_ct_policy_enforcer_unittest.cc b/components/certificate_transparency/chrome_ct_policy_enforcer_unittest.cc index 49dee17f80fab8..94bd706063347e 100644 --- a/components/certificate_transparency/chrome_ct_policy_enforcer_unittest.cc +++ b/components/certificate_transparency/chrome_ct_policy_enforcer_unittest.cc @@ -10,9 +10,12 @@ #include "base/build_time.h" #include "base/cxx17_backports.h" +#include "base/strings/string_number_conversions.h" +#include "base/test/scoped_feature_list.h" #include "base/test/simple_test_clock.h" #include "base/time/time.h" #include "base/version.h" +#include "components/certificate_transparency/ct_features.h" #include "components/certificate_transparency/ct_known_logs.h" #include "crypto/rsa_private_key.h" #include "crypto/sha2.h" @@ -537,6 +540,311 @@ TEST_F(ChromeCTPolicyEnforcerTest, TimestampUpdates) { NetLogWithSource())); } +class ChromeCTPolicyEnforcerTest2022Policy : public ChromeCTPolicyEnforcerTest { + public: + void SetUp() override { + scoped_feature_list_.InitAndEnableFeature( + features::kCertificateTransparency2022Policy); + ChromeCTPolicyEnforcerTest::SetUp(); + } + + private: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +TEST_F(ChromeCTPolicyEnforcerTest2022Policy, + 2022PolicyNotInEffectBeforeTargetDate) { + // Old policy should enforce one Google log requirement. + ChromeCTPolicyEnforcer* chrome_policy_enforcer = + static_cast(policy_enforcer_.get()); + SCTList scts; + FillListWithSCTsOfOrigin(SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION, + 2, std::vector(), true, &scts); + std::map operator_history; + for (size_t i = 0; i < scts.size(); i++) { + OperatorHistoryEntry entry; + entry.current_operator_ = "Operator " + base::NumberToString(i); + operator_history[scts[i]->log_id] = entry; + // Set timestamp to 1 day before new policy comes in effect. + EXPECT_TRUE(base::Time::FromUTCExploded({2022, 1, 0, 31, 0, 0, 0, 0}, + &scts[i]->timestamp)); + } + chrome_policy_enforcer->SetOperatorHistoryForTesting(operator_history); + + EXPECT_EQ(CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS, + policy_enforcer_->CheckCompliance(chain_.get(), scts, + NetLogWithSource())); +} + +TEST_F(ChromeCTPolicyEnforcerTest2022Policy, + 2022PolicyInEffectAfterTargetDate) { + // New policy should allow SCTs from all non-Google operators to comply as + // long as diversity requirement is fulfilled. + ChromeCTPolicyEnforcer* chrome_policy_enforcer = + static_cast(policy_enforcer_.get()); + SCTList scts; + FillListWithSCTsOfOrigin(SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION, + 2, std::vector(), true, &scts); + std::map operator_history; + for (size_t i = 0; i < scts.size(); i++) { + OperatorHistoryEntry entry; + entry.current_operator_ = "Operator " + base::NumberToString(i); + operator_history[scts[i]->log_id] = entry; + // Set timestamp to the day new policy comes in effect. + EXPECT_TRUE(base::Time::FromUTCExploded({2022, 2, 0, 1, 0, 0, 0, 0}, + &scts[i]->timestamp)); + } + chrome_policy_enforcer->SetOperatorHistoryForTesting(operator_history); + + EXPECT_EQ(CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS, + policy_enforcer_->CheckCompliance(chain_.get(), scts, + NetLogWithSource())); +} + +class ChromeCTPolicyEnforcerTest2022PolicyAllCerts + : public ChromeCTPolicyEnforcerTest { + public: + void SetUp() override { + scoped_feature_list_.InitAndEnableFeature( + features::kCertificateTransparency2022PolicyAllCerts); + ChromeCTPolicyEnforcerTest::SetUp(); + } + + private: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +TEST_F(ChromeCTPolicyEnforcerTest2022PolicyAllCerts, UpdatedSCTRequirements) { + ChromeCTPolicyEnforcer* chrome_policy_enforcer = + static_cast(policy_enforcer_.get()); + + std::unique_ptr private_key( + crypto::RSAPrivateKey::Create(1024)); + ASSERT_TRUE(private_key); + + // Test multiple validity periods + base::Time time_2015_3_0_25_11_25_0_0 = + CreateTime({2015, 3, 0, 25, 11, 25, 0, 0}); + + base::Time time_2015_9_0_20_11_25_0_0 = + CreateTime({2015, 9, 0, 20, 11, 25, 0, 0}); + + base::Time time_2015_9_0_21_11_25_0_0 = + CreateTime({2015, 9, 0, 21, 11, 25, 0, 0}); + + base::Time time_2016_3_0_25_11_25_0_0 = + CreateTime({2016, 3, 0, 25, 11, 25, 0, 0}); + + const struct TestData { + base::Time validity_start; + base::Time validity_end; + size_t scts_required; + } kTestData[] = {{// Cert valid for -12 months (nonsensical), needs 2 SCTs. + time_2016_3_0_25_11_25_0_0, time_2015_3_0_25_11_25_0_0, 2}, + {// Cert valid for 179 days, needs 2 SCTs. + time_2015_3_0_25_11_25_0_0, time_2015_9_0_20_11_25_0_0, 2}, + {// Cert valid for exactly 180 days, needs 3 SCTs. + time_2015_3_0_25_11_25_0_0, time_2015_9_0_21_11_25_0_0, 3}, + {// Cert valid for over 180 days, needs 3 SCTs. + time_2015_3_0_25_11_25_0_0, time_2016_3_0_25_11_25_0_0, 3}}; + + for (size_t i = 0; i < base::size(kTestData); ++i) { + SCOPED_TRACE(i); + const base::Time& validity_start = kTestData[i].validity_start; + const base::Time& validity_end = kTestData[i].validity_end; + size_t scts_required = kTestData[i].scts_required; + + // Create a self-signed certificate with exactly the validity period. + std::string cert_data; + ASSERT_TRUE(net::x509_util::CreateSelfSignedCert( + private_key->key(), net::x509_util::DIGEST_SHA256, "CN=test", + i * 10 + scts_required, validity_start, validity_end, {}, &cert_data)); + scoped_refptr cert(X509Certificate::CreateFromBytes( + base::as_bytes(base::make_span(cert_data)))); + ASSERT_TRUE(cert); + + std::map operator_history; + for (size_t j = 0; j <= scts_required - 1; ++j) { + SCTList scts; + FillListWithSCTsOfOrigin(SignedCertificateTimestamp::SCT_EMBEDDED, j, + std::vector(), false, &scts); + // Add different operators to the logs so the SCTs comply with operator + // diversity. + for (size_t k = 0; k < scts.size(); k++) { + OperatorHistoryEntry entry; + entry.current_operator_ = "Operator " + base::NumberToString(k); + operator_history[scts[k]->log_id] = entry; + } + chrome_policy_enforcer->SetOperatorHistoryForTesting(operator_history); + CTPolicyCompliance expected; + if (j == scts_required) { + // If the scts provided are as many as are required, the cert should be + // declared as compliant. + expected = CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS; + } else if (j == 1) { + // If a single SCT is provided, it should trip the diversity check. + expected = CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS; + } else { + // In any other case, the 'not enough' check should trip. + expected = CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS; + } + EXPECT_EQ(expected, policy_enforcer_->CheckCompliance(cert.get(), scts, + NetLogWithSource())) + << " for: " << (validity_end - validity_start).InDays() << " and " + << scts_required << " scts=" << scts.size() << " j=" << j; + } + } +} + +TEST_F(ChromeCTPolicyEnforcerTest2022PolicyAllCerts, + DoesNotConformToCTPolicyAllLogsSameOperator) { + ChromeCTPolicyEnforcer* chrome_policy_enforcer = + static_cast(policy_enforcer_.get()); + SCTList scts; + FillListWithSCTsOfOrigin(SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION, + 2, std::vector(), true, &scts); + std::map operator_history; + for (size_t i = 0; i < scts.size(); i++) { + OperatorHistoryEntry entry; + entry.current_operator_ = "Operator"; + operator_history[scts[i]->log_id] = entry; + } + chrome_policy_enforcer->SetOperatorHistoryForTesting(operator_history); + + EXPECT_EQ(CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS, + policy_enforcer_->CheckCompliance(chain_.get(), scts, + NetLogWithSource())); +} + +TEST_F(ChromeCTPolicyEnforcerTest2022PolicyAllCerts, + ConformsToCTPolicyDifferentOperators) { + ChromeCTPolicyEnforcer* chrome_policy_enforcer = + static_cast(policy_enforcer_.get()); + SCTList scts; + FillListWithSCTsOfOrigin(SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION, + 2, std::vector(), true, &scts); + std::map operator_history; + for (size_t i = 0; i < scts.size(); i++) { + OperatorHistoryEntry entry; + entry.current_operator_ = "Operator " + base::NumberToString(i); + operator_history[scts[i]->log_id] = entry; + } + chrome_policy_enforcer->SetOperatorHistoryForTesting(operator_history); + + EXPECT_EQ(CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS, + policy_enforcer_->CheckCompliance(chain_.get(), scts, + NetLogWithSource())); +} + +TEST_F(ChromeCTPolicyEnforcerTest2022PolicyAllCerts, + ConformsToPolicyDueToOperatorSwitch) { + ChromeCTPolicyEnforcer* chrome_policy_enforcer = + static_cast(policy_enforcer_.get()); + SCTList scts; + FillListWithSCTsOfOrigin(SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION, + 2, std::vector(), true, &scts); + std::map operator_history; + // Set all logs to the same operator. + for (auto sct : scts) { + OperatorHistoryEntry entry; + entry.current_operator_ = "Same Operator"; + operator_history[sct->log_id] = entry; + } + // Set the previous operator of one of the logs to a different one, with an + // end time after the SCT timestamp. + operator_history[scts[1]->log_id].previous_operators_.emplace_back( + "Different Operator", + scts[1]->timestamp + base::Seconds(1) - base::Time::UnixEpoch()); + chrome_policy_enforcer->SetOperatorHistoryForTesting(operator_history); + + EXPECT_EQ(CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS, + policy_enforcer_->CheckCompliance(chain_.get(), scts, + NetLogWithSource())); +} + +TEST_F(ChromeCTPolicyEnforcerTest2022PolicyAllCerts, + DoesNotConformToPolicyDueToOperatorSwitch) { + ChromeCTPolicyEnforcer* chrome_policy_enforcer = + static_cast(policy_enforcer_.get()); + SCTList scts; + FillListWithSCTsOfOrigin(SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION, + 2, std::vector(), true, &scts); + std::map operator_history; + // Set logs to different operators + for (size_t i = 0; i < scts.size(); i++) { + OperatorHistoryEntry entry; + entry.current_operator_ = "Operator " + base::NumberToString(i); + operator_history[scts[i]->log_id] = entry; + } + // Set the previous operator of one of the logs to the same as the other log, + // with an end time after the SCT timestamp. + operator_history[scts[1]->log_id].previous_operators_.emplace_back( + "Operator 0", + scts[1]->timestamp + base::Seconds(1) - base::Time::UnixEpoch()); + chrome_policy_enforcer->SetOperatorHistoryForTesting(operator_history); + + EXPECT_EQ(CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS, + policy_enforcer_->CheckCompliance(chain_.get(), scts, + NetLogWithSource())); +} + +TEST_F(ChromeCTPolicyEnforcerTest2022PolicyAllCerts, MultipleOperatorSwitches) { + ChromeCTPolicyEnforcer* chrome_policy_enforcer = + static_cast(policy_enforcer_.get()); + SCTList scts; + FillListWithSCTsOfOrigin(SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION, + 2, std::vector(), true, &scts); + std::map operator_history; + // Set logs to different operators + for (size_t i = 0; i < scts.size(); i++) { + OperatorHistoryEntry entry; + entry.current_operator_ = "Operator " + base::NumberToString(i); + operator_history[scts[i]->log_id] = entry; + } + // Set multiple previous operators, the first should be ignored since it + // stopped operating before the SCT timestamp. + operator_history[scts[1]->log_id].previous_operators_.emplace_back( + "Different Operator", + scts[1]->timestamp - base::Seconds(1) - base::Time::UnixEpoch()); + operator_history[scts[1]->log_id].previous_operators_.emplace_back( + "Operator 0", + scts[1]->timestamp + base::Seconds(1) - base::Time::UnixEpoch()); + chrome_policy_enforcer->SetOperatorHistoryForTesting(operator_history); + + EXPECT_EQ(CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS, + policy_enforcer_->CheckCompliance(chain_.get(), scts, + NetLogWithSource())); +} + +TEST_F(ChromeCTPolicyEnforcerTest2022PolicyAllCerts, + MultipleOperatorSwitchesBeforeSCTTimestamp) { + ChromeCTPolicyEnforcer* chrome_policy_enforcer = + static_cast(policy_enforcer_.get()); + SCTList scts; + FillListWithSCTsOfOrigin(SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION, + 2, std::vector(), true, &scts); + std::map operator_history; + // Set all logs to the same operator. + for (auto sct : scts) { + OperatorHistoryEntry entry; + entry.current_operator_ = "Same Operator"; + operator_history[sct->log_id] = entry; + } + // Set multiple previous operators, all of them should be ignored since they + // all stopped operating before the SCT timestamp. + operator_history[scts[1]->log_id].previous_operators_.emplace_back( + "Different Operator", + scts[1]->timestamp - base::Seconds(2) - base::Time::UnixEpoch()); + operator_history[scts[1]->log_id].previous_operators_.emplace_back( + "Yet Another Different Operator", + scts[1]->timestamp - base::Seconds(1) - base::Time::UnixEpoch()); + chrome_policy_enforcer->SetOperatorHistoryForTesting(operator_history); + + EXPECT_EQ(CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS, + policy_enforcer_->CheckCompliance(chain_.get(), scts, + NetLogWithSource())); +} + } // namespace } // namespace certificate_transparency diff --git a/components/certificate_transparency/ct_features.cc b/components/certificate_transparency/ct_features.cc index 21834dc736c76c..ccdb588888d40e 100644 --- a/components/certificate_transparency/ct_features.cc +++ b/components/certificate_transparency/ct_features.cc @@ -19,5 +19,12 @@ const base::Feature kCertificateTransparencyComponentUpdater{ base::FEATURE_ENABLED_BY_DEFAULT}; #endif +const base::Feature kCertificateTransparency2022Policy{ + "CertificateTransparency2022Policy", base::FEATURE_DISABLED_BY_DEFAULT}; + +const base::Feature kCertificateTransparency2022PolicyAllCerts{ + "CertificateTransparency2022PolicyAllCerts", + base::FEATURE_DISABLED_BY_DEFAULT}; + } // namespace features } // namespace certificate_transparency diff --git a/components/certificate_transparency/ct_features.h b/components/certificate_transparency/ct_features.h index ca2e9267828af1..79774e1e653bb6 100644 --- a/components/certificate_transparency/ct_features.h +++ b/components/certificate_transparency/ct_features.h @@ -12,6 +12,19 @@ namespace features { extern const base::Feature kCertificateTransparencyComponentUpdater; +// If enabled, the 2022 CT policy which removes the one Google log +// requirement, introduces log operator diversity requirements, and increases +// the number of embedded SCTs required for certificates with a lifetime over +// 180 days (from 2 to 3) will be used for any certificate issued after February +// 1, 2022. +extern const base::Feature kCertificateTransparency2022Policy; + +// If enabled, the 2022 CT policy which removes the one Google log +// requirement, introduces log operator diversity requirements, and increases +// the number of embedded SCTs required for certificates with a lifetime over +// 180 days (from 2 to 3) will be used for all certificates. +extern const base::Feature kCertificateTransparency2022PolicyAllCerts; + } // namespace features } // namespace certificate_transparency From c3656b5c7c77559dab608c221f4615497cfc9906 Mon Sep 17 00:00:00 2001 From: Chris Mumford Date: Tue, 14 Dec 2021 23:44:52 +0000 Subject: [PATCH 02/77] [serial] Running error callback for SDP query. Ensure the error callback to BluetoothSocketMac::Connect() will be called if the success callback is not. Bug: 1261555 Change-Id: I2d5878fbd7f34ac48b0f7b2d6d6f0515cc7375c5 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3331523 Reviewed-by: Matt Reynolds Commit-Queue: Chris Mumford Cr-Commit-Position: refs/heads/main@{#951725} --- device/bluetooth/bluetooth_socket_mac.mm | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/device/bluetooth/bluetooth_socket_mac.mm b/device/bluetooth/bluetooth_socket_mac.mm index f6705be62dfc5d..b8c08f6035f8b3 100644 --- a/device/bluetooth/bluetooth_socket_mac.mm +++ b/device/bluetooth/bluetooth_socket_mac.mm @@ -76,6 +76,15 @@ - (instancetype)initWithSocket:(scoped_refptr)socket return self; } +- (void)dealloc { + if (_error_callback) { + // The delegate's sdpQueryComplete was not called. This may happen if no + // target is specified. + std::move(_error_callback).Run("No target"); + } + [super dealloc]; +} + - (void)sdpQueryComplete:(IOBluetoothDevice*)device status:(IOReturn)status { DCHECK_EQ(device, _device); _socket->OnSDPQueryComplete(status, device, std::move(_success_callback), From b24b1885796251757ccde08e7610774305c176dc Mon Sep 17 00:00:00 2001 From: Minoru Chikamune Date: Tue, 14 Dec 2021 23:46:58 +0000 Subject: [PATCH 03/77] [Code Health] Modernize Value API in extension_messages_apitest.cc Bug: 1187061 Change-Id: I931c65283a49ae3cda640cff934e71c691da37f7 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3330387 Reviewed-by: Reilly Grant Commit-Queue: Minoru Chikamune Cr-Commit-Position: refs/heads/main@{#951726} --- chrome/browser/extensions/extension_messages_apitest.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chrome/browser/extensions/extension_messages_apitest.cc b/chrome/browser/extensions/extension_messages_apitest.cc index e219dd32a5de63..2fc7226aa9a831 100644 --- a/chrome/browser/extensions/extension_messages_apitest.cc +++ b/chrome/browser/extensions/extension_messages_apitest.cc @@ -80,9 +80,9 @@ class MessageSender : public ExtensionHostRegistry::Observer { static std::unique_ptr BuildEventArguments( const bool last_message, const std::string& data) { - std::unique_ptr event(new base::DictionaryValue()); - event->SetBoolean("lastMessage", last_message); - event->SetString("data", data); + base::Value event(base::Value::Type::DICTIONARY); + event.SetBoolKey("lastMessage", last_message); + event.SetStringKey("data", data); std::unique_ptr arguments(new base::ListValue()); arguments->Append(std::move(event)); return arguments; From acefb24f60d1bedfc07ed41334b506d1df30fe68 Mon Sep 17 00:00:00 2001 From: Minoru Chikamune Date: Tue, 14 Dec 2021 23:47:22 +0000 Subject: [PATCH 04/77] [Code Health] Modernize Value API in debugger_api.h Bug: 1187061 Change-Id: I228a32001622b89a749eb447e1f95bea2138643d Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3334438 Reviewed-by: Reilly Grant Commit-Queue: Minoru Chikamune Cr-Commit-Position: refs/heads/main@{#951727} --- .../extensions/api/debugger/debugger_api.cc | 67 +++++++++---------- .../extensions/api/debugger/debugger_api.h | 6 +- 2 files changed, 33 insertions(+), 40 deletions(-) diff --git a/chrome/browser/extensions/api/debugger/debugger_api.cc b/chrome/browser/extensions/api/debugger/debugger_api.cc index 96f7b8b525e375..73e19fb183e551 100644 --- a/chrome/browser/extensions/api/debugger/debugger_api.cc +++ b/chrome/browser/extensions/api/debugger/debugger_api.cc @@ -309,11 +309,11 @@ void ExtensionDevToolsClientHost::SendMessageToBackend( DebuggerSendCommandFunction* function, const std::string& method, SendCommand::Params::CommandParams* command_params) { - base::DictionaryValue protocol_request; + base::Value protocol_request(base::Value::Type::DICTIONARY); int request_id = ++last_request_id_; pending_requests_[request_id] = function; - protocol_request.SetInteger("id", request_id); - protocol_request.SetString("method", method); + protocol_request.SetIntKey("id", request_id); + protocol_request.SetStringKey("method", method); if (command_params) { protocol_request.SetKey("params", command_params->additional_properties.Clone()); @@ -376,27 +376,27 @@ void ExtensionDevToolsClientHost::DispatchProtocolMessage( base::StringPiece message_str(reinterpret_cast(message.data()), message.size()); - std::unique_ptr result = base::JSONReader::ReadDeprecated( + absl::optional result = base::JSONReader::Read( message_str, base::JSON_REPLACE_INVALID_CHARACTERS); if (!result || !result->is_dict()) { LOG(ERROR) << "Tried to send invalid message to extension: " << message_str; return; } - base::DictionaryValue* dictionary = - static_cast(result.get()); + base::Value dictionary = std::move(result.value()); - absl::optional id = dictionary->FindIntKey("id"); + absl::optional id = dictionary.FindIntKey("id"); if (!id) { - std::string method_name; - if (!dictionary->GetString("method", &method_name)) + std::string* method_name = dictionary.FindStringKey("method"); + if (!method_name) return; OnEvent::Params params; - base::DictionaryValue* params_value; - if (dictionary->GetDictionary("params", ¶ms_value)) - params.additional_properties.Swap(params_value); + if (base::Value* params_value = dictionary.FindDictKey("params")) { + params.additional_properties.Swap( + static_cast(params_value)); + } - auto args(OnEvent::Create(debuggee_, method_name, params)); + auto args(OnEvent::Create(debuggee_, *method_name, params)); auto event = std::make_unique(events::DEBUGGER_ON_EVENT, OnEvent::kEventName, std::move(args), profile_); @@ -407,7 +407,7 @@ void ExtensionDevToolsClientHost::DispatchProtocolMessage( if (it == pending_requests_.end()) return; - it->second->SendResponseBody(dictionary); + it->second->SendResponseBody(std::move(dictionary)); pending_requests_.erase(it); } } @@ -654,20 +654,19 @@ ExtensionFunction::ResponseAction DebuggerSendCommandFunction::Run() { return RespondLater(); } -void DebuggerSendCommandFunction::SendResponseBody( - base::DictionaryValue* response) { - base::Value* error_body; - if (response->Get("error", &error_body)) { +void DebuggerSendCommandFunction::SendResponseBody(base::Value response) { + if (base::Value* error_body = response.FindKey("error")) { std::string error; base::JSONWriter::Write(*error_body, &error); Respond(Error(std::move(error))); return; } - base::DictionaryValue* result_body; SendCommand::Results::Result result; - if (response->GetDictionary("result", &result_body)) - result.additional_properties.Swap(result_body); + if (base::Value* result_body = response.FindDictKey("result")) { + result.additional_properties.Swap( + static_cast(result_body)); + } Respond(ArgumentList(SendCommand::Results::Create(result))); } @@ -693,35 +692,33 @@ const char kTargetTypeBackgroundPage[] = "background_page"; const char kTargetTypeWorker[] = "worker"; const char kTargetTypeOther[] = "other"; -std::unique_ptr SerializeTarget( - scoped_refptr host) { - std::unique_ptr dictionary( - new base::DictionaryValue()); - dictionary->SetString(kTargetIdField, host->GetId()); - dictionary->SetString(kTargetTitleField, host->GetTitle()); - dictionary->SetBoolean(kTargetAttachedField, host->IsAttached()); - dictionary->SetString(kTargetUrlField, host->GetURL().spec()); +base::Value SerializeTarget(scoped_refptr host) { + base::Value dictionary(base::Value::Type::DICTIONARY); + dictionary.SetStringKey(kTargetIdField, host->GetId()); + dictionary.SetStringKey(kTargetTitleField, host->GetTitle()); + dictionary.SetBoolKey(kTargetAttachedField, host->IsAttached()); + dictionary.SetStringKey(kTargetUrlField, host->GetURL().spec()); std::string type = host->GetType(); std::string target_type = kTargetTypeOther; if (type == DevToolsAgentHost::kTypePage) { int tab_id = extensions::ExtensionTabUtil::GetTabId(host->GetWebContents()); - dictionary->SetInteger(kTargetTabIdField, tab_id); + dictionary.SetIntKey(kTargetTabIdField, tab_id); target_type = kTargetTypePage; } else if (type == ChromeDevToolsManagerDelegate::kTypeBackgroundPage) { - dictionary->SetString(kTargetExtensionIdField, host->GetURL().host()); + dictionary.SetStringKey(kTargetExtensionIdField, host->GetURL().host()); target_type = kTargetTypeBackgroundPage; } else if (type == DevToolsAgentHost::kTypeServiceWorker || type == DevToolsAgentHost::kTypeSharedWorker) { target_type = kTargetTypeWorker; } - dictionary->SetString(kTargetTypeField, target_type); + dictionary.SetStringKey(kTargetTypeField, target_type); GURL favicon_url = host->GetFaviconURL(); if (favicon_url.is_valid()) - dictionary->SetString(kTargetFaviconUrlField, favicon_url.spec()); + dictionary.SetStringKey(kTargetFaviconUrlField, favicon_url.spec()); return dictionary; } @@ -735,8 +732,8 @@ DebuggerGetTargetsFunction::~DebuggerGetTargetsFunction() = default; ExtensionFunction::ResponseAction DebuggerGetTargetsFunction::Run() { content::DevToolsAgentHost::List list = DevToolsAgentHost::GetOrCreateAll(); std::unique_ptr result(new base::ListValue()); - for (size_t i = 0; i < list.size(); ++i) - result->Append(SerializeTarget(list[i])); + for (auto& i : list) + result->Append(SerializeTarget(i)); return RespondNow( OneArgument(base::Value::FromUniquePtrValue(std::move(result)))); diff --git a/chrome/browser/extensions/api/debugger/debugger_api.h b/chrome/browser/extensions/api/debugger/debugger_api.h index e38d5a8a923fe6..9a4f5af19402f6 100644 --- a/chrome/browser/extensions/api/debugger/debugger_api.h +++ b/chrome/browser/extensions/api/debugger/debugger_api.h @@ -20,10 +20,6 @@ using extensions::api::debugger::Debuggee; // Base debugger function. -namespace base { -class DictionaryValue; -} - namespace extensions { class ExtensionDevToolsClientHost; @@ -77,7 +73,7 @@ class DebuggerSendCommandFunction : public DebuggerFunction { DECLARE_EXTENSION_FUNCTION("debugger.sendCommand", DEBUGGER_SENDCOMMAND) DebuggerSendCommandFunction(); - void SendResponseBody(base::DictionaryValue* result); + void SendResponseBody(base::Value result); void SendDetachedError(); protected: From c2f0af4c8fea9fe70bef538db030c322b3dc7848 Mon Sep 17 00:00:00 2001 From: Chris Mumford Date: Tue, 14 Dec 2021 23:48:03 +0000 Subject: [PATCH 05/77] [bluetooth] Add BluetoothUUID operator<<. Convert BluetoothUUID::PrintTo() to Bluetooth::operator<<(...). This still still allows gtest() macros to work, and also makes it easier to do other debugging-type things like: LOG(INFO) << "UUID is: " << uuid; This was tested by intentionally introducing this test failure: BluetoothUUID invalid; EXPECT_EQ(uuid5, invalid); Without the operator the test output was: Expected equality of these values: uuid5 Which is: 56-byte object <01-00 00-00 AA-AA AA-AA 08-31 31-30 ... 00-00> invalid Which is: 56-byte object <00-00 00-00 AA-AA AA-AA 00-00 00-00 ... 00-00> With the operator this is the output: Expected equality of these values: uuid5 Which is: 00001101-0000-1000-8000-00805f9b34fb invalid Which is: Bug: none Change-Id: If2287e8619950adc57bca64300c3188f5fa92ab7 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3330919 Reviewed-by: Matt Reynolds Commit-Queue: Chris Mumford Cr-Commit-Position: refs/heads/main@{#951728} --- device/bluetooth/public/cpp/bluetooth_uuid.cc | 4 ++-- device/bluetooth/public/cpp/bluetooth_uuid.h | 5 +++-- .../bluetooth/public/cpp/bluetooth_uuid_unittest.cc | 13 +++++++++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/device/bluetooth/public/cpp/bluetooth_uuid.cc b/device/bluetooth/public/cpp/bluetooth_uuid.cc index 019e8431c1dd9b..92ea80af7fd501 100644 --- a/device/bluetooth/public/cpp/bluetooth_uuid.cc +++ b/device/bluetooth/public/cpp/bluetooth_uuid.cc @@ -150,8 +150,8 @@ bool BluetoothUUID::operator!=(const BluetoothUUID& uuid) const { return canonical_value_ != uuid.canonical_value_; } -void PrintTo(const BluetoothUUID& uuid, std::ostream* out) { - *out << uuid.canonical_value(); +std::ostream& operator<<(std::ostream& os, BluetoothUUID uuid) { + return os << uuid.canonical_value(); } } // namespace device diff --git a/device/bluetooth/public/cpp/bluetooth_uuid.h b/device/bluetooth/public/cpp/bluetooth_uuid.h index 0c27ae71059626..cfa61ce9366674 100644 --- a/device/bluetooth/public/cpp/bluetooth_uuid.h +++ b/device/bluetooth/public/cpp/bluetooth_uuid.h @@ -5,6 +5,7 @@ #ifndef DEVICE_BLUETOOTH_PUBLIC_CPP_BLUETOOTH_UUID_H_ #define DEVICE_BLUETOOTH_PUBLIC_CPP_BLUETOOTH_UUID_H_ +#include #include #include @@ -108,8 +109,8 @@ class BluetoothUUID { std::string canonical_value_; }; -// This is required by gtest to print a readable output on test failures. -void PrintTo(const BluetoothUUID& uuid, std::ostream* out); +// Output the 128-bit canonical string representation of `uuid` to `os`. +std::ostream& operator<<(std::ostream& os, BluetoothUUID uuid); struct BluetoothUUIDHash { size_t operator()(const device::BluetoothUUID& uuid) const { diff --git a/device/bluetooth/public/cpp/bluetooth_uuid_unittest.cc b/device/bluetooth/public/cpp/bluetooth_uuid_unittest.cc index 40bc572df1aadc..2afc706136e9d6 100644 --- a/device/bluetooth/public/cpp/bluetooth_uuid_unittest.cc +++ b/device/bluetooth/public/cpp/bluetooth_uuid_unittest.cc @@ -6,6 +6,8 @@ #include +#include + #include "base/cxx17_backports.h" #include "base/strings/string_piece.h" #include "build/build_config.h" @@ -170,4 +172,15 @@ TEST(BluetoothUUIDTest, BluetoothUUID_CaseInsensitive) { } } +TEST(BluetoothUUIDTest, BluetoothUUID_stream_insertion_operator) { + const std::string uuid_str("00001abc-0000-1000-8000-00805f9b34fb"); + + BluetoothUUID uuid(uuid_str); + std::ostringstream ss; + ss << uuid; + const std::string out = ss.str(); + + EXPECT_EQ(uuid_str, out); +} + } // namespace device From bf952bfe1c9fcac74cef1646e4422fe5fc552898 Mon Sep 17 00:00:00 2001 From: Lucas Radaelli Date: Tue, 14 Dec 2021 23:48:59 +0000 Subject: [PATCH 06/77] [fuchsia][a11y] Implements Semantic Provider library. This change implements a library that manages the connection with the Fuchsia Semantics API. This allows clients to just add / delete semantic nodes, leaving the tree life cycle management up to the library. Clients can register handlers to be notified of specific Semantic events. Please see .h for more documentation. Test: SemanticProviderTest.* Bug: fuchsia:86654 Change-Id: I025ec894a23a6e13812d3642e91ac800cae51eee Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3261610 Reviewed-by: Kevin Marshall Reviewed-by: David Tseng Commit-Queue: Lucas Radaelli Cr-Commit-Position: refs/heads/main@{#951729} --- ui/accessibility/BUILD.gn | 11 + ui/accessibility/platform/BUILD.gn | 11 +- .../platform/fuchsia/semantic_provider.cc | 306 ++++++++++++ .../platform/fuchsia/semantic_provider.h | 229 +++++++++ .../fuchsia/semantic_provider_test.cc | 441 ++++++++++++++++++ 5 files changed, 996 insertions(+), 2 deletions(-) create mode 100644 ui/accessibility/platform/fuchsia/semantic_provider.cc create mode 100644 ui/accessibility/platform/fuchsia/semantic_provider.h create mode 100644 ui/accessibility/platform/fuchsia/semantic_provider_test.cc diff --git a/ui/accessibility/BUILD.gn b/ui/accessibility/BUILD.gn index 2ccf84b7f5eddc..f146baea5f7023 100644 --- a/ui/accessibility/BUILD.gn +++ b/ui/accessibility/BUILD.gn @@ -285,6 +285,17 @@ test("accessibility_unittests") { "//ui/gfx:test_support", ] + if (is_fuchsia) { + sources += [ "platform/fuchsia/semantic_provider_test.cc" ] + + deps += [ + "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.accessibility.semantics", + "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.math", + "//third_party/fuchsia-sdk/sdk/pkg/scenic_cpp", + "//third_party/fuchsia-sdk/sdk/pkg/sys_cpp", + ] + } + if (has_native_accessibility) { # This test depends heavily on NativeViewAccessible, which is only # implemented on these platforms. diff --git a/ui/accessibility/platform/BUILD.gn b/ui/accessibility/platform/BUILD.gn index 20df50a688fc15..853cb4b9fd6e65 100644 --- a/ui/accessibility/platform/BUILD.gn +++ b/ui/accessibility/platform/BUILD.gn @@ -93,10 +93,17 @@ source_set("platform") { "fuchsia/accessibility_bridge_fuchsia.h", "fuchsia/accessibility_bridge_fuchsia_registry.cc", "fuchsia/accessibility_bridge_fuchsia_registry.h", + "fuchsia/semantic_provider.cc", + "fuchsia/semantic_provider.h", ] - public_deps += - [ "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.accessibility.semantics" ] + public_deps += [ + "//base", + "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.accessibility.semantics", + "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.accessibility.semantics", + "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.math", + "//third_party/fuchsia-sdk/sdk/pkg/sys_cpp", + ] } if (has_native_accessibility) { diff --git a/ui/accessibility/platform/fuchsia/semantic_provider.cc b/ui/accessibility/platform/fuchsia/semantic_provider.cc new file mode 100644 index 00000000000000..dd0a9776fc5e3d --- /dev/null +++ b/ui/accessibility/platform/fuchsia/semantic_provider.cc @@ -0,0 +1,306 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/accessibility/platform/fuchsia/semantic_provider.h" + +#include + +#include "base/check.h" +#include "base/check_op.h" +#include "base/fuchsia/fuchsia_logging.h" +#include "base/fuchsia/process_context.h" + +namespace ui { +namespace { + +using fuchsia::accessibility::semantics::Node; + +constexpr uint32_t kFuchsiaRootNodeId = 0; +constexpr size_t kMaxOperationsPerBatch = 16; + +} // namespace + +AXFuchsiaSemanticProvider::Batch::Batch(Type type) : type_(type) {} +AXFuchsiaSemanticProvider::Batch::Batch(Batch&& other) = default; +AXFuchsiaSemanticProvider::Batch::~Batch() = default; + +bool AXFuchsiaSemanticProvider::Batch::IsFull() const { + return ( + (type_ == Type::kUpdate && updates_.size() >= kMaxOperationsPerBatch) || + (type_ == Type::kDelete && + delete_node_ids_.size() >= kMaxOperationsPerBatch)); +} + +void AXFuchsiaSemanticProvider::Batch::Append( + fuchsia::accessibility::semantics::Node node) { + DCHECK_EQ(type_, Type::kUpdate); + DCHECK(!IsFull()); + updates_.push_back(std::move(node)); +} + +void AXFuchsiaSemanticProvider::Batch::AppendDeletion(uint32_t delete_node_id) { + DCHECK_EQ(type_, Type::kDelete); + DCHECK(!IsFull()); + delete_node_ids_.push_back(delete_node_id); +} + +void AXFuchsiaSemanticProvider::Batch::Apply( + fuchsia::accessibility::semantics::SemanticTreePtr* semantic_tree) { + if (type_ == Type::kUpdate && !updates_.empty()) + (*semantic_tree)->UpdateSemanticNodes(std::move(updates_)); + else if (type_ == Type::kDelete && !delete_node_ids_.empty()) + (*semantic_tree)->DeleteSemanticNodes(std::move(delete_node_ids_)); +} + +AXFuchsiaSemanticProvider::NodeInfo ::NodeInfo() = default; +AXFuchsiaSemanticProvider::NodeInfo ::~NodeInfo() = default; + +AXFuchsiaSemanticProvider::Delegate::Delegate() = default; +AXFuchsiaSemanticProvider::Delegate::~Delegate() = default; + +AXFuchsiaSemanticProvider::AXFuchsiaSemanticProvider( + fuchsia::ui::views::ViewRef view_ref, + float pixel_scale, + Delegate* delegate) + : view_ref_(std::move(view_ref)), + pixel_scale_(pixel_scale), + delegate_(delegate), + semantic_listener_binding_(this) { + sys::ComponentContext* component_context = base::ComponentContextForProcess(); + DCHECK(component_context); + DCHECK(delegate_); + + component_context->svc() + ->Connect() + ->RegisterViewForSemantics(std::move(view_ref_), + semantic_listener_binding_.NewBinding(), + semantic_tree_.NewRequest()); + semantic_tree_.set_error_handler([this](zx_status_t status) { + ZX_LOG(ERROR, status) << "SemanticTree disconnected"; + delegate_->OnSemanticsManagerConnectionClosed(); + semantic_updates_enabled_ = false; + }); +} + +AXFuchsiaSemanticProvider::~AXFuchsiaSemanticProvider() = default; + +bool AXFuchsiaSemanticProvider::Update( + fuchsia::accessibility::semantics::Node node) { + if (!semantic_updates_enabled()) + return false; + + DCHECK(node.has_node_id()); + + if (node.node_id() != kFuchsiaRootNodeId) { + auto found_not_reachable = not_reachable_.find(node.node_id()); + const bool is_not_reachable = found_not_reachable != not_reachable_.end(); + const absl::optional parent_node_id = + GetParentForNode(node.node_id()); + if (is_not_reachable && parent_node_id) { + // Connection parent -> |node| exists now. + not_reachable_.erase(found_not_reachable); + nodes_[node.node_id()].parents.insert(*parent_node_id); + } else if (!parent_node_id) { + // No node or multiple nodes points to this one, so it is not reachable. + if (!is_not_reachable) + not_reachable_[node.node_id()] = {}; + } + } + + // If the node is not present in the map, the list of children will be empty + // so this is a no-op in the call below. + std::vector& children = nodes_[node.node_id()].children; + + // Before updating the node, update the list of children to be not reachable, + // in case the new list of children change. + MarkChildrenAsNotReachable(children, node.node_id()); + children = node.has_child_ids() ? node.child_ids() : std::vector(); + MarkChildrenAsReachable(children, node.node_id()); + + Batch& batch = GetCurrentUnfilledBatch(Batch::Type::kUpdate); + batch.Append(std::move(node)); + TryToCommit(); + return true; +} + +void AXFuchsiaSemanticProvider::TryToCommit() { + // Don't send out updates while the tree is mid-mutation. + if (commit_inflight_ || batches_.empty()) + return; + + // If a tree has nodes but no root, wait until the root is present or all + // nodes are deleted. + if (!nodes_.empty() && nodes_.find(kFuchsiaRootNodeId) == nodes_.end()) + return; + + if (!not_reachable_.empty()) + return; + + for (auto& batch : batches_) { + batch.Apply(&semantic_tree_); + } + + batches_.clear(); + semantic_tree_->CommitUpdates( + fit::bind_member(this, &AXFuchsiaSemanticProvider::OnCommitComplete)); + commit_inflight_ = true; +} + +bool AXFuchsiaSemanticProvider::Delete(uint32_t node_id) { + if (!semantic_updates_enabled()) + return false; + + auto it = nodes_.find(node_id); + if (it == nodes_.end()) + return false; + + if (it->second.parents.empty()) { + // No node points to this one, so it is safe to remove it from the tree. + not_reachable_.erase(node_id); + } else { + not_reachable_[node_id] = + it->second + .parents; // Zero or more parents can be pointing to this node. + } + MarkChildrenAsNotReachable(it->second.children, node_id); + + nodes_.erase(it); + + Batch& batch = GetCurrentUnfilledBatch(Batch::Type::kDelete); + batch.AppendDeletion(node_id); + TryToCommit(); + return true; +} + +void AXFuchsiaSemanticProvider::SendEvent( + fuchsia::accessibility::semantics::SemanticEvent event) { + semantic_tree_->SendSemanticEvent(std::move(event), [](auto...) {}); +} + +bool AXFuchsiaSemanticProvider::HasPendingUpdates() const { + return commit_inflight_ || !batches_.empty(); +} + +bool AXFuchsiaSemanticProvider::Clear() { + if (!semantic_updates_enabled()) + return false; + + batches_.clear(); + not_reachable_.clear(); + nodes_.clear(); + Batch& batch = GetCurrentUnfilledBatch(Batch::Type::kDelete); + batch.AppendDeletion(kFuchsiaRootNodeId); + TryToCommit(); + return true; +} + +void AXFuchsiaSemanticProvider::OnAccessibilityActionRequested( + uint32_t node_id, + fuchsia::accessibility::semantics::Action action, + fuchsia::accessibility::semantics::SemanticListener:: + OnAccessibilityActionRequestedCallback callback) { + if (delegate_->OnAccessibilityAction(node_id, action)) + callback(true); + + // The action was not handled. + callback(false); +} + +void AXFuchsiaSemanticProvider::HitTest(fuchsia::math::PointF local_point, + HitTestCallback callback) { + fuchsia::math::PointF point; + point.x = local_point.x * pixel_scale_; + point.y = local_point.y * pixel_scale_; + + delegate_->OnHitTest(point, std::move(callback)); + return; +} + +void AXFuchsiaSemanticProvider::OnSemanticsModeChanged( + bool update_enabled, + OnSemanticsModeChangedCallback callback) { + if (semantic_updates_enabled_ != update_enabled) + delegate_->OnSemanticsEnabled(update_enabled); + + semantic_updates_enabled_ = update_enabled; + callback(); +} + +void AXFuchsiaSemanticProvider::MarkChildrenAsNotReachable( + const std::vector& child_ids, + uint32_t parent_id) { + for (const uint32_t child_id : child_ids) { + const auto it = nodes_.find(child_id); + if (it != nodes_.end()) { + it->second.parents.erase(parent_id); + if (it->second.parents.empty()) + not_reachable_[child_id] = {}; + else + not_reachable_.erase(child_id); + } else { + auto not_reachable_it = not_reachable_.find(child_id); + // Child id is no longer in the regular map, deletes it also from + // not_reachable_ if no parent points to it anymore. + if (not_reachable_it != not_reachable_.end()) { + not_reachable_it->second.erase(parent_id); + if (not_reachable_it->second.empty()) + not_reachable_.erase(not_reachable_it); + } + } + } +} + +void AXFuchsiaSemanticProvider::MarkChildrenAsReachable( + const std::vector& child_ids, + uint32_t parent_id) { + for (const uint32_t child_id : child_ids) { + auto it = nodes_.find(child_id); + if (it == nodes_.end()) + not_reachable_[child_id].insert(parent_id); + else { + it->second.parents.insert(parent_id); + if (it->second.parents.size() == 1) + not_reachable_.erase(child_id); + else + not_reachable_[child_id].insert(parent_id); + } + } +} + +absl::optional AXFuchsiaSemanticProvider::GetParentForNode( + const uint32_t node_id) { + const auto it = nodes_.find(node_id); + if (it != nodes_.end()) { + if (it->second.parents.size() == 1) + return *it->second.parents.begin(); + else + return absl::nullopt; + } + + const auto not_reachable_it = not_reachable_.find(node_id); + if (not_reachable_it != not_reachable_.end()) { + if (not_reachable_it->second.size() == 1) + return *not_reachable_it->second.begin(); + else + return absl::nullopt; + } + + return absl::nullopt; +} + +AXFuchsiaSemanticProvider::Batch& +AXFuchsiaSemanticProvider::GetCurrentUnfilledBatch(Batch::Type type) { + if (batches_.empty() || batches_.back().type() != type || + batches_.back().IsFull()) + batches_.push_back(Batch(type)); + + return batches_.back(); +} + +void AXFuchsiaSemanticProvider::OnCommitComplete() { + commit_inflight_ = false; + TryToCommit(); +} + +} // namespace ui \ No newline at end of file diff --git a/ui/accessibility/platform/fuchsia/semantic_provider.h b/ui/accessibility/platform/fuchsia/semantic_provider.h new file mode 100644 index 00000000000000..dce679da8f466c --- /dev/null +++ b/ui/accessibility/platform/fuchsia/semantic_provider.h @@ -0,0 +1,229 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_ACCESSIBILITY_PLATFORM_FUCHSIA_SEMANTIC_PROVIDER_H_ +#define UI_ACCESSIBILITY_PLATFORM_FUCHSIA_SEMANTIC_PROVIDER_H_ + +#include +#include +#include + +#include +#include +#include + +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/accessibility/ax_export.h" + +namespace ui { + +// Manages the connection with the Fuchsia Semantics API. +// +// Clients instantiate this class, which connects to the Fuchsia semantics API. +// Semantic nodes can be added or deleted. When a batch of nodes would leave the +// Fuchsia semantic tree in a valid state, they are committed. Please see +// |fuchsia.accessibility.semantics| API for more documentation on valid +// semantic trees. +class AX_EXPORT AXFuchsiaSemanticProvider + : public fuchsia::accessibility::semantics::SemanticListener { + public: + // A delegate that can be registered by clients of this library to be notified + // about Semantic changes. + class Delegate { + public: + Delegate(); + virtual ~Delegate(); + + // Called when the FIDL channel to the Semantics Manager is closed. If this + // callback returns true, an attempt to reconnect will be made. + virtual bool OnSemanticsManagerConnectionClosed() = 0; + + // Processes an incoming accessibility action from Fuchsia. It + // receives the Fuchsia node ID and the action requested. If this + // method returns true, this means that the action will be handled. + virtual bool OnAccessibilityAction( + uint32_t node_id, + fuchsia::accessibility::semantics::Action action) = 0; + + // Processes an incoming hit test request from Fuchsia. It + // receives a point in Scenic View pixel coordinates and a callback to + // return the result when the hit test is done. Please see + // |fuchsia.accessibility.semantics.SemanticListener| for documentation on + // hit tests. + virtual void OnHitTest(fuchsia::math::PointF point, + HitTestCallback callback) = 0; + + // Called whenever Fuchsia enables / disables semantic updates. + virtual void OnSemanticsEnabled(bool enabled) = 0; + }; + + // Arguments: + // |view_ref|: identifies the view providing semantics. Please consult + // |fuchsia.accessibility.semantics| API documentation. + // |pixel_scale|: Scales Scenic view coordinates to pixel coordinates. + // |delegate|: Handles semantic requests, please see Delegate class for more + // documentation. Caller is responsible for ensuring that |delegate| outlives + // |this|. + // During construction, this class connects to + // |fuchsia.accessibility.semantics.SemanticsManager| to register itself as a + // semantic provider. + AXFuchsiaSemanticProvider(fuchsia::ui::views::ViewRef view_ref, + float pixel_scale, + Delegate* delegate); + ~AXFuchsiaSemanticProvider() override; + + // Returns true if Fuchsia has enabled semantics. + bool semantic_updates_enabled() const { return semantic_updates_enabled_; } + + // Adds a semantic node to be updated. It is mandatory that the node has at + // least an unique ID. + bool Update(fuchsia::accessibility::semantics::Node node); + + // Marks a semantic node to be deleted. Returns false if the node is not + // present in the list of semantic nodes known by this provider. + bool Delete(uint32_t node_id); + + // Clears the semantic tree. + bool Clear(); + + // Sends an accessibility event to Fuchsia. Please consult + // https://cs.opensource.google/fuchsia/fuchsia/+/master:sdk/fidl/fuchsia.accessibility.semantics/semantics_manager.fidl + // for documentation on events. + void SendEvent(fuchsia::accessibility::semantics::SemanticEvent event); + + // Returns true if there are pending updates or deletions to be made. + bool HasPendingUpdates() const; + + private: + // Holds information about a Fuchsia Semantic Node. It contains only the + // fields needed to check that the resulting tree would be valid. + struct NodeInfo { + NodeInfo(); + ~NodeInfo(); + + // During a tree update a node may have multiple parents pointing to it, + // although after all updates are processed only one should be present. + std::set parents; + std::vector children; + }; + + // Represents a batch of nodes to be sent to Fuchsia. + // Batches can hold exactly one type: a series of updates or a series of + // deletions. + class Batch { + public: + enum class Type { kUpdate, kDelete }; + + Batch(Type type); + Batch(Batch&& other); + ~Batch(); + Batch(const Batch& other) = delete; + + Type type() const { return type_; } + + // Returns true if the batch has reached its size limit. + bool IsFull() const; + + // Adds an update or deletion to the batch. This fails if the batch is full + // or if the new item is not the same type of the batch. + void Append(fuchsia::accessibility::semantics::Node node); + void AppendDeletion(uint32_t delete_node_id); + + // Sends enqueued operations to SemanticsManager. + void Apply( + fuchsia::accessibility::semantics::SemanticTreePtr* semantic_tree); + + private: + Type type_; + std::vector updates_; + std::vector delete_node_ids_; + }; + + // Attempts to commit the pending updates to Fuchsia if the resulting updates + // would leave the final tree in a valid state. + void TryToCommit(); + + // Returns a batch that can receive an update or deletion depending on |type|. + Batch& GetCurrentUnfilledBatch(Batch::Type type); + + // Invoked whenever Fuchsia responds that a commit was received. This tries to + // commit again if there are pending upedates or deletions. + void OnCommitComplete(); + + // Mark all |child_ids| not reachable from |parent_id|, meaning: + // - If |parent_id| was the only parent, the children are now disconnected + // from the tree. + // - If |parent_id| was an additional parent, now the children are connected + // to a single parent in the tree. + // - If the children do not exist, remove them from the list of nodes waiting + // to be updated. + void MarkChildrenAsNotReachable(const std::vector& child_ids, + uint32_t parent_id); + + // Mark all |child_ids| reachable from |parent_id|, meaning: + // - If |parent_id| is the only parent, the children are now connected to the + // tree and are all reachable. + // - If |parent_id| is an additional parent, now the children are not + // connected to the tree as multiple parents point to them. + // - If the children do not exist, the parent waits for the nodes to be + // created. + void MarkChildrenAsReachable(const std::vector& child_ids, + uint32_t parent_id); + + // Returns the ID of the parent of this node if it has one. If it does not + // have a parent or it has multiple parents, returns absl::nullopt. + absl::optional GetParentForNode(const uint32_t node_id); + + // fuchsia::accessibility::semantics::SemanticListener: + void OnAccessibilityActionRequested( + uint32_t node_id, + fuchsia::accessibility::semantics::Action action, + fuchsia::accessibility::semantics::SemanticListener:: + OnAccessibilityActionRequestedCallback callback) override; + + // fuchsia::accessibility::semantics::SemanticListener: + void HitTest(::fuchsia::math::PointF local_point, + HitTestCallback callback) override; + + // fuchsia::accessibility::semantics::SemanticListener: + void OnSemanticsModeChanged(bool update_enabled, + OnSemanticsModeChangedCallback callback) override; + + fuchsia::ui::views::ViewRef view_ref_; + float pixel_scale_; + Delegate* const delegate_; + + fidl::Binding + semantic_listener_binding_; + + fuchsia::accessibility::semantics::SemanticTreePtr semantic_tree_; + + bool semantic_updates_enabled_ = true; + + // Nodes from this tree. If not empty, to be considered a valid tree, there + // must be: + // - A node which node id is equal to kFuchsiaRootNodeId; + // - Each node except the root has only one parent; + // - All children pointed by a parent exist in the tree. + // Only the node ID and the child IDs of the node are stored here because at + // this point we only check to see if the tree is valid. + std::map nodes_; + + // Key == the node ID that is not reachable from the root of the tree, value + // == 0 or more parents that point to this node. Note that nodes can be listed + // here but still be present in |nodes_|. This may happen, for example, if the + // parent of the node was deleted and there is no path from the root to it, so + // the node waits for a parent to connect to it. + std::map> not_reachable_; + + // Stores batches of node updates or deletions to be sent to Fuchsia. Note + // that a batch contains only updates or deletions, because they are pushed to + // Fuchsia differently. + std::vector batches_; + + bool commit_inflight_; +}; + +} // namespace ui +#endif // UI_ACCESSIBILITY_PLATFORM_FUCHSIA_SEMANTIC_PROVIDER_H_ diff --git a/ui/accessibility/platform/fuchsia/semantic_provider_test.cc b/ui/accessibility/platform/fuchsia/semantic_provider_test.cc new file mode 100644 index 00000000000000..ba5e469dba85cb --- /dev/null +++ b/ui/accessibility/platform/fuchsia/semantic_provider_test.cc @@ -0,0 +1,441 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "semantic_provider.h" + +#include +#include + +#include +#include + +#include "base/auto_reset.h" +#include "base/callback.h" +#include "base/fuchsia/process_context.h" +#include "base/fuchsia/scoped_service_binding.h" +#include "base/fuchsia/test_component_context_for_process.h" +#include "base/run_loop.h" +#include "base/test/bind.h" +#include "base/test/task_environment.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace ui { +namespace { + +using fuchsia::accessibility::semantics::Node; + +class AXFuchsiaSemanticProviderDelegate + : public AXFuchsiaSemanticProvider::Delegate { + public: + AXFuchsiaSemanticProviderDelegate() = default; + ~AXFuchsiaSemanticProviderDelegate() override = default; + + bool OnSemanticsManagerConnectionClosed() override { + on_semantics_manager_connectionClosed_called_ = true; + return true; + } + + bool OnAccessibilityAction( + uint32_t node_id, + fuchsia::accessibility::semantics::Action action) override { + on_accessibility_action_called_ = true; + on_accessibility_action_node_id_ = node_id; + on_accessibility_action_action_ = std::move(action); + return true; + } + + void OnHitTest(fuchsia::math::PointF point, + AXFuchsiaSemanticProvider::HitTestCallback callback) override { + on_hit_test_called_ = true; + on_hit_test_point_ = std::move(point); + } + + void OnSemanticsEnabled(bool enabled) override { + on_semantics_enabled_called_ = true; + } + + bool on_semantics_manager_connectionClosed_called_; + bool on_accessibility_action_called_; + uint32_t on_accessibility_action_node_id_ = 10000000; + fuchsia::accessibility::semantics::Action on_accessibility_action_action_; + bool on_hit_test_called_; + fuchsia::math::PointF on_hit_test_point_; + bool on_semantics_enabled_called_; +}; + +// Returns a semantic tree of the form: +// (0 (1 2 (3 4 (5)))) +std::vector TreeNodes() { + Node node_0; + node_0.set_node_id(0u); + node_0.set_child_ids({1u, 2u}); + + Node node_1; + node_1.set_node_id(1u); + + Node node_2; + node_2.set_node_id(2u); + node_2.set_child_ids({3u, 4u}); + + Node node_3; + node_3.set_node_id(3u); + + Node node_4; + node_4.set_node_id(4u); + node_4.set_child_ids({5u}); + + Node node_5; + node_5.set_node_id(5u); + + std::vector update; + update.push_back(std::move(node_0)); + update.push_back(std::move(node_1)); + update.push_back(std::move(node_2)); + update.push_back(std::move(node_3)); + update.push_back(std::move(node_4)); + update.push_back(std::move(node_5)); + return update; +} + +class AXFuchsiaSemanticProviderTest + : public ::testing::Test, + public fuchsia::accessibility::semantics::SemanticsManager, + public fuchsia::accessibility::semantics::SemanticTree { + public: + AXFuchsiaSemanticProviderTest() + : semantics_manager_bindings_(test_context_.additional_services(), this), + semantic_tree_binding_(this) {} + ~AXFuchsiaSemanticProviderTest() override = default; + AXFuchsiaSemanticProviderTest(const AXFuchsiaSemanticProviderTest&) = delete; + AXFuchsiaSemanticProviderTest& operator=( + const AXFuchsiaSemanticProviderTest&) = delete; + void SetUp() override { + auto view_ref_pair = scenic::ViewRefPair::New(); + delegate_ = std::make_unique(); + + semantic_provider_ = std::make_unique( + std::move(view_ref_pair.view_ref), 2.0f, delegate_.get()); + } + + protected: + // fuchsia::accessibility::semantics::SemanticsManager implementation. + void RegisterViewForSemantics( + fuchsia::ui::views::ViewRef view_ref, + fidl::InterfaceHandle + listener, + fidl::InterfaceRequest + semantic_tree_request) final { + semantic_listener_ = listener.Bind(); + semantic_tree_binding_.Bind(std::move(semantic_tree_request)); + } + + // fuchsia::accessibility::semantics::SemanticTree implementation. + void UpdateSemanticNodes( + std::vector nodes) final { + num_update_semantic_nodes_called_++; + } + void DeleteSemanticNodes(std::vector node_ids) final { + num_delete_semantic_nodes_called_++; + } + void CommitUpdates(CommitUpdatesCallback callback) final { callback(); } + void SendSemanticEvent( + fuchsia::accessibility::semantics::SemanticEvent semantic_event, + SendSemanticEventCallback callback) override { + callback(); + } + + // Required because of |test_context_|. + base::test::SingleThreadTaskEnvironment task_environment_{ + base::test::SingleThreadTaskEnvironment::MainThreadType::IO}; + base::TestComponentContextForProcess test_context_; + // Binding to fake Semantics Manager Fuchsia service, implemented by this test + // class. + base::ScopedServiceBinding< + fuchsia::accessibility::semantics::SemanticsManager> + semantics_manager_bindings_; + + uint32_t num_update_semantic_nodes_called_ = 0; + uint32_t num_delete_semantic_nodes_called_ = 0; + + base::RepeatingClosure on_commit_; + + fuchsia::accessibility::semantics::SemanticListenerPtr semantic_listener_; + fidl::Binding + semantic_tree_binding_; + std::unique_ptr delegate_; + std::unique_ptr semantic_provider_; +}; + +TEST_F(AXFuchsiaSemanticProviderTest, HandlesOnSemanticsConnectionClosed) { + base::RunLoop loop; + loop.RunUntilIdle(); + semantic_tree_binding_.Close(ZX_ERR_PEER_CLOSED); + loop.RunUntilIdle(); + EXPECT_TRUE(delegate_->on_semantics_manager_connectionClosed_called_); +} + +TEST_F(AXFuchsiaSemanticProviderTest, HandlesOnAccessibilityAction) { + base::RunLoop loop; + loop.RunUntilIdle(); + bool action_handled = false; + semantic_listener_->OnAccessibilityActionRequested( + /*node_id=*/1u, fuchsia::accessibility::semantics::Action::DEFAULT, + [&action_handled](bool handled) { action_handled = handled; }); + loop.RunUntilIdle(); + EXPECT_TRUE(action_handled); + EXPECT_TRUE(delegate_->on_accessibility_action_called_); + EXPECT_EQ(delegate_->on_accessibility_action_node_id_, 1u); + EXPECT_EQ(delegate_->on_accessibility_action_action_, + fuchsia::accessibility::semantics::Action::DEFAULT); +} + +TEST_F(AXFuchsiaSemanticProviderTest, HandlesOnHitTest) { + base::RunLoop loop; + loop.RunUntilIdle(); + + // Note that the point is sent here and will be converted according to the + // device scale used. Only then it gets sent to the handler, which receives + // the value already with the proper scaling. + fuchsia::math::PointF point; + point.x = 4; + point.y = 6; + semantic_listener_->HitTest(std::move(point), [](auto...) {}); + loop.RunUntilIdle(); + EXPECT_TRUE(delegate_->on_hit_test_called_); + EXPECT_EQ(delegate_->on_hit_test_point_.x, 8.0); + EXPECT_EQ(delegate_->on_hit_test_point_.y, 12.0); +} + +TEST_F(AXFuchsiaSemanticProviderTest, HandlesOnSemanticsEnabled) { + base::RunLoop loop; + loop.RunUntilIdle(); + semantic_listener_->OnSemanticsModeChanged(false, [](auto...) {}); + loop.RunUntilIdle(); + EXPECT_TRUE(delegate_->on_semantics_enabled_called_); +} + +TEST_F(AXFuchsiaSemanticProviderTest, SendsRootOnly) { + base::RunLoop loop; + loop.RunUntilIdle(); + Node root; + root.set_node_id(0u); + EXPECT_TRUE(semantic_provider_->Update(std::move(root))); + loop.RunUntilIdle(); + EXPECT_EQ(num_update_semantic_nodes_called_, 1u); + EXPECT_FALSE(semantic_provider_->HasPendingUpdates()); +} + +TEST_F(AXFuchsiaSemanticProviderTest, SendsNodesFromRootToLeaves) { + base::RunLoop loop; + loop.RunUntilIdle(); + auto tree_nodes = TreeNodes(); + for (auto& node : tree_nodes) { + EXPECT_TRUE(semantic_provider_->Update(std::move(node))); + } + loop.RunUntilIdle(); + EXPECT_EQ(num_update_semantic_nodes_called_, 1u); + EXPECT_FALSE(semantic_provider_->HasPendingUpdates()); +} + +TEST_F(AXFuchsiaSemanticProviderTest, SendsNodesFromLeavesToRoot) { + base::RunLoop loop; + loop.RunUntilIdle(); + auto nodes = TreeNodes(); + std::reverse(nodes.begin(), nodes.end()); + for (auto& node : nodes) { + EXPECT_TRUE(semantic_provider_->Update(std::move(node))); + } + loop.RunUntilIdle(); + EXPECT_EQ(num_update_semantic_nodes_called_, 1u); + + EXPECT_FALSE(semantic_provider_->HasPendingUpdates()); +} + +TEST_F(AXFuchsiaSemanticProviderTest, + SendsNodesOnlyAfterParentNoLongerPointsToDeletedChild) { + base::RunLoop loop; + loop.RunUntilIdle(); + auto tree_nodes = TreeNodes(); + for (auto& node : tree_nodes) { + EXPECT_TRUE(semantic_provider_->Update(std::move(node))); + } + loop.RunUntilIdle(); + EXPECT_EQ(num_update_semantic_nodes_called_, 1u); + EXPECT_FALSE(semantic_provider_->HasPendingUpdates()); + + // Deletes node 5, which is a child of 4. + EXPECT_TRUE(semantic_provider_->Delete(5u)); + loop.RunUntilIdle(); + + // Commit is pending, because the parent still points to the child. + EXPECT_TRUE(semantic_provider_->HasPendingUpdates()); + + Node node_4; + node_4.set_node_id(4u); + node_4.set_child_ids({}); + EXPECT_TRUE(semantic_provider_->Update(std::move(node_4))); + loop.RunUntilIdle(); + EXPECT_EQ(num_update_semantic_nodes_called_, 2u); + EXPECT_EQ(num_delete_semantic_nodes_called_, 1u); + + EXPECT_FALSE(semantic_provider_->HasPendingUpdates()); +} + +TEST_F(AXFuchsiaSemanticProviderTest, + SendsNodesOnlyAfterDanglingChildIsDeleted) { + base::RunLoop loop; + loop.RunUntilIdle(); + auto tree_nodes = TreeNodes(); + for (auto& node : tree_nodes) { + EXPECT_TRUE(semantic_provider_->Update(std::move(node))); + } + loop.RunUntilIdle(); + EXPECT_EQ(num_update_semantic_nodes_called_, 1u); + EXPECT_FALSE(semantic_provider_->HasPendingUpdates()); + + Node node_4; + node_4.set_node_id(4u); + node_4.set_child_ids({}); // This removes child 5. + EXPECT_TRUE(semantic_provider_->Update(std::move(node_4))); + loop.RunUntilIdle(); + EXPECT_TRUE(semantic_provider_->HasPendingUpdates()); + + EXPECT_TRUE(semantic_provider_->Delete(5u)); + loop.RunUntilIdle(); + EXPECT_EQ(num_update_semantic_nodes_called_, 2u); + EXPECT_EQ(num_delete_semantic_nodes_called_, 1u); + EXPECT_FALSE(semantic_provider_->HasPendingUpdates()); +} + +TEST_F(AXFuchsiaSemanticProviderTest, ReparentsNodeWithADeletion) { + base::RunLoop loop; + loop.RunUntilIdle(); + auto tree_nodes = TreeNodes(); + for (auto& node : tree_nodes) { + EXPECT_TRUE(semantic_provider_->Update(std::move(node))); + } + loop.RunUntilIdle(); + EXPECT_EQ(num_update_semantic_nodes_called_, 1u); + EXPECT_FALSE(semantic_provider_->HasPendingUpdates()); + + // Deletes node 4 to reparent its child (5). + EXPECT_TRUE(semantic_provider_->Delete(4u)); + loop.RunUntilIdle(); + EXPECT_TRUE(semantic_provider_->HasPendingUpdates()); + + // Add child 5 to another node. + Node node_1; + node_1.set_node_id(1u); + node_1.set_child_ids({5u}); + EXPECT_TRUE(semantic_provider_->Update(std::move(node_1))); + loop.RunUntilIdle(); + EXPECT_TRUE(semantic_provider_->HasPendingUpdates()); + + Node node_4; + node_4.set_node_id(4u); + node_4.set_child_ids({}); + EXPECT_TRUE(semantic_provider_->Update(std::move(node_4))); + loop.RunUntilIdle(); + + EXPECT_EQ(num_update_semantic_nodes_called_, 2u); + EXPECT_EQ(num_delete_semantic_nodes_called_, 1u); + EXPECT_FALSE(semantic_provider_->HasPendingUpdates()); +} + +TEST_F(AXFuchsiaSemanticProviderTest, ReparentsNodeWithAnUpdate) { + base::RunLoop loop; + loop.RunUntilIdle(); + auto tree_nodes = TreeNodes(); + for (auto& node : tree_nodes) { + EXPECT_TRUE(semantic_provider_->Update(std::move(node))); + } + loop.RunUntilIdle(); + EXPECT_EQ(num_update_semantic_nodes_called_, 1u); + EXPECT_FALSE(semantic_provider_->HasPendingUpdates()); + + // Add child 5 to another node. Note that 5 will have two parents, and the + // commit must be held until it has only one. + Node node_1; + node_1.set_node_id(1u); + node_1.set_child_ids({5u}); + EXPECT_TRUE(semantic_provider_->Update(std::move(node_1))); + loop.RunUntilIdle(); + EXPECT_TRUE(semantic_provider_->HasPendingUpdates()); + + // Updates node 4 to no longer point to 5. + Node node_4; + node_4.set_node_id(4u); + node_4.set_child_ids({}); + EXPECT_TRUE(semantic_provider_->Update(std::move(node_4))); + loop.RunUntilIdle(); + + EXPECT_EQ(num_update_semantic_nodes_called_, 2u); + EXPECT_EQ(num_delete_semantic_nodes_called_, 0u); + EXPECT_FALSE(semantic_provider_->HasPendingUpdates()); +} + +TEST_F(AXFuchsiaSemanticProviderTest, ChangesRoot) { + base::RunLoop loop; + loop.RunUntilIdle(); + auto tree_nodes = TreeNodes(); + for (auto& node : tree_nodes) { + EXPECT_TRUE(semantic_provider_->Update(std::move(node))); + } + loop.RunUntilIdle(); + EXPECT_EQ(num_update_semantic_nodes_called_, 1u); + EXPECT_FALSE(semantic_provider_->HasPendingUpdates()); + + Node new_root; + new_root.set_node_id(0u); + new_root.set_child_ids({1u, 2u}); + EXPECT_TRUE(semantic_provider_->Update(std::move(new_root))); + loop.RunUntilIdle(); + EXPECT_EQ(num_update_semantic_nodes_called_, 2u); + EXPECT_EQ(num_delete_semantic_nodes_called_, 0u); + EXPECT_FALSE(semantic_provider_->HasPendingUpdates()); +} + +TEST_F(AXFuchsiaSemanticProviderTest, BatchesUpdates) { + base::RunLoop loop; + loop.RunUntilIdle(); + std::vector updates; + for (uint32_t i = 0; i < 30; ++i) { + Node node; + node.set_node_id(i); + node.set_child_ids({i + 1}); + updates.push_back(std::move(node)); + } + updates.back().clear_child_ids(); + + for (auto& node : updates) { + EXPECT_TRUE(semantic_provider_->Update(std::move(node))); + } + loop.RunUntilIdle(); + + // 30 nodes in batches of 16 (default value of maximum nodes per update call), + // should result in two update calls to the semantics API. + EXPECT_EQ(num_update_semantic_nodes_called_, 2u); + EXPECT_FALSE(semantic_provider_->HasPendingUpdates()); +} + +TEST_F(AXFuchsiaSemanticProviderTest, ClearsTree) { + base::RunLoop loop; + loop.RunUntilIdle(); + auto tree_nodes = TreeNodes(); + for (auto& node : tree_nodes) { + EXPECT_TRUE(semantic_provider_->Update(std::move(node))); + } + loop.RunUntilIdle(); + EXPECT_EQ(num_update_semantic_nodes_called_, 1u); + EXPECT_FALSE(semantic_provider_->HasPendingUpdates()); + + semantic_provider_->Clear(); + loop.RunUntilIdle(); + EXPECT_EQ(num_update_semantic_nodes_called_, 1u); + EXPECT_EQ(num_delete_semantic_nodes_called_, 1u); + EXPECT_FALSE(semantic_provider_->HasPendingUpdates()); +} + +} // namespace +} // namespace ui \ No newline at end of file From 6e6892246dde4e18bffe833adfd639c501f68fb2 Mon Sep 17 00:00:00 2001 From: chromium-autoroll Date: Tue, 14 Dec 2021 23:52:58 +0000 Subject: [PATCH 07/77] Roll ANGLE from fefd7ae66ad9 to 36fcf80b1f2a (5 revisions) https://chromium.googlesource.com/angle/angle.git/+log/fefd7ae66ad9..36fcf80b1f2a 2021-12-14 m.maiya@samsung.com Vulkan: Consolidate SamplerYcbcrConversionCache 2021-12-14 jmadill@chromium.org Upgrade restricted traces to new trace format. 2021-12-14 hailinzhang@google.com Vulkan: Fix dynamic partial update buffer data issue. 2021-12-14 penghuang@chromium.org Support creating EGLImage from VkImage 2021-12-14 jmadill@chromium.org Capture/Replay: Update process for trace upgrading. If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/angle-chromium-autoroll Please CC timvp@google.com on the revert to ensure that a human is aware of the problem. To file a bug in ANGLE: https://bugs.chromium.org/p/angleproject/issues/entry To file a bug in Chromium: https://bugs.chromium.org/p/chromium/issues/entry To report a problem with the AutoRoller itself, please file a bug: https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md Cq-Include-Trybots: luci.chromium.try:android_optional_gpu_tests_rel;luci.chromium.try:linux_optional_gpu_tests_rel;luci.chromium.try:mac_optional_gpu_tests_rel;luci.chromium.try:win_optional_gpu_tests_rel;luci.chromium.try:linux-swangle-try-x64;luci.chromium.try:win-swangle-try-x86 Bug: chromium:1264439 Tbr: timvp@google.com Test: Test: Texture2DTestES3.*Yuv*Vulkan Change-Id: I0caefd8d58db53a43fab7b555119a94b3f76ce12 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3339766 Commit-Queue: chromium-autoroll Bot-Commit: chromium-autoroll Cr-Commit-Position: refs/heads/main@{#951730} --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index f0f3e70557eb67..364f73d6d738d8 100644 --- a/DEPS +++ b/DEPS @@ -247,7 +247,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling ANGLE # and whatever else without interference from each other. - 'angle_revision': 'fefd7ae66ad94d8fcb16ef9f5f7304c1b4b9fbf8', + 'angle_revision': '36fcf80b1f2a99fdaa46d044994dfe96a08d7362', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling SwiftShader # and whatever else without interference from each other. From 207210ee3859429266455e83ac11d8cb7ad6aa2c Mon Sep 17 00:00:00 2001 From: Sinan Sahin Date: Tue, 14 Dec 2021 23:53:19 +0000 Subject: [PATCH 08/77] [GMNext] Make primary inverse text colors dynamic Bug: 1233703, 1241971 Change-Id: I48f754855b6ae462637daf152b279707a0ff3039 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3331374 Reviewed-by: Sky Malice Reviewed-by: Theresa Sullivan Commit-Queue: Sinan Sahin Cr-Commit-Position: refs/heads/main@{#951731} --- .../ui/messages/snackbar/SnackbarView.java | 2 +- components/browser_ui/styles/android/BUILD.gn | 1 + .../default_text_color_on_accent1_list.xml | 10 ++++++++++ .../res/values/semantic_colors_dynamic.xml | 2 ++ .../styles/android/java/res/values/styles.xml | 15 +++++++++++++++ .../java/res/layout/textbubble_text.xml | 2 +- .../res/layout/textbubble_text_with_image.xml | 2 +- ui/android/BUILD.gn | 2 +- ...t_text_color_on_accent1_baseline_list.xml} | 4 ++-- ui/android/java/res/values-night/colors.xml | 4 ++-- ui/android/java/res/values-v17/styles.xml | 19 ++----------------- .../res/values/semantic_colors_adaptive.xml | 4 ++-- .../ui/widget/TextViewWithTightWrapTest.java | 4 ++-- 13 files changed, 42 insertions(+), 29 deletions(-) create mode 100644 components/browser_ui/styles/android/java/res/color/default_text_color_on_accent1_list.xml rename ui/android/java/res/color/{default_text_color_on_accent1_list.xml => default_text_color_on_accent1_baseline_list.xml} (74%) diff --git a/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/SnackbarView.java b/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/SnackbarView.java index 99d3736fc53cd9..d01675cf2340ed 100644 --- a/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/SnackbarView.java +++ b/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/SnackbarView.java @@ -268,7 +268,7 @@ private static int getBackgroundColor(View view, Snackbar snackbar) { private static int getTextAppearance(Snackbar snackbar) { if (snackbar.getTheme() == Snackbar.Theme.GOOGLE) { - return R.style.TextAppearance_TextMedium_Primary_Baseline_Inverse; + return R.style.TextAppearance_TextMedium_Primary_OnAccent1; } assert snackbar.getTheme() == Snackbar.Theme.BASIC; diff --git a/components/browser_ui/styles/android/BUILD.gn b/components/browser_ui/styles/android/BUILD.gn index 5b52da64e2d529..a8dcf9e4063068 100644 --- a/components/browser_ui/styles/android/BUILD.gn +++ b/components/browser_ui/styles/android/BUILD.gn @@ -33,6 +33,7 @@ android_resources("java_resources") { "java/res/color/default_text_color_hint_list.xml", "java/res/color/default_text_color_link_tint_list.xml", "java/res/color/default_text_color_list.xml", + "java/res/color/default_text_color_on_accent1_list.xml", "java/res/color/default_text_color_secondary_list.xml", "java/res/color/progress_bar_bg_color.xml", "java/res/color/selection_control_button_tint.xml", diff --git a/components/browser_ui/styles/android/java/res/color/default_text_color_on_accent1_list.xml b/components/browser_ui/styles/android/java/res/color/default_text_color_on_accent1_list.xml new file mode 100644 index 00000000000000..3cc6de85b59a65 --- /dev/null +++ b/components/browser_ui/styles/android/java/res/color/default_text_color_on_accent1_list.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/components/browser_ui/styles/android/java/res/values/semantic_colors_dynamic.xml b/components/browser_ui/styles/android/java/res/values/semantic_colors_dynamic.xml index fa0db4de1c55fe..4c1c5bfa36a554 100644 --- a/components/browser_ui/styles/android/java/res/values/semantic_colors_dynamic.xml +++ b/components/browser_ui/styles/android/java/res/values/semantic_colors_dynamic.xml @@ -13,6 +13,7 @@ ?attr/colorPrimary ?attr/colorOnSurface ?attr/colorPrimary + ?attr/colorOnPrimary ?attr/colorOutline ?attr/colorSurfaceVariant --> @@ -26,6 +27,7 @@ @color/default_icon_color_accent1_baseline @color/default_text_color_baseline @color/default_text_color_blue_baseline + @color/default_text_color_on_accent1_baseline @color/hairline_stroke_color_baseline @color/divider_line_bg_color_baseline diff --git a/components/browser_ui/styles/android/java/res/values/styles.xml b/components/browser_ui/styles/android/java/res/values/styles.xml index 9615ac9b111cb9..ab4e2a5b300019 100644 --- a/components/browser_ui/styles/android/java/res/values/styles.xml +++ b/components/browser_ui/styles/android/java/res/values/styles.xml @@ -273,6 +273,21 @@ @color/default_text_color_link_tint_list + + + + + + + - - - - - - - +
+ +
\ No newline at end of file diff --git a/chrome/browser/resources/access_code_cast/error_message/error_message.ts b/chrome/browser/resources/access_code_cast/error_message/error_message.ts new file mode 100644 index 00000000000000..09457057dc977c --- /dev/null +++ b/chrome/browser/resources/access_code_cast/error_message/error_message.ts @@ -0,0 +1,124 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'chrome://resources/cr_elements/icons.m.js'; +import 'chrome://resources/cr_elements/shared_vars_css.m.js'; +import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js'; + +import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +import {AddSinkResultCode} from '../access_code_cast.mojom-webui.js'; +import {RouteRequestResultCode} from '../route_request_result_code.mojom-webui.js'; + +enum ErrorMessage { + NO_ERROR, + GENERIC, + ACCESS_CODE, + NETWORK, + PERMISSION, + TOO_MANY_REQUESTS, +} + +export class ErrorMessageElement extends PolymerElement { + private static readonly ADD_RESULT_MESSAGE_CODES: + [ErrorMessage, AddSinkResultCode[]][] = [ + [ErrorMessage.NO_ERROR, [AddSinkResultCode.OK]], + [ErrorMessage.GENERIC, [ + AddSinkResultCode.UNKNOWN_ERROR, + AddSinkResultCode.SINK_CREATION_ERROR + ]], + [ErrorMessage.ACCESS_CODE, [ + AddSinkResultCode.INVALID_ACCESS_CODE, + AddSinkResultCode.ACCESS_CODE_NOT_FOUND + ]], + [ErrorMessage.NETWORK, [ + AddSinkResultCode.HTTP_RESPONSE_CODE_ERROR, + AddSinkResultCode.RESPONSE_MALFORMED, + AddSinkResultCode.EMPTY_RESPONSE, + AddSinkResultCode.SERVICE_NOT_PRESENT, + AddSinkResultCode.SERVER_ERROR + ]], + [ErrorMessage.PERMISSION, [AddSinkResultCode.AUTH_ERROR]], + [ErrorMessage.TOO_MANY_REQUESTS, [AddSinkResultCode.TOO_MANY_REQUESTS]], + ]; + + private static readonly CAST_RESULT_MESSAGE_CODES: + [ErrorMessage, RouteRequestResultCode[]][] = [ + [ErrorMessage.NO_ERROR, [RouteRequestResultCode.OK]], + [ErrorMessage.GENERIC, [ + RouteRequestResultCode.UNKNOWN_ERROR, + RouteRequestResultCode.INVALID_ORIGIN, + RouteRequestResultCode.OFF_THE_RECORD_MISMATCH, + RouteRequestResultCode.NO_SUPPORTED_PROVIDER, + RouteRequestResultCode.CANCELLED, + RouteRequestResultCode.ROUTE_ALREADY_EXISTS, + RouteRequestResultCode.DESKTOP_PICKER_FAILED, + RouteRequestResultCode.ROUTE_ALREADY_TERMINATED + ]], + [ErrorMessage.NETWORK, [ + RouteRequestResultCode.TIMED_OUT, + RouteRequestResultCode.ROUTE_NOT_FOUND, + RouteRequestResultCode.SINK_NOT_FOUND + ]], + ]; + + private static readonly ADD_RESULT_MESSAGE_MAP = + new Map(ErrorMessageElement.ADD_RESULT_MESSAGE_CODES); + + private static readonly CAST_RESULT_MESSAGE_MAP = + new Map(ErrorMessageElement.CAST_RESULT_MESSAGE_CODES); + + // Needed for Polymer data binding + private errorMessageEnum = ErrorMessage; + + static get is() { + return 'c2c-error-message'; + } + + static get template() { + return html`{__html_template__}`; + } + + private messageCode = ErrorMessage.NO_ERROR; + + setAddSinkError(resultCode: AddSinkResultCode) { + this.messageCode = this.findErrorMessage(resultCode, + ErrorMessageElement.ADD_RESULT_MESSAGE_MAP); + } + + setCastError(resultCode: RouteRequestResultCode) { + this.messageCode = this.findErrorMessage(resultCode, + ErrorMessageElement.CAST_RESULT_MESSAGE_MAP); + } + + setNoError() { + this.messageCode = ErrorMessage.NO_ERROR; + } + + getMessageCode() { + return this.messageCode; + } + + isEqual(a: ErrorMessage, b: ErrorMessage) { + return a === b; + } + + isNotEqual(a: ErrorMessage, b: ErrorMessage) { + return a !== b; + } + + private findErrorMessage( + resultCode: AddSinkResultCode|RouteRequestResultCode, + messageCodes: Map) { + for (const key of messageCodes.keys()) { + if (messageCodes.get(key)!.includes(resultCode)) { + return key; + } + } + + return ErrorMessage.NO_ERROR; + } +} + +customElements.define(ErrorMessageElement.is, ErrorMessageElement); \ No newline at end of file diff --git a/chrome/browser/ui/webui/access_code_cast/access_code_cast_ui.cc b/chrome/browser/ui/webui/access_code_cast/access_code_cast_ui.cc index 89677d05ad9b67..0a9d9ddd7b9965 100644 --- a/chrome/browser/ui/webui/access_code_cast/access_code_cast_ui.cc +++ b/chrome/browser/ui/webui/access_code_cast/access_code_cast_ui.cc @@ -118,6 +118,11 @@ AccessCodeCastUI::AccessCodeCastUI(content::WebUI* web_ui) {"cast", IDS_ACCESS_CODE_CAST_CAST}, {"close", IDS_CLOSE}, {"dialogTitle", IDS_ACCESS_CODE_CAST_DIALOG_TITLE}, + {"errorAccessCode", IDS_ACCESS_CODE_CAST_ERROR_ACCESS_CODE}, + {"errorNetwork", IDS_ACCESS_CODE_CAST_ERROR_NETWORK}, + {"errorPermission", IDS_ACCESS_CODE_CAST_ERROR_PERMISSION}, + {"errorTooManyRequests", IDS_ACCESS_CODE_CAST_ERROR_TOO_MANY_REQUESTS}, + {"errorUnknown", IDS_ACCESS_CODE_CAST_ERROR_UNKNOWN}, {"useCamera", IDS_ACCESS_CODE_CAST_USE_CAMERA}, }; diff --git a/chrome/test/data/webui/access_code_cast/access_code_cast_browsertest.js b/chrome/test/data/webui/access_code_cast/access_code_cast_browsertest.js index 8af144d1ba2ec2..54181b87f722ef 100644 --- a/chrome/test/data/webui/access_code_cast/access_code_cast_browsertest.js +++ b/chrome/test/data/webui/access_code_cast/access_code_cast_browsertest.js @@ -59,3 +59,19 @@ var AccessCodeCastCodeInputElementTest = class extends AccessCodeCastBrowserTest TEST_F('AccessCodeCastCodeInputElementTest', 'All', function() { mocha.run(); }); + +// eslint-disable-next-line no-var +var AccessCodeCastErrorMessageElementTest = class extends AccessCodeCastBrowserTest { + /** @override */ + get browsePreload() { + return 'chrome://access-code-cast/test_loader.html?module=access_code_cast/error_message_test.js'; + } +}; + +/** + * This browsertest acts as a thin wrapper to run the unit tests found + * at code_input_test.js + */ +TEST_F('AccessCodeCastErrorMessageElementTest', 'All', function() { + mocha.run(); +}); diff --git a/chrome/test/data/webui/access_code_cast/access_code_cast_test.js b/chrome/test/data/webui/access_code_cast/access_code_cast_test.js index 354d539980ee89..9dbb1b765c30a1 100644 --- a/chrome/test/data/webui/access_code_cast/access_code_cast_test.js +++ b/chrome/test/data/webui/access_code_cast/access_code_cast_test.js @@ -162,4 +162,58 @@ suite('AccessCodeCastAppTest', () => { assertFalse(visited); } ); + + test('addSinkAndCast surfaces errors', async () => { + let testProxy = createTestProxy( + AddSinkResultCode.UNKNOWN_ERROR, + RouteRequestResultCode.OK, + () => {} + ); + BrowserProxy.setInstance(testProxy); + app.setAccessCodeForTest('qwerty'); + + assertEquals(0, app.$.errorMessage.getMessageCode()); + await app.addSinkAndCast(); + assertEquals(1, app.$.errorMessage.getMessageCode()); + + testProxy = createTestProxy( + AddSinkResultCode.INVALID_ACCESS_CODE, + RouteRequestResultCode.OK, + () => {} + ); + BrowserProxy.setInstance(testProxy); + + await app.addSinkAndCast(); + assertEquals(2, app.$.errorMessage.getMessageCode()); + + testProxy = createTestProxy( + AddSinkResultCode.SERVICE_NOT_PRESENT, + RouteRequestResultCode.OK, + () => {} + ); + BrowserProxy.setInstance(testProxy); + + await app.addSinkAndCast(); + assertEquals(3, app.$.errorMessage.getMessageCode()); + + testProxy = createTestProxy( + AddSinkResultCode.AUTH_ERROR, + RouteRequestResultCode.OK, + () => {} + ); + BrowserProxy.setInstance(testProxy); + + await app.addSinkAndCast(); + assertEquals(4, app.$.errorMessage.getMessageCode()); + + testProxy = createTestProxy( + AddSinkResultCode.TOO_MANY_REQUESTS, + RouteRequestResultCode.OK, + () => {} + ); + BrowserProxy.setInstance(testProxy); + + await app.addSinkAndCast(); + assertEquals(5, app.$.errorMessage.getMessageCode()); + }); }); diff --git a/chrome/test/data/webui/access_code_cast/error_message_test.js b/chrome/test/data/webui/access_code_cast/error_message_test.js new file mode 100644 index 00000000000000..44c34059560d16 --- /dev/null +++ b/chrome/test/data/webui/access_code_cast/error_message_test.js @@ -0,0 +1,88 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'chrome://access-code-cast/error_message/error_message.js'; + +import {AddSinkResultCode} from 'chrome://access-code-cast/access_code_cast.mojom-webui.js'; +import {RouteRequestResultCode} from 'chrome://access-code-cast/route_request_result_code.mojom-webui.js'; + +suite('ErrorMessageElementTest', () => { + /** @type {!ErrorMessageElement} */ + let c2cErrorMessage; + + setup(() => { + PolymerTest.clearBody(); + + c2cErrorMessage = document.createElement('c2c-error-message'); + document.body.appendChild(c2cErrorMessage); + }); + + test('setAddSinkError', () => { + c2cErrorMessage.setNoError(); + + const testValues = [ + {addResult: AddSinkResultCode.UNKNOWN_ERROR, expectedMessage: 1}, + {addResult: AddSinkResultCode.OK, expectedMessage: 0}, + {addResult: AddSinkResultCode.AUTH_ERROR, expectedMessage: 4}, + { + addResult: AddSinkResultCode.HTTP_RESPONSE_CODE_ERROR, + expectedMessage: 3 + }, + {addResult: AddSinkResultCode.RESPONSE_MALFORMED, expectedMessage: 3}, + {addResult: AddSinkResultCode.EMPTY_RESPONSE, expectedMessage: 3}, + {addResult: AddSinkResultCode.INVALID_ACCESS_CODE, expectedMessage: 2}, + {addResult: AddSinkResultCode.ACCESS_CODE_NOT_FOUND, expectedMessage: 2}, + {addResult: AddSinkResultCode.TOO_MANY_REQUESTS, expectedMessage: 5}, + {addResult: AddSinkResultCode.SERVICE_NOT_PRESENT, expectedMessage: 3}, + {addResult: AddSinkResultCode.SERVER_ERROR, expectedMessage: 3}, + {addResult: AddSinkResultCode.SINK_CREATION_ERROR, expectedMessage: 1}, + ]; + + for (let i = 0; i < testValues.length; i++) { + c2cErrorMessage.setAddSinkError(testValues[i].addResult); + assertEquals(testValues[i].expectedMessage, c2cErrorMessage.messageCode); + c2cErrorMessage.setNoError(); + } + }); + + test('setCastError', () => { + c2cErrorMessage.setNoError(); + + const testValues = [ + {castResult: RouteRequestResultCode.UNKNOWN_ERROR, expectedMessage: 1}, + {castResult: RouteRequestResultCode.OK, expectedMessage: 0}, + {castResult: RouteRequestResultCode.TIMED_OUT, expectedMessage: 3}, + {castResult: RouteRequestResultCode.ROUTE_NOT_FOUND, expectedMessage: 3}, + {castResult: RouteRequestResultCode.SINK_NOT_FOUND, expectedMessage: 3}, + {castResult: RouteRequestResultCode.INVALID_ORIGIN, expectedMessage: 1}, + { + castResult: RouteRequestResultCode.OFF_THE_RECORD_MISMATCH, + expectedMessage: 1 + }, + { + castResult: RouteRequestResultCode.NO_SUPPORTED_PROVIDER, + expectedMessage: 1 + }, + {castResult: RouteRequestResultCode.CANCELLED, expectedMessage: 1}, + { + castResult: RouteRequestResultCode.ROUTE_ALREADY_EXISTS, + expectedMessage: 1 + }, + { + castResult: RouteRequestResultCode.DESKTOP_PICKER_FAILED, + expectedMessage: 1 + }, + { + castResult: RouteRequestResultCode.ROUTE_ALREADY_TERMINATED, + expectedMessage: 1 + }, + ]; + + for (let i = 0; i < testValues.length; i++) { + c2cErrorMessage.setCastError(testValues[i].castResult); + assertEquals(testValues[i].expectedMessage, c2cErrorMessage.messageCode); + c2cErrorMessage.setNoError(); + } + }); +}); \ No newline at end of file From 59edb308ff9b8b64599a969b7f0910b408b13fe6 Mon Sep 17 00:00:00 2001 From: chromium-autoroll Date: Wed, 15 Dec 2021 00:42:57 +0000 Subject: [PATCH 24/77] Roll Skia from 68e240d9cdb3 to fde20db7cab7 (3 revisions) https://skia.googlesource.com/skia.git/+log/68e240d9cdb3..fde20db7cab7 2021-12-14 brianosman@google.com Rename SkSL's 2D cross product builtin function 2021-12-14 johnstiles@google.com Add new SkVM trace op 'trace_scope' to track SkSL scope depth. 2021-12-14 egdaniel@google.com Remove workaround for not executing no op discard OpsTask. If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-autoroll Please CC bungeman@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Chromium: https://bugs.chromium.org/p/chromium/issues/entry To report a problem with the AutoRoller itself, please file a bug: https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md Cq-Include-Trybots: luci.chromium.try:android_optional_gpu_tests_rel;luci.chromium.try:linux-blink-rel;luci.chromium.try:linux-chromeos-compile-dbg;luci.chromium.try:linux_optional_gpu_tests_rel;luci.chromium.try:mac_optional_gpu_tests_rel;luci.chromium.try:win_optional_gpu_tests_rel Cq-Do-Not-Cancel-Tryjobs: true Bug: chromium:1279794 Tbr: bungeman@google.com Change-Id: Ib6bd07556a219773df3c65e84e9c2646c284bcf1 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3339360 Commit-Queue: chromium-autoroll Bot-Commit: chromium-autoroll Cr-Commit-Position: refs/heads/main@{#951747} --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 364f73d6d738d8..7091606083c90b 100644 --- a/DEPS +++ b/DEPS @@ -239,7 +239,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Skia # and whatever else without interference from each other. - 'skia_revision': '68e240d9cdb3f09fc0c9de834aaef04aa3b3f579', + 'skia_revision': 'fde20db7cab795e979d0ef92a91cfffdd761bbda', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. From d167b9a0e2e421294a4416aa57c947f4397dd880 Mon Sep 17 00:00:00 2001 From: Lingqi Chi Date: Wed, 15 Dec 2021 00:44:57 +0000 Subject: [PATCH 25/77] Prerender: Break kEmbedderTriggeredAndCrossOriginRedirected down After I81488239162aea19e4c783461b78d377e14afa1a, which broke kEmbedderTriggeredAndRedirected, we find that many cancellation cases were caused by kEmbedderTriggeredAndCrossOriginRedirected. So this CL continues breaking the metrics down by analyzing the cross-origin redirection cases. Bug: 1274468 Change-Id: I494dd2ed7346963c2c008740b7966de89ad9b90c Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3314413 Reviewed-by: Asami Doi Reviewed-by: Takashi Toyoshima Commit-Queue: Lingqi Chi Cr-Commit-Position: refs/heads/main@{#951748} --- .../prerender/prerender_browsertest.cc | 238 ++++++++++++++++-- .../browser/prerender/prerender_metrics.cc | 37 +++ content/browser/prerender/prerender_metrics.h | 52 ++++ .../prerender_navigation_throttle.cc | 73 +++++- tools/metrics/histograms/enums.xml | 22 ++ .../metadata/navigation/histograms.xml | 85 ++++++- .../histograms/metadata/page/histograms.xml | 67 +++-- 7 files changed, 511 insertions(+), 63 deletions(-) diff --git a/content/browser/prerender/prerender_browsertest.cc b/content/browser/prerender/prerender_browsertest.cc index f559cb388d819c..cad0904cfd8feb 100644 --- a/content/browser/prerender/prerender_browsertest.cc +++ b/content/browser/prerender/prerender_browsertest.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include + #include "base/barrier_closure.h" #include "base/base_switches.h" #include "base/callback_helpers.h" @@ -74,6 +76,7 @@ #include "mojo/public/cpp/bindings/remote_set.h" #include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/controllable_http_response.h" +#include "net/test/embedded_test_server/default_handlers.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "net/test/embedded_test_server/http_request.h" #include "net/test/embedded_test_server/http_response.h" @@ -89,6 +92,7 @@ #include "ui/shell_dialogs/select_file_dialog.h" #include "ui/shell_dialogs/select_file_dialog_factory.h" #include "url/gurl.h" +#include "url/url_constants.h" #if BUILDFLAG(ENABLE_PLUGINS) #include "content/common/pepper_plugin.mojom.h" @@ -4456,9 +4460,8 @@ IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, SkipCrossOriginPrerender) { PrerenderHost::FinalStatus::kCrossOriginNavigation, 1); } -IN_PROC_BROWSER_TEST_F( - PrerenderBrowserTest, - CancelEmbedderTriggeredPrerenderingSameOriginRedirection) { +IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, + EmbedderTrigger_SameOriginRedirection) { base::HistogramTester histogram_tester; const GURL kInitialUrl = GetUrl("/empty.html"); ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); @@ -4485,34 +4488,229 @@ IN_PROC_BROWSER_TEST_F( PrerenderHost::FinalStatus::kEmbedderTriggeredAndSameOriginRedirected, 1); } -IN_PROC_BROWSER_TEST_F( - PrerenderBrowserTest, - CancelEmbedderTriggeredPrerenderingCrossOriginRedirection) { - base::HistogramTester histogram_tester; - const GURL kInitialUrl = GetUrl("/empty.html"); - ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); - const GURL kRedirectedUrl = GetCrossOriginUrl("/empty.html?prerender"); - const GURL kPrerenderingUrl = - GetUrl("/server-redirect?" + kRedirectedUrl.spec()); - - RedirectChainObserver redirect_chain_observer(*shell()->web_contents(), - kRedirectedUrl); +void PrerenderEmbedderTriggeredCrossOriginRedirectionPage( + WebContentsImpl& web_contents, + const GURL& prerendering_url, + const GURL& cross_origin_url) { + RedirectChainObserver redirect_chain_observer{web_contents, cross_origin_url}; // Start prerendering by embedder triggered prerendering. std::unique_ptr prerender_handle = - web_contents_impl()->StartPrerendering(kPrerenderingUrl, - PrerenderTriggerType::kEmbedder, - "EmbedderSuffixForTest"); + web_contents.StartPrerendering(prerendering_url, + PrerenderTriggerType::kEmbedder, + "EmbedderSuffixForTest"); EXPECT_TRUE(prerender_handle); - test::PrerenderTestHelper::WaitForPrerenderLoadCompletion( - *shell()->web_contents(), kPrerenderingUrl); + test::PrerenderTestHelper::WaitForPrerenderLoadCompletion(web_contents, + prerendering_url); + ASSERT_EQ(2u, redirect_chain_observer.redirect_chain().size()); +} +void CheckExpectedCrossOriginMetrics( + const base::HistogramTester& histogram_tester, + PrerenderCrossOriginRedirectionMismatch mismatch_type, + absl::optional + protocol_change, + absl::optional domain_change) { histogram_tester.ExpectUniqueSample( "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_" "EmbedderSuffixForTest", PrerenderHost::FinalStatus::kEmbedderTriggeredAndCrossOriginRedirected, 1); + histogram_tester.ExpectUniqueSample( + "Prerender.Experimental.PrerenderCrossOriginRedirectionMismatch.Embedder_" + "EmbedderSuffixForTest", + mismatch_type, 1); + if (protocol_change.has_value()) { + histogram_tester.ExpectUniqueSample( + "Prerender.Experimental.CrossOriginRedirectionProtocolChange.Embedder_" + "EmbedderSuffixForTest", + protocol_change.value(), 1); + } + if (domain_change.has_value()) { + histogram_tester.ExpectUniqueSample( + "Prerender.Experimental.CrossOriginRedirectionDomain.Embedder_" + "EmbedderSuffixForTest", + domain_change.value(), 1); + } +} + +// Tests PrerenderCrossOriginRedirectionMismatch.kSchemeHostPortMismatch was +// recorded when a prerendering navigaton was redireted to another origin with +// different scheme, host and port. +IN_PROC_BROWSER_TEST_F( + PrerenderBrowserTest, + EmbedderTrigger_CrossOriginRedirection_SchemeHostPortMismatch) { + base::HistogramTester histogram_tester; + embedded_test_server()->AddDefaultHandlers(GetTestDataFilePath()); + ASSERT_TRUE(embedded_test_server()->Start()); + GURL initial_url = GetUrl("/empty.html"); + ASSERT_TRUE(NavigateToURL(shell(), initial_url)); + + // The redirected_url's origin completely differs from the prerendering one. + GURL redirected_url = embedded_test_server()->GetURL("b.test", "/empty.html"); + GURL prerendering_url = GetUrl("/server-redirect?" + redirected_url.spec()); + ASSERT_NE(prerendering_url.scheme(), redirected_url.scheme()); + ASSERT_NE(prerendering_url.host(), redirected_url.host()); + ASSERT_NE(prerendering_url.port(), redirected_url.port()); + + PrerenderEmbedderTriggeredCrossOriginRedirectionPage( + *web_contents_impl(), prerendering_url, redirected_url); + CheckExpectedCrossOriginMetrics( + histogram_tester, + PrerenderCrossOriginRedirectionMismatch::kSchemeHostPortMismatch, + /*protocol_change=*/absl::nullopt, /*domain_change=*/absl::nullopt); +} + +// Tests a prerendering navigaton goes with HTTP protocol, and being redirected +// to upgrade its protocol to HTTPS. +IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, + EmbedderTrigger_CrossOriginRedirection_ProtocolUpgrade) { + base::HistogramTester histogram_tester; + embedded_test_server()->AddDefaultHandlers(GetTestDataFilePath()); + ASSERT_TRUE(embedded_test_server()->Start()); + + GURL initial_url = embedded_test_server()->GetURL("a.test", "/empty.html"); + ASSERT_TRUE(NavigateToURL(shell(), initial_url)); + + // Redirect to another url with protocol upgraded. + GURL redirected_url = ssl_server().GetURL("a.test", "/empty.html"); + GURL prerendering_url = embedded_test_server()->GetURL( + "a.test", "/server-redirect?" + redirected_url.spec()); + ASSERT_NE(prerendering_url.scheme(), redirected_url.scheme()); + ASSERT_NE(prerendering_url.port(), redirected_url.port()); + ASSERT_EQ(prerendering_url.scheme(), url::kHttpScheme); + ASSERT_EQ(redirected_url.scheme(), url::kHttpsScheme); + + PrerenderEmbedderTriggeredCrossOriginRedirectionPage( + *web_contents_impl(), prerendering_url, redirected_url); + CheckExpectedCrossOriginMetrics( + histogram_tester, + PrerenderCrossOriginRedirectionMismatch::kSchemePortMismatch, + PrerenderCrossOriginRedirectionProtocolChange::kHttpProtocolUpgrade, + /*domain_change=*/absl::nullopt); +} + +// Similar to +// CancelEmbedderTriggeredPrerenderingCrossOriginRedirection_ProtocolUpgrade, +// tests a prerendering navigaton goes with HTTPS protocol, and being redirected +// to upgrade its protocol to HTTPS. +IN_PROC_BROWSER_TEST_F( + PrerenderBrowserTest, + EmbedderTrigger_CrossOriginRedirection_ProtocolDowngrade) { + base::HistogramTester histogram_tester; + GURL initial_url = GetUrl("/empty.html"); + ASSERT_TRUE(NavigateToURL(shell(), initial_url)); + + GURL::Replacements downgrade_protocol; + downgrade_protocol.SetSchemeStr(url::kHttpScheme); + std::string port_str(base::NumberToString(ssl_server().port() + 1)); + downgrade_protocol.SetPortStr(port_str); + ASSERT_TRUE(NavigateToURL(shell(), initial_url)); + + // Redirect to another url with protocol upgraded. + GURL redirected_url = + GetUrl("/empty.html").ReplaceComponents(downgrade_protocol); + GURL prerendering_url = GetUrl("/server-redirect?" + redirected_url.spec()); + ASSERT_NE(prerendering_url.scheme(), redirected_url.scheme()); + ASSERT_NE(prerendering_url.port(), redirected_url.port()); + ASSERT_EQ(prerendering_url.scheme(), url::kHttpsScheme); + ASSERT_EQ(redirected_url.scheme(), "http"); + + PrerenderEmbedderTriggeredCrossOriginRedirectionPage( + *web_contents_impl(), prerendering_url, redirected_url); + CheckExpectedCrossOriginMetrics( + histogram_tester, + PrerenderCrossOriginRedirectionMismatch::kSchemePortMismatch, + PrerenderCrossOriginRedirectionProtocolChange::kHttpProtocolDowngrade, + /*domain_change=*/absl::nullopt); +} + +// Tests PrerenderCrossOriginRedirectionMismatch.kHostMismatch and +// PrerenderCrossOriginRedirectionDomain.kRedirectToSubDomain are recorded +// when the prerendering navigation is redirected to a subdomain URL. +IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, + EmbedderTrigger_CrossOriginRedirection_ToSubdomain) { + base::HistogramTester histogram_tester; + GURL initial_url = GetUrl("/empty.html"); + ASSERT_TRUE(NavigateToURL(shell(), initial_url)); + + GURL::Replacements set_host; + set_host.SetHostStr("www.a.test"); + + GURL redirected_url = GetUrl("/empty.html").ReplaceComponents(set_host); + GURL prerendering_url = GetUrl("/server-redirect?" + redirected_url.spec()); + + PrerenderEmbedderTriggeredCrossOriginRedirectionPage( + *web_contents_impl(), prerendering_url, redirected_url); + CheckExpectedCrossOriginMetrics( + histogram_tester, PrerenderCrossOriginRedirectionMismatch::kHostMismatch, + /*protocol_change=*/absl::nullopt, + PrerenderCrossOriginRedirectionDomain::kRedirectToSubDomain); +} + +// Tests PrerenderCrossOriginRedirectionMismatch.kHostMismatch and +// PrerenderCrossOriginRedirectionDomain.kRedirectFromSubDomain are recorded +// when the prerendering navigation is redirected to a subdomain URL. +IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, + EmbedderTrigger_CrossOriginRedirection_FromSubdomain) { + base::HistogramTester histogram_tester; + GURL initial_url = GetUrl("/empty.html"); + ASSERT_TRUE(NavigateToURL(shell(), initial_url)); + + GURL::Replacements set_host; + set_host.SetHostStr("www.a.test"); + + GURL redirected_url = GetUrl("/empty.html"); + GURL prerendering_url = GetUrl("/server-redirect?" + redirected_url.spec()) + .ReplaceComponents(set_host); + + PrerenderEmbedderTriggeredCrossOriginRedirectionPage( + *web_contents_impl(), prerendering_url, redirected_url); + CheckExpectedCrossOriginMetrics( + histogram_tester, PrerenderCrossOriginRedirectionMismatch::kHostMismatch, + /*protocol_change=*/absl::nullopt, + PrerenderCrossOriginRedirectionDomain::kRedirectFromSubDomain); +} + +// Tests PrerenderCrossOriginRedirectionMismatch.kHostMismatch and +// PrerenderCrossOriginRedirectionDomain.kCrossDomain are recorded +// when the prerendering navigation is redirected to a different domain. +IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, + EmbedderTrigger_CrossOriginRedirection_DifferentDomain) { + base::HistogramTester histogram_tester; + GURL kInitialUrl = GetUrl("/empty.html"); + ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); + GURL kRedirectedUrl = GetCrossOriginUrl("/empty.html?prerender"); + GURL kPrerenderingUrl = GetUrl("/server-redirect?" + kRedirectedUrl.spec()); + PrerenderEmbedderTriggeredCrossOriginRedirectionPage( + *web_contents_impl(), kPrerenderingUrl, kRedirectedUrl); + CheckExpectedCrossOriginMetrics( + histogram_tester, PrerenderCrossOriginRedirectionMismatch::kHostMismatch, + /*protocol_change=*/absl::nullopt, + PrerenderCrossOriginRedirectionDomain::kCrossDomain); +} + +// Tests that a prerendering navigation is redirected to another origin whose +// port differs from the prerendering one. The prerender should be cancelled and +// `PrerenderCrossOriginRedirectionCase::kPortMismatch` should be recorded. +IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, + EmbedderTrigger_CrossOriginRedirection_PortChanged) { + base::HistogramTester histogram_tester; + GURL initial_url = GetUrl("/empty.html"); + ASSERT_TRUE(NavigateToURL(shell(), initial_url)); + + std::string port_str(base::NumberToString(ssl_server().port() + 1)); + GURL::Replacements set_port; + set_port.SetPortStr(port_str); + GURL redirected_url = initial_url.ReplaceComponents(set_port); + GURL prerendering_url = GetUrl("/server-redirect?" + redirected_url.spec()); + PrerenderEmbedderTriggeredCrossOriginRedirectionPage( + *web_contents_impl(), prerendering_url, redirected_url); + CheckExpectedCrossOriginMetrics( + histogram_tester, PrerenderCrossOriginRedirectionMismatch::kPortMismatch, + /*protocol_change=*/absl::nullopt, + /*domain_change=*/absl::nullopt); } namespace { diff --git a/content/browser/prerender/prerender_metrics.cc b/content/browser/prerender/prerender_metrics.cc index 828175e827f543..863837d3ae3612 100644 --- a/content/browser/prerender/prerender_metrics.cc +++ b/content/browser/prerender/prerender_metrics.cc @@ -7,6 +7,7 @@ #include "base/metrics/histogram_functions.h" #include "base/metrics/metrics_hashes.h" #include "content/browser/renderer_host/render_frame_host_impl.h" +#include "content/public/browser/prerender_trigger_type.h" #include "services/metrics/public/cpp/ukm_builders.h" #include "services/metrics/public/cpp/ukm_recorder.h" @@ -87,4 +88,40 @@ void RecordPrerenderHostFinalStatus( status); } +void RecordPrerenderRedirectionMismatchType( + PrerenderCrossOriginRedirectionMismatch mismatch_type, + PrerenderTriggerType trigger_type, + const std::string& embedder_histogram_suffix) { + DCHECK_EQ(trigger_type, PrerenderTriggerType::kEmbedder); + base::UmaHistogramEnumeration( + GenerateHistogramName( + "Prerender.Experimental.PrerenderCrossOriginRedirectionMismatch", + trigger_type, embedder_histogram_suffix), + mismatch_type); +} + +void RecordPrerenderRedirectionProtocolChange( + PrerenderCrossOriginRedirectionProtocolChange change_type, + PrerenderTriggerType trigger_type, + const std::string& embedder_histogram_suffix) { + DCHECK_EQ(trigger_type, PrerenderTriggerType::kEmbedder); + base::UmaHistogramEnumeration( + GenerateHistogramName( + "Prerender.Experimental.CrossOriginRedirectionProtocolChange", + trigger_type, embedder_histogram_suffix), + change_type); +} + +void RecordPrerenderRedirectionDomain( + PrerenderCrossOriginRedirectionDomain domain_type, + PrerenderTriggerType trigger_type, + const std::string& embedder_histogram_suffix) { + DCHECK_EQ(trigger_type, PrerenderTriggerType::kEmbedder); + base::UmaHistogramEnumeration( + GenerateHistogramName( + "Prerender.Experimental.CrossOriginRedirectionDomain", trigger_type, + embedder_histogram_suffix), + domain_type); +} + } // namespace content diff --git a/content/browser/prerender/prerender_metrics.h b/content/browser/prerender/prerender_metrics.h index 3a089f37e32aae..fc5b7ef3254c42 100644 --- a/content/browser/prerender/prerender_metrics.h +++ b/content/browser/prerender/prerender_metrics.h @@ -26,6 +26,40 @@ enum class PrerenderCancelledInterface { kMaxValue = kNotificationService }; +// Used by PrerenderNavigationThrottle, to track the cross-origin cancellation +// reason, and break it down into more cases. +// Do not modify this enum. +enum class PrerenderCrossOriginRedirectionMismatch { + kShouldNotBeReported = 0, + kPortMismatch = 1, + kHostMismatch = 2, + kHostPortMismatch = 3, + kSchemeMismatch = 4, + kSchemePortMismatch = 5, + kSchemeHostMismatch = 6, + kSchemeHostPortMismatch = 7, + kMaxValue = kSchemeHostPortMismatch +}; + +// Used by PrerenderNavigationThrottle. This is a breakdown enum for +// PrerenderCrossOriginRedirectionMismatch.kSchemePortMismatch. +// Do not modify this enum. +enum class PrerenderCrossOriginRedirectionProtocolChange { + kHttpProtocolUpgrade = 0, + kHttpProtocolDowngrade = 1, + kMaxValue = kHttpProtocolDowngrade +}; + +// Used by PrerenderNavigationThrottle. This is a breakdown enum for +// PrerenderCrossOriginRedirectionMismatch.kHostMismatch. +// Do not modify this enum. +enum class PrerenderCrossOriginRedirectionDomain { + kRedirectToSubDomain = 0, + kRedirectFromSubDomain = 1, + kCrossDomain = 2, + kMaxValue = kCrossDomain +}; + void RecordPrerenderCancelledInterface(const std::string& interface_name); void RecordPrerenderTriggered(ukm::SourceId ukm_id); @@ -40,6 +74,24 @@ void RecordPrerenderHostFinalStatus( PrerenderTriggerType trigger_type, const std::string& embedder_histogram_suffix); +void RecordPrerenderRedirectionMismatchType( + PrerenderCrossOriginRedirectionMismatch case_type, + PrerenderTriggerType trigger_type, + const std::string& embedder_histogram_suffix); + +// Records whether the redirection was caused by HTTP protocol upgrade. +void RecordPrerenderRedirectionProtocolChange( + PrerenderCrossOriginRedirectionProtocolChange change_type, + PrerenderTriggerType trigger_type, + const std::string& embedder_histogram_suffix); + +// Records whether the prerendering navigation was redirected to a subdomain +// page. +void RecordPrerenderRedirectionDomain( + PrerenderCrossOriginRedirectionDomain domain_type, + PrerenderTriggerType trigger_type, + const std::string& embedder_histogram_suffix); + } // namespace content #endif // CONTENT_BROWSER_PRERENDER_PRERENDER_METRICS_H_ diff --git a/content/browser/prerender/prerender_navigation_throttle.cc b/content/browser/prerender/prerender_navigation_throttle.cc index f59a044fba1596..b8847d757a6a41 100644 --- a/content/browser/prerender/prerender_navigation_throttle.cc +++ b/content/browser/prerender/prerender_navigation_throttle.cc @@ -6,11 +6,15 @@ #include "content/browser/prerender/prerender_host.h" #include "content/browser/prerender/prerender_host_registry.h" +#include "content/browser/prerender/prerender_metrics.h" #include "content/browser/renderer_host/frame_tree.h" #include "content/browser/renderer_host/frame_tree_node.h" #include "content/browser/renderer_host/navigation_request.h" #include "content/browser/renderer_host/render_frame_host_delegate.h" +#include "content/public/browser/prerender_trigger_type.h" #include "third_party/blink/public/common/features.h" +#include "url/origin.h" +#include "url/url_constants.h" namespace content { @@ -24,6 +28,54 @@ bool IsDisallowedHttpResponseCode(int response_code) { return response_code < 100 || response_code > 399; } +// For the given two origins, analyze what kind of redirection happened. +void AnalyzeCrossOriginRedirection( + const url::Origin& current_origin, + const url::Origin& initial_origin, + PrerenderTriggerType trigger_type, + const std::string& embedder_histogram_suffix) { + DCHECK_NE(initial_origin, current_origin); + DCHECK_EQ(trigger_type, PrerenderTriggerType::kEmbedder); + DCHECK(current_origin.GetURL().SchemeIsHTTPOrHTTPS()); + DCHECK(initial_origin.GetURL().SchemeIsHTTPOrHTTPS()); + + std::bitset<3> bits; + bits[2] = current_origin.scheme() != initial_origin.scheme(); + bits[1] = current_origin.host() != initial_origin.host(); + bits[0] = current_origin.port() != initial_origin.port(); + DCHECK(bits.any()); + auto mismatch_type = + static_cast(bits.to_ulong()); + + RecordPrerenderRedirectionMismatchType(mismatch_type, trigger_type, + embedder_histogram_suffix); + + if (mismatch_type == + PrerenderCrossOriginRedirectionMismatch::kSchemePortMismatch) { + RecordPrerenderRedirectionProtocolChange( + current_origin.scheme() == url::kHttpsScheme + ? PrerenderCrossOriginRedirectionProtocolChange:: + kHttpProtocolUpgrade + : PrerenderCrossOriginRedirectionProtocolChange:: + kHttpProtocolDowngrade, + trigger_type, embedder_histogram_suffix); + return; + } + if (mismatch_type == PrerenderCrossOriginRedirectionMismatch::kHostMismatch) { + if (current_origin.DomainIs(initial_origin.host())) { + RecordPrerenderRedirectionDomain( + PrerenderCrossOriginRedirectionDomain::kRedirectToSubDomain, + trigger_type, embedder_histogram_suffix); + return; + } + RecordPrerenderRedirectionDomain( + initial_origin.DomainIs(current_origin.host()) + ? PrerenderCrossOriginRedirectionDomain::kRedirectFromSubDomain + : PrerenderCrossOriginRedirectionDomain::kCrossDomain, + trigger_type, embedder_histogram_suffix); + } +} + } // namespace PrerenderNavigationThrottle::~PrerenderNavigationThrottle() = default; @@ -130,13 +182,20 @@ PrerenderNavigationThrottle::WillStartOrRedirectRequest(bool is_redirection) { if (is_redirection) { url::Origin initial_origin = url::Origin::Create(prerender_host->GetInitialUrl()); - prerender_host_registry->CancelHost( - frame_tree_node->frame_tree_node_id(), - initial_origin == prerendering_origin - ? PrerenderHost::FinalStatus:: - kEmbedderTriggeredAndSameOriginRedirected - : PrerenderHost::FinalStatus:: - kEmbedderTriggeredAndCrossOriginRedirected); + if (initial_origin == prerendering_origin) { + prerender_host_registry->CancelHost( + frame_tree_node->frame_tree_node_id(), + PrerenderHost::FinalStatus:: + kEmbedderTriggeredAndSameOriginRedirected); + } else { + AnalyzeCrossOriginRedirection( + prerendering_origin, initial_origin, prerender_host->trigger_type(), + prerender_host->embedder_histogram_suffix()); + prerender_host_registry->CancelHost( + frame_tree_node->frame_tree_node_id(), + PrerenderHost::FinalStatus:: + kEmbedderTriggeredAndCrossOriginRedirected); + } return CANCEL; } diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index bafdb587bdd5c8..f1047b5af04b31 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml @@ -70259,6 +70259,28 @@ Called by update_net_trust_anchors.py.--> label="[main frame send, main frame change, other send, other change]"/> + + + + + + + + + + + + + + + + + + + + + + Deprecated Dec 12 2014. diff --git a/tools/metrics/histograms/metadata/navigation/histograms.xml b/tools/metrics/histograms/metadata/navigation/histograms.xml index 0421eccea2e46f..12965af6e5e6ab 100644 --- a/tools/metrics/histograms/metadata/navigation/histograms.xml +++ b/tools/metrics/histograms/metadata/navigation/histograms.xml @@ -34,6 +34,21 @@ chromium-metrics-reviews@google.com. + + + + + + Base histogram. Use suffixes of this histogram instead. Non-suffix name + was removed in Nov 2021. + + + + + + @@ -1610,6 +1625,41 @@ chromium-metrics-reviews@google.com. + + lingqi@chromium.org + nhiroki@chromium.org + src/content/browser/prerender/OWNERS + + A breakdown metric for analyzing cross-origin redirection cases during + embedder-triggered prerendering. Recorded when a prerendering page was + redirected to a subdomain. + + Note that for now it is not used for prerendering that are triggered by + speculation rules. + + + + + + lingqi@chromium.org + nhiroki@chromium.org + src/content/browser/prerender/OWNERS + + A breakdown metric for analyzing cross-origin redirection cases during + embedder-triggered prerendering, Recorded when redirections were caused due + to HTTP protocol upgrades. + + Note that for now it is not used for prerendering that are triggered by + speculation rules. + + + + nhiroki@chromium.org @@ -1648,6 +1698,27 @@ chromium-metrics-reviews@google.com. + + lingqi@chromium.org + nhiroki@chromium.org + src/content/browser/prerender/OWNERS + + A breakdown metric for analyzing cross-origin redirection cases during + embedder-triggered prerendering. Recorded when the embedder-triggered + prerendering was redirected to another origin that differs from the initial + one. + + The cases are encoded into 3 bits(scheme mismatch, host mismatch, port + mismatch). + + Note that for now it is not used for prerendering that are triggered by + speculation rules. + + + + @@ -1682,7 +1753,8 @@ chromium-metrics-reviews@google.com. - nhiroki@chromium.org toyoshim@chromium.org @@ -1692,16 +1764,7 @@ chromium-metrics-reviews@google.com. Final status for a prerendering attempt. Recorded by PrerenderHost or PrerenderHostRegistry in the browser process when the attempt finishes. - - - - Base histogram. Use suffixes of this histogram instead. Non-suffix name - was removed in Dec 2021. - - - - - + - - - - Base histogram. Use suffixes of this histogram instead. Non-suffix name - was removed in Nov 2021. - - - - - - @@ -720,7 +709,7 @@ chromium-metrics-reviews@google.com. ksakamoto@chromium.org asamidoi@chromium.org @@ -730,11 +719,15 @@ chromium-metrics-reviews@google.com. prerendered and were later activated. Note that prerendered page loads are excluded from PageLoad.InteractiveTiming.FirstInputDelay4. - + + + ksakamoto@chromium.org asamidoi@chromium.org @@ -745,11 +738,15 @@ chromium-metrics-reviews@google.com. page loads are excluded from PageLoad.LayoutInstability.CumulativeShiftScore.MainFrame. - + + + ksakamoto@chromium.org asamidoi@chromium.org @@ -759,11 +756,15 @@ chromium-metrics-reviews@google.com. were prerendered and were later activated. Note that prerendered page loads are excluded from PageLoad.LayoutInstability.CumulativeShiftScore. - + + + ksakamoto@chromium.org asamidoi@chromium.org @@ -774,11 +775,15 @@ chromium-metrics-reviews@google.com. attribute of PerformanceNavigationTiming. Recorded when a prerendered page is activated. - + + + ksakamoto@chromium.org asamidoi@chromium.org @@ -790,11 +795,15 @@ chromium-metrics-reviews@google.com. loads are excluded from PageLoad.PaintTiming.NavigationToFirstContentfulPaint. - + + + ksakamoto@chromium.org asamidoi@chromium.org @@ -805,11 +814,15 @@ chromium-metrics-reviews@google.com. activation navigation start. Note that prerendered page loads are excluded from PageLoad.PaintTiming.NavigationToFirstPaint. - + + + ksakamoto@chromium.org asamidoi@chromium.org @@ -821,7 +834,11 @@ chromium-metrics-reviews@google.com. loads are excluded from PageLoad.PaintTiming.NavigationToLargestContentfulPaint2. - + + + Date: Wed, 15 Dec 2021 00:48:40 +0000 Subject: [PATCH 26/77] Reland "[Zenith] Keep the dialog open when clicking on the device selector" Original change's description: > [Zenith] Keep the dialog open when clicking on the device selector > > The drop down icon is a little bit small and users might click on the > device selector strip by mistake. > > This CL increase the size of the drop down button's ink drop. It also > stops the MousePressed event from bubbling to the MediaItemUI, which > brings the focus back to the tab that is playing the media. > > Bug: 1279499 > Change-Id: Ib7687023aec1bbf4bdaff760d16fa908aa08ac44 > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3334986 > Reviewed-by: Tommy Steimel > Commit-Queue: Muyao Xu > Cr-Commit-Position: refs/heads/main@{#951372} Bug: 1279499 Change-Id: I7e425082902b44d933b334f67c1591f327db5df9 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3338649 Reviewed-by: Tommy Steimel Commit-Queue: Muyao Xu Cr-Commit-Position: refs/heads/main@{#951749} --- .../media_item_ui_device_selector_view.cc | 8 +++++++- .../media_item_ui_device_selector_view.h | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.cc b/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.cc index 875a4f77b86232..689e37226f88f7 100644 --- a/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.cc +++ b/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.cc @@ -52,7 +52,7 @@ constexpr gfx::Insets kExpandButtonBorderInsets{4, 8}; // Constant for DropdownButton const int kDropdownButtonIconSize = 15; -const int kDropdownButtonBackgroundRadius = 10; +const int kDropdownButtonBackgroundRadius = 15; constexpr gfx::Insets kDropdownButtonBorderInsets{4}; // The maximum number of audio devices to count when recording the @@ -507,6 +507,12 @@ bool MediaItemUIDeviceSelectorView::IsDeviceSelectorExpanded() { return is_expanded_; } +bool MediaItemUIDeviceSelectorView::OnMousePressed( + const ui::MouseEvent& event) { + // Stop the mouse click event from bubbling to parent views. + return true; +} + void MediaItemUIDeviceSelectorView::AddObserver( MediaItemUIDeviceSelectorObserver* observer) { observers_.AddObserver(observer); diff --git a/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.h b/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.h index 384ed0e4c286e1..b6b609ab376da7 100644 --- a/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.h +++ b/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.h @@ -87,6 +87,9 @@ class MediaItemUIDeviceSelectorView void OnDropdownButtonClicked() override; bool IsDeviceSelectorExpanded() override; + // views::View + bool OnMousePressed(const ui::MouseEvent& event) override; + void AddObserver(MediaItemUIDeviceSelectorObserver* observer); views::Label* GetExpandDeviceSelectorLabelForTesting(); From 328ff9d796049a899e78c808f93778cc32ad5f74 Mon Sep 17 00:00:00 2001 From: chromium-autoroll Date: Wed, 15 Dec 2021 00:49:40 +0000 Subject: [PATCH 27/77] Roll Chrome Win32 PGO Profile Roll Chrome Win32 PGO profile from chrome-win32-main-1639504726-e7cdc104acb454455e958b92ea4ea0702a57e906.profdata to chrome-win32-main-1639515544-3861e0300d09429621942ad80a47946e7c2f9a70.profdata If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/pgo-win32-chromium Please CC pgo-profile-sheriffs@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Chromium main branch: https://bugs.chromium.org/p/chromium/issues/entry To report a problem with the AutoRoller itself, please file a bug: https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md Cq-Include-Trybots: luci.chrome.try:win-chrome Tbr: pgo-profile-sheriffs@google.com Change-Id: I52336d72ee153b83f16f0a3c275111f4e046a969 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3339359 Commit-Queue: chromium-autoroll Bot-Commit: chromium-autoroll Cr-Commit-Position: refs/heads/main@{#951750} --- chrome/build/win32.pgo.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt index 37e8b846a5f69b..4500bf1e165218 100644 --- a/chrome/build/win32.pgo.txt +++ b/chrome/build/win32.pgo.txt @@ -1 +1 @@ -chrome-win32-main-1639504726-e7cdc104acb454455e958b92ea4ea0702a57e906.profdata +chrome-win32-main-1639515544-3861e0300d09429621942ad80a47946e7c2f9a70.profdata From 63305a8129466c26b5c0b3188e08d5c7470962f0 Mon Sep 17 00:00:00 2001 From: Zijie He Date: Wed, 15 Dec 2021 00:50:04 +0000 Subject: [PATCH 28/77] Allow dynamic-modify-transform-without-baseval.html to timeout Extend the previous CL to allow timeouts too. https://crrev.com/c/3328912 Bug: 1269536 Change-Id: I0adc2a31117136e1d22a990070f73a42542b7dce Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3340098 Reviewed-by: David Dorwin Commit-Queue: Zijie He Cr-Commit-Position: refs/heads/main@{#951751} --- third_party/blink/web_tests/TestExpectations | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations index 9275f08ab6f846..010ee55ee2b15a 100644 --- a/third_party/blink/web_tests/TestExpectations +++ b/third_party/blink/web_tests/TestExpectations @@ -127,7 +127,7 @@ crbug.com/1254944 svg/dynamic-updates/SVGFEComponentTransferElement-svgdom-table # Fails flakily or times out crbug.com/1255285 virtual/threaded/transitions/transition-currentcolor.html [ Failure Pass ] crbug.com/1255285 virtual/threaded/transitions/transition-ends-before-animation-frame.html [ Timeout ] -crbug.com/1269536 svg/animations/dynamic-modify-transform-without-baseval.html [ Failure Pass ] +crbug.com/1269536 svg/animations/dynamic-modify-transform-without-baseval.html [ Failure Pass Timeout ] # Unknown media state flakiness (likely due to layout occuring earlier than expected): crbug.com/1254945 external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-insert-before.html [ Failure Pass ] From b73e172d9c60096c6cb6f2e239c2cfcd388d34d2 Mon Sep 17 00:00:00 2001 From: Matt Menke Date: Wed, 15 Dec 2021 00:53:28 +0000 Subject: [PATCH 29/77] FLEDGE: Add class that can batch requests. The batching logic queues up signals requests until the consumer tells it to issue a network request, and then issues one network request for all batched requests. This doesn't wire up batching, which requires a bit more work (namely, logic to decide when to send requests for scoring signals. Bidder worklets additionally need to be reused before we can meaningfully use it for bidder worklets). Bug: 1276639 Change-Id: Idf23cb9b91ab5e5801e03bc60cff1fc6866922a0 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3334501 Reviewed-by: Maks Orlovich Commit-Queue: Matt Menke Cr-Commit-Position: refs/heads/main@{#951752} --- content/services/auction_worklet/BUILD.gn | 3 + .../auction_worklet/bidder_worklet.cc | 9 +- .../services/auction_worklet/bidder_worklet.h | 6 +- .../auction_worklet/seller_worklet.cc | 11 +- .../services/auction_worklet/seller_worklet.h | 24 +- .../auction_worklet/trusted_signals.cc | 47 +- .../auction_worklet/trusted_signals.h | 30 +- .../trusted_signals_request_manager.cc | 200 +++++ .../trusted_signals_request_manager.h | 194 ++++ ...rusted_signals_request_manager_unittest.cc | 836 ++++++++++++++++++ .../trusted_signals_unittest.cc | 91 +- 11 files changed, 1349 insertions(+), 102 deletions(-) create mode 100644 content/services/auction_worklet/trusted_signals_request_manager.cc create mode 100644 content/services/auction_worklet/trusted_signals_request_manager.h create mode 100644 content/services/auction_worklet/trusted_signals_request_manager_unittest.cc diff --git a/content/services/auction_worklet/BUILD.gn b/content/services/auction_worklet/BUILD.gn index 4cd7d76797e977..c8076328fc96a9 100644 --- a/content/services/auction_worklet/BUILD.gn +++ b/content/services/auction_worklet/BUILD.gn @@ -59,6 +59,8 @@ source_set("auction_worklet") { "seller_worklet.h", "trusted_signals.cc", "trusted_signals.h", + "trusted_signals_request_manager.cc", + "trusted_signals_request_manager.h", "worklet_loader.cc", "worklet_loader.h", ] @@ -96,6 +98,7 @@ source_set("tests") { "bidder_worklet_unittest.cc", "debug_command_queue_unittest.cc", "seller_worklet_unittest.cc", + "trusted_signals_request_manager_unittest.cc", "trusted_signals_unittest.cc", "worklet_devtools_debug_test_util.cc", "worklet_devtools_debug_test_util.h", diff --git a/content/services/auction_worklet/bidder_worklet.cc b/content/services/auction_worklet/bidder_worklet.cc index 3f8f21a4558e48..e382e36067e38a 100644 --- a/content/services/auction_worklet/bidder_worklet.cc +++ b/content/services/auction_worklet/bidder_worklet.cc @@ -15,6 +15,7 @@ #include "base/callback.h" #include "base/cxx17_backports.h" #include "base/logging.h" +#include "base/memory/scoped_refptr.h" #include "base/strings/strcat.h" #include "base/strings/stringprintf.h" #include "base/time/time.h" @@ -224,7 +225,9 @@ void BidderWorklet::GenerateBid( !trusted_bidding_signals_keys_->empty()) { generate_bid_task->trusted_bidding_signals = TrustedSignals::LoadBiddingSignals( - url_loader_factory_.get(), *trusted_bidding_signals_keys_, + url_loader_factory_.get(), + std::set(trusted_bidding_signals_keys_->begin(), + trusted_bidding_signals_keys_->end()), top_window_origin.host(), *trusted_bidding_signals_url_, v8_helper_, base::BindOnce(&BidderWorklet::OnTrustedBiddingSignalsDownloaded, base::Unretained(this), generate_bid_task)); @@ -386,7 +389,7 @@ void BidderWorklet::V8State::GenerateBid( const url::Origin& browser_signal_top_window_origin, const url::Origin& browser_signal_seller_origin, base::Time auction_start_time, - std::unique_ptr trusted_bidding_signals_result, + scoped_refptr trusted_bidding_signals_result, GenerateBidCallbackInternal callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); @@ -717,7 +720,7 @@ void BidderWorklet::OnScriptDownloaded(WorkletLoader::Result worklet_script, void BidderWorklet::OnTrustedBiddingSignalsDownloaded( GenerateBidTaskList::iterator task, - std::unique_ptr result, + scoped_refptr result, absl::optional error_msg) { DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_); diff --git a/content/services/auction_worklet/bidder_worklet.h b/content/services/auction_worklet/bidder_worklet.h index f481ec28e9ae4d..331005ef6e6866 100644 --- a/content/services/auction_worklet/bidder_worklet.h +++ b/content/services/auction_worklet/bidder_worklet.h @@ -102,7 +102,7 @@ class BidderWorklet : public mojom::BidderWorklet { // Set while loading is in progress. std::unique_ptr trusted_bidding_signals; // Results of loading trusted bidding signals. - std::unique_ptr trusted_bidding_signals_result; + scoped_refptr trusted_bidding_signals_result; // Error message returned by attempt to load `trusted_bidding_signals_`. // Errors loading it are not fatal, so such errors are cached here and only // reported on bid completion. @@ -165,7 +165,7 @@ class BidderWorklet : public mojom::BidderWorklet { const url::Origin& browser_signal_top_window_origin, const url::Origin& browser_signal_seller_origin, base::Time auction_start_time, - std::unique_ptr trusted_bidding_signals_result, + scoped_refptr trusted_bidding_signals_result, GenerateBidCallbackInternal callback); void ConnectDevToolsAgent( @@ -213,7 +213,7 @@ class BidderWorklet : public mojom::BidderWorklet { void OnTrustedBiddingSignalsDownloaded( GenerateBidTaskList::iterator task, - std::unique_ptr result, + scoped_refptr result, absl::optional error_msg); // Checks if the script has been loaded successfully, and the diff --git a/content/services/auction_worklet/seller_worklet.cc b/content/services/auction_worklet/seller_worklet.cc index a6b661da0eef0a..1ebc4f8c85e0bb 100644 --- a/content/services/auction_worklet/seller_worklet.cc +++ b/content/services/auction_worklet/seller_worklet.cc @@ -13,6 +13,7 @@ #include "base/bind.h" #include "base/callback.h" #include "base/logging.h" +#include "base/memory/scoped_refptr.h" #include "base/strings/strcat.h" #include "base/time/time.h" #include "content/services/auction_worklet/auction_v8_helper.h" @@ -191,8 +192,10 @@ void SellerWorklet::ScoreAd( score_ad_task->trusted_scoring_signals = TrustedSignals::LoadScoringSignals( url_loader_factory_.get(), /*render_urls=*/ - std::vector{browser_signal_render_url.spec()}, - score_ad_task->browser_signal_ad_components, + std::set{browser_signal_render_url.spec()}, + /*ad_component_render_urls=*/ + {score_ad_task->browser_signal_ad_components.begin(), + score_ad_task->browser_signal_ad_components.end()}, browser_signal_top_window_origin.host(), *score_ad_task->auction_config->trusted_scoring_signals_url, v8_helper_, base::BindOnce(&SellerWorklet::OnTrustedScoringSignalsDownloaded, @@ -260,7 +263,7 @@ void SellerWorklet::V8State::ScoreAd( const std::string& ad_metadata_json, double bid, blink::mojom::AuctionAdConfigPtr auction_config, - std::unique_ptr trusted_scoring_signals, + scoped_refptr trusted_scoring_signals, const url::Origin& browser_signal_top_window_origin, const url::Origin& browser_signal_interest_group_owner, const GURL& browser_signal_render_url, @@ -530,7 +533,7 @@ void SellerWorklet::OnDownloadComplete(WorkletLoader::Result worklet_script, void SellerWorklet::OnTrustedScoringSignalsDownloaded( ScoreAdTaskList::iterator task, - std::unique_ptr result, + scoped_refptr result, absl::optional error_msg) { DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_); diff --git a/content/services/auction_worklet/seller_worklet.h b/content/services/auction_worklet/seller_worklet.h index e5d698c4a4a074..5464783e44a830 100644 --- a/content/services/auction_worklet/seller_worklet.h +++ b/content/services/auction_worklet/seller_worklet.h @@ -13,6 +13,7 @@ #include #include "base/callback.h" +#include "base/memory/scoped_refptr.h" #include "base/sequence_checker.h" #include "content/services/auction_worklet/auction_v8_helper.h" #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom-forward.h" @@ -128,17 +129,16 @@ class SellerWorklet : public mojom::SellerWorklet { void SetWorkletScript(WorkletLoader::Result worklet_script); - void ScoreAd( - const std::string& ad_metadata_json, - double bid, - blink::mojom::AuctionAdConfigPtr auction_config, - std::unique_ptr trusted_scoring_signals, - const url::Origin& browser_signal_top_window_origin, - const url::Origin& browser_signal_interest_group_owner, - const GURL& browser_signal_render_url, - const std::vector& browser_signal_ad_components, - uint32_t browser_signal_bidding_duration_msecs, - ScoreAdCallbackInternal callback); + void ScoreAd(const std::string& ad_metadata_json, + double bid, + blink::mojom::AuctionAdConfigPtr auction_config, + scoped_refptr trusted_scoring_signals, + const url::Origin& browser_signal_top_window_origin, + const url::Origin& browser_signal_interest_group_owner, + const GURL& browser_signal_render_url, + const std::vector& browser_signal_ad_components, + uint32_t browser_signal_bidding_duration_msecs, + ScoreAdCallbackInternal callback); void ReportResult(blink::mojom::AuctionAdConfigPtr auction_config, const url::Origin& browser_signal_top_window_origin, @@ -196,7 +196,7 @@ class SellerWorklet : public mojom::SellerWorklet { // V8 thread. void OnTrustedScoringSignalsDownloaded( ScoreAdTaskList::iterator task, - std::unique_ptr result, + scoped_refptr result, absl::optional error_msg); void DeliverScoreAdCallbackOnUserThread(ScoreAdTaskList::iterator task, diff --git a/content/services/auction_worklet/trusted_signals.cc b/content/services/auction_worklet/trusted_signals.cc index cae5f6937f7559..7bf27f05f1540e 100644 --- a/content/services/auction_worklet/trusted_signals.cc +++ b/content/services/auction_worklet/trusted_signals.cc @@ -7,10 +7,12 @@ #include #include #include +#include #include "base/bind.h" #include "base/callback.h" #include "base/check.h" +#include "base/memory/scoped_refptr.h" #include "base/strings/strcat.h" #include "base/strings/stringprintf.h" #include "content/services/auction_worklet/auction_downloader.h" @@ -148,8 +150,6 @@ TrustedSignals::Result::Result( : render_url_json_data_(std::move(render_url_json_data)), ad_component_json_data_(std::move(ad_component_json_data)) {} -TrustedSignals::Result::~Result() = default; - v8::Local TrustedSignals::Result::GetBiddingSignals( AuctionV8Helper* v8_helper, v8::Local context, @@ -192,23 +192,23 @@ v8::Local TrustedSignals::Result::GetScoringSignals( return out; } +TrustedSignals::Result::~Result() = default; + std::unique_ptr TrustedSignals::LoadBiddingSignals( network::mojom::URLLoaderFactory* url_loader_factory, - const std::vector& bidding_signals_keys, + std::set bidding_signals_keys, const std::string& hostname, const GURL& trusted_bidding_signals_url, scoped_refptr v8_helper, LoadSignalsCallback load_signals_callback) { DCHECK(!bidding_signals_keys.empty()); - std::unique_ptr trusted_signals = - base::WrapUnique(new TrustedSignals( - /*bidding_signals_keys=*/std::set( - bidding_signals_keys.begin(), bidding_signals_keys.end()), - /*render_urls=*/absl::nullopt, - /*ad_component_render_urls=*/absl::nullopt, - trusted_bidding_signals_url, std::move(v8_helper), - std::move(load_signals_callback))); + std::unique_ptr trusted_signals = base::WrapUnique( + new TrustedSignals(std::move(bidding_signals_keys), + /*render_urls=*/absl::nullopt, + /*ad_component_render_urls=*/absl::nullopt, + trusted_bidding_signals_url, std::move(v8_helper), + std::move(load_signals_callback))); std::string query_params = "hostname=" + net::EscapeQueryParamValue(hostname, /*use_plus=*/true) + @@ -222,8 +222,8 @@ std::unique_ptr TrustedSignals::LoadBiddingSignals( std::unique_ptr TrustedSignals::LoadScoringSignals( network::mojom::URLLoaderFactory* url_loader_factory, - const std::vector& render_urls, - const std::vector& ad_component_render_urls, + std::set render_urls, + std::set ad_component_render_urls, const std::string& hostname, const GURL& trusted_scoring_signals_url, scoped_refptr v8_helper, @@ -232,14 +232,9 @@ std::unique_ptr TrustedSignals::LoadScoringSignals( std::unique_ptr trusted_signals = base::WrapUnique(new TrustedSignals( - /*bidding_signals_keys=*/absl::nullopt, - /*render_urls=*/ - std::set(render_urls.begin(), render_urls.end()), - /*ad_component_render_urls=*/ - std::set(ad_component_render_urls.begin(), - ad_component_render_urls.end()), - trusted_scoring_signals_url, std::move(v8_helper), - std::move(load_signals_callback))); + /*bidding_signals_keys=*/absl::nullopt, std::move(render_urls), + std::move(ad_component_render_urls), trusted_scoring_signals_url, + std::move(v8_helper), std::move(load_signals_callback))); std::string query_params = "hostname=" + net::EscapeQueryParamValue(hostname, /*use_plus=*/true) + @@ -339,15 +334,15 @@ void TrustedSignals::HandleDownloadResultOnV8Thread( v8::Local v8_object = v8_data.As(); - std::unique_ptr result; + scoped_refptr result; if (bidding_signals_keys) { // Handle bidding signals case. - result = std::make_unique( + result = base::MakeRefCounted( ParseKeyValueMap(v8_helper.get(), v8_object, *bidding_signals_keys)); } else { // Handle scoring signals case. - result = std::make_unique( + result = base::MakeRefCounted( ParseChildKeyValueMap(v8_helper.get(), v8_object, "renderUrls", *render_urls), ParseChildKeyValueMap(v8_helper.get(), v8_object, @@ -362,7 +357,7 @@ void TrustedSignals::HandleDownloadResultOnV8Thread( void TrustedSignals::PostCallbackToUserThread( scoped_refptr user_thread_task_runner, base::WeakPtr weak_instance, - std::unique_ptr result, + scoped_refptr result, absl::optional error_msg) { user_thread_task_runner->PostTask( FROM_HERE, @@ -371,7 +366,7 @@ void TrustedSignals::PostCallbackToUserThread( } void TrustedSignals::DeliverCallbackOnUserThread( - std::unique_ptr result, + scoped_refptr result, absl::optional error_msg) { std::move(load_signals_callback_) .Run(std::move(result), std::move(error_msg)); diff --git a/content/services/auction_worklet/trusted_signals.h b/content/services/auction_worklet/trusted_signals.h index 5aeb5c46d16eef..10357a38b90e58 100644 --- a/content/services/auction_worklet/trusted_signals.h +++ b/content/services/auction_worklet/trusted_signals.h @@ -12,6 +12,7 @@ #include #include "base/callback.h" +#include "base/memory/ref_counted.h" #include "base/memory/scoped_refptr.h" #include "services/network/public/mojom/url_loader_factory.mojom-forward.h" #include "third_party/abseil-cpp/absl/types/optional.h" @@ -43,7 +44,7 @@ class TrustedSignals { // // This can be created and destroyed on any thread, but GetSignals() can only // be used on the V8 thread. - class Result { + class Result : public base::RefCountedThreadSafe { public: // Constructor for bidding signals. explicit Result(std::map bidder_json_data); @@ -53,9 +54,6 @@ class TrustedSignals { std::map ad_component_json_data); explicit Result(const Result&) = delete; - - ~Result(); - Result& operator=(const Result&) = delete; // Retrieves the trusted bidding signals associated with the passed in keys, @@ -84,16 +82,22 @@ class TrustedSignals { const std::vector& ad_component_render_urls) const; private: + friend class base::RefCountedThreadSafe; + + ~Result(); + // Map of keys to the associated JSON data for trusted bidding signals. - absl::optional> bidder_json_data_; + const absl::optional> bidder_json_data_; // Map of keys to the associated JSON data for trusted scoring signals. - absl::optional> render_url_json_data_; - absl::optional> ad_component_json_data_; + const absl::optional> + render_url_json_data_; + const absl::optional> + ad_component_json_data_; }; using LoadSignalsCallback = - base::OnceCallback result, + base::OnceCallback result, absl::optional error_msg)>; explicit TrustedSignals(const TrustedSignals&) = delete; @@ -112,7 +116,7 @@ class TrustedSignals { // There are no lifetime constraints of `url_loader_factory`. static std::unique_ptr LoadBiddingSignals( network::mojom::URLLoaderFactory* url_loader_factory, - const std::vector& bidding_signals_keys, + std::set bidding_signals_keys, const std::string& hostname, const GURL& trusted_bidding_signals_url, scoped_refptr v8_helper, @@ -121,8 +125,8 @@ class TrustedSignals { // Just like LoadBiddingSignals() above, but for fetching seller signals. static std::unique_ptr LoadScoringSignals( network::mojom::URLLoaderFactory* url_loader_factory, - const std::vector& render_urls, - const std::vector& ad_component_render_urls, + std::set render_urls, + std::set ad_component_render_urls, const std::string& hostname, const GURL& trusted_scoring_signals_url, scoped_refptr v8_helper, @@ -161,11 +165,11 @@ class TrustedSignals { static void PostCallbackToUserThread( scoped_refptr user_thread_task_runner, base::WeakPtr weak_instance, - std::unique_ptr result, + scoped_refptr result, absl::optional error_msg); // Called on user thread. - void DeliverCallbackOnUserThread(std::unique_ptr, + void DeliverCallbackOnUserThread(scoped_refptr, absl::optional error_msg); // Keys being fetched. For bidding signals, only `bidding_signals_keys_` is diff --git a/content/services/auction_worklet/trusted_signals_request_manager.cc b/content/services/auction_worklet/trusted_signals_request_manager.cc new file mode 100644 index 00000000000000..e2d9d041b9e447 --- /dev/null +++ b/content/services/auction_worklet/trusted_signals_request_manager.cc @@ -0,0 +1,200 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/services/auction_worklet/trusted_signals_request_manager.h" + +#include +#include +#include +#include + +#include "base/callback.h" +#include "base/check.h" +#include "base/containers/unique_ptr_adapters.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_refptr.h" +#include "base/memory/weak_ptr.h" +#include "base/notreached.h" +#include "content/services/auction_worklet/auction_v8_helper.h" +#include "content/services/auction_worklet/trusted_signals.h" +#include "services/network/public/mojom/url_loader_factory.mojom-forward.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "url/gurl.h" +#include "url/origin.h" + +namespace auction_worklet { + +TrustedSignalsRequestManager::TrustedSignalsRequestManager( + Type type, + network::mojom::URLLoaderFactory* url_loader_factory, + const url::Origin& top_level_origin, + const GURL& trusted_signals_url, + AuctionV8Helper* v8_helper) + : type_(type), + url_loader_factory_(url_loader_factory), + top_level_origin_(top_level_origin), + trusted_signals_url_(trusted_signals_url), + v8_helper_(v8_helper) {} + +TrustedSignalsRequestManager::~TrustedSignalsRequestManager() { + // All outstanding Requests should have been destroyed before `this`. + DCHECK(queued_requests_.empty()); + DCHECK(batched_requests_.empty()); +} + +std::unique_ptr +TrustedSignalsRequestManager::RequestBiddingSignals( + const std::vector& keys, + LoadSignalsCallback load_signals_callback) { + DCHECK_EQ(Type::kBiddingSignals, type_); + + std::unique_ptr request = std::make_unique( + this, std::set(keys.begin(), keys.end()), + std::move(load_signals_callback)); + queued_requests_.insert(request.get()); + return request; +} + +std::unique_ptr +TrustedSignalsRequestManager::RequestScoringSignals( + const GURL& render_url, + const std::vector& ad_component_render_urls, + LoadSignalsCallback load_signals_callback) { + DCHECK_EQ(Type::kScoringSignals, type_); + + std::unique_ptr request = std::make_unique( + this, render_url, + std::set(ad_component_render_urls.begin(), + ad_component_render_urls.end()), + std::move(load_signals_callback)); + queued_requests_.insert(request.get()); + return request; +} + +void TrustedSignalsRequestManager::StartBatchedTrustedSignalsRequest() { + if (queued_requests_.empty()) + return; + + BatchedTrustedSignalsRequest* batched_request = + batched_requests_ + .emplace(std::make_unique()) + .first->get(); + batched_request->requests = std::move(queued_requests_); + if (type_ == Type::kBiddingSignals) { + // Append all keys into a single set, and clear each request's list of keys, + // as it's no longer needed. Consumers provide their own list of keys again + // when they request v8 objects from the TrustedSignals::Results returned by + // `this`. + std::set keys; + for (RequestImpl* request : batched_request->requests) { + keys.insert(request->bidder_keys_->begin(), request->bidder_keys_->end()); + request->bidder_keys_.reset(); + request->batched_request_ = batched_request; + } + batched_request->trusted_signals = TrustedSignals::LoadBiddingSignals( + url_loader_factory_, std::move(keys), top_level_origin_.host(), + trusted_signals_url_, v8_helper_, + base::BindOnce(&TrustedSignalsRequestManager::OnSignalsLoaded, + base::Unretained(this), batched_request)); + return; + } + + DCHECK_EQ(type_, Type::kScoringSignals); + // Append urls into two sets, and clear each request's URLs, as they're no + // longer needed. + std::set render_urls; + std::set ad_component_render_urls; + for (RequestImpl* request : batched_request->requests) { + render_urls.insert(request->render_url_->spec()); + ad_component_render_urls.insert(request->ad_component_render_urls_->begin(), + request->ad_component_render_urls_->end()); + request->render_url_.reset(); + request->ad_component_render_urls_.reset(); + request->batched_request_ = batched_request; + } + batched_request->trusted_signals = TrustedSignals::LoadScoringSignals( + url_loader_factory_, std::move(render_urls), + std::move(ad_component_render_urls), top_level_origin_.host(), + trusted_signals_url_, v8_helper_, + base::BindOnce(&TrustedSignalsRequestManager::OnSignalsLoaded, + base::Unretained(this), batched_request)); +} + +TrustedSignalsRequestManager::RequestImpl::RequestImpl( + TrustedSignalsRequestManager* trusted_signals_request_manager, + std::set bidder_keys, + LoadSignalsCallback load_signals_callback) + : bidder_keys_(std::move(bidder_keys)), + load_signals_callback_(std::move(load_signals_callback)), + trusted_signals_request_manager_(trusted_signals_request_manager) { + DCHECK(!bidder_keys_->empty()); +} + +TrustedSignalsRequestManager::RequestImpl::RequestImpl( + TrustedSignalsRequestManager* trusted_signals_request_manager, + const GURL& render_url, + std::set ad_component_render_urls, + LoadSignalsCallback load_signals_callback) + : render_url_(render_url), + ad_component_render_urls_(std::move(ad_component_render_urls)), + load_signals_callback_(std::move(load_signals_callback)), + trusted_signals_request_manager_(trusted_signals_request_manager) {} + +TrustedSignalsRequestManager::RequestImpl::~RequestImpl() { + if (trusted_signals_request_manager_) + trusted_signals_request_manager_->OnRequestDestroyed(this); +} + +TrustedSignalsRequestManager::BatchedTrustedSignalsRequest:: + BatchedTrustedSignalsRequest() = default; + +TrustedSignalsRequestManager::BatchedTrustedSignalsRequest:: + ~BatchedTrustedSignalsRequest() = default; + +void TrustedSignalsRequestManager::OnSignalsLoaded( + BatchedTrustedSignalsRequest* batched_request, + scoped_refptr result, + absl::optional error_msg) { + DCHECK(batched_requests_.find(batched_request) != batched_requests_.end()); + for (RequestImpl* request : batched_request->requests) { + DCHECK_EQ(request->batched_request_, batched_request); + + // Remove association with `this` and `batched_request` before invoking + // callback, which may destroy the Request. + request->trusted_signals_request_manager_ = nullptr; + request->batched_request_ = nullptr; + + // It is illegal for this this to destroy another request, so + // `batched_request->requests` should not be affected by invoking this, + // other than the current element's pointer potentially now pointing to a + // destroyed object. + std::move(request->load_signals_callback_).Run(result, error_msg); + } + batched_requests_.erase(batched_requests_.find(batched_request)); +} + +void TrustedSignalsRequestManager::OnRequestDestroyed(RequestImpl* request) { + // If the request is not assigned to a BatchedTrustedSignalsRequest, it's + // still in `queued_requests_`, so remove it from that. + if (!request->batched_request_) { + size_t removed = queued_requests_.erase(request); + DCHECK_EQ(removed, 1u); + return; + } + + // Otherwise, it should not be in `queued_requests_`. + DCHECK_EQ(queued_requests_.count(request), 0u); + + // But it should be in the `requests` set of the BatchedTrustedSignalsRequest + // it's pointing to. + size_t removed = request->batched_request_->requests.erase(request); + DCHECK_EQ(removed, 1u); + + // Cancel and delete the corresponding BatchedTrustedSignalsRequest if it's + // no longer associated with any live requests. + if (request->batched_request_->requests.empty()) + batched_requests_.erase(batched_requests_.find(request->batched_request_)); +} + +} // namespace auction_worklet diff --git a/content/services/auction_worklet/trusted_signals_request_manager.h b/content/services/auction_worklet/trusted_signals_request_manager.h new file mode 100644 index 00000000000000..eb70c1a873c4e9 --- /dev/null +++ b/content/services/auction_worklet/trusted_signals_request_manager.h @@ -0,0 +1,194 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_SERVICES_AUCTION_WORKLET_TRUSTED_SIGNALS_REQUEST_MANAGER_H_ +#define CONTENT_SERVICES_AUCTION_WORKLET_TRUSTED_SIGNALS_REQUEST_MANAGER_H_ + +#include +#include +#include +#include + +#include "base/callback.h" +#include "base/containers/unique_ptr_adapters.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_refptr.h" +#include "base/memory/weak_ptr.h" +#include "content/services/auction_worklet/trusted_signals.h" +#include "services/network/public/mojom/url_loader_factory.mojom-forward.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "url/gurl.h" +#include "url/origin.h" + +namespace auction_worklet { + +class AuctionV8Helper; +class TrustedSignals; + +// Manages trusted signals requests and responses. Currently only batches +// requests. +// +// TODO(https://crbug.com/1276639): Cache responses as well. +class TrustedSignalsRequestManager { + public: + // Use same callback and result classes as TrustedSignals. + using LoadSignalsCallback = TrustedSignals::LoadSignalsCallback; + using Result = TrustedSignals::Result; + + enum class Type { + kBiddingSignals, + kScoringSignals, + }; + + // Represents a single pending request for TrustedSignals from a consumer. + // Destroying it cancels the request. All live Requests must be destroyed + // before the TrustedSignalsRequestManager used to create them. + // + // It is illegal to destroy other pending Requests when a Request's callback + // is invoked. + class Request { + public: + Request(Request&) = delete; + Request& operator=(Request&) = delete; + virtual ~Request() = default; + + protected: + Request() = default; + }; + + // Creates a TrustedSignalsRequestManager object for a worklet with the + // provided parameters. `type` indicates if this is for bidding or scoring + // signals. A single TrustedSignalsRequestManager object may only be used with + // worklets with a single `trusted_signals_url` and running auctions for a + // single `top_level_origin`. + // + // `url_loader_factory` must remain valid for the lifetime of the + // TrustedSignalsRequestManager. Keeps an owning reference to `v8_helper`. + TrustedSignalsRequestManager( + Type type, + network::mojom::URLLoaderFactory* url_loader_factory, + const url::Origin& top_level_origin, + const GURL& trusted_signals_url, + AuctionV8Helper* v8_helper); + + explicit TrustedSignalsRequestManager(const TrustedSignalsRequestManager&) = + delete; + TrustedSignalsRequestManager& operator=(const TrustedSignalsRequestManager&) = + delete; + + ~TrustedSignalsRequestManager(); + + // Queues a bidding signals request. Does not start a network request until + // StartBatchedTrustedSignalsRequest() is invoked. `this` must be of Type + // kBiddingSignals. + std::unique_ptr RequestBiddingSignals( + const std::vector& keys, + LoadSignalsCallback load_signals_callback); + + // Queues a scoring signals request. Does not start a network request until + // StartBatchedTrustedSignalsRequest() is invoked. `this` must be of Type + // kScoringSignals. + // + // `ad_component_render_urls` are taken as a vector of std::strings so that + // the format matches the one accepted by ScoringSignals::Result, which + // minimizes conversions. + std::unique_ptr RequestScoringSignals( + const GURL& render_url, + const std::vector& ad_component_render_urls, + LoadSignalsCallback load_signals_callback); + + // Starts a single TrustedSignals request for all currently queued Requests. + void StartBatchedTrustedSignalsRequest(); + + private: + struct BatchedTrustedSignalsRequest; + + class RequestImpl : public Request { + public: + RequestImpl(TrustedSignalsRequestManager* trusted_signals_request_manager, + std::set bidder_keys, + LoadSignalsCallback load_signals_callback); + + RequestImpl(TrustedSignalsRequestManager* trusted_signals_request_manager, + const GURL& render_urls, + std::set ad_component_render_urls, + LoadSignalsCallback load_signals_callback); + + RequestImpl(RequestImpl&) = delete; + RequestImpl& operator=(RequestImpl&) = delete; + ~RequestImpl() override; + + private: + friend class TrustedSignalsRequestManager; + + // Used for requests for bidder signals. Must be non-null and non-empty for + // bidder signals requests, null for scoring signals requests. + absl::optional> bidder_keys_; + + // Used for requests for scoring signals. `render_url_` must be non-null + // and non-empty for scoring signals requests, and + // `ad_component_render_urls_` non-null. Both must be null for bidding + // signals requests. + absl::optional render_url_; + // Stored as a std::set for simpler + absl::optional> ad_component_render_urls_; + + LoadSignalsCallback load_signals_callback_; + + // The TrustedSignalsRequestManager that created `this`. Cleared on + // completion. + TrustedSignalsRequestManager* trusted_signals_request_manager_ = nullptr; + + // If this request is currently assigned to a batched request, points to + // that request. nullptr otherwise. + BatchedTrustedSignalsRequest* batched_request_ = nullptr; + }; + + // Manages a single TrustedSignals object, which is associated with one or + // more Requests. Tracks all associated live Requests, and manages invoking + // their callbacks. Only created when a TrustedSignals request is started. + // Lives entirely on the UI thread. + struct BatchedTrustedSignalsRequest { + public: + BatchedTrustedSignalsRequest(); + BatchedTrustedSignalsRequest(BatchedTrustedSignalsRequest&) = delete; + BatchedTrustedSignalsRequest& operator=(BatchedTrustedSignalsRequest&) = + delete; + ~BatchedTrustedSignalsRequest(); + + std::unique_ptr trusted_signals; + + // The batched Requests this is for. + std::set requests; + }; + + void OnSignalsLoaded(BatchedTrustedSignalsRequest* batched_request, + scoped_refptr result, + absl::optional error_msg); + + // Called when a request is destroyed. If it's in `queued_requests_`, removes + // it. If there's a BatchedTrustedSignalsRequest for it, disassociates the + // request with it, cancelling the request if it's no longer needed. + void OnRequestDestroyed(RequestImpl* request); + + const Type type_; + network::mojom::URLLoaderFactory* const url_loader_factory_; + const url::Origin top_level_origin_; + const GURL trusted_signals_url_; + const scoped_refptr v8_helper_; + + // All live requests that haven't yet been assigned to a + // BatchedTrustedSignalsRequest. + std::set queued_requests_; + + std::set, + base::UniquePtrComparator> + batched_requests_; + + base::WeakPtrFactory weak_ptr_factory{this}; +}; + +} // namespace auction_worklet + +#endif // CONTENT_SERVICES_AUCTION_WORKLET_TRUSTED_SIGNALS_REQUEST_MANAGER_H_ diff --git a/content/services/auction_worklet/trusted_signals_request_manager_unittest.cc b/content/services/auction_worklet/trusted_signals_request_manager_unittest.cc new file mode 100644 index 00000000000000..5f4cb5406f5fe3 --- /dev/null +++ b/content/services/auction_worklet/trusted_signals_request_manager_unittest.cc @@ -0,0 +1,836 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/services/auction_worklet/trusted_signals_request_manager.h" + +#include +#include +#include +#include + +#include "base/bind.h" +#include "base/memory/scoped_refptr.h" +#include "base/run_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/test/bind.h" +#include "base/test/task_environment.h" +#include "content/services/auction_worklet/auction_v8_helper.h" +#include "content/services/auction_worklet/trusted_signals.h" +#include "content/services/auction_worklet/worklet_test_util.h" +#include "net/http/http_status_code.h" +#include "services/network/test/test_url_loader_factory.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" +#include "v8/include/v8-context.h" +#include "v8/include/v8-forward.h" + +namespace auction_worklet { +namespace { + +// Common JSON used for most bidding signals tests. +const char kBaseBiddingJson[] = R"( + { + "key1": 1, + "key2": [2], + "key3": "3" + } +)"; + +// Common JSON used for most scoring signals tests. +const char kBaseScoringJson[] = R"( + { + "renderUrls": { + "https://foo.test/": 1, + "https://bar.test/": [2] + }, + "adComponentRenderUrls": { + "https://foosub.test/": 2, + "https://barsub.test/": [3], + "https://bazsub.test/": "4" + } + } +)"; + +const char kTopLevelOrigin[] = "https://publisher"; + +// Callback for loading signals that stores the result and runs a closure to +// exit a message loop. +void LoadSignalsCallback(scoped_refptr* results_out, + absl::optional* error_msg_out, + base::OnceClosure quit_closure, + scoped_refptr result, + absl::optional error_msg) { + *results_out = std::move(result); + *error_msg_out = std::move(error_msg); + EXPECT_EQ(results_out->get() == nullptr, error_msg_out->has_value()); + std::move(quit_closure).Run(); +} + +// Callback that should never be invoked, for cancellation tests. +void NeverInvokedLoadSignalsCallback( + scoped_refptr result, + absl::optional error_msg) { + ADD_FAILURE() << "This should not be invoked"; +} + +class TrustedSignalsRequestManagerTest : public testing::Test { + public: + TrustedSignalsRequestManagerTest() + : v8_helper_( + AuctionV8Helper::Create(AuctionV8Helper::CreateTaskRunner())), + bidding_request_manager_( + TrustedSignalsRequestManager::Type::kBiddingSignals, + &url_loader_factory_, + url::Origin::Create(GURL(kTopLevelOrigin)), + trusted_signals_url_, + v8_helper_.get()), + scoring_request_manager_( + TrustedSignalsRequestManager::Type::kScoringSignals, + &url_loader_factory_, + url::Origin::Create(GURL(kTopLevelOrigin)), + trusted_signals_url_, + v8_helper_.get()) {} + + ~TrustedSignalsRequestManagerTest() override { + task_environment_.RunUntilIdle(); + } + + // Sets the HTTP response and then fetches bidding signals and waits for + // completion. Returns nullptr on failure. + scoped_refptr FetchBiddingSignalsWithResponse( + const GURL& url, + const std::string& response, + const std::vector& trusted_bidding_signals_keys) { + AddJsonResponse(&url_loader_factory_, url, response); + return FetchBiddingSignals(trusted_bidding_signals_keys); + } + + // Fetches bidding signals and waits for completion. Returns nullptr on + // failure. + scoped_refptr FetchBiddingSignals( + const std::vector& trusted_bidding_signals_keys) { + scoped_refptr signals; + base::RunLoop run_loop; + auto request = bidding_request_manager_.RequestBiddingSignals( + std::move(trusted_bidding_signals_keys), + base::BindOnce(&LoadSignalsCallback, &signals, &error_msg_, + run_loop.QuitClosure())); + bidding_request_manager_.StartBatchedTrustedSignalsRequest(); + run_loop.Run(); + return signals; + } + + // Sets the HTTP response and then fetches scoring signals and waits for + // completion. Returns nullptr on failure. + scoped_refptr FetchScoringSignalsWithResponse( + const GURL& url, + const std::string& response, + const GURL& render_url, + const std::vector& ad_component_render_urls) { + AddJsonResponse(&url_loader_factory_, url, response); + return FetchScoringSignals(render_url, ad_component_render_urls); + } + + // Fetches scoring signals and waits for completion. Returns nullptr on + // failure. + scoped_refptr FetchScoringSignals( + const GURL& render_url, + const std::vector& ad_component_render_urls) { + scoped_refptr signals; + base::RunLoop run_loop; + auto request = scoring_request_manager_.RequestScoringSignals( + render_url, ad_component_render_urls, + base::BindOnce(&LoadSignalsCallback, &signals, &error_msg_, + run_loop.QuitClosure())); + scoring_request_manager_.StartBatchedTrustedSignalsRequest(); + run_loop.Run(); + return signals; + } + + // Returns the results of calling TrustedSignals::Result::GetBiddingSignals() + // with `trusted_bidding_signals_keys`. Returns value as a JSON std::string, + // for easy testing. + std::string ExtractBiddingSignals( + TrustedSignals::Result* signals, + std::vector trusted_bidding_signals_keys) { + base::RunLoop run_loop; + + std::string result; + v8_helper_->v8_runner()->PostTask( + FROM_HERE, base::BindLambdaForTesting([&]() { + AuctionV8Helper::FullIsolateScope isolate_scope(v8_helper_.get()); + v8::Isolate* isolate = v8_helper_->isolate(); + // Could use the scratch context, but using a separate one more + // closely resembles actual use. + v8::Local context = v8::Context::New(isolate); + v8::Context::Scope context_scope(context); + + v8::Local value = signals->GetBiddingSignals( + v8_helper_.get(), context, trusted_bidding_signals_keys); + + if (!v8_helper_->ExtractJson(context, value, &result)) { + result = "JSON extraction failed."; + } + run_loop.Quit(); + })); + run_loop.Run(); + return result; + } + + // Returns the results of calling TrustedSignals::Result::GetScoringSignals() + // with the provided parameters. Returns value as a JSON std::string, for easy + // testing. + std::string ExtractScoringSignals( + TrustedSignals::Result* signals, + const GURL& render_url, + const std::vector& ad_component_render_urls) { + base::RunLoop run_loop; + + std::string result; + v8_helper_->v8_runner()->PostTask( + FROM_HERE, base::BindLambdaForTesting([&]() { + AuctionV8Helper::FullIsolateScope isolate_scope(v8_helper_.get()); + v8::Isolate* isolate = v8_helper_->isolate(); + // Could use the scratch context, but using a separate one more + // closely resembles actual use. + v8::Local context = v8::Context::New(isolate); + v8::Context::Scope context_scope(context); + + v8::Local value = signals->GetScoringSignals( + v8_helper_.get(), context, render_url, ad_component_render_urls); + + if (!v8_helper_->ExtractJson(context, value, &result)) { + result = "JSON extraction failed."; + } + run_loop.Quit(); + })); + run_loop.Run(); + return result; + } + + protected: + base::test::TaskEnvironment task_environment_; + + // URL without query params attached. + const GURL trusted_signals_url_ = GURL("https://url.test/"); + + // The fetch helpers store the most recent error message, if any, here. + absl::optional error_msg_; + + network::TestURLLoaderFactory url_loader_factory_; + scoped_refptr v8_helper_; + TrustedSignalsRequestManager bidding_request_manager_; + TrustedSignalsRequestManager scoring_request_manager_; +}; + +TEST_F(TrustedSignalsRequestManagerTest, BiddingSignalsError) { + url_loader_factory_.AddResponse( + "https://url.test/?hostname=publisher&keys=key1", kBaseBiddingJson, + net::HTTP_NOT_FOUND); + EXPECT_FALSE(FetchBiddingSignals({"key1"})); + EXPECT_EQ( + "Failed to load https://url.test/?hostname=publisher&keys=key1 " + "HTTP status = 404 Not Found.", + error_msg_); +} + +TEST_F(TrustedSignalsRequestManagerTest, ScoringSignalsError) { + url_loader_factory_.AddResponse( + "https://url.test/" + "?hostname=publisher&renderUrls=https%3A%2F%2Ffoo.test%2F", + kBaseScoringJson, net::HTTP_NOT_FOUND); + EXPECT_FALSE(FetchScoringSignals(GURL("https://foo.test/"), + /*ad_component_render_urls=*/{})); + EXPECT_EQ( + "Failed to load " + "https://url.test/" + "?hostname=publisher&renderUrls=https%3A%2F%2Ffoo.test%2F " + "HTTP status = 404 Not Found.", + error_msg_); +} + +TEST_F(TrustedSignalsRequestManagerTest, BiddingSignalsBatchedRequestError) { + url_loader_factory_.AddResponse( + "https://url.test/?hostname=publisher&keys=key1,key2", kBaseBiddingJson, + net::HTTP_NOT_FOUND); + + base::RunLoop run_loop1; + scoped_refptr signals1; + absl::optional error_msg1; + auto request1 = bidding_request_manager_.RequestBiddingSignals( + {"key1"}, base::BindOnce(&LoadSignalsCallback, &signals1, &error_msg1, + run_loop1.QuitClosure())); + + base::RunLoop run_loop2; + scoped_refptr signals2; + absl::optional error_msg2; + auto request2 = bidding_request_manager_.RequestBiddingSignals( + {"key2"}, base::BindOnce(&LoadSignalsCallback, &signals2, &error_msg2, + run_loop2.QuitClosure())); + + bidding_request_manager_.StartBatchedTrustedSignalsRequest(); + + const char kExpectedError[] = + "Failed to load https://url.test/?hostname=publisher&keys=key1,key2 " + "HTTP status = 404 Not Found."; + + run_loop1.Run(); + EXPECT_FALSE(signals1); + EXPECT_EQ(kExpectedError, error_msg1); + + run_loop2.Run(); + EXPECT_FALSE(signals2); + EXPECT_EQ(kExpectedError, error_msg2); +} + +TEST_F(TrustedSignalsRequestManagerTest, ScoringSignalsBatchedRequestError) { + url_loader_factory_.AddResponse( + "https://url.test/?hostname=publisher&" + "renderUrls=https%3A%2F%2Fbar.test%2F,https%3A%2F%2Ffoo.test%2F", + kBaseScoringJson, net::HTTP_NOT_FOUND); + + base::RunLoop run_loop1; + scoped_refptr signals1; + absl::optional error_msg1; + auto request1 = scoring_request_manager_.RequestScoringSignals( + GURL("https://foo.test/"), + /*ad_component_render_urls=*/{}, + base::BindOnce(&LoadSignalsCallback, &signals1, &error_msg1, + run_loop1.QuitClosure())); + + base::RunLoop run_loop2; + scoped_refptr signals2; + absl::optional error_msg2; + auto request2 = scoring_request_manager_.RequestScoringSignals( + GURL("https://bar.test/"), + /*ad_component_render_urls=*/{}, + base::BindOnce(&LoadSignalsCallback, &signals2, &error_msg2, + run_loop2.QuitClosure())); + + scoring_request_manager_.StartBatchedTrustedSignalsRequest(); + + const char kExpectedError[] = + "Failed to load https://url.test/?hostname=publisher" + "&renderUrls=https%3A%2F%2Fbar.test%2F,https%3A%2F%2Ffoo.test%2F " + "HTTP status = 404 Not Found."; + + run_loop1.Run(); + EXPECT_FALSE(signals1); + EXPECT_EQ(kExpectedError, error_msg1); + + run_loop2.Run(); + EXPECT_FALSE(signals2); + EXPECT_EQ(kExpectedError, error_msg2); +} + +TEST_F(TrustedSignalsRequestManagerTest, BiddingSignalsOneRequest) { + const std::vector kKeys{"key2", "key1"}; + scoped_refptr signals = + FetchBiddingSignalsWithResponse( + GURL("https://url.test/?hostname=publisher&keys=key1,key2"), + kBaseBiddingJson, kKeys); + ASSERT_TRUE(signals); + EXPECT_FALSE(error_msg_.has_value()); + EXPECT_EQ(R"({"key2":[2],"key1":1})", + ExtractBiddingSignals(signals.get(), kKeys)); +} + +TEST_F(TrustedSignalsRequestManagerTest, ScoringSignalsOneRequest) { + const GURL kRenderUrl = GURL("https://foo.test/"); + const std::vector kAdComponentRenderUrls{ + "https://foosub.test/", "https://barsub.test/", "https://bazsub.test/"}; + // URLs are currently added in lexical order. + scoped_refptr signals = + FetchScoringSignalsWithResponse( + GURL("https://url.test/?hostname=publisher" + "&renderUrls=https%3A%2F%2Ffoo.test%2F" + "&adComponentRenderUrls=https%3A%2F%2Fbarsub.test%2F," + "https%3A%2F%2Fbazsub.test%2F,https%3A%2F%2Ffoosub.test%2F"), + kBaseScoringJson, kRenderUrl, kAdComponentRenderUrls); + ASSERT_TRUE(signals); + EXPECT_FALSE(error_msg_.has_value()); + EXPECT_EQ( + R"({"renderUrl":{"https://foo.test/":1},")" + R"(adComponentRenderUrls":{"https://foosub.test/":2,)" + R"("https://barsub.test/":[3],"https://bazsub.test/":"4"}})", + ExtractScoringSignals(signals.get(), kRenderUrl, kAdComponentRenderUrls)); +} + +TEST_F(TrustedSignalsRequestManagerTest, BiddingSignalsSequentialRequests) { + // Use partially overlapping keys, to cover both the shared and distinct key + // cases. + const std::vector kKeys1{"key1", "key3"}; + const std::vector kKeys2{"key2", "key3"}; + + // Note that these responses use different values for the shared key. + scoped_refptr signals1 = + FetchBiddingSignalsWithResponse( + GURL("https://url.test/?hostname=publisher&keys=key1,key3"), + R"({"key1":1,"key3":3})", kKeys1); + ASSERT_TRUE(signals1); + EXPECT_FALSE(error_msg_.has_value()); + EXPECT_EQ(R"({"key1":1,"key3":3})", + ExtractBiddingSignals(signals1.get(), kKeys1)); + + scoped_refptr signals2 = + FetchBiddingSignalsWithResponse( + GURL("https://url.test/?hostname=publisher&keys=key2,key3"), + R"({"key2":[2],"key3":[3]})", kKeys2); + ASSERT_TRUE(signals1); + EXPECT_FALSE(error_msg_.has_value()); + EXPECT_EQ(R"({"key2":[2],"key3":[3]})", + ExtractBiddingSignals(signals2.get(), kKeys2)); +} + +TEST_F(TrustedSignalsRequestManagerTest, ScoringSignalsSequentialRequests) { + // Use partially overlapping keys, to cover both the shared and distinct key + // cases. + + const GURL kRenderUrl1 = GURL("https://foo.test/"); + const std::vector kAdComponentRenderUrls1{ + "https://foosub.test/", "https://bazsub.test/"}; + + const GURL kRenderUrl2 = GURL("https://bar.test/"); + const std::vector kAdComponentRenderUrls2{ + "https://barsub.test/", "https://bazsub.test/"}; + + // Note that these responses use different values for the shared key. + scoped_refptr signals1 = + FetchScoringSignalsWithResponse( + GURL("https://url.test/?hostname=publisher" + "&renderUrls=https%3A%2F%2Ffoo.test%2F" + "&adComponentRenderUrls=https%3A%2F%2Fbazsub.test%2F," + "https%3A%2F%2Ffoosub.test%2F"), + R"( +{ + "renderUrls": { + "https://foo.test/": 1 + }, + "adComponentRenderUrls": { + "https://foosub.test/": 2, + "https://bazsub.test/": 3 + } +} + )", + kRenderUrl1, kAdComponentRenderUrls1); + ASSERT_TRUE(signals1); + EXPECT_FALSE(error_msg_.has_value()); + EXPECT_EQ(R"({"renderUrl":{"https://foo.test/":1},")" + R"(adComponentRenderUrls":{"https://foosub.test/":2,)" + R"("https://bazsub.test/":3}})", + ExtractScoringSignals(signals1.get(), kRenderUrl1, + kAdComponentRenderUrls1)); + + scoped_refptr signals2 = + FetchScoringSignalsWithResponse( + GURL("https://url.test/?hostname=publisher" + "&renderUrls=https%3A%2F%2Fbar.test%2F" + "&adComponentRenderUrls=https%3A%2F%2Fbarsub.test%2F," + "https%3A%2F%2Fbazsub.test%2F"), + R"( +{ + "renderUrls": { + "https://bar.test/": 4 + }, + "adComponentRenderUrls": { + "https://barsub.test/": 5, + "https://bazsub.test/": 6 + } +} + )", + kRenderUrl2, kAdComponentRenderUrls2); + ASSERT_TRUE(signals2); + EXPECT_FALSE(error_msg_.has_value()); + EXPECT_EQ(R"({"renderUrl":{"https://bar.test/":4},")" + R"(adComponentRenderUrls":{"https://barsub.test/":5,)" + R"("https://bazsub.test/":6}})", + ExtractScoringSignals(signals2.get(), kRenderUrl2, + kAdComponentRenderUrls2)); +} + +// Test the case where there are multiple network requests live at once. +TEST_F(TrustedSignalsRequestManagerTest, + BiddingSignalsSimultaneousNetworkRequests) { + // Use partially overlapping keys, to cover both the shared and distinct key + // cases. + + const std::vector kKeys1{"key1", "key3"}; + const GURL kUrl1 = + GURL("https://url.test/?hostname=publisher&keys=key1,key3"); + + const std::vector kKeys2{"key2", "key3"}; + const GURL kUrl2 = + GURL("https://url.test/?hostname=publisher&keys=key2,key3"); + + base::RunLoop run_loop1; + scoped_refptr signals1; + absl::optional error_msg1; + auto request1 = bidding_request_manager_.RequestBiddingSignals( + kKeys1, base::BindOnce(&LoadSignalsCallback, &signals1, &error_msg1, + run_loop1.QuitClosure())); + + bidding_request_manager_.StartBatchedTrustedSignalsRequest(); + + base::RunLoop run_loop2; + scoped_refptr signals2; + absl::optional error_msg2; + auto request2 = bidding_request_manager_.RequestBiddingSignals( + kKeys2, base::BindOnce(&LoadSignalsCallback, &signals2, &error_msg2, + run_loop2.QuitClosure())); + + bidding_request_manager_.StartBatchedTrustedSignalsRequest(); + + base::RunLoop().RunUntilIdle(); + + ASSERT_TRUE(url_loader_factory_.IsPending(kUrl1.spec())); + ASSERT_TRUE(url_loader_factory_.IsPending(kUrl2.spec())); + + // Note that these responses use different values for the shared key. + AddJsonResponse(&url_loader_factory_, kUrl1, R"({"key1":1,"key3":3})"); + AddJsonResponse(&url_loader_factory_, kUrl2, R"({"key2":[2],"key3":[3]})"); + + run_loop1.Run(); + EXPECT_FALSE(error_msg1); + ASSERT_TRUE(signals1); + EXPECT_EQ(R"({"key1":1,"key3":3})", + ExtractBiddingSignals(signals1.get(), kKeys1)); + + run_loop2.Run(); + EXPECT_FALSE(error_msg2); + ASSERT_TRUE(signals2); + EXPECT_EQ(R"({"key2":[2],"key3":[3]})", + ExtractBiddingSignals(signals2.get(), kKeys2)); +} + +// Test the case where there are multiple network requests live at once. +TEST_F(TrustedSignalsRequestManagerTest, + ScoringSignalsSimultaneousNetworkRequests) { + // Use partially overlapping keys, to cover both the shared and distinct key + // cases. + + const GURL kRenderUrl1 = GURL("https://foo.test/"); + const std::vector kAdComponentRenderUrls1{ + "https://foosub.test/", "https://bazsub.test/"}; + const GURL kSignalsUrl1 = GURL( + "https://url.test/?hostname=publisher" + "&renderUrls=https%3A%2F%2Ffoo.test%2F" + "&adComponentRenderUrls=https%3A%2F%2Fbazsub.test%2F," + "https%3A%2F%2Ffoosub.test%2F"); + const GURL kRenderUrl2 = GURL("https://bar.test/"); + const std::vector kAdComponentRenderUrls2{ + "https://barsub.test/", "https://bazsub.test/"}; + const GURL kSignalsUrl2 = GURL( + "https://url.test/?hostname=publisher" + "&renderUrls=https%3A%2F%2Fbar.test%2F" + "&adComponentRenderUrls=https%3A%2F%2Fbarsub.test%2F," + "https%3A%2F%2Fbazsub.test%2F"); + + base::RunLoop run_loop1; + scoped_refptr signals1; + absl::optional error_msg1; + auto request1 = scoring_request_manager_.RequestScoringSignals( + kRenderUrl1, kAdComponentRenderUrls1, + base::BindOnce(&LoadSignalsCallback, &signals1, &error_msg1, + run_loop1.QuitClosure())); + + scoring_request_manager_.StartBatchedTrustedSignalsRequest(); + + base::RunLoop run_loop2; + scoped_refptr signals2; + absl::optional error_msg2; + auto request2 = scoring_request_manager_.RequestScoringSignals( + kRenderUrl2, kAdComponentRenderUrls2, + base::BindOnce(&LoadSignalsCallback, &signals2, &error_msg2, + run_loop2.QuitClosure())); + + scoring_request_manager_.StartBatchedTrustedSignalsRequest(); + + base::RunLoop().RunUntilIdle(); + + ASSERT_TRUE(url_loader_factory_.IsPending(kSignalsUrl1.spec())); + ASSERT_TRUE(url_loader_factory_.IsPending(kSignalsUrl2.spec())); + + // Note that these responses use different values for the shared key. + + AddJsonResponse(&url_loader_factory_, kSignalsUrl1, R"( +{ + "renderUrls": { + "https://foo.test/": 1 + }, + "adComponentRenderUrls": { + "https://foosub.test/": 2, + "https://bazsub.test/": 3 + } +} + )"); + + AddJsonResponse(&url_loader_factory_, kSignalsUrl2, R"( +{ + "renderUrls": { + "https://bar.test/": 4 + }, + "adComponentRenderUrls": { + "https://barsub.test/": 5, + "https://bazsub.test/": 6 + } +} + )"); + + run_loop1.Run(); + EXPECT_FALSE(error_msg1); + ASSERT_TRUE(signals1); + EXPECT_EQ(R"({"renderUrl":{"https://foo.test/":1},")" + R"(adComponentRenderUrls":{"https://foosub.test/":2,)" + R"("https://bazsub.test/":3}})", + ExtractScoringSignals(signals1.get(), kRenderUrl1, + kAdComponentRenderUrls1)); + + run_loop2.Run(); + EXPECT_FALSE(error_msg2); + ASSERT_TRUE(signals2); + EXPECT_EQ(R"({"renderUrl":{"https://bar.test/":4},")" + R"(adComponentRenderUrls":{"https://barsub.test/":5,)" + R"("https://bazsub.test/":6}})", + ExtractScoringSignals(signals2.get(), kRenderUrl2, + kAdComponentRenderUrls2)); +} + +TEST_F(TrustedSignalsRequestManagerTest, BiddingSignalsBatchedRequests) { + // Use partially overlapping keys, to cover both the shared and distinct key + // cases. + const std::vector kKeys1{"key1", "key3"}; + const std::vector kKeys2{"key2", "key3"}; + + AddJsonResponse( + &url_loader_factory_, + GURL("https://url.test/?hostname=publisher&keys=key1,key2,key3"), + kBaseBiddingJson); + + base::RunLoop run_loop1; + scoped_refptr signals1; + absl::optional error_msg1; + auto request1 = bidding_request_manager_.RequestBiddingSignals( + kKeys1, base::BindOnce(&LoadSignalsCallback, &signals1, &error_msg1, + run_loop1.QuitClosure())); + + base::RunLoop run_loop2; + scoped_refptr signals2; + absl::optional error_msg2; + auto request2 = bidding_request_manager_.RequestBiddingSignals( + kKeys2, base::BindOnce(&LoadSignalsCallback, &signals2, &error_msg2, + run_loop2.QuitClosure())); + + bidding_request_manager_.StartBatchedTrustedSignalsRequest(); + + run_loop1.Run(); + EXPECT_FALSE(error_msg1); + ASSERT_TRUE(signals1); + EXPECT_EQ(R"({"key1":1,"key3":"3"})", + ExtractBiddingSignals(signals1.get(), kKeys1)); + + run_loop2.Run(); + EXPECT_FALSE(error_msg2); + ASSERT_TRUE(signals2); + EXPECT_EQ(R"({"key2":[2],"key3":"3"})", + ExtractBiddingSignals(signals2.get(), kKeys2)); +} + +TEST_F(TrustedSignalsRequestManagerTest, ScoringSignalsBatchedRequests) { + // Use partially overlapping keys, to cover both the shared and distinct key + // cases. + const GURL kRenderUrl1 = GURL("https://foo.test/"); + const std::vector kAdComponentRenderUrls1{ + "https://foosub.test/", "https://bazsub.test/"}; + + const GURL kRenderUrl2 = GURL("https://bar.test/"); + const std::vector kAdComponentRenderUrls2{ + "https://barsub.test/", "https://bazsub.test/"}; + + const GURL kRenderUrl3 = GURL("https://foo.test/"); + const std::vector kAdComponentRenderUrls3{}; + + AddJsonResponse( + &url_loader_factory_, + GURL("https://url.test/?hostname=publisher" + "&renderUrls=https%3A%2F%2Fbar.test%2F,https%3A%2F%2Ffoo.test%2F" + "&adComponentRenderUrls=https%3A%2F%2Fbarsub.test%2F," + "https%3A%2F%2Fbazsub.test%2F,https%3A%2F%2Ffoosub.test%2F"), + kBaseScoringJson); + + base::RunLoop run_loop1; + scoped_refptr signals1; + absl::optional error_msg1; + auto request1 = scoring_request_manager_.RequestScoringSignals( + kRenderUrl1, kAdComponentRenderUrls1, + base::BindOnce(&LoadSignalsCallback, &signals1, &error_msg1, + run_loop1.QuitClosure())); + + base::RunLoop run_loop2; + scoped_refptr signals2; + absl::optional error_msg2; + auto request2 = scoring_request_manager_.RequestScoringSignals( + kRenderUrl2, kAdComponentRenderUrls2, + base::BindOnce(&LoadSignalsCallback, &signals2, &error_msg2, + run_loop2.QuitClosure())); + + base::RunLoop run_loop3; + scoped_refptr signals3; + absl::optional error_msg3; + auto request3 = scoring_request_manager_.RequestScoringSignals( + kRenderUrl3, kAdComponentRenderUrls3, + base::BindOnce(&LoadSignalsCallback, &signals3, &error_msg3, + run_loop3.QuitClosure())); + + scoring_request_manager_.StartBatchedTrustedSignalsRequest(); + + run_loop1.Run(); + EXPECT_FALSE(error_msg1); + ASSERT_TRUE(signals1); + EXPECT_EQ(R"({"renderUrl":{"https://foo.test/":1},")" + R"(adComponentRenderUrls":{"https://foosub.test/":2,)" + R"("https://bazsub.test/":"4"}})", + ExtractScoringSignals(signals1.get(), kRenderUrl1, + kAdComponentRenderUrls1)); + + run_loop2.Run(); + EXPECT_FALSE(error_msg2); + ASSERT_TRUE(signals2); + EXPECT_EQ(R"({"renderUrl":{"https://bar.test/":[2]},")" + R"(adComponentRenderUrls":{"https://barsub.test/":[3],)" + R"("https://bazsub.test/":"4"}})", + ExtractScoringSignals(signals2.get(), kRenderUrl2, + kAdComponentRenderUrls2)); + + run_loop3.Run(); + EXPECT_FALSE(error_msg3); + ASSERT_TRUE(signals3); + EXPECT_EQ(R"({"renderUrl":{"https://foo.test/":1}})", + ExtractScoringSignals(signals3.get(), kRenderUrl3, + kAdComponentRenderUrls3)); +} + +// Make two requests, cancel both, then try to start a network request. No +// requests should be made. Only test bidders, since sellers have no significant +// differences in this path. +TEST_F(TrustedSignalsRequestManagerTest, CancelAllQueuedRequests) { + const std::vector kKeys1{"key1"}; + const std::vector kKeys2{"key2"}; + + auto request1 = bidding_request_manager_.RequestBiddingSignals( + kKeys1, base::BindOnce(&NeverInvokedLoadSignalsCallback)); + auto request2 = bidding_request_manager_.RequestBiddingSignals( + kKeys2, base::BindOnce(&NeverInvokedLoadSignalsCallback)); + + request1.reset(); + request2.reset(); + + bidding_request_manager_.StartBatchedTrustedSignalsRequest(); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0, url_loader_factory_.NumPending()); +} + +// Make two requests, cancel the first one, then try to start a network request. +// A request should be made, but only for the key in the request that was not +// cancelled. Only test bidders, since sellers have no significant differences +// in this path. +TEST_F(TrustedSignalsRequestManagerTest, CancelOneRequest) { + const std::vector kKeys1{"key1"}; + const std::vector kKeys2{"key2"}; + + // The request for `key1` will be cancelled before the network request is + // created. + AddJsonResponse(&url_loader_factory_, + GURL("https://url.test/?hostname=publisher&keys=key2"), + kBaseBiddingJson); + + auto request1 = bidding_request_manager_.RequestBiddingSignals( + kKeys1, base::BindOnce(&NeverInvokedLoadSignalsCallback)); + + base::RunLoop run_loop2; + scoped_refptr signals2; + absl::optional error_msg2; + auto request2 = bidding_request_manager_.RequestBiddingSignals( + kKeys2, base::BindOnce(&LoadSignalsCallback, &signals2, &error_msg2, + run_loop2.QuitClosure())); + + request1.reset(); + + bidding_request_manager_.StartBatchedTrustedSignalsRequest(); + base::RunLoop().RunUntilIdle(); + + run_loop2.Run(); + EXPECT_FALSE(error_msg2); + ASSERT_TRUE(signals2); + EXPECT_EQ(R"({"key2":[2]})", ExtractBiddingSignals(signals2.get(), kKeys2)); +} + +// Make two requests, try to start a network request, then cancel both requests. +// The network request should be cancelled. Only test bidders, since sellers +// have no significant differences in this path. +TEST_F(TrustedSignalsRequestManagerTest, CancelAllLiveRequests) { + const std::vector kKeys1{"key1"}; + const std::vector kKeys2{"key2"}; + const GURL kSignalsUrl = + GURL("https://url.test/?hostname=publisher&keys=key1,key2"); + + auto request1 = bidding_request_manager_.RequestBiddingSignals( + kKeys1, base::BindOnce(&NeverInvokedLoadSignalsCallback)); + auto request2 = bidding_request_manager_.RequestBiddingSignals( + kKeys2, base::BindOnce(&NeverInvokedLoadSignalsCallback)); + + // Wait for network request to be made, which should include both keys in the + // URLs. + bidding_request_manager_.StartBatchedTrustedSignalsRequest(); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(url_loader_factory_.IsPending(kSignalsUrl.spec())); + + // Cancel both requests, which should cancel the network request. + request1.reset(); + request2.reset(); + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE( + (*url_loader_factory_.pending_requests())[0].client.is_connected()); +} + +// Make two requests, try to start a network request, then cancel the first one. +// The request that was not cancelled should complete normally. Only test +// bidders, since sellers have no significant differences in this path. +TEST_F(TrustedSignalsRequestManagerTest, CancelOneLiveRequest) { + const std::vector kKeys1{"key1"}; + const std::vector kKeys2{"key2"}; + const GURL kSignalsUrl = + GURL("https://url.test/?hostname=publisher&keys=key1,key2"); + + auto request1 = bidding_request_manager_.RequestBiddingSignals( + kKeys1, base::BindOnce(&NeverInvokedLoadSignalsCallback)); + + base::RunLoop run_loop2; + scoped_refptr signals2; + absl::optional error_msg2; + auto request2 = bidding_request_manager_.RequestBiddingSignals( + kKeys2, base::BindOnce(&LoadSignalsCallback, &signals2, &error_msg2, + run_loop2.QuitClosure())); + + // Wait for network request to be made, which should include both keys in the + // URLs. + bidding_request_manager_.StartBatchedTrustedSignalsRequest(); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(url_loader_factory_.IsPending(kSignalsUrl.spec())); + + // Cancel `request1` and then serve the JSON. + request1.reset(); + AddJsonResponse(&url_loader_factory_, kSignalsUrl, kBaseBiddingJson); + + // `request2` should still complete. + run_loop2.Run(); + EXPECT_FALSE(error_msg2); + ASSERT_TRUE(signals2); + EXPECT_EQ(R"({"key2":[2]})", ExtractBiddingSignals(signals2.get(), kKeys2)); + + // The callback of `request1` should not be invoked, since it was cancelled. + base::RunLoop().RunUntilIdle(); +} + +} // namespace +} // namespace auction_worklet diff --git a/content/services/auction_worklet/trusted_signals_unittest.cc b/content/services/auction_worklet/trusted_signals_unittest.cc index 2babf9b88a09a3..aad7fc7fa1a632 100644 --- a/content/services/auction_worklet/trusted_signals_unittest.cc +++ b/content/services/auction_worklet/trusted_signals_unittest.cc @@ -4,11 +4,14 @@ #include "content/services/auction_worklet/trusted_signals.h" +#include +#include #include #include #include #include "base/bind.h" +#include "base/memory/scoped_refptr.h" #include "base/run_loop.h" #include "base/synchronization/waitable_event.h" #include "base/test/bind.h" @@ -69,26 +72,27 @@ class TrustedSignalsTest : public testing::Test { // Sets the HTTP response and then fetches bidding signals and waits for // completion. Returns nullptr on failure. - std::unique_ptr FetchBiddingSignalsWithResponse( + scoped_refptr FetchBiddingSignalsWithResponse( const GURL& url, const std::string& response, - std::vector trusted_bidding_signals_keys, + std::set trusted_bidding_signals_keys, const std::string& hostname) { AddJsonResponse(&url_loader_factory_, url, response); - return FetchBiddingSignals(trusted_bidding_signals_keys, hostname); + return FetchBiddingSignals(std::move(trusted_bidding_signals_keys), + hostname); } // Fetches bidding signals and waits for completion. Returns nullptr on // failure. - std::unique_ptr FetchBiddingSignals( - std::vector trusted_bidding_signals_keys, + scoped_refptr FetchBiddingSignals( + std::set trusted_bidding_signals_keys, const std::string& hostname) { CHECK(!load_signals_run_loop_); DCHECK(!load_signals_result_); auto bidding_signals = TrustedSignals::LoadBiddingSignals( - &url_loader_factory_, std::move(trusted_bidding_signals_keys), - std::move(hostname), base_url_, v8_helper_, + &url_loader_factory_, std::move(trusted_bidding_signals_keys), hostname, + base_url_, v8_helper_, base::BindOnce(&TrustedSignalsTest::LoadSignalsCallback, base::Unretained(this))); WaitForLoadComplete(); @@ -97,29 +101,29 @@ class TrustedSignalsTest : public testing::Test { // Sets the HTTP response and then fetches scoring signals and waits for // completion. Returns nullptr on failure. - std::unique_ptr FetchScoringSignalsWithResponse( + scoped_refptr FetchScoringSignalsWithResponse( const GURL& url, const std::string& response, - std::vector render_urls, - std::vector ad_component_render_urls, + std::set render_urls, + std::set ad_component_render_urls, const std::string& hostname) { AddJsonResponse(&url_loader_factory_, url, response); - return FetchScoringSignals(render_urls, ad_component_render_urls, hostname); + return FetchScoringSignals(std::move(render_urls), + std::move(ad_component_render_urls), hostname); } // Fetches scoring signals and waits for completion. Returns nullptr on // failure. - std::unique_ptr FetchScoringSignals( - std::vector render_urls, - std::vector ad_component_render_urls, + scoped_refptr FetchScoringSignals( + std::set render_urls, + std::set ad_component_render_urls, const std::string& hostname) { CHECK(!load_signals_run_loop_); DCHECK(!load_signals_result_); auto scoring_signals = TrustedSignals::LoadScoringSignals( &url_loader_factory_, std::move(render_urls), - std::move(ad_component_render_urls), std::move(hostname), base_url_, - v8_helper_, + std::move(ad_component_render_urls), hostname, base_url_, v8_helper_, base::BindOnce(&TrustedSignalsTest::LoadSignalsCallback, base::Unretained(this))); WaitForLoadComplete(); @@ -198,11 +202,11 @@ class TrustedSignalsTest : public testing::Test { } protected: - void LoadSignalsCallback(std::unique_ptr result, + void LoadSignalsCallback(scoped_refptr result, absl::optional error_msg) { load_signals_result_ = std::move(result); error_msg_ = std::move(error_msg); - EXPECT_EQ(load_signals_result_ == nullptr, error_msg_.has_value()); + EXPECT_EQ(load_signals_result_.get() == nullptr, error_msg_.has_value()); load_signals_run_loop_->Quit(); } @@ -215,7 +219,7 @@ class TrustedSignalsTest : public testing::Test { // creating the worklet, to cause a crash if the callback is invoked // synchronously. std::unique_ptr load_signals_run_loop_; - std::unique_ptr load_signals_result_; + scoped_refptr load_signals_result_; absl::optional error_msg_; network::TestURLLoaderFactory url_loader_factory_; @@ -293,7 +297,7 @@ TEST_F(TrustedSignalsTest, ScoringSignalsResponseNotObject) { } TEST_F(TrustedSignalsTest, ScoringSignalsExpectedEntriesNotPresent) { - std::unique_ptr signals = + scoped_refptr signals = FetchScoringSignalsWithResponse( GURL("https://url.test/?hostname=publisher" "&renderUrls=https%3A%2F%2Ffoo.test%2F" @@ -311,7 +315,7 @@ TEST_F(TrustedSignalsTest, ScoringSignalsExpectedEntriesNotPresent) { } TEST_F(TrustedSignalsTest, ScoringSignalsNestedEntriesNotObjects) { - std::unique_ptr signals = + scoped_refptr signals = FetchScoringSignalsWithResponse( GURL("https://url.test/?hostname=publisher" "&renderUrls=https%3A%2F%2Ffoo.test%2F" @@ -329,7 +333,7 @@ TEST_F(TrustedSignalsTest, ScoringSignalsNestedEntriesNotObjects) { } TEST_F(TrustedSignalsTest, BiddingSignalsKeyMissing) { - std::unique_ptr signals = + scoped_refptr signals = FetchBiddingSignalsWithResponse( GURL("https://url.test/?hostname=publisher&keys=key4"), kBaseBiddingJson, {"key4"}, kHostname); @@ -338,7 +342,7 @@ TEST_F(TrustedSignalsTest, BiddingSignalsKeyMissing) { } TEST_F(TrustedSignalsTest, ScoringSignalsKeysMissing) { - std::unique_ptr signals = + scoped_refptr signals = FetchScoringSignalsWithResponse( GURL("https://url.test/?hostname=publisher" "&renderUrls=https%3A%2F%2Ffoo.test%2F" @@ -357,7 +361,7 @@ TEST_F(TrustedSignalsTest, ScoringSignalsKeysMissing) { } TEST_F(TrustedSignalsTest, BiddingSignalsOneKey) { - std::unique_ptr signals = + scoped_refptr signals = FetchBiddingSignalsWithResponse( GURL("https://url.test/?hostname=publisher&keys=key1"), kBaseBiddingJson, {"key1"}, kHostname); @@ -366,7 +370,7 @@ TEST_F(TrustedSignalsTest, BiddingSignalsOneKey) { } TEST_F(TrustedSignalsTest, ScoringSignalsForOneRenderUrl) { - std::unique_ptr signals = + scoped_refptr signals = FetchScoringSignalsWithResponse( GURL("https://url.test/" "?hostname=publisher&renderUrls=https%3A%2F%2Ffoo.test%2F"), @@ -382,7 +386,7 @@ TEST_F(TrustedSignalsTest, ScoringSignalsForOneRenderUrl) { } TEST_F(TrustedSignalsTest, BiddingSignalsMultipleKeys) { - std::unique_ptr signals = + scoped_refptr signals = FetchBiddingSignalsWithResponse( GURL("https://url.test/?hostname=publisher&keys=key1,key2,key3,key5"), kBaseBiddingJson, {"key3", "key1", "key5", "key2"}, kHostname); @@ -399,7 +403,7 @@ TEST_F(TrustedSignalsTest, BiddingSignalsMultipleKeys) { TEST_F(TrustedSignalsTest, ScoringSignalsMultipleUrls) { // URLs are currently added in lexical order. - std::unique_ptr signals = + scoped_refptr signals = FetchScoringSignalsWithResponse( GURL("https://url.test/?hostname=publisher" "&renderUrls=https%3A%2F%2Fbar.test%2F," @@ -426,22 +430,25 @@ TEST_F(TrustedSignalsTest, ScoringSignalsMultipleUrls) { } TEST_F(TrustedSignalsTest, BiddingSignalsDuplicateKeys) { - std::vector bidder_signals{"key1", "key2", "key2", "key1", - "key2"}; - std::unique_ptr signals = + std::vector bidder_signals_vector{"key1", "key2", "key2", "key1", + "key2"}; + scoped_refptr signals = FetchBiddingSignalsWithResponse( GURL("https://url.test/?hostname=publisher&keys=key1,key2"), - kBaseBiddingJson, bidder_signals, kHostname); + kBaseBiddingJson, + std::set{bidder_signals_vector.begin(), + bidder_signals_vector.end()}, + kHostname); ASSERT_TRUE(signals); EXPECT_EQ(R"({"key1":1,"key2":[2]})", - ExtractBiddingSignals(signals.get(), bidder_signals)); + ExtractBiddingSignals(signals.get(), bidder_signals_vector)); } TEST_F(TrustedSignalsTest, ScoringSignalsDuplicateKeys) { - std::vector ad_component_render_urls{ + std::vector ad_component_render_urls_vector{ "https://barsub.test/", "https://foosub.test/", "https://foosub.test/", "https://barsub.test/"}; - std::unique_ptr signals = + scoped_refptr signals = FetchScoringSignalsWithResponse( GURL("https://url.test/?hostname=publisher" "&renderUrls=https%3A%2F%2Fbar.test%2F,https%3A%2F%2Ffoo.test%2F" @@ -451,7 +458,9 @@ TEST_F(TrustedSignalsTest, ScoringSignalsDuplicateKeys) { /*render_urls=*/ {"https://foo.test/", "https://foo.test/", "https://bar.test/", "https://bar.test/", "https://foo.test/"}, - ad_component_render_urls, kHostname); + std::set{ad_component_render_urls_vector.begin(), + ad_component_render_urls_vector.end()}, + kHostname); ASSERT_TRUE(signals); EXPECT_FALSE(error_msg_.has_value()); EXPECT_EQ(R"({"renderUrl":{"https://bar.test/":[2]},")" @@ -459,14 +468,14 @@ TEST_F(TrustedSignalsTest, ScoringSignalsDuplicateKeys) { R"("https://barsub.test/":[3],"https://foosub.test/":2}})", ExtractScoringSignals(signals.get(), /*render_url=*/GURL("https://bar.test/"), - ad_component_render_urls)); + ad_component_render_urls_vector)); } // Test when a single URL is used as both a `renderUrl` and // `adComponentRenderUrl`. TEST_F(TrustedSignalsTest, ScoringSignalsSharedUrl) { // URLs are currently added in lexical order. - std::unique_ptr signals = + scoped_refptr signals = FetchScoringSignalsWithResponse( GURL("https://url.test/?hostname=publisher" "&renderUrls=https%3A%2F%2Fshared.test%2F" @@ -488,7 +497,7 @@ TEST_F(TrustedSignalsTest, ScoringSignalsSharedUrl) { } TEST_F(TrustedSignalsTest, BiddingSignalsEscapeQueryParams) { - std::unique_ptr signals = + scoped_refptr signals = FetchBiddingSignalsWithResponse( GURL("https://url.test/" "?hostname=pub+li%26sher&keys=key+6,key%2C8,key%3D7"), @@ -500,7 +509,7 @@ TEST_F(TrustedSignalsTest, BiddingSignalsEscapeQueryParams) { } TEST_F(TrustedSignalsTest, ScoringSignalsEscapeQueryParams) { - std::unique_ptr signals = + scoped_refptr signals = FetchScoringSignalsWithResponse( GURL("https://url.test/?hostname=pub+li%26sher" "&renderUrls=https%3A%2F%2Ffoo.test%2F%3F%26%3D" @@ -540,7 +549,7 @@ TEST_F(TrustedSignalsTest, BiddingSignalsDeleteBeforeCallback) { auto bidding_signals = TrustedSignals::LoadBiddingSignals( &url_loader_factory_, {"key1"}, "publisher", base_url_, v8_helper_, - base::BindOnce([](std::unique_ptr result, + base::BindOnce([](scoped_refptr result, absl::optional error_msg) { ADD_FAILURE() << "Callback should not be invoked since loader deleted"; })); @@ -564,7 +573,7 @@ TEST_F(TrustedSignalsTest, ScoringSignalsDeleteBeforeCallback) { &url_loader_factory_, /*render_urls=*/{"http://foo.test/"}, /*ad_component_render_urls=*/{}, "publisher", base_url_, v8_helper_, - base::BindOnce([](std::unique_ptr result, + base::BindOnce([](scoped_refptr result, absl::optional error_msg) { ADD_FAILURE() << "Callback should not be invoked since loader deleted"; })); From f591619833631921fa92314c13876aa3f076029e Mon Sep 17 00:00:00 2001 From: Juan Mojica Date: Wed, 15 Dec 2021 00:53:59 +0000 Subject: [PATCH 30/77] Reland "Launch Lens image search on desktop at ToT." This fixes the broken unit test by enabling the feature param directly in the test. This does not fix any aforementioned latency issues. This is a reland of b6f385ec3db1423580cabd0f5a2e4281c341cd44 Original change's description: > Launch Lens image search on desktop at ToT. > > Also disables the image search URL change if on a non-desktop platform. > > Bug: 1168677, 1222313 > Change-Id: I855e3838e344ba260b8829a2f8cc355f2c31e2da > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3328902 > Reviewed-by: Ben Goldberger > Reviewed-by: Orin Jaworski > Commit-Queue: Juan Mojica > Cr-Commit-Position: refs/heads/main@{#951102} Bug: 1168677, 1222313 Change-Id: I5af7d720dbff2f9ee13422758fa094a03a40b896 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3338437 Reviewed-by: Orin Jaworski Reviewed-by: Ben Goldberger Commit-Queue: Juan Mojica Cr-Commit-Position: refs/heads/main@{#951753} --- .../views/lens/lens_side_panel_controller_unittest.cc | 8 +++++--- components/lens/lens_features.cc | 10 +++++----- components/search_engines/search_terms_data.cc | 4 ++++ 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/chrome/browser/ui/views/lens/lens_side_panel_controller_unittest.cc b/chrome/browser/ui/views/lens/lens_side_panel_controller_unittest.cc index 1a82b6b1f13a19..d46e5f87518ad0 100644 --- a/chrome/browser/ui/views/lens/lens_side_panel_controller_unittest.cc +++ b/chrome/browser/ui/views/lens/lens_side_panel_controller_unittest.cc @@ -29,9 +29,11 @@ class LensSidePanelControllerTest : public TestWithBrowserView { public: void SetUp() override { base::test::ScopedFeatureList features; - features.InitWithFeatures( - {features::kLensRegionSearch, ::features::kSidePanel, - reading_list::switches::kReadLater}, + features.InitWithFeaturesAndParameters( + {{features::kLensRegionSearch, + {{"region-search-enable-side-panel", "true"}}}, + {::features::kSidePanel, {}}, + {reading_list::switches::kReadLater, {}}}, {}); TestWithBrowserView::SetUp(); // Create the lens side panel controller in BrowserView. diff --git a/components/lens/lens_features.cc b/components/lens/lens_features.cc index 485403bab34a99..82b1d47da9dc67 100644 --- a/components/lens/lens_features.cc +++ b/components/lens/lens_features.cc @@ -11,10 +11,10 @@ namespace lens { namespace features { const base::Feature kLensStandalone{"LensStandalone", - base::FEATURE_DISABLED_BY_DEFAULT}; + base::FEATURE_ENABLED_BY_DEFAULT}; const base::Feature kLensRegionSearch{"LensRegionSearch", - base::FEATURE_DISABLED_BY_DEFAULT}; + base::FEATURE_ENABLED_BY_DEFAULT}; const base::FeatureParam kRegionSearchMacCursorFix{ &kLensRegionSearch, "region-search-mac-cursor-fix", true}; @@ -32,13 +32,13 @@ const base::FeatureParam kRegionSearchUseMenuItemAltText4{ &kLensRegionSearch, "use-menu-item-alt-text-4", true}; const base::FeatureParam kEnableUKMLoggingForRegionSearch{ - &kLensRegionSearch, "region-search-enable-ukm-logging", false}; + &kLensRegionSearch, "region-search-enable-ukm-logging", true}; const base::FeatureParam kEnableUKMLoggingForImageSearch{ - &kLensStandalone, "enable-ukm-logging", false}; + &kLensStandalone, "enable-ukm-logging", true}; const base::FeatureParam kEnableSidePanelForLensRegionSearch{ - &kLensRegionSearch, "region-search-enable-side-panel", true}; + &kLensRegionSearch, "region-search-enable-side-panel", false}; const base::FeatureParam kEnableSidePanelForLensImageSearch{ &kLensStandalone, "enable-side-panel", false}; diff --git a/components/search_engines/search_terms_data.cc b/components/search_engines/search_terms_data.cc index 52eca7441d866f..cd86498aa8a029 100644 --- a/components/search_engines/search_terms_data.cc +++ b/components/search_engines/search_terms_data.cc @@ -5,6 +5,7 @@ #include "components/search_engines/search_terms_data.h" #include "base/check.h" +#include "build/build_config.h" #include "components/google/core/common/google_util.h" #include "components/lens/lens_features.h" #include "url/gurl.h" @@ -20,6 +21,7 @@ std::string SearchTermsData::GoogleBaseURLValue() const { std::string SearchTermsData::GoogleBaseSearchByImageURLValue() const { const std::string kGoogleHomepageURLPath = std::string("searchbyimage/"); +#if !defined(OS_IOS) && !defined(OS_ANDROID) // If both LensStandalone and LensRegionSearch features are enabled, // LensStandalone parameters will take precedence even if the values differ. if (base::FeatureList::IsEnabled(lens::features::kLensStandalone)) { @@ -27,6 +29,8 @@ std::string SearchTermsData::GoogleBaseSearchByImageURLValue() const { } else if (base::FeatureList::IsEnabled(lens::features::kLensRegionSearch)) { return lens::features::GetHomepageURLForRegionSearch(); } +#endif // !defined(OS_IOS) && !defined(OS_ANDROID) + return google_util::kGoogleHomepageURL + kGoogleHomepageURLPath; } From d46541cc69f68de0eecaf0d59de5df1aec3529cb Mon Sep 17 00:00:00 2001 From: Xianzhu Wang Date: Wed, 15 Dec 2021 00:55:39 +0000 Subject: [PATCH 31/77] SVG foreignObject should not be treated as stacked in CullRectUpdater For now LayoutSVGForeignObject's IsStacked() is true, so we need to check !IsReplacedNormalFlowStacking() to exclude foreign objects from stacked children. TODO: We need to let IsStacked() of LayoutSVGForeignObject return false, but we need thorough tests when doing that. Bug: 1275018, 1279797 Change-Id: I82bc7a75dfbe377cd1254c82df33cb896b67aa7a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3338499 Auto-Submit: Xianzhu Wang Reviewed-by: Philip Rogers Commit-Queue: Xianzhu Wang Cr-Commit-Position: refs/heads/main@{#951754} --- .../core/layout/layout_tree_as_text.cc | 10 ++++-- .../renderer/core/paint/cull_rect_updater.cc | 9 +++-- .../core/paint/cull_rect_updater_test.cc | 36 +++++++++++++++++++ 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/third_party/blink/renderer/core/layout/layout_tree_as_text.cc b/third_party/blink/renderer/core/layout/layout_tree_as_text.cc index 6693529babf106..751d5d50db598c 100644 --- a/third_party/blink/renderer/core/layout/layout_tree_as_text.cc +++ b/third_party/blink/renderer/core/layout/layout_tree_as_text.cc @@ -669,8 +669,14 @@ static void Write(WTF::TextStream& ts, layer.GetLayoutObject().StyleRef().GetBlendMode()); } - if ((behavior & kLayoutAsTextShowPaintProperties) && layer.SelfNeedsRepaint()) - ts << " needsRepaint"; + if (behavior & kLayoutAsTextShowPaintProperties) { + if (layer.SelfOrDescendantNeedsRepaint()) + ts << " needsRepaint"; + if (layer.NeedsCullRectUpdate()) + ts << " needsCullRectUpdate"; + if (layer.DescendantNeedsCullRectUpdate()) + ts << " descendantNeedsCullRectUpdate"; + } ts << "\n"; diff --git a/third_party/blink/renderer/core/paint/cull_rect_updater.cc b/third_party/blink/renderer/core/paint/cull_rect_updater.cc index 68285c590c1390..e1602322ac0c48 100644 --- a/third_party/blink/renderer/core/paint/cull_rect_updater.cc +++ b/third_party/blink/renderer/core/paint/cull_rect_updater.cc @@ -178,7 +178,8 @@ void CullRectUpdater::UpdateForDescendants(PaintLayer& layer, // different from PaintLayerPaintOrderIterator(kAllChildren) which iterates // children in paint order. for (auto* child = layer.FirstChild(); child; child = child->NextSibling()) { - if (child->GetLayoutObject().IsStacked()) { + if (!child->IsReplacedNormalFlowStacking() && + child->GetLayoutObject().IsStacked()) { // In the above example, during UpdateForDescendants(child), this // forces cull rect update of |stacked-child| which will be updated in // the next loop during UpdateForDescendants(layer). @@ -191,8 +192,10 @@ void CullRectUpdater::UpdateForDescendants(PaintLayer& layer, // Then stacked children (which may not be direct children in PaintLayer // hierarchy) in paint order. PaintLayerPaintOrderIterator iterator(&layer, kStackedChildren); - while (PaintLayer* child = iterator.Next()) - UpdateRecursively(*child, layer, force_update_children); + while (PaintLayer* child = iterator.Next()) { + if (!child->IsReplacedNormalFlowStacking()) + UpdateRecursively(*child, layer, force_update_children); + } } bool CullRectUpdater::UpdateForSelf(PaintLayer& layer, diff --git a/third_party/blink/renderer/core/paint/cull_rect_updater_test.cc b/third_party/blink/renderer/core/paint/cull_rect_updater_test.cc index 6f3c856ae4ec14..b80f78c2790db3 100644 --- a/third_party/blink/renderer/core/paint/cull_rect_updater_test.cc +++ b/third_party/blink/renderer/core/paint/cull_rect_updater_test.cc @@ -142,4 +142,40 @@ TEST_F(CullRectUpdaterTest, StackedChildOfNonStackingContextScroller) { EXPECT_EQ(gfx::Rect(0, 2800, 200, 4200), GetCullRect("child").Rect()); } +TEST_F(CullRectUpdaterTest, SVGForeignObject) { + GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(false); + SetBodyInnerHTML(R"HTML( +
+ + +
Child
+
+
+
+ )HTML"); + + auto* child = GetPaintLayerByElementId("child"); + auto* foreign = GetPaintLayerByElementId("foreign"); + auto* svg = GetPaintLayerByElementId("svg"); + EXPECT_FALSE(child->NeedsCullRectUpdate()); + EXPECT_FALSE(foreign->DescendantNeedsCullRectUpdate()); + EXPECT_FALSE(svg->DescendantNeedsCullRectUpdate()); + + GetDocument().getElementById("scroller")->scrollTo(0, 500); + UpdateAllLifecyclePhasesForTest(); + EXPECT_FALSE(child->NeedsCullRectUpdate()); + EXPECT_FALSE(foreign->DescendantNeedsCullRectUpdate()); + EXPECT_FALSE(svg->DescendantNeedsCullRectUpdate()); + + child->SetNeedsCullRectUpdate(); + EXPECT_TRUE(child->NeedsCullRectUpdate()); + EXPECT_TRUE(foreign->DescendantNeedsCullRectUpdate()); + EXPECT_TRUE(svg->DescendantNeedsCullRectUpdate()); + + UpdateAllLifecyclePhasesForTest(); + EXPECT_FALSE(child->NeedsCullRectUpdate()); + EXPECT_FALSE(foreign->DescendantNeedsCullRectUpdate()); + EXPECT_FALSE(svg->DescendantNeedsCullRectUpdate()); +} + } // namespace blink From 7bf66426124a524d6a24cd650f6b8cf913c4a704 Mon Sep 17 00:00:00 2001 From: Addison Luh Date: Wed, 15 Dec 2021 00:55:56 +0000 Subject: [PATCH 32/77] Fix memory leak in CupsPrintersManagerTest. Found via lsan. Bug: 1280040 Change-Id: I5bc64491519f23c090750da366b89012f0feba2e Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3340231 Commit-Queue: Addison Luh Auto-Submit: Addison Luh Reviewed-by: Sean Kau Commit-Queue: Sean Kau Cr-Commit-Position: refs/heads/main@{#951755} --- .../chromeos/printing/cups_printers_manager_unittest.cc | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/chrome/browser/chromeos/printing/cups_printers_manager_unittest.cc b/chrome/browser/chromeos/printing/cups_printers_manager_unittest.cc index febee56c4d2a9b..09be74d4912506 100644 --- a/chrome/browser/chromeos/printing/cups_printers_manager_unittest.cc +++ b/chrome/browser/chromeos/printing/cups_printers_manager_unittest.cc @@ -391,7 +391,11 @@ class CupsPrintersManagerTest : public testing::Test, manager_->AddObserver(this); } - ~CupsPrintersManagerTest() override {} + ~CupsPrintersManagerTest() override { + // Fast forwarding so that delayed tasks like |SendScannerCountToUMA| will + // run and not leak memory in unused callbacks. + task_environment_.FastForwardUntilNoTasksRemain(); + } // CupsPrintersManager::Observer implementation void OnPrintersChanged(PrinterClass printer_class, @@ -431,7 +435,8 @@ class CupsPrintersManagerTest : public testing::Test, // See // //docs/threading_and_tasks_testing.md#mainthreadtype-trait content::BrowserTaskEnvironment task_environment_{ - base::test::TaskEnvironment::MainThreadType::IO}; + base::test::TaskEnvironment::MainThreadType::IO, + base::test::TaskEnvironment::TimeSource::MOCK_TIME}; // Captured printer lists from observer callbacks. base::flat_map> observed_printers_; From 945f6dad867e8c59460b37047a72b0659a468c83 Mon Sep 17 00:00:00 2001 From: Mike Dougherty Date: Wed, 15 Dec 2021 01:03:00 +0000 Subject: [PATCH 33/77] Remove IOS Tab Grid Bulk Actions flag The BulkActions feature is launching to 100% so the flag can be removed. Change-Id: Ic7ed01a4520a39c81fc74f23bf16d2593a0409b9 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3248360 Reviewed-by: Sylvain Defresne Commit-Queue: Mike Dougherty Cr-Commit-Position: refs/heads/main@{#951756} --- chrome/browser/flag-metadata.json | 5 -- ios/chrome/browser/flags/about_flags.mm | 3 - .../flags/ios_chrome_flag_descriptions.cc | 5 -- .../flags/ios_chrome_flag_descriptions.h | 4 - .../ui/tab_switcher/tab_grid/features.h | 6 -- .../ui/tab_switcher/tab_grid/features.mm | 7 -- .../tab_switcher/tab_grid/grid/grid_cell.mm | 18 ++--- .../tab_grid/grid/grid_context_menu_helper.mm | 10 +-- .../tab_grid/tab_grid_bottom_toolbar.mm | 46 ++++++------ .../tab_switcher/tab_grid/tab_grid_egtest.mm | 73 ------------------- .../tab_grid/tab_grid_top_toolbar.mm | 59 +++++++-------- .../tab_grid/tab_grid_view_controller.mm | 3 +- ios/chrome/test/earl_grey/chrome_earl_grey.h | 3 - ios/chrome/test/earl_grey/chrome_earl_grey.mm | 19 ++--- .../chrome_earl_grey_app_interface.h | 3 - .../chrome_earl_grey_app_interface.mm | 5 -- .../variations/fieldtrial_testing_config.json | 15 ---- 17 files changed, 67 insertions(+), 217 deletions(-) diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json index 9ee86a5a33a0dc..ca7af70ec0f0a9 100644 --- a/chrome/browser/flag-metadata.json +++ b/chrome/browser/flag-metadata.json @@ -5309,11 +5309,6 @@ "owners": ["elainechien", "romanarora"], "expiry_milestone": 110 }, - { - "name": "tabs-bulkactions-ios", - "owners": [ "mrefaat", "michaeldo", "bling-flags@google.com" ], - "expiry_milestone": 98 - }, { "name": "tabs-search-ios", "owners": [ "mrefaat", "michaeldo", "bling-flags@google.com" ], diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm index 742de13cc9b087..2579b1dc595312 100644 --- a/ios/chrome/browser/flags/about_flags.mm +++ b/ios/chrome/browser/flags/about_flags.mm @@ -594,9 +594,6 @@ kInterestFeedV2ClickAndViewActionsConditionalUploadDescription, flags_ui::kOsIos, FEATURE_VALUE_TYPE(feed::kInterestFeedV2ClicksAndViewsConditionalUpload)}, - {"tabs-bulkactions-ios", flag_descriptions::kTabsBulkActionsName, - flag_descriptions::kTabsBulkActionsDescription, flags_ui::kOsIos, - FEATURE_VALUE_TYPE(kTabsBulkActions)}, {"tabs-search-ios", flag_descriptions::kTabsSearchName, flag_descriptions::kTabsSearchDescription, flags_ui::kOsIos, FEATURE_VALUE_TYPE(kTabsSearch)}, diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc index 4a2bdf9c5c4aed..173a66e2898fcf 100644 --- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc +++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc @@ -491,11 +491,6 @@ const char kStartSurfaceDescription[] = "Enable showing the Start Surface when launching Chrome via clicking the " "icon or the app switcher."; -const char kTabsBulkActionsName[] = "Enable Tab Grid Bulk Actions"; -const char kTabsBulkActionsDescription[] = - "Enables the selection mode in the Tab grid where users can perform " - "actions on multiple tabs at once for iOS 13 and above."; - const char kTabsSearchName[] = "Enable Tabs Search"; const char kTabsSearchDescription[] = "Enables the search mode in the Tab grid where users can search open tabs " diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h index 6a63ee9b29e89a..54b559148a0ed0 100644 --- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h +++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h @@ -442,10 +442,6 @@ extern const char kSyncTrustedVaultPassphrasePromoDescription[]; extern const char kSyncTrustedVaultPassphraseRecoveryName[]; extern const char kSyncTrustedVaultPassphraseRecoveryDescription[]; -// Title and description for the flag to enable tabs bulk actions feature. -extern const char kTabsBulkActionsName[]; -extern const char kTabsBulkActionsDescription[]; - // Title and description for the flag to enable tabs search feature. extern const char kTabsSearchName[]; extern const char kTabsSearchDescription[]; diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/features.h b/ios/chrome/browser/ui/tab_switcher/tab_grid/features.h index 6b1d23ba0c0145..62769bbd7294fa 100644 --- a/ios/chrome/browser/ui/tab_switcher/tab_grid/features.h +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/features.h @@ -7,15 +7,9 @@ #include "base/feature_list.h" -// Feature flag to enable Bulk Actions. -extern const base::Feature kTabsBulkActions; - // Feature flag to enable Tabs Search. extern const base::Feature kTabsSearch; -// Whether the kTabsBulkActions flag is enabled. -bool IsTabsBulkActionsEnabled(); - // Whether the kTabsSearch flag is enabled. bool IsTabsSearchEnabled(); diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/features.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/features.mm index 58faa21e7707e7..95276d4d0736de 100644 --- a/ios/chrome/browser/ui/tab_switcher/tab_grid/features.mm +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/features.mm @@ -8,16 +8,9 @@ #error "This file requires ARC support." #endif -const base::Feature kTabsBulkActions{"TabsBulkActions", - base::FEATURE_ENABLED_BY_DEFAULT}; - const base::Feature kTabsSearch{"TabsSearch", base::FEATURE_DISABLED_BY_DEFAULT}; -bool IsTabsBulkActionsEnabled() { - return base::FeatureList::IsEnabled(kTabsBulkActions); -} - bool IsTabsSearchEnabled() { return base::FeatureList::IsEnabled(kTabsSearch); } diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_cell.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_cell.mm index 87dba22c9288d2..e11625a60ef53f 100644 --- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_cell.mm +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_cell.mm @@ -223,7 +223,7 @@ - (BOOL)isAccessibilityElement { } - (NSArray*)accessibilityCustomActions { - if (IsTabsBulkActionsEnabled() && self.isInSelectionMode) { + if (self.isInSelectionMode) { // If the cell is in tab grid selection mode, only allow toggling the // selection state. return nil; @@ -376,17 +376,15 @@ - (UIView*)setupTopBar { closeIconView.image = [[UIImage imageNamed:@"grid_cell_close_button"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - if (IsTabsBulkActionsEnabled()) { - UIImageView* selectIconView = [[UIImageView alloc] init]; - selectIconView.translatesAutoresizingMaskIntoConstraints = NO; - selectIconView.contentMode = UIViewContentModeScaleAspectFit; - selectIconView.hidden = !self.isInSelectionMode; + UIImageView* selectIconView = [[UIImageView alloc] init]; + selectIconView.translatesAutoresizingMaskIntoConstraints = NO; + selectIconView.contentMode = UIViewContentModeScaleAspectFit; + selectIconView.hidden = !self.isInSelectionMode; - selectIconView.image = [self selectIconImageForCurrentState]; + selectIconView.image = [self selectIconImageForCurrentState]; - [topBar addSubview:selectIconView]; - _selectIconView = selectIconView; - } + [topBar addSubview:selectIconView]; + _selectIconView = selectIconView; [topBar addSubview:iconView]; [topBar addSubview:activityIndicator]; diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_context_menu_helper.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_context_menu_helper.mm index 3ba046135a7355..44cc1df9777f74 100644 --- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_context_menu_helper.mm +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_context_menu_helper.mm @@ -150,12 +150,10 @@ - (UIContextMenuConfiguration*)contextMenuConfigurationForGridCell: } } - if (IsTabsBulkActionsEnabled()) { - if ([self.contextMenuDelegate respondsToSelector:@selector(selectTabs)]) { - [menuElements addObject:[actionFactory actionToSelectTabsWithBlock:^{ - [self.contextMenuDelegate selectTabs]; - }]]; - } + if ([self.contextMenuDelegate respondsToSelector:@selector(selectTabs)]) { + [menuElements addObject:[actionFactory actionToSelectTabsWithBlock:^{ + [self.contextMenuDelegate selectTabs]; + }]]; } if ([self.contextMenuDelegate diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.mm index 898baa65b86ba8..2052733b95cec5 100644 --- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.mm +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.mm @@ -98,7 +98,6 @@ - (void)setPage:(TabGridPage)page { - (void)setMode:(TabGridMode)mode { if (_mode == mode) return; - DCHECK(IsTabsBulkActionsEnabled() || mode == TabGridModeNormal); _mode = mode; // Reset selected tabs count when mode changes. self.selectedTabsCount = 0; @@ -262,28 +261,26 @@ - (void)setupViews { [[UIBarButtonItem alloc] initWithCustomView:_smallNewTabButton]; // Create selection mode buttons - if (IsTabsBulkActionsEnabled()) { - _editButton = [[UIBarButtonItem alloc] init]; - _editButton.tintColor = UIColorFromRGB(kTabGridToolbarTextButtonColor); - _editButton.title = l10n_util::GetNSString(IDS_IOS_TAB_GRID_EDIT_BUTTON); - _editButton.accessibilityIdentifier = kTabGridEditButtonIdentifier; - - _addToButton = [[UIBarButtonItem alloc] init]; - _addToButton.tintColor = UIColorFromRGB(kTabGridToolbarTextButtonColor); - _addToButton.title = l10n_util::GetNSString(IDS_IOS_TAB_GRID_ADD_TO_BUTTON); - _addToButton.accessibilityIdentifier = kTabGridEditAddToButtonIdentifier; - _shareButton = [[UIBarButtonItem alloc] - initWithBarButtonSystemItem:UIBarButtonSystemItemAction - target:nil - action:nil]; - _shareButton.tintColor = UIColorFromRGB(kTabGridToolbarTextButtonColor); - _shareButton.accessibilityIdentifier = kTabGridEditShareButtonIdentifier; - _closeTabsButton = [[UIBarButtonItem alloc] init]; - _closeTabsButton.tintColor = UIColorFromRGB(kTabGridToolbarTextButtonColor); - _closeTabsButton.accessibilityIdentifier = - kTabGridEditCloseTabsButtonIdentifier; - [self updateCloseTabsButtonTitle]; - } + _editButton = [[UIBarButtonItem alloc] init]; + _editButton.tintColor = UIColorFromRGB(kTabGridToolbarTextButtonColor); + _editButton.title = l10n_util::GetNSString(IDS_IOS_TAB_GRID_EDIT_BUTTON); + _editButton.accessibilityIdentifier = kTabGridEditButtonIdentifier; + + _addToButton = [[UIBarButtonItem alloc] init]; + _addToButton.tintColor = UIColorFromRGB(kTabGridToolbarTextButtonColor); + _addToButton.title = l10n_util::GetNSString(IDS_IOS_TAB_GRID_ADD_TO_BUTTON); + _addToButton.accessibilityIdentifier = kTabGridEditAddToButtonIdentifier; + _shareButton = [[UIBarButtonItem alloc] + initWithBarButtonSystemItem:UIBarButtonSystemItemAction + target:nil + action:nil]; + _shareButton.tintColor = UIColorFromRGB(kTabGridToolbarTextButtonColor); + _shareButton.accessibilityIdentifier = kTabGridEditShareButtonIdentifier; + _closeTabsButton = [[UIBarButtonItem alloc] init]; + _closeTabsButton.tintColor = UIColorFromRGB(kTabGridToolbarTextButtonColor); + _closeTabsButton.accessibilityIdentifier = + kTabGridEditCloseTabsButtonIdentifier; + [self updateCloseTabsButtonTitle]; _compactConstraints = @[ [_toolbar.topAnchor constraintEqualToAnchor:self.topAnchor], @@ -347,7 +344,6 @@ - (void)updateLayout { -kTabGridFloatingButtonVerticalInset; if (self.mode == TabGridModeSelection) { - DCHECK(IsTabsBulkActionsEnabled()); [_toolbar setItems:@[ _closeTabsButton, _spaceItem, _shareButton, _spaceItem, _addToButton ]]; @@ -358,7 +354,7 @@ - (void)updateLayout { return; } UIBarButtonItem* leadingButton = _closeAllOrUndoButton; - if (IsTabsBulkActionsEnabled() && !_undoActive) + if (!_undoActive) leadingButton = _editButton; UIBarButtonItem* trailingButton = _doneButton; diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_egtest.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_egtest.mm index ad6b405a6d9277..1f69295f9d67ea 100644 --- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_egtest.mm +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_egtest.mm @@ -98,28 +98,6 @@ @implementation TabGridTestCase - (AppLaunchConfiguration)appConfigurationForTestCase { AppLaunchConfiguration config; - // Features are enabled or disabled based on the name of the test that is - // running. This is done because it is inefficient to use - // ensureAppLaunchedWithConfiguration for each test. - if ([self isRunningTest:@selector(testCloseAllAndUndoCloseAll)] || - [self isRunningTest:@selector - (testUndoCloseAllNotAvailableAfterNewTabCreation)] || - [self isRunningTest:@selector(testTabGridBulkActionCloseTabs)] || - [self isRunningTest:@selector(testTabGridBulkActionDeselectAll)] || - [self isRunningTest:@selector(testTabGridBulkActionSelectAll)] || - [self isRunningTest:@selector(testTabGridBulkActionAddToBookmarks)] || - [self isRunningTest:@selector(testTabGridBulkActionAddToReadingList)] || - [self isRunningTest:@selector(testTabGridBulkActionShare)]) { - config.features_enabled.push_back(kTabsBulkActions); - } else if ( - [self isRunningTest:@selector - (testCloseAllAndUndoCloseAll_BulkActionsDisabled)] || - [self isRunningTest:@selector - (testUndoCloseAllNotAvailableAfterNewTabCreation_BulkActionsDisabled - )]) { - config.features_disabled.push_back(kTabsBulkActions); - } - config.features_disabled.push_back(kStartSurface); return config; @@ -175,33 +153,6 @@ - (void)testClosingFirstCell { assertWithMatcher:grey_sufficientlyVisible()]; } -// Tests that tapping Close All shows no tabs, shows Undo button, and displays -// the empty state. Then tests tapping Undo shows Close All button again. -// Validates this case when Tab Grid Bulk Actions feature is disabled. -- (void)testCloseAllAndUndoCloseAll_BulkActionsDisabled { - [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()] - performAction:grey_tap()]; - [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCloseAllButton()] - performAction:grey_tap()]; - [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(0)] - assertWithMatcher:grey_nil()]; - [[EarlGrey - selectElementWithMatcher:chrome_test_util::TabGridUndoCloseAllButton()] - assertWithMatcher:grey_sufficientlyVisible()]; - [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCloseAllButton()] - assertWithMatcher:grey_nil()]; - [[EarlGrey selectElementWithMatcher:chrome_test_util:: - TabGridRegularTabsEmptyStateView()] - assertWithMatcher:grey_sufficientlyVisible()]; - [[EarlGrey - selectElementWithMatcher:chrome_test_util::TabGridUndoCloseAllButton()] - performAction:grey_tap()]; - [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(0)] - assertWithMatcher:grey_sufficientlyVisible()]; - [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCloseAllButton()] - assertWithMatcher:grey_sufficientlyVisible()]; -} - // Tests that tapping Close All shows no tabs, shows Undo button, and displays // the empty state. Then tests tapping Undo shows Close All button again. // Validates this case when Tab Grid Bulk Actions feature is enabled. @@ -242,30 +193,6 @@ - (void)testCloseAllAndUndoCloseAll { assertWithMatcher:grey_sufficientlyVisible()]; } -// Tests that the Undo button is no longer available after tapping Close All, -// then creating a new tab, then coming back to the tab grid. -// Validates this case when Tab Grid Bulk Actions feature is disabled. -- (void)testUndoCloseAllNotAvailableAfterNewTabCreation_BulkActionsDisabled { - [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()] - performAction:grey_tap()]; - [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCloseAllButton()] - performAction:grey_tap()]; - [[EarlGrey - selectElementWithMatcher:chrome_test_util::TabGridUndoCloseAllButton()] - assertWithMatcher:grey_sufficientlyVisible()]; - // Create a new tab then come back to tab grid. - [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridNewTabButton()] - performAction:grey_tap()]; - [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()] - performAction:grey_tap()]; - // Undo is no longer available. - [[EarlGrey - selectElementWithMatcher:chrome_test_util::TabGridUndoCloseAllButton()] - assertWithMatcher:grey_nil()]; - [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCloseAllButton()] - assertWithMatcher:grey_sufficientlyVisible()]; -} - // Tests that the Undo button is no longer available after tapping Close All, // then creating a new tab, then coming back to the tab grid. // Validates this case when Tab Grid Bulk Actions feature is enabled. diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_top_toolbar.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_top_toolbar.mm index 825d1a767fdfb1..c743885658901a 100644 --- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_top_toolbar.mm +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_top_toolbar.mm @@ -275,7 +275,7 @@ - (void)setItemsForTraitCollection:(UITraitCollection*)traitCollection { } // In Landscape normal mode leading button is always "closeAll", or "Edit" if // bulk actions feature is enabled. - if (IsTabsBulkActionsEnabled() && !_undoActive) + if (!_undoActive) _leadingButton = _editButton; else _leadingButton = _closeAllOrUndoButton; @@ -290,7 +290,7 @@ - (void)setItemsForTraitCollection:(UITraitCollection*)traitCollection { return; } - if (IsTabsBulkActionsEnabled() && _mode == TabGridModeSelection) { + if (_mode == TabGridModeSelection) { // In the selection mode, Done button is much smaller than SelectAll // we need to calculate the difference on the width and use it as a // fixed space to make sure that the title is still centered. @@ -353,37 +353,34 @@ - (void)setupViews { _doneButton.accessibilityIdentifier = kTabGridDoneButtonIdentifier; _doneButton.title = l10n_util::GetNSString(IDS_IOS_TAB_GRID_DONE_BUTTON); - if (IsTabsBulkActionsEnabled()) { - _editButton = [[UIBarButtonItem alloc] init]; - _editButton.tintColor = UIColorFromRGB(kTabGridToolbarTextButtonColor); - _editButton.title = l10n_util::GetNSString(IDS_IOS_TAB_GRID_EDIT_BUTTON); - _editButton.accessibilityIdentifier = kTabGridEditButtonIdentifier; + _editButton = [[UIBarButtonItem alloc] init]; + _editButton.tintColor = UIColorFromRGB(kTabGridToolbarTextButtonColor); + _editButton.title = l10n_util::GetNSString(IDS_IOS_TAB_GRID_EDIT_BUTTON); + _editButton.accessibilityIdentifier = kTabGridEditButtonIdentifier; - _selectAllButton = [[UIBarButtonItem alloc] init]; - _selectAllButton.tintColor = UIColorFromRGB(kTabGridToolbarTextButtonColor); - _selectAllButton.title = - l10n_util::GetNSString(IDS_IOS_TAB_GRID_SELECT_ALL_BUTTON); - _selectAllButton.accessibilityIdentifier = - kTabGridEditSelectAllButtonIdentifier; - - _selectedTabsItem = [[UIBarButtonItem alloc] init]; - _selectedTabsItem.title = - l10n_util::GetNSString(IDS_IOS_TAB_GRID_SELECT_TABS_TITLE); - _selectedTabsItem.tintColor = - UIColorFromRGB(kTabGridToolbarTextButtonColor); - _selectedTabsItem.action = nil; - _selectedTabsItem.target = nil; - _selectedTabsItem.enabled = NO; - [_selectedTabsItem setTitleTextAttributes:@{ - NSForegroundColorAttributeName : - UIColorFromRGB(kTabGridToolbarTextButtonColor), - NSFontAttributeName : [[UIFontMetrics - metricsForTextStyle:UIFontTextStyleBody] - scaledFontForFont:[UIFont systemFontOfSize:kSelectionModeButtonSize - weight:UIFontWeightSemibold]] - } - forState:UIControlStateDisabled]; + _selectAllButton = [[UIBarButtonItem alloc] init]; + _selectAllButton.tintColor = UIColorFromRGB(kTabGridToolbarTextButtonColor); + _selectAllButton.title = + l10n_util::GetNSString(IDS_IOS_TAB_GRID_SELECT_ALL_BUTTON); + _selectAllButton.accessibilityIdentifier = + kTabGridEditSelectAllButtonIdentifier; + + _selectedTabsItem = [[UIBarButtonItem alloc] init]; + _selectedTabsItem.title = + l10n_util::GetNSString(IDS_IOS_TAB_GRID_SELECT_TABS_TITLE); + _selectedTabsItem.tintColor = UIColorFromRGB(kTabGridToolbarTextButtonColor); + _selectedTabsItem.action = nil; + _selectedTabsItem.target = nil; + _selectedTabsItem.enabled = NO; + [_selectedTabsItem setTitleTextAttributes:@{ + NSForegroundColorAttributeName : + UIColorFromRGB(kTabGridToolbarTextButtonColor), + NSFontAttributeName : + [[UIFontMetrics metricsForTextStyle:UIFontTextStyleBody] + scaledFontForFont:[UIFont systemFontOfSize:kSelectionModeButtonSize + weight:UIFontWeightSemibold]] } + forState:UIControlStateDisabled]; if (IsTabsSearchEnabled()) { _searchButton = [[UIBarButtonItem alloc] diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm index b1d4a44d2e197e..8523124ccbf6f1 100644 --- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm @@ -254,8 +254,7 @@ - (void)viewDidLoad { [self setupTopToolbar]; [self setupBottomToolbar]; - if (IsTabsBulkActionsEnabled()) - [self setupEditButton]; + [self setupEditButton]; // Hide the toolbars and the floating button, so they can fade in the first // time there's a transition into this view controller. diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.h b/ios/chrome/test/earl_grey/chrome_earl_grey.h index 1ead32f9a9ca18..d56854eae792da 100644 --- a/ios/chrome/test/earl_grey/chrome_earl_grey.h +++ b/ios/chrome/test/earl_grey/chrome_earl_grey.h @@ -643,9 +643,6 @@ UIWindow* GetAnyKeyWindow(); // Returns whether the ContextMenuActionsRefresh feature is enabled. - (BOOL)isContextMenuActionsRefreshEnabled; -// Returns whether the TabGridBulkActions feature is enabled. -- (BOOL)isTabGridBulkActionsEnabled; - #pragma mark - Popup Blocking // Gets the current value of the popup content setting preference for the diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.mm b/ios/chrome/test/earl_grey/chrome_earl_grey.mm index 0d0e984653ac5d..3713de59c1696a 100644 --- a/ios/chrome/test/earl_grey/chrome_earl_grey.mm +++ b/ios/chrome/test/earl_grey/chrome_earl_grey.mm @@ -681,16 +681,11 @@ - (void)purgeCachedWebViewPages { - (void)triggerRestoreViaTabGridRemoveAllUndo { [ChromeEarlGrey showTabSwitcher]; GREYWaitForAppToIdle(@"App failed to idle"); - if ([self isTabGridBulkActionsEnabled]) { - [ChromeEarlGrey - waitForAndTapButton:grey_allOf(chrome_test_util::TabGridEditButton(), - grey_sufficientlyVisible(), nil)]; - [ChromeEarlGrey - waitForAndTapButton:chrome_test_util::TabGridEditMenuCloseAllButton()]; - } else { - [ChromeEarlGrey - waitForAndTapButton:chrome_test_util::TabGridCloseAllButton()]; - } + [ChromeEarlGrey + waitForAndTapButton:grey_allOf(chrome_test_util::TabGridEditButton(), + grey_sufficientlyVisible(), nil)]; + [ChromeEarlGrey + waitForAndTapButton:chrome_test_util::TabGridEditMenuCloseAllButton()]; [ChromeEarlGrey waitForAndTapButton:chrome_test_util::TabGridUndoCloseAllButton()]; [ChromeEarlGrey waitForAndTapButton:chrome_test_util::TabGridDoneButton()]; @@ -1221,10 +1216,6 @@ - (BOOL)isContextMenuActionsRefreshEnabled { return [ChromeEarlGreyAppInterface isContextMenuActionsRefreshEnabled]; } -- (BOOL)isTabGridBulkActionsEnabled { - return [ChromeEarlGreyAppInterface isTabGridBulkActionsEnabled]; -} - #pragma mark - ScopedBlockPopupsPref - (ContentSetting)popupPrefValue { diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h index eeb3f92e20b350..ef0872e1792cae 100644 --- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h +++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h @@ -512,9 +512,6 @@ // Returns whether the ContextMenuActionsRefresh feature is enabled. + (BOOL)isContextMenuActionsRefreshEnabled; -// Returns whether the TabGridBulkActions feature is enabled. -+ (BOOL)isTabGridBulkActionsEnabled; - #pragma mark - Popup Blocking // Gets the current value of the popup content setting preference for the diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm index 89b07681dc1204..d0ff8acb27bac8 100644 --- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm +++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm @@ -30,7 +30,6 @@ #import "ios/chrome/browser/ui/commands/application_commands.h" #import "ios/chrome/browser/ui/default_promo/default_browser_utils.h" #import "ios/chrome/browser/ui/main/scene_state.h" -#import "ios/chrome/browser/ui/tab_switcher/tab_grid/features.h" #import "ios/chrome/browser/ui/ui_feature_flags.h" #import "ios/chrome/browser/ui/util/named_guide.h" #import "ios/chrome/browser/ui/util/rtl_geometry.h" @@ -1008,10 +1007,6 @@ + (BOOL)isContextMenuActionsRefreshEnabled { return IsContextMenuActionsRefreshEnabled(); } -+ (BOOL)isTabGridBulkActionsEnabled { - return IsTabsBulkActionsEnabled(); -} - #pragma mark - ScopedBlockPopupsPref + (ContentSetting)popupPrefValue { diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json index 46b7d1c639f6af..656617add19215 100644 --- a/testing/variations/fieldtrial_testing_config.json +++ b/testing/variations/fieldtrial_testing_config.json @@ -7684,21 +7684,6 @@ ] } ], - "TabsBulkActions": [ - { - "platforms": [ - "ios" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "TabsBulkActions" - ] - } - ] - } - ], "ToolbarMicIphAndroid": [ { "platforms": [ From ebe7dd43795cb5e9acc1da53c6414ad3c4b2e4b9 Mon Sep 17 00:00:00 2001 From: Moe Ahmadi Date: Wed, 15 Dec 2021 01:04:39 +0000 Subject: [PATCH 34/77] [ZPS] Adds support for prefetch requests to ZeroSuggestProvider Adds support for prefetch requests to ZeroSuggestProvider by overriding ACProvider::StartPrefetch(). The provider differentiates between the prefetch and non-prefetch requests by storing a boolean along with the owned instance of network::SimpleURLLoader. This boolean is used for logging purposes as well as not informing the ACController when the response for prefetch-requests becomes available. The provider uses HTTP caching by communicating the cache duration to RemoteSuggestionsService via SearchTermsArgs iff a positive cache duration is specified. Otherwise it uses the existing caching mechanism which involves storing/using the HTTP response in user prefs. This CL also improves the metrics reported by the provider by adding a new histogram for prefetch requests, logging whether the response is loaded from HTTP cache, and the request round-trip-time which will be used to fine-tune the optimal caching duration. Bug: 1262373 Change-Id: I92af3c4989b0a518659dcad6226e40b7c59ef442 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3319792 Reviewed-by: Justin Donnelly Reviewed-by: Mark Pearson Commit-Queue: Mohamad Ahmadi Cr-Commit-Position: refs/heads/main@{#951757} --- .../omnibox/browser/omnibox_field_trial.h | 6 +- .../omnibox/browser/zero_suggest_provider.cc | 99 +++++--- .../omnibox/browser/zero_suggest_provider.h | 31 ++- .../browser/zero_suggest_provider_unittest.cc | 213 ++++++++++++++---- tools/metrics/histograms/enums.xml | 14 +- .../metadata/omnibox/histograms.xml | 52 +++++ 6 files changed, 335 insertions(+), 80 deletions(-) diff --git a/components/omnibox/browser/omnibox_field_trial.h b/components/omnibox/browser/omnibox_field_trial.h index d940a48e7c11f8..9f5b0321c076c1 100644 --- a/components/omnibox/browser/omnibox_field_trial.h +++ b/components/omnibox/browser/omnibox_field_trial.h @@ -554,7 +554,11 @@ extern const base::FeatureParam // Zero Suggest // Specifies the HTTP cache duration for the zero prefix suggest responses. If // the provided value is a positive number, the cache duration will be sent as a -// query string parameter in the zero suggest requests. +// query string parameter in the zero suggest requests and relayed back in the +// response cache control headers. +// This param is tied to omnibox::kZeroSuggestPrefetching which controls +// prefetching and theoretically works with any caching mechanism. If no valid +// HTTP cache duration is provided the existing caching mechanism is used. extern const base::FeatureParam kZeroSuggestCacheDurationSec; // New params should be inserted above this comment and formatted as: diff --git a/components/omnibox/browser/zero_suggest_provider.cc b/components/omnibox/browser/zero_suggest_provider.cc index 7cb03279628a46..402c2794c2c77d 100644 --- a/components/omnibox/browser/zero_suggest_provider.cc +++ b/components/omnibox/browser/zero_suggest_provider.cc @@ -14,6 +14,7 @@ #include "base/feature_list.h" #include "base/i18n/case_conversion.h" #include "base/json/json_string_value_serializer.h" +#include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" #include "base/metrics/user_metrics.h" #include "base/strings/string_util.h" @@ -68,24 +69,34 @@ enum class ZeroSuggestEligibility { ELIGIBLE_MAX_VALUE }; -// TODO(hfung): The histogram code was copied and modified from -// search_provider.cc. Refactor and consolidate the code. -// We keep track in a histogram how many suggest requests we send, how -// many suggest requests we invalidate (e.g., due to a user typing -// another character), and how many replies we receive. +// Keeps track of how many Suggest requests are sent, how many requests were +// invalidated, e.g., due to user starting to type, how many responses were +// received, and how many of those responses were loaded from the HTTP cache. // These values are written to logs. New enum values can be added, but existing // enums must never be renumbered or deleted and reused. enum ZeroSuggestRequestsHistogramValue { ZERO_SUGGEST_REQUEST_SENT = 1, ZERO_SUGGEST_REQUEST_INVALIDATED = 2, - ZERO_SUGGEST_REPLY_RECEIVED = 3, + ZERO_SUGGEST_RESPONSE_RECEIVED = 3, + ZERO_SUGGEST_RESPONSE_LOADED_FROM_HTTP_CACHE = 4, ZERO_SUGGEST_MAX_REQUEST_HISTOGRAM_VALUE }; void LogOmniboxZeroSuggestRequest( - ZeroSuggestRequestsHistogramValue request_value) { - UMA_HISTOGRAM_ENUMERATION("Omnibox.ZeroSuggestRequests", request_value, - ZERO_SUGGEST_MAX_REQUEST_HISTOGRAM_VALUE); + ZeroSuggestRequestsHistogramValue request_value, + bool is_prefetch) { + base::UmaHistogramEnumeration( + is_prefetch ? "Omnibox.ZeroSuggestRequests.Prefetch" + : "Omnibox.ZeroSuggestRequests.NonPrefetch", + request_value, ZERO_SUGGEST_MAX_REQUEST_HISTOGRAM_VALUE); +} + +void LogOmniboxZeroSuggestRequestRoundTripTime(base::TimeDelta round_trip_time, + bool is_prefetch) { + base::UmaHistogramTimes( + is_prefetch ? "Omnibox.ZeroSuggestRequests.Prefetch.RoundTripTime" + : "Omnibox.ZeroSuggestRequests.NonPrefetch.RoundTripTime", + round_trip_time); } // Relevance value to use if it was not set explicitly by the server. @@ -143,6 +154,16 @@ void ZeroSuggestProvider::RegisterProfilePrefs(PrefRegistrySimple* registry) { void ZeroSuggestProvider::Start(const AutocompleteInput& input, bool minimal_changes) { + Start(input, minimal_changes, /*is_prefetch=*/false); +} + +void ZeroSuggestProvider::StartPrefetch(const AutocompleteInput& input) { + Start(input, /*minimal_changes=*/false, /*is_prefetch=*/true); +} + +void ZeroSuggestProvider::Start(const AutocompleteInput& input, + bool minimal_changes, + bool is_prefetch) { TRACE_EVENT0("omnibox", "ZeroSuggestProvider::Start"); matches_.clear(); Stop(true, false); @@ -166,6 +187,11 @@ void ZeroSuggestProvider::Start(const AutocompleteInput& input, TemplateURLRef::SearchTermsArgs search_terms_args; search_terms_args.page_classification = current_page_classification_; search_terms_args.focus_type = input.focus_type(); + const int cache_duration_sec = + OmniboxFieldTrial::kZeroSuggestCacheDurationSec.Get(); + if (cache_duration_sec > 0) { + search_terms_args.zero_suggest_cache_duration_sec = cache_duration_sec; + } GURL suggest_url = RemoteSuggestionsService::EndpointUrl( search_terms_args, client()->GetTemplateURLService()); if (!suggest_url.is_valid()) @@ -177,32 +203,36 @@ void ZeroSuggestProvider::Start(const AutocompleteInput& input, done_ = false; - MaybeUseCachedSuggestions(); + // If no valid HTTP cache duration is provided via the cache duration feature + // param, OmniboxFieldTrial::kZeroSuggestCacheDurationSec, the response will + // never be loaded from the HTTP cache. In that case, continue to use the + // stored response, if applicable. + if (cache_duration_sec <= 0) { + MaybeUseStoredResponse(); + } search_terms_args.current_page_url = result_type_running_ == REMOTE_SEND_URL ? current_query_ : std::string(); - // Create a request for suggestions, routing completion to - // OnRemoteSuggestionsLoaderAvailable. client() ->GetRemoteSuggestionsService(/*create_if_necessary=*/true) ->CreateSuggestionsRequest( search_terms_args, client()->GetTemplateURLService(), base::BindOnce( &ZeroSuggestProvider::OnRemoteSuggestionsLoaderAvailable, - weak_ptr_factory_.GetWeakPtr()), - base::BindOnce( - &ZeroSuggestProvider::OnURLLoadComplete, - base::Unretained(this) /* this owns SimpleURLLoader */)); + weak_ptr_factory_.GetWeakPtr(), is_prefetch), + base::BindOnce(&ZeroSuggestProvider::OnURLLoadComplete, + base::Unretained(this) /* this owns SimpleURLLoader */, + is_prefetch, base::TimeTicks::Now())); } void ZeroSuggestProvider::Stop(bool clear_cached_results, bool due_to_user_inactivity) { - if (loader_) - LogOmniboxZeroSuggestRequest(ZERO_SUGGEST_REQUEST_INVALIDATED); + if (loader_) { + LogOmniboxZeroSuggestRequest(ZERO_SUGGEST_REQUEST_INVALIDATED, + /*is_prefetch=*/is_prefetch_loader_); + } loader_.reset(); - - // TODO(krb): It would allow us to remove some guards if we could also cancel - // the TopSites::GetMostVisitedURLs request. + is_prefetch_loader_ = false; done_ = true; result_type_running_ = NONE; @@ -312,12 +342,20 @@ void ZeroSuggestProvider::RecordDeletionResult(bool success) { } void ZeroSuggestProvider::OnURLLoadComplete( + bool is_prefetch, + base::TimeTicks request_time, const network::SimpleURLLoader* source, std::unique_ptr response_body) { DCHECK(!done_); DCHECK_EQ(loader_.get(), source); - LogOmniboxZeroSuggestRequest(ZERO_SUGGEST_REPLY_RECEIVED); + LogOmniboxZeroSuggestRequestRoundTripTime( + base::TimeTicks::Now() - request_time, is_prefetch); + LogOmniboxZeroSuggestRequest(ZERO_SUGGEST_RESPONSE_RECEIVED, is_prefetch); + if (source->LoadedFromCache()) { + LogOmniboxZeroSuggestRequest(ZERO_SUGGEST_RESPONSE_LOADED_FROM_HTTP_CACHE, + is_prefetch); + } const bool results_updated = response_body && source->NetError() == net::OK && @@ -326,9 +364,14 @@ void ZeroSuggestProvider::OnURLLoadComplete( UpdateResults(SearchSuggestionParser::ExtractJsonData( source, std::move(response_body))); loader_.reset(); + is_prefetch_loader_ = false; done_ = true; result_type_running_ = NONE; - listener_->OnProviderUpdate(results_updated); + + // Do not notify the provider listener for prefetch requests. + if (!is_prefetch) { + listener_->OnProviderUpdate(results_updated); + } } bool ZeroSuggestProvider::UpdateResults(const std::string& json_data) { @@ -337,8 +380,7 @@ bool ZeroSuggestProvider::UpdateResults(const std::string& json_data) { if (!data) return false; - // When running the REMOTE_NO_URL variant, we want to store suggestion - // responses if non-empty. + // Store non-empty response if running the REMOTE_NO_URL variant. if (result_type_running_ == REMOTE_NO_URL && !json_data.empty()) { client()->GetPrefs()->SetString(omnibox::kZeroSuggestCachedResults, json_data); @@ -390,12 +432,14 @@ AutocompleteMatch ZeroSuggestProvider::NavigationToMatch( } void ZeroSuggestProvider::OnRemoteSuggestionsLoaderAvailable( + bool is_prefetch, std::unique_ptr loader) { // RemoteSuggestionsService has already started |loader|, so here it's // only necessary to grab its ownership until results come in to // OnURLLoadComplete(). loader_ = std::move(loader); - LogOmniboxZeroSuggestRequest(ZERO_SUGGEST_REQUEST_SENT); + is_prefetch_loader_ = is_prefetch; + LogOmniboxZeroSuggestRequest(ZERO_SUGGEST_REQUEST_SENT, is_prefetch); } void ZeroSuggestProvider::ConvertResultsToAutocompleteMatches() { @@ -523,7 +567,8 @@ bool ZeroSuggestProvider::AllowZeroSuggestSuggestions( return true; } -void ZeroSuggestProvider::MaybeUseCachedSuggestions() { +void ZeroSuggestProvider::MaybeUseStoredResponse() { + // Use the stored response only if running the REMOTE_NO_URL variant. if (result_type_running_ != REMOTE_NO_URL) { return; } diff --git a/components/omnibox/browser/zero_suggest_provider.h b/components/omnibox/browser/zero_suggest_provider.h index 72c60459d1bbc9..82d631474e1b71 100644 --- a/components/omnibox/browser/zero_suggest_provider.h +++ b/components/omnibox/browser/zero_suggest_provider.h @@ -68,6 +68,7 @@ class ZeroSuggestProvider : public BaseSearchProvider { // AutocompleteProvider: void Start(const AutocompleteInput& input, bool minimal_changes) override; + void StartPrefetch(const AutocompleteInput& input) override; void Stop(bool clear_cached_results, bool due_to_user_inactivity) override; void DeleteMatch(const AutocompleteMatch& match) override; @@ -113,6 +114,12 @@ class ZeroSuggestProvider : public BaseSearchProvider { ZeroSuggestProvider(const ZeroSuggestProvider&) = delete; ZeroSuggestProvider& operator=(const ZeroSuggestProvider&) = delete; + // Called by Start() or StartPrefetch() with the appropriate arguments. + // Contains the implementation to start a request for suggestions. + void Start(const AutocompleteInput& input, + bool minimal_changes, + bool is_prefetch); + // BaseSearchProvider: const TemplateURL* GetTemplateURL(bool is_keyword) const override; const AutocompleteInput GetInput(bool is_keyword) const override; @@ -121,7 +128,11 @@ class ZeroSuggestProvider : public BaseSearchProvider { void RecordDeletionResult(bool success) override; // Called when the network request for suggestions has completed. - void OnURLLoadComplete(const network::SimpleURLLoader* source, + // `is_prefetch` and `request_time` are bound to this callback and indicate if + // the request is a prefetch one and the time it was issued respectively. + void OnURLLoadComplete(bool is_prefetch, + base::TimeTicks request_time, + const network::SimpleURLLoader* source, std::unique_ptr response_body); // The function updates |results_| with data parsed from |json_data|. @@ -151,11 +162,12 @@ class ZeroSuggestProvider : public BaseSearchProvider { // page. AutocompleteMatch MatchForCurrentText(); - // When the user is in the remote omnibox suggestions field trial, we ask - // the RemoteSuggestionsService for a loader to retrieve recommendations. - // When the loader has started, the remote suggestion service then calls - // back to this function with the |loader| to pass its ownership to |this|. + // Called when RemoteSuggestionsService starts `loader` for the provider to + // take over its ownership. `is_prefetch` is bound to this callback and + // indicates if the request is a prefetch one. The value of `is_prefetch` is + // stored in `is_prefetch_loader_` for the duration of the loader's lifetime. void OnRemoteSuggestionsLoaderAvailable( + bool is_prefetch, std::unique_ptr loader); // Whether zero suggest suggestions are allowed in the given context. @@ -167,9 +179,8 @@ class ZeroSuggestProvider : public BaseSearchProvider { // functions, so the reader has to look in two places. bool AllowZeroSuggestSuggestions(const AutocompleteInput& input) const; - // Checks whether we have a set of zero suggest results cached, and if so - // populates |matches_| with cached results. - void MaybeUseCachedSuggestions(); + // Populates |matches_| using the stored zero suggest response, if any. + void MaybeUseStoredResponse(); // Returns the type of results that should be generated for the current // context. @@ -205,6 +216,10 @@ class ZeroSuggestProvider : public BaseSearchProvider { // Loader used to retrieve results. std::unique_ptr loader_; + // Indicate whether `loader_` is retrieving prefetch results. Used for metrics + // when the provider is stopped. + bool is_prefetch_loader_; + // The verbatim match for the current text, which is always a URL. AutocompleteMatch current_text_match_; diff --git a/components/omnibox/browser/zero_suggest_provider_unittest.cc b/components/omnibox/browser/zero_suggest_provider_unittest.cc index 160de5ece58b68..0e13963ad1ae7f 100644 --- a/components/omnibox/browser/zero_suggest_provider_unittest.cc +++ b/components/omnibox/browser/zero_suggest_provider_unittest.cc @@ -12,6 +12,7 @@ #include "base/metrics/field_trial.h" #include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" +#include "base/test/metrics/histogram_tester.h" #include "base/test/scoped_feature_list.h" #include "base/test/task_environment.h" #include "build/build_config.h" @@ -36,6 +37,7 @@ #include "third_party/metrics_proto/omnibox_event.pb.h" namespace { + class FakeAutocompleteProviderClient : public MockAutocompleteProviderClient { public: FakeAutocompleteProviderClient() @@ -84,9 +86,14 @@ class FakeAutocompleteProviderClient : public MockAutocompleteProviderClient { std::unique_ptr pref_service_; TestSchemeClassifier scheme_classifier_; }; + +bool MaybeUseStoredResponse() { + return OmniboxFieldTrial::kZeroSuggestCacheDurationSec.Get() <= 0; +} + } // namespace -class ZeroSuggestProviderTest : public testing::Test, +class ZeroSuggestProviderTest : public testing::TestWithParam, public AutocompleteProviderListener { public: ZeroSuggestProviderTest() = default; @@ -108,6 +115,8 @@ class ZeroSuggestProviderTest : public testing::Test, TemplateURLRef::SearchTermsArgs search_terms_args; search_terms_args.page_classification = page_classification; search_terms_args.focus_type = OmniboxFocusType::ON_FOCUS; + search_terms_args.zero_suggest_cache_duration_sec = + OmniboxFieldTrial::kZeroSuggestCacheDurationSec.Get(); return RemoteSuggestionsService::EndpointUrl( search_terms_args, client_->GetTemplateURLService()); } @@ -129,6 +138,7 @@ class ZeroSuggestProviderTest : public testing::Test, variations::VariationsIdsProvider::Mode::kUseSignedInState}; std::unique_ptr client_; scoped_refptr provider_; + bool provider_did_notify_; }; void ZeroSuggestProviderTest::SetUp() { @@ -143,12 +153,28 @@ void ZeroSuggestProviderTest::SetUp() { turl_model->search_terms_data())); provider_ = ZeroSuggestProvider::Create(client_.get(), this); + provider_did_notify_ = false; + + // Ensure the cache is empty. + PrefService* prefs = client_->GetPrefs(); + prefs->SetString(omnibox::kZeroSuggestCachedResults, std::string()); + + scoped_feature_list_ = std::make_unique(); + scoped_feature_list_->InitAndEnableFeatureWithParameters( + omnibox::kZeroSuggestPrefetching, + {{OmniboxFieldTrial::kZeroSuggestCacheDurationSec.name, GetParam()}}); } void ZeroSuggestProviderTest::OnProviderUpdate(bool updated_matches) { + provider_did_notify_ = true; } -TEST_F(ZeroSuggestProviderTest, AllowZeroSuggestSuggestions) { +INSTANTIATE_TEST_SUITE_P(All, + ZeroSuggestProviderTest, + ::testing::ValuesIn({std::string("0"), + std::string("60")})); + +TEST_P(ZeroSuggestProviderTest, AllowZeroSuggestSuggestions) { std::string input_url = "https://example.com/"; AutocompleteInput prefix_input(base::ASCIIToUTF16(input_url), @@ -230,7 +256,7 @@ TEST_F(ZeroSuggestProviderTest, AllowZeroSuggestSuggestions) { } // TODO(tommycli): Break up this test into smaller ones. -TEST_F(ZeroSuggestProviderTest, TypeOfResultToRun) { +TEST_P(ZeroSuggestProviderTest, TypeOfResultToRun) { // Verifies the unconfigured state. Returns platorm-specific defaults. // TODO(tommycli): The remote_no_url_allowed idiom seems kind of confusing, // its true meaning seems closer to "expect_remote_no_url". Ideally we can @@ -289,8 +315,8 @@ TEST_F(ZeroSuggestProviderTest, TypeOfResultToRun) { #endif // Unless we allow remote suggestions for signed-out users. - scoped_feature_list_ = std::make_unique(); - scoped_feature_list_->InitAndEnableFeature( + base::test::ScopedFeatureList features; + features.InitAndEnableFeature( omnibox::kOmniboxTrendingZeroPrefixSuggestionsOnNTP); ExpectPlatformSpecificDefaultZeroSuggestBehavior( other_input, @@ -312,7 +338,7 @@ TEST_F(ZeroSuggestProviderTest, TypeOfResultToRun) { /*remote_no_url_allowed=*/false); } -TEST_F(ZeroSuggestProviderTest, TypeOfResultToRunForContextualWeb) { +TEST_P(ZeroSuggestProviderTest, TypeOfResultToRunForContextualWeb) { std::string input_url = "https://example.com/"; GURL suggest_url = GetSuggestURL(metrics::OmniboxEventProto::OTHER); @@ -399,7 +425,7 @@ TEST_F(ZeroSuggestProviderTest, TypeOfResultToRunForContextualWeb) { } } -TEST_F(ZeroSuggestProviderTest, TestDoesNotReturnMatchesForPrefix) { +TEST_P(ZeroSuggestProviderTest, TestDoesNotReturnMatchesForPrefix) { // Use NTP because REMOTE_NO_URL is enabled by default for NTP. AutocompleteInput prefix_input( u"foobar input", @@ -423,7 +449,7 @@ TEST_F(ZeroSuggestProviderTest, TestDoesNotReturnMatchesForPrefix) { EXPECT_EQ(0, test_loader_factory()->NumPending()); } -TEST_F(ZeroSuggestProviderTest, TestStartWillStopForSomeInput) { +TEST_P(ZeroSuggestProviderTest, TestStartWillStopForSomeInput) { EXPECT_CALL(*client_, IsAuthenticated()) .WillRepeatedly(testing::Return(true)); @@ -445,20 +471,15 @@ TEST_F(ZeroSuggestProviderTest, TestStartWillStopForSomeInput) { EXPECT_TRUE(provider_->done_); } -TEST_F(ZeroSuggestProviderTest, TestPsuggestZeroSuggestCachingFirstRun) { +TEST_P(ZeroSuggestProviderTest, TestPsuggestZeroSuggestCachingFirstRun) { EXPECT_CALL(*client_, IsAuthenticated()) .WillRepeatedly(testing::Return(true)); - // Ensure the cache is empty. - PrefService* prefs = client_->GetPrefs(); - prefs->SetString(omnibox::kZeroSuggestCachedResults, std::string()); - AutocompleteInput input = CreateNTPOnFocusInputForRemoteNoUrl(); provider_->Start(input, false); ASSERT_EQ(ZeroSuggestProvider::REMOTE_NO_URL, provider_->GetResultTypeRunningForTesting()); - EXPECT_TRUE(prefs->GetString(omnibox::kZeroSuggestCachedResults).empty()); EXPECT_TRUE(provider_->matches().empty()); GURL suggest_url = GetSuggestURL( @@ -471,13 +492,22 @@ TEST_F(ZeroSuggestProviderTest, TestPsuggestZeroSuggestCachingFirstRun) { test_loader_factory()->AddResponse(suggest_url.spec(), json_response); base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(provider_->done()); + + // Expect the provider to have notified the provider listener. + EXPECT_TRUE(provider_did_notify_); EXPECT_EQ(3U, provider_->matches().size()); // 3 results, no verbatim match - EXPECT_EQ(json_response, - prefs->GetString(omnibox::kZeroSuggestCachedResults)); + if (MaybeUseStoredResponse()) { + PrefService* prefs = client_->GetPrefs(); + EXPECT_EQ(json_response, + prefs->GetString(omnibox::kZeroSuggestCachedResults)); + } } -TEST_F(ZeroSuggestProviderTest, TestPsuggestZeroSuggestHasCachedResults) { +TEST_P(ZeroSuggestProviderTest, TestPsuggestZeroSuggestHasCachedResults) { + base::HistogramTester histogram_tester; + EXPECT_CALL(*client_, IsAuthenticated()) .WillRepeatedly(testing::Return(true)); @@ -493,11 +523,13 @@ TEST_F(ZeroSuggestProviderTest, TestPsuggestZeroSuggestHasCachedResults) { ASSERT_EQ(ZeroSuggestProvider::REMOTE_NO_URL, provider_->GetResultTypeRunningForTesting()); - // Expect that matches get populated synchronously out of the cache. - ASSERT_EQ(3U, provider_->matches().size()); // 3 results, no verbatim match - EXPECT_EQ(u"search1", provider_->matches()[0].contents); - EXPECT_EQ(u"search2", provider_->matches()[1].contents); - EXPECT_EQ(u"search3", provider_->matches()[2].contents); + if (MaybeUseStoredResponse()) { + // Expect that matches get populated synchronously out of the cache. + ASSERT_EQ(3U, provider_->matches().size()); // 3 results, no verbatim match + EXPECT_EQ(u"search1", provider_->matches()[0].contents); + EXPECT_EQ(u"search2", provider_->matches()[1].contents); + EXPECT_EQ(u"search3", provider_->matches()[2].contents); + } GURL suggest_url = GetSuggestURL( metrics::OmniboxEventProto::INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS); @@ -508,19 +540,44 @@ TEST_F(ZeroSuggestProviderTest, TestPsuggestZeroSuggestHasCachedResults) { test_loader_factory()->AddResponse(suggest_url.spec(), json_response2); base::RunLoop().RunUntilIdle(); - - // Expect the same results after the response has been handled. - ASSERT_EQ(3U, provider_->matches().size()); // 3 results, no verbatim match - EXPECT_EQ(u"search1", provider_->matches()[0].contents); - EXPECT_EQ(u"search2", provider_->matches()[1].contents); - EXPECT_EQ(u"search3", provider_->matches()[2].contents); - - // Expect the new results have been stored. - EXPECT_EQ(json_response2, - prefs->GetString(omnibox::kZeroSuggestCachedResults)); + EXPECT_TRUE(provider_->done()); + + // Expect the provider to have notified the provider listener. + EXPECT_TRUE(provider_did_notify_); + + // Expect correct histograms to have been logged. + histogram_tester.ExpectTotalCount("Omnibox.ZeroSuggestRequests.NonPrefetch", + 2); + histogram_tester.ExpectBucketCount("Omnibox.ZeroSuggestRequests.NonPrefetch", + 1 /*ZERO_SUGGEST_REQUEST_SENT*/, 1); + histogram_tester.ExpectBucketCount("Omnibox.ZeroSuggestRequests.NonPrefetch", + 3 /*ZERO_SUGGEST_RESPONSE_RECEIVED*/, 1); + histogram_tester.ExpectTotalCount( + "Omnibox.ZeroSuggestRequests.NonPrefetch.RoundTripTime", 1); + histogram_tester.ExpectTotalCount("Omnibox.ZeroSuggestRequests.Prefetch", 0); + histogram_tester.ExpectTotalCount( + "Omnibox.ZeroSuggestRequests.Prefetch.RoundTripTime", 0); + + if (MaybeUseStoredResponse()) { + // Expect the same results after the response has been handled. + ASSERT_EQ(3U, provider_->matches().size()); // 3 results, no verbatim match + EXPECT_EQ(u"search1", provider_->matches()[0].contents); + EXPECT_EQ(u"search2", provider_->matches()[1].contents); + EXPECT_EQ(u"search3", provider_->matches()[2].contents); + + // Expect the new results to have been stored. + EXPECT_EQ(json_response2, + prefs->GetString(omnibox::kZeroSuggestCachedResults)); + } else { + // Expect fresh results after the response has been handled. + ASSERT_EQ(3U, provider_->matches().size()); // 3 results, no verbatim match + EXPECT_EQ(u"search4", provider_->matches()[0].contents); + EXPECT_EQ(u"search5", provider_->matches()[1].contents); + EXPECT_EQ(u"search6", provider_->matches()[2].contents); + } } -TEST_F(ZeroSuggestProviderTest, TestPsuggestZeroSuggestReceivedEmptyResults) { +TEST_P(ZeroSuggestProviderTest, TestPsuggestZeroSuggestReceivedEmptyResults) { EXPECT_CALL(*client_, IsAuthenticated()) .WillRepeatedly(testing::Return(true)); @@ -536,11 +593,13 @@ TEST_F(ZeroSuggestProviderTest, TestPsuggestZeroSuggestReceivedEmptyResults) { ASSERT_EQ(ZeroSuggestProvider::REMOTE_NO_URL, provider_->GetResultTypeRunningForTesting()); - // Expect that matches get populated synchronously out of the cache. - ASSERT_EQ(3U, provider_->matches().size()); // 3 results, no verbatim match - EXPECT_EQ(u"search1", provider_->matches()[0].contents); - EXPECT_EQ(u"search2", provider_->matches()[1].contents); - EXPECT_EQ(u"search3", provider_->matches()[2].contents); + if (MaybeUseStoredResponse()) { + // Expect that matches get populated synchronously out of the cache. + ASSERT_EQ(3U, provider_->matches().size()); // 3 results, no verbatim match + EXPECT_EQ(u"search1", provider_->matches()[0].contents); + EXPECT_EQ(u"search2", provider_->matches()[1].contents); + EXPECT_EQ(u"search3", provider_->matches()[2].contents); + } GURL suggest_url = GetSuggestURL( metrics::OmniboxEventProto::INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS); @@ -549,11 +608,83 @@ TEST_F(ZeroSuggestProviderTest, TestPsuggestZeroSuggestReceivedEmptyResults) { test_loader_factory()->AddResponse(suggest_url.spec(), empty_response); base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(provider_->done()); + + // Expect the provider to have notified the provider listener. + EXPECT_TRUE(provider_did_notify_); // Expect that the matches have been cleared. ASSERT_TRUE(provider_->matches().empty()); - // Expect the new results have been stored. - EXPECT_EQ(empty_response, - prefs->GetString(omnibox::kZeroSuggestCachedResults)); + if (MaybeUseStoredResponse()) { + // Expect the new results to have been stored. + EXPECT_EQ(empty_response, + prefs->GetString(omnibox::kZeroSuggestCachedResults)); + } +} + +TEST_P(ZeroSuggestProviderTest, TestPsuggestZeroSuggestPrefetch) { + base::HistogramTester histogram_tester; + + EXPECT_CALL(*client_, IsAuthenticated()) + .WillRepeatedly(testing::Return(true)); + + // Set up the pref to cache the response from the previous run. + std::string json_response( + "[\"\",[\"search1\", \"search2\", \"search3\"]," + "[],[],{\"google:suggestrelevance\":[602, 601, 600]," + "\"google:verbatimrelevance\":1300}]"); + PrefService* prefs = client_->GetPrefs(); + prefs->SetString(omnibox::kZeroSuggestCachedResults, json_response); + + AutocompleteInput input = CreateNTPOnFocusInputForRemoteNoUrl(); + provider_->StartPrefetch(input); + ASSERT_EQ(ZeroSuggestProvider::REMOTE_NO_URL, + provider_->GetResultTypeRunningForTesting()); + + GURL suggest_url = GetSuggestURL( + metrics::OmniboxEventProto::INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS); + EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec())); + std::string json_response2( + "[\"\",[\"search4\", \"search5\", \"search6\"]," + "[],[],{\"google:suggestrelevance\":[602, 601, 600]," + "\"google:verbatimrelevance\":1300}]"); + test_loader_factory()->AddResponse(suggest_url.spec(), json_response2); + + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(provider_->done()); + + // Expect correct histograms to have been logged. + histogram_tester.ExpectTotalCount("Omnibox.ZeroSuggestRequests.Prefetch", 2); + histogram_tester.ExpectBucketCount("Omnibox.ZeroSuggestRequests.Prefetch", + 1 /*ZERO_SUGGEST_REQUEST_SENT*/, 1); + histogram_tester.ExpectBucketCount("Omnibox.ZeroSuggestRequests.Prefetch", + 3 /*ZERO_SUGGEST_RESPONSE_RECEIVED*/, 1); + histogram_tester.ExpectTotalCount( + "Omnibox.ZeroSuggestRequests.Prefetch.RoundTripTime", 1); + histogram_tester.ExpectTotalCount("Omnibox.ZeroSuggestRequests.NonPrefetch", + 0); + histogram_tester.ExpectTotalCount( + "Omnibox.ZeroSuggestRequests.NonPrefetch.RoundTripTime", 0); + + // Expect the provider not to have notified the provider listener. + EXPECT_FALSE(provider_did_notify_); + + if (MaybeUseStoredResponse()) { + // Expect the same results after the response has been handled. + ASSERT_EQ(3U, provider_->matches().size()); // 3 results, no verbatim match + EXPECT_EQ(u"search1", provider_->matches()[0].contents); + EXPECT_EQ(u"search2", provider_->matches()[1].contents); + EXPECT_EQ(u"search3", provider_->matches()[2].contents); + + // Expect the new results to have been stored. + EXPECT_EQ(json_response2, + prefs->GetString(omnibox::kZeroSuggestCachedResults)); + } else { + // Expect fresh results after the response has been handled. + ASSERT_EQ(3U, provider_->matches().size()); // 3 results, no verbatim match + EXPECT_EQ(u"search4", provider_->matches()[0].contents); + EXPECT_EQ(u"search5", provider_->matches()[1].contents); + EXPECT_EQ(u"search6", provider_->matches()[2].contents); + } } diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index f1047b5af04b31..5a603e1977537c 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml @@ -64677,9 +64677,17 @@ Called by update_net_trust_anchors.py.-->
- - - + Suggest requests that were sent. + + Suggest requests that were invalidated, e.g., due to user starting to type. + + + Suggest responses that were received. Includes both those received directly + from the server and those loaded from the HTTP cache. + + + Subset of Suggest responses that were loaded from the HTTP cache. + diff --git a/tools/metrics/histograms/metadata/omnibox/histograms.xml b/tools/metrics/histograms/metadata/omnibox/histograms.xml index 67d8ce2b55e57d..e9e9eafec42224 100644 --- a/tools/metrics/histograms/metadata/omnibox/histograms.xml +++ b/tools/metrics/histograms/metadata/omnibox/histograms.xml @@ -1246,6 +1246,10 @@ chromium-metrics-reviews@google.com. + + No longer reported as of 2021-12. Omnibox.ZeroSuggestRequests.Prefetch and + Omnibox.ZeroSuggestRequests.NonPrefetch are being reported instead. + ender@chromium.org tommycli@chromium.org chrome-omnibox-team@google.com @@ -1256,6 +1260,54 @@ chromium-metrics-reviews@google.com. + + mahmadi@chromium.org + chrome-omnibox-team@google.com + + Counts the number of non-prefetch zero suggest requests (requests for + suggestions when the user has focused but not modified the omnibox) the + omnibox sent, requests that were invalidated, responses that were received, + and the subset of responses that were loaded from HTTP cache. + + + + + mahmadi@chromium.org + chrome-omnibox-team@google.com + + Records the time elapsed between non-prefetch zero suggest requests + (requests for suggestions when the user has focused but not modified the + omnibox) sent and the responses received; whether or not the response is + loaded from HTTP cache. + + + + + mahmadi@chromium.org + chrome-omnibox-team@google.com + + Counts the number of prefetch zero suggest requests (requests for + suggestions when the user opens a new tab or brings one to foreground) the + omnibox sent, requests that were invalidated, responses that were received, + and the subset of responses that were loaded from HTTP cache. + + + + + mahmadi@chromium.org + chrome-omnibox-team@google.com + + Records the time elapsed between prefetch zero suggest requests (requests + for suggestions when the user opens a new tab or brings one to foreground) + sent and the responses received; whether or not the response is loaded from + HTTP cache. + + + From 28f464e2661cb541e417cd56d95b725cbf1b131d Mon Sep 17 00:00:00 2001 From: Roger Tinkoff Date: Wed, 15 Dec 2021 01:06:11 +0000 Subject: [PATCH 35/77] ash: Crash fix for mic mute toggle in login screen Instead of DCHECK(), return absl::nullopt if reg_cache or cap_cache is nullptr. Bug: 1274440 Change-Id: I12b2adedd319441c7c93b90054aadf1143175b65 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3339812 Reviewed-by: James Cook Commit-Queue: Roger Tinkoff Cr-Commit-Position: refs/heads/main@{#951758} --- .../ui/ash/microphone_mute_notification_delegate_impl.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/chrome/browser/ui/ash/microphone_mute_notification_delegate_impl.cc b/chrome/browser/ui/ash/microphone_mute_notification_delegate_impl.cc index ef75e0c8e941db..fb17b4df7f11d5 100644 --- a/chrome/browser/ui/ash/microphone_mute_notification_delegate_impl.cc +++ b/chrome/browser/ui/ash/microphone_mute_notification_delegate_impl.cc @@ -91,10 +91,13 @@ MicrophoneMuteNotificationDelegateImpl:: absl::optional MicrophoneMuteNotificationDelegateImpl::GetAppAccessingMicrophone() { apps::AppRegistryCache* reg_cache = GetActiveUserAppRegistryCache(); - DCHECK(reg_cache); apps::AppCapabilityAccessCache* cap_cache = GetActiveUserAppCapabilityAccessCache(); - DCHECK(cap_cache); + // A reg_cache and/or cap_cache of value nullptr is possible if we have + // no active user, e.g. the login screen, so we test and return nullopt + // in that case instead of using DCHECK(). + if (!reg_cache || !cap_cache) + return absl::nullopt; return GetAppAccessingMicrophone(cap_cache, reg_cache); } From c610e72984f5712e9c33143e6b8578e874a945e9 Mon Sep 17 00:00:00 2001 From: Anton Swifton Date: Wed, 15 Dec 2021 01:06:39 +0000 Subject: [PATCH 36/77] Shimless: update the finalization page. https://screenshot.googleplex.com/7UgWjrMYi5AfmfP https://screenshot.googleplex.com/3kYfYe8KCsFCeDG Bug: 1198187 Test: local Change-Id: I592964701c209c99034fd61cfcfdf8010612b45d Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3337796 Reviewed-by: Gavin Williams Commit-Queue: Anton Swifton Cr-Commit-Position: refs/heads/main@{#951759} --- .../shimless_rma/resources/wrapup_finalize_page.html | 12 +++++++++--- .../shimless_rma/resources/wrapup_finalize_page.js | 7 +------ chromeos/chromeos_strings.grd | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/ash/webui/shimless_rma/resources/wrapup_finalize_page.html b/ash/webui/shimless_rma/resources/wrapup_finalize_page.html index a5d9a1be22d136..b8c5bbf7bdc75c 100644 --- a/ash/webui/shimless_rma/resources/wrapup_finalize_page.html +++ b/ash/webui/shimless_rma/resources/wrapup_finalize_page.html @@ -1,13 +1,19 @@ - +

[[i18n('finalizePageTitleText')]]

-
-

[[finalizationMessage_]]

+
+ + +
diff --git a/ash/webui/shimless_rma/resources/wrapup_finalize_page.js b/ash/webui/shimless_rma/resources/wrapup_finalize_page.js index 21626a1251d752..f6ea4b98cc7d25 100644 --- a/ash/webui/shimless_rma/resources/wrapup_finalize_page.js +++ b/ash/webui/shimless_rma/resources/wrapup_finalize_page.js @@ -74,12 +74,7 @@ export class WrapupFinalizePage extends WrapupFinalizePageBase { * @param {number} progress */ onFinalizationUpdated(status, progress) { - if (status === FinalizationStatus.kInProgress) { - this.finalizationMessage_ = this.i18n( - finalizationStatusTextKeys[status], Math.round(progress * 100)); - } else { - this.finalizationMessage_ = this.i18n(finalizationStatusTextKeys[status]); - } + this.finalizationMessage_ = this.i18n(finalizationStatusTextKeys[status]); this.finalizationComplete_ = status === FinalizationStatus.kComplete || status === FinalizationStatus.kFailedNonBlocking; this.dispatchEvent(new CustomEvent( diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd index 76b227d30d56c7..994662f5372abb 100644 --- a/chromeos/chromeos_strings.grd +++ b/chromeos/chromeos_strings.grd @@ -2237,7 +2237,7 @@ Try tapping the mic to ask me anything. Finalizing repair - Progress $125%. + Finalizing the device. Do not turn off power. Complete. From 7193af990b927cd218230c40ea042ccf6c2f3944 Mon Sep 17 00:00:00 2001 From: Asami Doi Date: Wed, 15 Dec 2021 01:08:49 +0000 Subject: [PATCH 37/77] PlzDedicatedWorker: early return when the ancestor frame is closed This CL updates ThrottleWorkerMainScriptFetch() to make it return early because the ancestor RenderFrameHost can have already been closed while DedicatedWorker is alive. See comments at dedicated_worker_host.cc. https://source.chromium.org/chromium/chromium/src/+/main:content/browser/worker_host/dedicated_worker_host.cc;l=699-700;drc=eb7f6bd3fd48714bcc3b6a8b2bf99b6be4d9a9bd;bpv=1;bpt=1 It will fix the null-dereference in `rfh->frame_tree_node()` when the PlzDedicatedWorker feature is enabled. Bug: 1265599 Change-Id: If0040de2c0c2ac7b8ebeff907ed22b79415bb343 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3338156 Reviewed-by: Andrey Kosyakov Commit-Queue: Asami Doi Cr-Commit-Position: refs/heads/main@{#951760} --- content/browser/devtools/devtools_instrumentation.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/content/browser/devtools/devtools_instrumentation.cc b/content/browser/devtools/devtools_instrumentation.cc index 35177de0e935b1..8318afb25f04b4 100644 --- a/content/browser/devtools/devtools_instrumentation.cc +++ b/content/browser/devtools/devtools_instrumentation.cc @@ -490,11 +490,13 @@ void ThrottleWorkerMainScriptFetch( WorkerDevToolsAgentHost* agent_host = WorkerDevToolsManager::GetInstance().GetDevToolsHostFromToken( devtools_worker_token); - DCHECK(agent_host); + if (!agent_host) + return; RenderFrameHostImpl* rfh = RenderFrameHostImpl::FromID(ancestor_render_frame_host_id); - DCHECK(rfh); + if (!rfh) + return; FrameTreeNode* ftn = rfh->frame_tree_node(); DispatchToAgents(ftn, &protocol::TargetHandler::AddWorkerThrottle, agent_host, From 731871b2d2efe2ae460313ccd723a89f6c67c029 Mon Sep 17 00:00:00 2001 From: Gavin Williams Date: Wed, 15 Dec 2021 01:09:07 +0000 Subject: [PATCH 38/77] Shimless: Restyle calibration component chips - Match the chip styling to the repair component chips - Add a "failed" icon - Reduce chip statuses to either "checked" or "failed" to match new calibration rules screenshot: http://screen/J6Tsb2CZt9QGX93 Bug: 1198187 Change-Id: I3624eee32ae0f7d2293a7f36c0f21db81b6e1c04 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3338396 Reviewed-by: Gavin Dodd Commit-Queue: Gavin Williams Cr-Commit-Position: refs/heads/main@{#951761} --- .../resources/calibration_component_chip.html | 63 ++++++----- .../resources/calibration_component_chip.js | 28 +---- .../reimaging_calibration_failed_page.html | 6 +- .../reimaging_calibration_failed_page.js | 36 ++---- .../reimaging_calibration_failed_page_test.js | 103 +++++++----------- 5 files changed, 90 insertions(+), 146 deletions(-) diff --git a/ash/webui/shimless_rma/resources/calibration_component_chip.html b/ash/webui/shimless_rma/resources/calibration_component_chip.html index b52e653cb2b2f2..3e4a5fcf1ce324 100644 --- a/ash/webui/shimless_rma/resources/calibration_component_chip.html +++ b/ash/webui/shimless_rma/resources/calibration_component_chip.html @@ -3,48 +3,51 @@ padding: 1px; } - /* TODO(gavindodd): update colors to CrOS */ - :host([skip]) #containerButton { - background-color: lightgray; + /* TODO(gavinwill): update colors to CrOS */ + :host([checked]) #componentButton { + background-color: lightskyblue; } - :host([completed]) #containerButton { - background-color: yellowgreen; + #componentButton { + align-items: normal; + border-radius: 4px; + box-shadow: var(--cr-card-shadow); + height: 70px; + margin-bottom: 20px; + margin-inline-end: 10px; + width: 190px; } - :host([failed]) #containerButton { - background-color: IndianRed; + #labelDiv { + color: grey; + flex-basis: 155px; + margin-bottom: auto; + margin-top: auto; + padding-inline-start: 20px; } - #containerDiv { - height: 40px; - width: 180px; + :host([checked]) #labelDiv { + color: blue; } - #containerButton { - border-radius: 10px; - box-shadow: 0px 2px 2px 2px #bbb; - height: 40px; - width: 180px; + #checkIcon { + margin-top: 6px; } - .icon-top-right { - height: 15px; - position: relative; - right: 20px; - top: -5px; - width: 15px; + #infoIcon { + fill: red; + margin-inline-end: 5px; } -
- + +
+ [[componentName]] - - - - +
[[componentId]]
+
+ -
+ diff --git a/ash/webui/shimless_rma/resources/calibration_component_chip.js b/ash/webui/shimless_rma/resources/calibration_component_chip.js index 6dd421ae94e87a..547025b9738f11 100644 --- a/ash/webui/shimless_rma/resources/calibration_component_chip.js +++ b/ash/webui/shimless_rma/resources/calibration_component_chip.js @@ -30,13 +30,7 @@ export class CalibrationComponentChipElement extends PolymerElement { static get properties() { return { /** @type {boolean} */ - disabled: { - type: Boolean, - value: false, - }, - - /** @type {boolean} */ - skip: { + checked: { notify: true, reflectToAttribute: true, type: Boolean, @@ -44,32 +38,16 @@ export class CalibrationComponentChipElement extends PolymerElement { }, /** @type {boolean} */ - completed: { - notify: true, - reflectToAttribute: true, - type: Boolean, - value: false, - }, - - /** @type {boolean} */ - failed: { - notify: true, - reflectToAttribute: true, - type: Boolean, - value: false, - }, + failed: {type: Boolean, value: false}, /** @type {string} */ componentName: {type: String, value: ''}, - - /** @type {string} */ - componentStatus: {type: String, value: ''} }; } /** @protected */ onComponentButtonClicked_() { - this.skip = !this.skip; + this.checked = !this.checked; } click() { diff --git a/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.html b/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.html index 1cb16f52999218..6dac8b354ee843 100644 --- a/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.html +++ b/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.html @@ -20,10 +20,8 @@

[[i18n('calibrationFailedTitleText')]]

diff --git a/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.js b/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.js index 73685624d64456..3330122844d516 100644 --- a/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.js +++ b/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.js @@ -31,10 +31,8 @@ import {CalibrationComponentStatus, CalibrationStatus, ComponentType, ShimlessRm * component: !ComponentType, * id: string, * name: string, - * skip: boolean, - * completed: boolean, + * checked: boolean, * failed: boolean, - * disabled: boolean * }} */ let ComponentCheckbox; @@ -93,23 +91,15 @@ export class ReimagingCalibrationFailedPage extends return; } - /** @type {!Array} */ - const componentList = []; - result.components.forEach(item => { - const component = assert(item.component); - - componentList.push({ + this.componentCheckboxes_ = result.components.map(item => { + return { component: item.component, id: ComponentTypeToId[item.component], name: this.i18n(ComponentTypeToId[item.component]), - skip: item.status === CalibrationStatus.kCalibrationSkip, - completed: item.status === CalibrationStatus.kCalibrationComplete, + checked: false, failed: item.status === CalibrationStatus.kCalibrationFailed, - disabled: item.status === CalibrationStatus.kCalibrationComplete || - item.status === CalibrationStatus.kCalibrationInProgress - }); + }; }); - this.componentCheckboxes_ = componentList; }); } @@ -119,16 +109,12 @@ export class ReimagingCalibrationFailedPage extends */ getComponentsList_() { return this.componentCheckboxes_.map(item => { - /** @type {!CalibrationStatus} */ - let status = CalibrationStatus.kCalibrationWaiting; - if (item.skip) { - status = CalibrationStatus.kCalibrationSkip; - } else if (item.completed) { - status = CalibrationStatus.kCalibrationComplete; - } else if (item.disabled) { - status = CalibrationStatus.kCalibrationInProgress; - } - return {component: item.component, status: status, progress: 0.0}; + return { + component: item.component, + status: item.checked ? CalibrationStatus.kCalibrationWaiting : + CalibrationStatus.kCalibrationSkip, + progress: 0.0 + }; }); } diff --git a/chrome/test/data/webui/chromeos/shimless_rma/reimaging_calibration_failed_page_test.js b/chrome/test/data/webui/chromeos/shimless_rma/reimaging_calibration_failed_page_test.js index aaf02b6e44cb83..b4aad6197ce6c7 100644 --- a/chrome/test/data/webui/chromeos/shimless_rma/reimaging_calibration_failed_page_test.js +++ b/chrome/test/data/webui/chromeos/shimless_rma/reimaging_calibration_failed_page_test.js @@ -8,7 +8,7 @@ import {FakeShimlessRmaService} from 'chrome://shimless-rma/fake_shimless_rma_se import {setShimlessRmaServiceForTesting} from 'chrome://shimless-rma/mojo_interface_provider.js'; import {ReimagingCalibrationFailedPage} from 'chrome://shimless-rma/reimaging_calibration_failed_page.js'; import {ShimlessRma} from 'chrome://shimless-rma/shimless_rma.js'; -import {CalibrationComponentStatus, CalibrationStatus} from 'chrome://shimless-rma/shimless_rma_types.js'; +import {CalibrationComponentStatus, CalibrationStatus, ComponentType} from 'chrome://shimless-rma/shimless_rma_types.js'; import {assertDeepEquals, assertEquals, assertFalse, assertNotEquals, assertTrue} from '../../chai_assert.js'; import {flushTasks} from '../../test_util.js'; @@ -73,7 +73,6 @@ export function reimagingCalibrationFailedPageTest() { function clickComponentCameraToggle() { const cameraComponent = component.shadowRoot.querySelector('#componentCamera'); - assertFalse(cameraComponent.disabled); cameraComponent.click(); return flushTasks(); } @@ -96,39 +95,6 @@ export function reimagingCalibrationFailedPageTest() { return component.getComponentsList_(); } - /** - * @param {!Array} components - * @return {!Array} - */ - function getExpectedComponentsList(components) { - /** @type {!Array} */ - let expectedComponents = []; - components.forEach(componentStatus => { - let status = componentStatus.status; - if (status === CalibrationStatus.kCalibrationFailed) { - status = CalibrationStatus.kCalibrationWaiting; - } - expectedComponents.push({ - component: componentStatus.component, - status: status, - progress: 0.0 - }); - }); - return expectedComponents; - } - - /** - * @param {!Array} components - * @return {!Array} - */ - function getAllSkippedComponentsList(components) { - components.forEach(component => { - component.status = CalibrationStatus.kCalibrationSkip; - component.progress = 0.0; - }); - return components; - } - test('Initializes', async () => { await initializeCalibrationPage(fakeCalibrationComponents); @@ -144,48 +110,51 @@ export function reimagingCalibrationFailedPageTest() { const touchpadComponent = component.shadowRoot.querySelector('#componentTouchpad'); assertEquals('Camera', cameraComponent.componentName); - assertFalse(cameraComponent.disabled); - assertFalse(cameraComponent.skip); - assertFalse(cameraComponent.completed); + assertFalse(cameraComponent.checked); + assertFalse(cameraComponent.failed); assertEquals('Battery', batteryComponent.componentName); - assertTrue(batteryComponent.disabled); - assertFalse(batteryComponent.skip); - assertTrue(batteryComponent.completed); + assertFalse(batteryComponent.checked); + assertFalse(batteryComponent.failed); assertEquals( 'Base Accelerometer', baseAccelerometerComponent.componentName); - assertTrue(baseAccelerometerComponent.disabled); - assertFalse(baseAccelerometerComponent.skip); - assertFalse(baseAccelerometerComponent.completed); + assertFalse(baseAccelerometerComponent.checked); + assertFalse(baseAccelerometerComponent.failed); assertEquals('Lid Accelerometer', lidAccelerometerComponent.componentName); - assertFalse(lidAccelerometerComponent.disabled); - assertFalse(lidAccelerometerComponent.skip); - assertFalse(lidAccelerometerComponent.completed); + assertFalse(lidAccelerometerComponent.checked); + assertTrue(lidAccelerometerComponent.failed); assertEquals('Touchpad', touchpadComponent.componentName); - assertFalse(touchpadComponent.disabled); - assertTrue(touchpadComponent.skip); - assertFalse(touchpadComponent.completed); + assertFalse(touchpadComponent.checked); + assertFalse(touchpadComponent.failed); }); test('ToggleComponent', async () => { await initializeCalibrationPage(fakeCalibrationComponents); + getComponentsList().forEach( + component => + assertEquals(CalibrationStatus.kCalibrationSkip, component.status)); + + // Click the camera button to check it. + await clickComponentCameraToggle(); + // Camera should be the first entry in the list. + assertEquals( + CalibrationStatus.kCalibrationWaiting, getComponentsList()[0].status); + + // Click the camera button to check it. await clickComponentCameraToggle(); - let expectedComponents = - getExpectedComponentsList(fakeCalibrationComponents); - let components = getComponentsList(); - assertNotEquals(expectedComponents, components); // Camera should be the first entry in the list. - expectedComponents[0].status = CalibrationStatus.kCalibrationSkip; - assertDeepEquals(expectedComponents, components); + assertEquals( + CalibrationStatus.kCalibrationSkip, getComponentsList()[0].status); }); test('NextButtonTriggersCalibrationComplete', async () => { const resolver = new PromiseResolver(); await initializeCalibrationPage(fakeCalibrationComponents); - let expectedComponents = - getAllSkippedComponentsList(fakeCalibrationComponents); let startCalibrationCalls = 0; service.startCalibration = (components) => { - assertDeepEquals(expectedComponents, components); + assertEquals(5, components.length); + components.forEach( + component => assertEquals( + CalibrationStatus.kCalibrationSkip, component.status)); startCalibrationCalls++; return resolver.promise; }; @@ -205,11 +174,21 @@ export function reimagingCalibrationFailedPageTest() { test('RetryButtonTriggersCalibration', async () => { const resolver = new PromiseResolver(); await initializeCalibrationPage(fakeCalibrationComponents); - let expectedComponents = - getExpectedComponentsList(fakeCalibrationComponents); + + getComponentsList().forEach( + component => + assertEquals(CalibrationStatus.kCalibrationSkip, component.status)); + await clickComponentCameraToggle(); + let startCalibrationCalls = 0; service.startCalibration = (components) => { - assertDeepEquals(expectedComponents, components); + assertEquals(5, components.length); + components.forEach( + component => assertEquals( + component.component === ComponentType.kCamera ? + CalibrationStatus.kCalibrationWaiting : + CalibrationStatus.kCalibrationSkip, + component.status)); startCalibrationCalls++; return resolver.promise; }; From 84a7b92694b3697c74be7beb7b442419da0cf6b2 Mon Sep 17 00:00:00 2001 From: Joanmarie Diggs Date: Wed, 15 Dec 2021 01:09:55 +0000 Subject: [PATCH 39/77] Fire accessible MENU_POPUP_START when menu is created on the fly AXEventGenerator::OnIgnoredChanged fires MENU_POPUP_START in addition to SUBTREE_CREATED for the ARIA menu role. That does not cover the case where the menu subtree did not already exist in the DOM, but was created on the fly. Fire the missing event in AXEventGenerator::OnNodeCreated, which should also handle the case where the new menu has ancestor elements also being added to the DOM. AX-Relnotes: Assistive technologies are now notified when an ARIA menu is created on the fly. Bug: 1254875 Change-Id: Ie384c19eab0f808bbc4637d0b87764514d76e0a7 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3310922 Reviewed-by: Aaron Leventhal Commit-Queue: Joanmarie Diggs Cr-Commit-Position: refs/heads/main@{#951762} --- .../browser_accessibility_manager.cc | 7 ++- .../dump_accessibility_events_browsertest.cc | 5 +++ .../WebContentsAccessibilityEventsTest.java | 6 +++ ...osed-via-inner-text-expected-auralinux.txt | 7 +++ ...ned-closed-via-inner-text-expected-mac.txt | 7 +++ ...ned-closed-via-inner-text-expected-win.txt | 7 +++ .../menu-opened-closed-via-inner-text.html | 43 +++++++++++++++++++ ui/accessibility/ax_event_generator.cc | 16 +++++++ ui/accessibility/ax_event_generator.h | 1 + 9 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 content/test/data/accessibility/event/menu-opened-closed-via-inner-text-expected-auralinux.txt create mode 100644 content/test/data/accessibility/event/menu-opened-closed-via-inner-text-expected-mac.txt create mode 100644 content/test/data/accessibility/event/menu-opened-closed-via-inner-text-expected-win.txt create mode 100644 content/test/data/accessibility/event/menu-opened-closed-via-inner-text.html diff --git a/content/browser/accessibility/browser_accessibility_manager.cc b/content/browser/accessibility/browser_accessibility_manager.cc index 9a848558fb9fcf..66c0d78fcecd2c 100644 --- a/content/browser/accessibility/browser_accessibility_manager.cc +++ b/content/browser/accessibility/browser_accessibility_manager.cc @@ -1435,8 +1435,11 @@ void BrowserAccessibilityManager::OnNodeWillBeDeleted(ui::AXTree* tree, if (wrapper == GetLastFocusedNode()) SetLastFocusedNode(nullptr); - // We fire these here, immediately, to ensure we can send platform - // notifications prior to the actual destruction of the object. + // TODO(accessibility): Move this to the AXEventGenerator which fires + // MENU_POPUP_START when a node with the menu role is created. The issue to + // be solved is that after the AXEventGenerator adds MENU_POPUP_END, the + // node gets removed from the tree. Then PostprocessEvents removes the + // events from that now-removed node, thus MENU_POPUP_END never gets fired. if (node->GetRole() == ax::mojom::Role::kMenu) FireGeneratedEvent(ui::AXEventGenerator::Event::MENU_POPUP_END, wrapper); } diff --git a/content/browser/accessibility/dump_accessibility_events_browsertest.cc b/content/browser/accessibility/dump_accessibility_events_browsertest.cc index 26974556065ec7..29cb4301cf5e51 100644 --- a/content/browser/accessibility/dump_accessibility_events_browsertest.cc +++ b/content/browser/accessibility/dump_accessibility_events_browsertest.cc @@ -1028,6 +1028,11 @@ IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest, RunEventTest(FILE_PATH_LITERAL("menu-opened-closed.html")); } +IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest, + AccessibilityEventsMenuOpenedClosedViaInnerText) { + RunEventTest(FILE_PATH_LITERAL("menu-opened-closed-via-inner-text.html")); +} + #if defined(OS_WIN) && defined(ADDRESS_SANITIZER) // TODO(crbug.com/1198056#c16): Test is flaky on Windows ASAN. #define MAYBE_AccessibilityEventsMenubarShowHideMenus \ diff --git a/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityEventsTest.java b/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityEventsTest.java index de8003c1a26f3d..46b962e28df954 100644 --- a/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityEventsTest.java +++ b/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityEventsTest.java @@ -825,6 +825,12 @@ public void test_menuOpenedClosed() { performTest("menu-opened-closed.html", EMPTY_EXPECTATIONS_FILE); } + @Test + @SmallTest + public void test_menuOpenedClosedViaInnerText() { + performTest("menu-opened-closed-via-inner-text.html", EMPTY_EXPECTATIONS_FILE); + } + @Test @SmallTest public void test_multipleAriaPropertiesChanged() { diff --git a/content/test/data/accessibility/event/menu-opened-closed-via-inner-text-expected-auralinux.txt b/content/test/data/accessibility/event/menu-opened-closed-via-inner-text-expected-auralinux.txt new file mode 100644 index 00000000000000..c4f97697b7977f --- /dev/null +++ b/content/test/data/accessibility/event/menu-opened-closed-via-inner-text-expected-auralinux.txt @@ -0,0 +1,7 @@ +STATE-CHANGE:SHOWING:TRUE role=ROLE_MENU name='(null)' ENABLED,FOCUSABLE,SENSITIVE,SHOWING,VERTICAL,VISIBLE +=== Start Continuation === +STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='(null)' ENABLED,FOCUSABLE,SENSITIVE,SHOWING,VERTICAL,VISIBLE +=== Start Continuation === +STATE-CHANGE:SHOWING:TRUE role=ROLE_MENU name='(null)' ENABLED,FOCUSABLE,SENSITIVE,SHOWING,VERTICAL,VISIBLE +=== Start Continuation === +STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='(null)' ENABLED,FOCUSABLE,SENSITIVE,SHOWING,VERTICAL,VISIBLE diff --git a/content/test/data/accessibility/event/menu-opened-closed-via-inner-text-expected-mac.txt b/content/test/data/accessibility/event/menu-opened-closed-via-inner-text-expected-mac.txt new file mode 100644 index 00000000000000..6063402476f31a --- /dev/null +++ b/content/test/data/accessibility/event/menu-opened-closed-via-inner-text-expected-mac.txt @@ -0,0 +1,7 @@ +AXMenuOpened on AXMenu +=== Start Continuation === +AXMenuClosed on AXWebArea +=== Start Continuation === +AXMenuOpened on AXMenu +=== Start Continuation === +AXMenuClosed on AXWebArea diff --git a/content/test/data/accessibility/event/menu-opened-closed-via-inner-text-expected-win.txt b/content/test/data/accessibility/event/menu-opened-closed-via-inner-text-expected-win.txt new file mode 100644 index 00000000000000..cef4e90a177359 --- /dev/null +++ b/content/test/data/accessibility/event/menu-opened-closed-via-inner-text-expected-win.txt @@ -0,0 +1,7 @@ +EVENT_SYSTEM_MENUPOPUPSTART on role=ROLE_SYSTEM_MENUPOPUP FOCUSABLE IA2_STATE_VERTICAL SetSize=3 +=== Start Continuation === +EVENT_SYSTEM_MENUPOPUPEND on role=ROLE_SYSTEM_MENUPOPUP FOCUSABLE IA2_STATE_VERTICAL SetSize=3 +=== Start Continuation === +EVENT_SYSTEM_MENUPOPUPSTART on role=ROLE_SYSTEM_MENUPOPUP FOCUSABLE IA2_STATE_VERTICAL SetSize=3 +=== Start Continuation === +EVENT_SYSTEM_MENUPOPUPEND on role=ROLE_SYSTEM_MENUPOPUP FOCUSABLE IA2_STATE_VERTICAL SetSize=3 diff --git a/content/test/data/accessibility/event/menu-opened-closed-via-inner-text.html b/content/test/data/accessibility/event/menu-opened-closed-via-inner-text.html new file mode 100644 index 00000000000000..231cae031d9dce --- /dev/null +++ b/content/test/data/accessibility/event/menu-opened-closed-via-inner-text.html @@ -0,0 +1,43 @@ + + +
+ diff --git a/ui/accessibility/ax_event_generator.cc b/ui/accessibility/ax_event_generator.cc index 24125ba70f0abf..ea9f7459323bf7 100644 --- a/ui/accessibility/ax_event_generator.cc +++ b/ui/accessibility/ax_event_generator.cc @@ -780,6 +780,14 @@ void AXEventGenerator::OnTreeDataChanged(AXTree* tree, void AXEventGenerator::OnNodeWillBeDeleted(AXTree* tree, AXNode* node) { live_region_tracker_->OnNodeWillBeDeleted(*node); + // TODO(accessibility): This should also handle firing MENU_POPUP_END when a + // node with the menu role is removed. The issue to be solved is that after we + // add MENU_POPUP_END here, the node gets removed from the tree. Then + // PostprocessEvents removes the events from that now-removed node, thus + // MENU_POPUP_END never gets fired. We work around this issue currently by + // firing the event from BrowserAccessibilityManager. Adding the ability to + // fire generated events immediately should make it possible to fire + // MENU_POPUP_END here. DCHECK_EQ(tree_, tree); tree_events_.erase(node->id()); } @@ -802,6 +810,14 @@ void AXEventGenerator::OnNodeReparented(AXTree* tree, AXNode* node) { AddEvent(node, Event::PARENT_CHANGED); } +void AXEventGenerator::OnNodeCreated(AXTree* tree, AXNode* node) { + DCHECK_EQ(tree_, tree); + if (node->GetRole() == ax::mojom::Role::kMenu && + !node->IsInvisibleOrIgnored()) { + AddEvent(node, Event::MENU_POPUP_START); + } +} + void AXEventGenerator::OnAtomicUpdateFinished( AXTree* tree, bool root_changed, diff --git a/ui/accessibility/ax_event_generator.h b/ui/accessibility/ax_event_generator.h index cd3ad791c47f76..75f9ef51c49fcb 100644 --- a/ui/accessibility/ax_event_generator.h +++ b/ui/accessibility/ax_event_generator.h @@ -300,6 +300,7 @@ class AX_EXPORT AXEventGenerator : public AXTreeObserver { void OnNodeWillBeReparented(AXTree* tree, AXNode* node) override; void OnSubtreeWillBeReparented(AXTree* tree, AXNode* node) override; void OnNodeReparented(AXTree* tree, AXNode* node) override; + void OnNodeCreated(AXTree* tree, AXNode* node) override; void OnAtomicUpdateFinished(AXTree* tree, bool root_changed, const std::vector& changes) override; From 3bc8a83bddc8de47c1a4e79ee588134a44561b1c Mon Sep 17 00:00:00 2001 From: chromium-autoroll Date: Wed, 15 Dec 2021 01:12:42 +0000 Subject: [PATCH 40/77] Roll WebGPU CTS from 216e2ff413d3 to c843f8d63c8c (2 revisions) https://chromium.googlesource.com/external/github.com/gpuweb/cts.git/+log/216e2ff413d3..c843f8d63c8c 2021-12-14 jrprice@google.com wgsl: Remove [[block]] attribute from all tests (#857) 2021-12-14 hao.x.li@intel.com Add api,operation,rendering,indirect_draw:drawIndexedIndirect tests (#852) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/webgpu-cts-chromium-autoroll Please CC enga@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Chromium: https://bugs.chromium.org/p/chromium/issues/entry To report a problem with the AutoRoller itself, please file a bug: https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md Cq-Include-Trybots: luci.chromium.try:dawn-linux-x64-deps-rel;luci.chromium.try:dawn-mac-x64-deps-rel;luci.chromium.try:dawn-win10-x64-deps-rel;luci.chromium.try:dawn-win10-x86-deps-rel Bug: None Tbr: enga@google.com Change-Id: I9b436d17243817dc77955017d6bcaf5813750524 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3339749 Commit-Queue: chromium-autoroll Bot-Commit: chromium-autoroll Cr-Commit-Position: refs/heads/main@{#951763} --- DEPS | 2 +- third_party/blink/web_tests/wpt_internal/webgpu/cts.https.html | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/DEPS b/DEPS index 7091606083c90b..0a745079ccb666 100644 --- a/DEPS +++ b/DEPS @@ -1639,7 +1639,7 @@ deps = { Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'b1f3776e4913637221733a4da09f3339e783b771', 'src/third_party/webgpu-cts/src': - Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '216e2ff413d3ed5e7695626bb55ae41575818352', + Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'c843f8d63c8c17acfbb7d48e09059a581ba779b9', 'src/third_party/webrtc': Var('webrtc_git') + '/src.git' + '@' + '63b97de330fe3d4775b9b4df8ad15c7593d58fc0', diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/cts.https.html b/third_party/blink/web_tests/wpt_internal/webgpu/cts.https.html index c5246a4b3a7b1b..f7d2c112194292 100644 --- a/third_party/blink/web_tests/wpt_internal/webgpu/cts.https.html +++ b/third_party/blink/web_tests/wpt_internal/webgpu/cts.https.html @@ -1301,8 +1301,7 @@ - - + From 94eac62a049ebdb2c54e19b7a420499faa20fd27 Mon Sep 17 00:00:00 2001 From: Marlon Facey Date: Wed, 15 Dec 2021 01:16:37 +0000 Subject: [PATCH 41/77] [realbox] Add chrome://flags entry for using Google G icon in realbox This feature is default disabled Bug: 1279603 Change-Id: Ic408874fc5cc5204d625f196466d6bcd268674f7 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3339914 Reviewed-by: Mohamad Ahmadi Commit-Queue: Marlon Facey Cr-Commit-Position: refs/heads/main@{#951764} --- chrome/browser/about_flags.cc | 5 +++++ chrome/browser/flag-metadata.json | 5 +++++ chrome/browser/flag_descriptions.cc | 5 +++++ chrome/browser/flag_descriptions.h | 3 +++ tools/metrics/histograms/enums.xml | 2 ++ 5 files changed, 20 insertions(+) diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index a437923a1b6ec8..4109bff9ec802b 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc @@ -4985,6 +4985,11 @@ const FeatureEntry kFeatureEntries[] = { {"ntp-realbox-tail-suggest", flag_descriptions::kNtpRealboxTailSuggestName, flag_descriptions::kNtpRealboxTailSuggestDescription, kOsDesktop, FEATURE_VALUE_TYPE(omnibox::kNtpRealboxTailSuggest)}, + + {"ntp-realbox-use-google-g-icon", + flag_descriptions::kNtpRealboxUseGoogleGIconName, + flag_descriptions::kNtpRealboxUseGoogleGIconDescription, kOsDesktop, + FEATURE_VALUE_TYPE(ntp_features::kRealboxUseGoogleGIcon)}, #endif // !defined(OS_ANDROID) #if defined(DCHECK_IS_CONFIGURABLE) diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json index ca7af70ec0f0a9..0babcd6b5b2508 100644 --- a/chrome/browser/flag-metadata.json +++ b/chrome/browser/flag-metadata.json @@ -4033,6 +4033,11 @@ "owners": [ "mahmadi", "mfacey" ], "expiry_milestone": 100 }, + { + "name": "ntp-realbox-use-google-g-icon", + "owners": [ "mahmadi", "mfacey" ], + "expiry_milestone": 105 + }, { "name": "ntp-recipe-tasks-module", "owners": [ "mahmadi", "tiborg" ], diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc index 573e55409c78b8..c9ef78283da058 100644 --- a/chrome/browser/flag_descriptions.cc +++ b/chrome/browser/flag_descriptions.cc @@ -3729,6 +3729,11 @@ const char kNtpRealboxTailSuggestName[] = "NTP Realbox Tail Suggest"; const char kNtpRealboxTailSuggestDescription[] = "Properly formats the tail suggestions to match the Omnibox"; +const char kNtpRealboxUseGoogleGIconName[] = "NTP Realbox Google G Icon"; +const char kNtpRealboxUseGoogleGIconDescription[] = + "Shows Google G icon " + "instead of Search Loupe in realbox when enabled"; + const char kEnableReaderModeName[] = "Enable Reader Mode"; const char kEnableReaderModeDescription[] = "Allows viewing of simplified web pages by selecting 'Customize and " diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h index 5a37e0c19835b7..093ba1279c0ea0 100644 --- a/chrome/browser/flag_descriptions.h +++ b/chrome/browser/flag_descriptions.h @@ -2153,6 +2153,9 @@ extern const char kNtpRealboxSuggestionAnswersDescription[]; extern const char kNtpRealboxTailSuggestName[]; extern const char kNtpRealboxTailSuggestDescription[]; +extern const char kNtpRealboxUseGoogleGIconName[]; +extern const char kNtpRealboxUseGoogleGIconDescription[]; + extern const char kEnableReaderModeName[]; extern const char kEnableReaderModeDescription[]; diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index 5a603e1977537c..b948f9b08de9ce 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml @@ -50159,6 +50159,7 @@ from previous Chrome versions. + @@ -54616,6 +54617,7 @@ from previous Chrome versions. + From d67bd4e6cc5bb4311ec00aae53ebda009ea5b142 Mon Sep 17 00:00:00 2001 From: Keren Zhu Date: Wed, 15 Dec 2021 01:19:58 +0000 Subject: [PATCH 42/77] Reflect aura::Window's transient parent change in Widget::parent() An aura::Window may get reparented by TransientWindowManager::AddTransientChild() without updating Widget::parent(). This may result in an UAF when Widget::parent() is dereferenced after destroyed. This CL updates Widget::parent() to reflect the transient parent change. Note that aura::Window may also get reparented, but that is not the widget's logical parent. On ChromeOS, the application windows have a common native parent, but might have different transient(aka. logical) parent. Adds tests to exo_unittests and wm_unittests. The UAF issue seems most prominent on lacros ChromeOS, where exo provides a ShellSurface::SetParentWindow() API. Bug: 1242964 Change-Id: Ie51c7e92ae94e9700aa0c4e158a16d03875aac05 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3324727 Reviewed-by: Mitsuru Oshima Reviewed-by: Scott Violet Commit-Queue: Keren Zhu Cr-Commit-Position: refs/heads/main@{#951765} --- components/exo/shell_surface_unittest.cc | 52 +++++++++++++++++++ ui/views/widget/native_widget_aura.cc | 9 ++++ ui/views/widget/native_widget_aura.h | 5 ++ ui/views/widget/native_widget_delegate.h | 3 ++ ui/views/widget/widget.cc | 51 ++++++++++-------- ui/views/widget/widget.h | 16 ++++-- ui/wm/core/transient_window_manager.cc | 4 ++ .../core/transient_window_manager_unittest.cc | 32 ++++++++---- ui/wm/core/transient_window_observer.h | 6 ++- 9 files changed, 139 insertions(+), 39 deletions(-) diff --git a/components/exo/shell_surface_unittest.cc b/components/exo/shell_surface_unittest.cc index 02fe45d2a21d33..5e1ceee46cae3b 100644 --- a/components/exo/shell_surface_unittest.cc +++ b/components/exo/shell_surface_unittest.cc @@ -1830,4 +1830,56 @@ TEST_F(ShellSurfaceTest, NotifyOnWindowCreation) { EXPECT_EQ(1u, observer.observed_windows().size()); } +TEST_F(ShellSurfaceTest, Reparent) { + auto shell_surface1 = test::ShellSurfaceBuilder({20, 20}).BuildShellSurface(); + views::Widget* widget1 = shell_surface1->GetWidget(); + + // Create a second window. + auto shell_surface2 = test::ShellSurfaceBuilder({20, 20}).BuildShellSurface(); + views::Widget* widget2 = shell_surface2->GetWidget(); + + auto child_shell_surface = + test::ShellSurfaceBuilder({20, 20}).BuildShellSurface(); + child_shell_surface->SetParent(shell_surface1.get()); + views::Widget* child_widget = child_shell_surface->GetWidget(); + // By default, a child widget is not activatable. Explicitly make it + // activatable so that calling child_surface->RequestActivation() is + // possible. + child_widget->widget_delegate()->SetCanActivate(true); + + GrantPermissionToActivateIndefinitely(widget1->GetNativeWindow()); + GrantPermissionToActivateIndefinitely(widget2->GetNativeWindow()); + GrantPermissionToActivateIndefinitely(child_widget->GetNativeWindow()); + + shell_surface2->Activate(); + EXPECT_FALSE(child_widget->ShouldPaintAsActive()); + EXPECT_FALSE(widget1->ShouldPaintAsActive()); + EXPECT_TRUE(widget2->ShouldPaintAsActive()); + + child_shell_surface->Activate(); + // A widget should have the same paint-as-active state with its parent. + EXPECT_TRUE(child_widget->ShouldPaintAsActive()); + EXPECT_TRUE(widget1->ShouldPaintAsActive()); + EXPECT_FALSE(widget2->ShouldPaintAsActive()); + + // Reparent child to widget2. + child_shell_surface->SetParent(shell_surface2.get()); + EXPECT_TRUE(child_widget->ShouldPaintAsActive()); + EXPECT_TRUE(widget2->ShouldPaintAsActive()); + EXPECT_FALSE(widget1->ShouldPaintAsActive()); + + shell_surface1->Activate(); + EXPECT_FALSE(child_widget->ShouldPaintAsActive()); + EXPECT_FALSE(widget2->ShouldPaintAsActive()); + EXPECT_TRUE(widget1->ShouldPaintAsActive()); + + // Delete widget1 (i.e. the non-parent widget) shouldn't crash. + widget1->Close(); + shell_surface1.reset(); + + child_shell_surface->Activate(); + EXPECT_TRUE(child_widget->ShouldPaintAsActive()); + EXPECT_TRUE(widget2->ShouldPaintAsActive()); +} + } // namespace exo diff --git a/ui/views/widget/native_widget_aura.cc b/ui/views/widget/native_widget_aura.cc index 6ab9be02f3facb..70809b7568de95 100644 --- a/ui/views/widget/native_widget_aura.cc +++ b/ui/views/widget/native_widget_aura.cc @@ -208,6 +208,8 @@ void NativeWidgetAura::InitNativeWidget(Widget::InitParams params) { gfx::NativeView parent = params.parent; gfx::NativeView context = params.context; if (!params.child) { + wm::TransientWindowManager::GetOrCreate(window_)->AddObserver(this); + // Set up the transient child before the window is added. This way the // LayoutManager knows the window has a transient parent. if (parent && parent->GetType() != aura::client::WINDOW_TYPE_UNKNOWN) { @@ -1108,6 +1110,13 @@ aura::client::DragDropDelegate::DropCallback NativeWidgetAura::GetDropCallback( last_drop_operation_); } +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetAura, wm::TransientWindowObserver implementation: + +void NativeWidgetAura::OnTransientParentChanged(aura::Window* new_parent) { + delegate_->OnNativeWidgetParentChanged(new_parent); +} + //////////////////////////////////////////////////////////////////////////////// // NativeWidgetAura, protected: diff --git a/ui/views/widget/native_widget_aura.h b/ui/views/widget/native_widget_aura.h index b8f6a95fe3eb5b..50edf929a9e483 100644 --- a/ui/views/widget/native_widget_aura.h +++ b/ui/views/widget/native_widget_aura.h @@ -19,6 +19,7 @@ #include "ui/events/event_constants.h" #include "ui/views/views_export.h" #include "ui/views/widget/native_widget_private.h" +#include "ui/wm/core/transient_window_observer.h" #include "ui/wm/public/activation_change_observer.h" #include "ui/wm/public/activation_delegate.h" @@ -42,6 +43,7 @@ class VIEWS_EXPORT NativeWidgetAura : public internal::NativeWidgetPrivate, public aura::WindowObserver, public wm::ActivationDelegate, public wm::ActivationChangeObserver, + public wm::TransientWindowObserver, public aura::client::FocusChangeObserver, public aura::client::DragDropDelegate { public: @@ -222,6 +224,9 @@ class VIEWS_EXPORT NativeWidgetAura : public internal::NativeWidgetPrivate, aura::client::DragDropDelegate::DropCallback GetDropCallback( const ui::DropTargetEvent& event) override; + // aura::TransientWindowObserver: + void OnTransientParentChanged(aura::Window* new_parent) override; + protected: ~NativeWidgetAura() override; diff --git a/ui/views/widget/native_widget_delegate.h b/ui/views/widget/native_widget_delegate.h index 6abb63703ba830..f3239499fe8afc 100644 --- a/ui/views/widget/native_widget_delegate.h +++ b/ui/views/widget/native_widget_delegate.h @@ -76,6 +76,9 @@ class VIEWS_EXPORT NativeWidgetDelegate { // Called just after the native widget is destroyed. virtual void OnNativeWidgetDestroyed() = 0; + // Called after the native widget's parent has changed. + virtual void OnNativeWidgetParentChanged(gfx::NativeView parent) = 0; + // Returns the smallest size the window can be resized to by the user. virtual gfx::Size GetMinimumSize() const = 0; diff --git a/ui/views/widget/widget.cc b/ui/views/widget/widget.cc index b96ce533db4202..ef8434f61e00ed 100644 --- a/ui/views/widget/widget.cc +++ b/ui/views/widget/widget.cc @@ -283,30 +283,11 @@ void Widget::GetAllOwnedWidgets(gfx::NativeView native_view, Widgets* owned) { void Widget::ReparentNativeView(gfx::NativeView native_view, gfx::NativeView new_parent) { internal::NativeWidgetPrivate::ReparentNativeView(native_view, new_parent); - Widget* child_widget = GetWidgetForNativeView(native_view); Widget* parent_widget = new_parent ? GetWidgetForNativeView(new_parent) : nullptr; - if (child_widget) { - child_widget->parent_ = parent_widget; - - // Release the paint-as-active lock on the old parent. - bool has_lock_on_parent = !!child_widget->parent_paint_as_active_lock_; - child_widget->parent_paint_as_active_lock_.reset(); - child_widget->parent_paint_as_active_subscription_ = - base::CallbackListSubscription(); - - // Lock and subscribe to parent's paint-as-active. - if (parent_widget) { - if (has_lock_on_parent) - child_widget->parent_paint_as_active_lock_ = - parent_widget->LockPaintAsActive(); - child_widget->parent_paint_as_active_subscription_ = - parent_widget->RegisterPaintAsActiveChangedCallback( - base::BindRepeating(&Widget::OnParentShouldPaintAsActiveChanged, - base::Unretained(child_widget))); - } - } + if (child_widget) + child_widget->SetParent(parent_widget); } // static @@ -1220,7 +1201,6 @@ bool Widget::ShouldPaintAsActive() const { } void Widget::OnParentShouldPaintAsActiveChanged() { - DCHECK(parent()); // |native_widget_| has already been deleted and |this| is being deleted so // that we don't have to handle the event and also it's unsafe to reference // |native_widget_| in this case. @@ -1420,6 +1400,11 @@ void Widget::OnNativeWidgetDestroyed() { native_widget_destroyed_ = true; } +void Widget::OnNativeWidgetParentChanged(gfx::NativeView parent) { + Widget* parent_widget = parent ? GetWidgetForNativeView(parent) : nullptr; + SetParent(parent_widget); +} + gfx::Size Widget::GetMinimumSize() const { return non_client_view_ ? non_client_view_->GetMinimumSize() : gfx::Size(); } @@ -1875,6 +1860,28 @@ void Widget::SetInitialBoundsForFramelessWindow(const gfx::Rect& bounds) { } } +void Widget::SetParent(Widget* parent) { + if (parent == parent_) + return; + + parent_ = parent; + + // Release the paint-as-active lock on the old parent. + bool has_lock_on_parent = !!parent_paint_as_active_lock_; + parent_paint_as_active_lock_.reset(); + parent_paint_as_active_subscription_ = base::CallbackListSubscription(); + + // Lock and subscribe to parent's paint-as-active. + if (parent) { + if (has_lock_on_parent || native_widget_active_) + parent_paint_as_active_lock_ = parent->LockPaintAsActive(); + parent_paint_as_active_subscription_ = + parent->RegisterPaintAsActiveChangedCallback( + base::BindRepeating(&Widget::OnParentShouldPaintAsActiveChanged, + base::Unretained(this))); + } +} + bool Widget::GetSavedWindowPlacement(gfx::Rect* bounds, ui::WindowShowState* show_state) { // First we obtain the window's saved show-style and store it. We need to do diff --git a/ui/views/widget/widget.h b/ui/views/widget/widget.h index 70d1930905e2c0..5225b04b609228 100644 --- a/ui/views/widget/widget.h +++ b/ui/views/widget/widget.h @@ -928,8 +928,12 @@ class VIEWS_EXPORT Widget : public internal::NativeWidgetDelegate, focus_on_creation_ = focus_on_creation; } - // Returns the parent of this widget. Note that a top-level widget is not - // necessarily a root widget and can have a parent. + // Returns the parent of this widget. Note that + // * A top-level widget is not necessarily the root and may have a parent. + // * A child widget shares the same visual style, e.g. the dark/light theme, + // with its parent. + // * The native widget may change a widget's parent. + // * The native view's parent might or might not be the parent's native view. Widget* parent() { return parent_; } const Widget* parent() const { return parent_; } @@ -1006,6 +1010,7 @@ class VIEWS_EXPORT Widget : public internal::NativeWidgetDelegate, void OnNativeWidgetCreated() override; void OnNativeWidgetDestroying() override; void OnNativeWidgetDestroyed() override; + void OnNativeWidgetParentChanged(gfx::NativeView parent) override; gfx::Size GetMinimumSize() const override; gfx::Size GetMaximumSize() const override; void OnNativeWidgetMove() override; @@ -1132,6 +1137,9 @@ class VIEWS_EXPORT Widget : public internal::NativeWidgetDelegate, // Sizes and positions the frameless window just after it is created. void SetInitialBoundsForFramelessWindow(const gfx::Rect& bounds); + // Set the parent of this widget. + void SetParent(Widget* parent); + // Returns the bounds and "show" state from the delegate. Returns true if // the delegate wants to use a specified bounds. bool GetSavedWindowPlacement(gfx::Rect* bounds, @@ -1157,8 +1165,8 @@ class VIEWS_EXPORT Widget : public internal::NativeWidgetDelegate, // to Init() a default WidgetDelegate is created. raw_ptr widget_delegate_ = nullptr; - // The parent of this widget. This is the widget that associates with the - // |params.parent| supplied to Init(). If no parent is given or the native + // The parent of this widget. This is the widget that associates with + // the |params.parent| supplied to Init(). If no parent is given or the native // view parent has no associating Widget, this value will be nullptr. raw_ptr parent_ = nullptr; diff --git a/ui/wm/core/transient_window_manager.cc b/ui/wm/core/transient_window_manager.cc index 0bf41b42b99d01..39219ef2126f1f 100644 --- a/ui/wm/core/transient_window_manager.cc +++ b/ui/wm/core/transient_window_manager.cc @@ -84,6 +84,8 @@ void TransientWindowManager::AddTransientChild(Window* child) { for (auto& observer : observers_) observer.OnTransientChildAdded(window_, child); + for (auto& observer : child_manager->observers_) + observer.OnTransientParentChanged(window_); } void TransientWindowManager::RemoveTransientChild(Window* child) { @@ -108,6 +110,8 @@ void TransientWindowManager::RemoveTransientChild(Window* child) { for (auto& observer : observers_) observer.OnTransientChildRemoved(window_, child); + for (auto& observer : child_manager->observers_) + observer.OnTransientParentChanged(nullptr); } bool TransientWindowManager::IsStackingTransient( diff --git a/ui/wm/core/transient_window_manager_unittest.cc b/ui/wm/core/transient_window_manager_unittest.cc index e6df623d85cfc5..2ba6910606427c 100644 --- a/ui/wm/core/transient_window_manager_unittest.cc +++ b/ui/wm/core/transient_window_manager_unittest.cc @@ -24,8 +24,7 @@ namespace wm { class TestTransientWindowObserver : public TransientWindowObserver { public: - TestTransientWindowObserver() : add_count_(0), remove_count_(0) { - } + TestTransientWindowObserver() = default; TestTransientWindowObserver(const TestTransientWindowObserver&) = delete; TestTransientWindowObserver& operator=(const TestTransientWindowObserver&) = @@ -35,6 +34,7 @@ class TestTransientWindowObserver : public TransientWindowObserver { int add_count() const { return add_count_; } int remove_count() const { return remove_count_; } + int parent_change_count() const { return parent_change_count_; } // TransientWindowObserver overrides: void OnTransientChildAdded(Window* window, Window* transient) override { @@ -43,10 +43,14 @@ class TestTransientWindowObserver : public TransientWindowObserver { void OnTransientChildRemoved(Window* window, Window* transient) override { remove_count_++; } + void OnTransientParentChanged(Window* window) override { + parent_change_count_++; + } private: - int add_count_; - int remove_count_; + int add_count_ = 0; + int remove_count_ = 0; + int parent_change_count_ = 0; }; class WindowVisibilityObserver : public aura::WindowObserver { @@ -443,20 +447,26 @@ TEST_F(TransientWindowManagerTest, TransientWindowObserverNotified) { std::unique_ptr parent(CreateTestWindowWithId(0, root_window())); std::unique_ptr w1(CreateTestWindowWithId(1, parent.get())); - TestTransientWindowObserver test_observer; + TestTransientWindowObserver test_parent_observer, test_child_observer; TransientWindowManager::GetOrCreate(parent.get()) - ->AddObserver(&test_observer); + ->AddObserver(&test_parent_observer); + TransientWindowManager::GetOrCreate(w1.get())->AddObserver( + &test_child_observer); AddTransientChild(parent.get(), w1.get()); - EXPECT_EQ(1, test_observer.add_count()); - EXPECT_EQ(0, test_observer.remove_count()); + EXPECT_EQ(1, test_parent_observer.add_count()); + EXPECT_EQ(0, test_parent_observer.remove_count()); + EXPECT_EQ(1, test_child_observer.parent_change_count()); RemoveTransientChild(parent.get(), w1.get()); - EXPECT_EQ(1, test_observer.add_count()); - EXPECT_EQ(1, test_observer.remove_count()); + EXPECT_EQ(1, test_parent_observer.add_count()); + EXPECT_EQ(1, test_parent_observer.remove_count()); + EXPECT_EQ(2, test_child_observer.parent_change_count()); TransientWindowManager::GetOrCreate(parent.get()) - ->RemoveObserver(&test_observer); + ->RemoveObserver(&test_parent_observer); + TransientWindowManager::GetOrCreate(parent.get()) + ->RemoveObserver(&test_child_observer); } TEST_F(TransientWindowManagerTest, ChangeParent) { diff --git a/ui/wm/core/transient_window_observer.h b/ui/wm/core/transient_window_observer.h index 6a7a2e2ffe7050..d935a808dc8f04 100644 --- a/ui/wm/core/transient_window_observer.h +++ b/ui/wm/core/transient_window_observer.h @@ -17,11 +17,13 @@ class WM_CORE_EXPORT TransientWindowObserver { public: // Called when a transient child is added to |window|. virtual void OnTransientChildAdded(aura::Window* window, - aura::Window* transient) = 0; + aura::Window* transient) {} // Called when a transient child is removed from |window|. virtual void OnTransientChildRemoved(aura::Window* window, - aura::Window* transient) = 0; + aura::Window* transient) {} + + virtual void OnTransientParentChanged(aura::Window* new_parent) {} protected: virtual ~TransientWindowObserver() {} From 1fb9d004439f8f11d5b2ab70461c5cd15f5f1ebd Mon Sep 17 00:00:00 2001 From: Sammie Quon Date: Wed, 15 Dec 2021 01:25:21 +0000 Subject: [PATCH 43/77] desk_templates: Fix UAF when tabbing after deleting a template view. Crash was due to the grid being rebuilt while a templates view was highlighted. This would make `OverviewHighlightController::highlight_view_` invalid, but it wasn't getting set to nullptr. This patch checks for that case and notifies OverviewHighlightController that the view will be deleted soon. This should only be necessary for deletions once we stop doing unnecessary updates. Test: manual, added regression test Change-Id: I4f893a995c7904974a1b9713a534e5e4ab5efab9 Fixed: 1279649 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3339386 Reviewed-by: Richard Chui Commit-Queue: Sammie Quon Cr-Commit-Position: refs/heads/main@{#951766} --- .../templates/desks_templates_grid_view.cc | 33 +++++++++++++++++++ .../templates/desks_templates_item_view.cc | 12 ++++--- .../templates/desks_templates_unittest.cc | 25 ++++++++++++++ 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/ash/wm/desks/templates/desks_templates_grid_view.cc b/ash/wm/desks/templates/desks_templates_grid_view.cc index a591bda9e9f342..cbe579832ef28b 100644 --- a/ash/wm/desks/templates/desks_templates_grid_view.cc +++ b/ash/wm/desks/templates/desks_templates_grid_view.cc @@ -8,9 +8,14 @@ #include #include "ash/public/cpp/shell_window_ids.h" +#include "ash/shell.h" #include "ash/wm/desks/templates/desks_templates_animations.h" #include "ash/wm/desks/templates/desks_templates_item_view.h" +#include "ash/wm/desks/templates/desks_templates_name_view.h" #include "ash/wm/desks/templates/desks_templates_presenter.h" +#include "ash/wm/overview/overview_controller.h" +#include "ash/wm/overview/overview_highlight_controller.h" +#include "ash/wm/overview/overview_session.h" #include "ui/aura/window.h" #include "ui/base/metadata/metadata_impl_macros.h" #include "ui/compositor/layer.h" @@ -101,6 +106,34 @@ DesksTemplatesGridView::CreateDesksTemplatesGridWidget(aura::Window* root) { void DesksTemplatesGridView::UpdateGridUI( const std::vector& desk_templates, const gfx::Rect& grid_bounds) { + // Check if any of the template items or their name views have overview focus + // and notify the highlight controller. This should only be needed when a + // template item is deleted, but currently we call `UpdateGridUI` every time + // the model changes. + // TODO(richui): Remove this when `UpdateGridUI` is not rebuilt every time. + if (!grid_items_.empty()) { + auto* highlight_controller = Shell::Get() + ->overview_controller() + ->overview_session() + ->highlight_controller(); + if (highlight_controller->IsFocusHighlightVisible()) { + // Notify the highlight controller if any of the about to be destroyed + // views have overview focus to prevent use-after-free. + for (DesksTemplatesItemView* template_view : grid_items_) { + if (template_view->IsViewHighlighted()) { + highlight_controller->OnViewDestroyingOrDisabling(template_view); + return; + } + + if (template_view->name_view()->IsViewHighlighted()) { + highlight_controller->OnViewDestroyingOrDisabling( + template_view->name_view()); + return; + } + } + } + } + // Clear the layout manager before removing the child views to avoid // use-after-free bugs due to `Layout()`s being triggered. SetLayoutManager(nullptr); diff --git a/ash/wm/desks/templates/desks_templates_item_view.cc b/ash/wm/desks/templates/desks_templates_item_view.cc index 0444c79d4cabfe..20ab66fb5e8a5a 100644 --- a/ash/wm/desks/templates/desks_templates_item_view.cc +++ b/ash/wm/desks/templates/desks_templates_item_view.cc @@ -416,10 +416,14 @@ views::View* DesksTemplatesItemView::TargetForRect(views::View* root, void DesksTemplatesItemView::OnDeleteTemplate() { // Notify the highlight controller that we're going away. - OverviewSession* overview_session = - Shell::Get()->overview_controller()->overview_session(); - DCHECK(overview_session); - overview_session->highlight_controller()->OnViewDestroyingOrDisabling(this); + OverviewHighlightController* highlight_controller = + Shell::Get() + ->overview_controller() + ->overview_session() + ->highlight_controller(); + DCHECK(highlight_controller); + highlight_controller->OnViewDestroyingOrDisabling(this); + highlight_controller->OnViewDestroyingOrDisabling(name_view_); DesksTemplatesPresenter::Get()->DeleteEntry( desk_template_->uuid().AsLowercaseString()); diff --git a/ash/wm/desks/templates/desks_templates_unittest.cc b/ash/wm/desks/templates/desks_templates_unittest.cc index cb4c752126d013..e2d63e1ed754c9 100644 --- a/ash/wm/desks/templates/desks_templates_unittest.cc +++ b/ash/wm/desks/templates/desks_templates_unittest.cc @@ -1789,4 +1789,29 @@ TEST_F(DesksTemplatesTest, TemplateNameTestSpaces) { EXPECT_EQ(base::UTF8ToUTF16(template_name), name_view->GetText()); } +// Tests that there is no crash after we use the keyboard to change the name of +// a template. Regression test for https://crbug.com/1279649. +TEST_F(DesksTemplatesTest, EditTemplateNameWithKeyboardNoCrash) { + AddEntry(base::GUID::GenerateRandomV4(), "a", base::Time::Now()); + AddEntry(base::GUID::GenerateRandomV4(), "b", base::Time::Now()); + + OpenOverviewAndShowTemplatesGrid(); + DesksTemplatesNameView* name_view = + GetItemViewFromTemplatesGrid(0)->name_view(); + + // Tab until we focus the name view of the first template item. + SendKey(ui::VKEY_TAB); + SendKey(ui::VKEY_TAB); + ASSERT_EQ(name_view, GetHighlightedView()); + + // Rename template "a" to template "d". + SendKey(ui::VKEY_RETURN); + SendKey(ui::VKEY_D); + SendKey(ui::VKEY_RETURN); + WaitForDesksTemplatesUI(); + + // Verify that there is no crash after we tab again. + SendKey(ui::VKEY_TAB); +} + } // namespace ash From 77a814f3e1b108831994a78d29f091d8df5f84f3 Mon Sep 17 00:00:00 2001 From: Philip Rogers Date: Wed, 15 Dec 2021 01:40:19 +0000 Subject: [PATCH 44/77] Fix intersection ratio with pixel snapping and a root Bug: 1278897 Change-Id: Ib26c094d3c3dc33fa1f8f9303d07b1b49acc7592 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3340057 Reviewed-by: Stefan Zager Commit-Queue: Philip Rogers Cr-Commit-Position: refs/heads/main@{#951767} --- .../intersection_geometry.cc | 6 +-- ...ection-ratio-with-fractional-bounds-2.html | 37 +++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 third_party/blink/web_tests/external/wpt/intersection-observer/intersection-ratio-with-fractional-bounds-2.html diff --git a/third_party/blink/renderer/core/intersection_observer/intersection_geometry.cc b/third_party/blink/renderer/core/intersection_observer/intersection_geometry.cc index a97abd67f444b4..43c280e0de04d2 100644 --- a/third_party/blink/renderer/core/intersection_observer/intersection_geometry.cc +++ b/third_party/blink/renderer/core/intersection_observer/intersection_geometry.cc @@ -90,10 +90,10 @@ PhysicalRect InitializeRootRect(const LayoutObject* root, result = layout_view->OverflowClipRect(PhysicalOffset()); } else if (root->IsBox() && root->IsScrollContainer()) { result = To(root)->PhysicalContentBoxRect(); + } else if (root->IsBox()) { + result = To(root)->PhysicalBorderBoxRect(); } else { - // TODO(pdr, crbug.com/1020466): BorderBoundingBox is snapped. Should this - // use an unsnapped value such as PhysicalBorderBoxRect? - result = PhysicalRect(To(root)->BorderBoundingBox()); + result = To(root)->PhysicalLinesBoundingBox(); } ApplyMargin(result, margin, root->StyleRef().EffectiveZoom()); return result; diff --git a/third_party/blink/web_tests/external/wpt/intersection-observer/intersection-ratio-with-fractional-bounds-2.html b/third_party/blink/web_tests/external/wpt/intersection-observer/intersection-ratio-with-fractional-bounds-2.html new file mode 100644 index 00000000000000..1e250accd8e43e --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/intersection-observer/intersection-ratio-with-fractional-bounds-2.html @@ -0,0 +1,37 @@ + +IntersectionObserver ratio with fractional bounds + + + + + + + +
+
+ +
+
+ + From 36cae9d69886bcd42fbb8ec536a4faae7ed08c75 Mon Sep 17 00:00:00 2001 From: chromium-autoroll Date: Wed, 15 Dec 2021 01:43:07 +0000 Subject: [PATCH 45/77] Roll Depot Tools from 2777fd9c6a0a to 9e5809e98f33 (2 revisions) https://chromium.googlesource.com/chromium/tools/depot_tools.git/+log/2777fd9c6a0a..9e5809e98f33 2021-12-14 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2021-12-14 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/depot-tools-chromium-autoroll Please CC apolito@google.com,gavinmak@google.com,sokcevic@google.com,ajp@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Chromium: https://bugs.chromium.org/p/chromium/issues/entry To report a problem with the AutoRoller itself, please file a bug: https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md Bug: None Tbr: apolito@google.com,gavinmak@google.com,sokcevic@google.com,ajp@google.com Change-Id: I9e5c786b81a79a347e94257d73c0a14c559fcf09 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3339362 Commit-Queue: chromium-autoroll Bot-Commit: chromium-autoroll Cr-Commit-Position: refs/heads/main@{#951768} --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 0a745079ccb666..98cf7477417a10 100644 --- a/DEPS +++ b/DEPS @@ -1038,7 +1038,7 @@ deps = { }, 'src/third_party/depot_tools': - Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '2777fd9c6a0ae1d00b9ea5de5fab0a5324e2dd5d', + Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '9e5809e98f33a5f9f9d3b69a5bd826ce8a7a5a81', 'src/third_party/devtools-frontend/src': Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'), From 743810ea986f5f1787662d94992e151779fc95a0 Mon Sep 17 00:00:00 2001 From: Brian Sheedy Date: Wed, 15 Dec 2021 01:43:20 +0000 Subject: [PATCH 46/77] Fix unexpected passes typo Fixes a typo where "CI" was used instead of "TRY" in the unexpected pass finder shared code, causing tryjob data to be unused. Change-Id: I3ad53c80f063c303963c91cc0f62dbaa7bcc0e04 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3340297 Commit-Queue: Brian Sheedy Auto-Submit: Brian Sheedy Reviewed-by: Yuly Novikov Commit-Queue: Yuly Novikov Cr-Commit-Position: refs/heads/main@{#951769} --- testing/unexpected_passes_common/queries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/unexpected_passes_common/queries.py b/testing/unexpected_passes_common/queries.py index b27269e5c20b8b..eb1e5c4ef6dcff 100644 --- a/testing/unexpected_passes_common/queries.py +++ b/testing/unexpected_passes_common/queries.py @@ -95,7 +95,7 @@ def FillExpectationMapForTryBuilders(self, expectation_map, builders): """ logging.info('Filling test expectation map with try results') return self._FillExpectationMapForBuilders(expectation_map, builders, - constants.BuilderTypes.CI) + constants.BuilderTypes.TRY) def _FillExpectationMapForBuilders(self, expectation_map, builders, builder_type): From 1686cdcb2d2410302e81207b46c386620152cf75 Mon Sep 17 00:00:00 2001 From: Alex Turner Date: Wed, 15 Dec 2021 01:44:19 +0000 Subject: [PATCH 47/77] Extend subresource filter evaluation performance metrics Extends their expiry by a year. These metrics continue to be used to analyze the performance impact of upcoming changes to subresource filter list evaluation (e.g. crbug.com/1227873). Bug: 1274712, 1274713 Change-Id: Ia7697b8a3fcb1ca1c1278cc04ff8e4bd3833419e Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3337176 Reviewed-by: Charlie Harrison Commit-Queue: Alex Turner Cr-Commit-Position: refs/heads/main@{#951770} --- .../metrics/histograms/metadata/subresource/histograms.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/metrics/histograms/metadata/subresource/histograms.xml b/tools/metrics/histograms/metadata/subresource/histograms.xml index 66ded5d161e711..ff36f28998cf35 100644 --- a/tools/metrics/histograms/metadata/subresource/histograms.xml +++ b/tools/metrics/histograms/metadata/subresource/histograms.xml @@ -613,7 +613,7 @@ chromium-metrics-reviews@google.com. + units="microseconds" expires_after="2023-01-12"> alexmt@chromium.org chrome-ads-histograms@google.com @@ -631,7 +631,7 @@ chromium-metrics-reviews@google.com. + units="microseconds" expires_after="2023-01-12"> alexmt@chromium.org chrome-ads-histograms@google.com @@ -709,7 +709,7 @@ chromium-metrics-reviews@google.com. + units="microseconds" expires_after="2023-01-12"> alexmt@chromium.org chrome-ads-histograms@google.com From f7a0fdb62049e14af8d9f7d4175fdb81bb1e3c38 Mon Sep 17 00:00:00 2001 From: chromium-autoroll Date: Wed, 15 Dec 2021 01:44:28 +0000 Subject: [PATCH 48/77] Roll Dawn from f296710f64b6 to 5397f9f9d0fa (2 revisions) https://dawn.googlesource.com/dawn.git/+log/f296710f64b6..5397f9f9d0fa 2021-12-14 enga@chromium.org Add basic or stub implementations of upstream instance/adapter APIs 2021-12-14 enga@chromium.org Remove dawn_native::DeviceDescriptor typedef If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/dawn-chromium-autoroll Please CC enga@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Dawn: https://bugs.chromium.org/p/dawn/issues/entry To file a bug in Chromium: https://bugs.chromium.org/p/chromium/issues/entry To report a problem with the AutoRoller itself, please file a bug: https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md Cq-Include-Trybots: luci.chromium.try:dawn-linux-x64-deps-rel;luci.chromium.try:dawn-mac-x64-deps-rel;luci.chromium.try:dawn-win10-x64-deps-rel;luci.chromium.try:dawn-win10-x86-deps-rel Bug: None Tbr: enga@google.com Change-Id: I96b242a24324cbda79594726ae614574e222444c Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3339155 Commit-Queue: chromium-autoroll Bot-Commit: chromium-autoroll Cr-Commit-Position: refs/heads/main@{#951771} --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 98cf7477417a10..6d20603623a50d 100644 --- a/DEPS +++ b/DEPS @@ -354,7 +354,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. - 'dawn_revision': 'f296710f64b6625d204262c09463401def70cea6', + 'dawn_revision': '5397f9f9d0fa2d4adb03553f60d3fc4d4db5936c', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. From 707a8f6264c1e38fb5cfaa0d4f0e9f51e72075d9 Mon Sep 17 00:00:00 2001 From: Carlos IL Date: Wed, 15 Dec 2021 01:51:07 +0000 Subject: [PATCH 49/77] Add CertificateTransparencyAndroid to field trial testing config Bug: 1264628 Change-Id: Ie45ba4fa5f27cc6499318b6bfe567a65bc17367d Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3338977 Commit-Queue: Carlos IL Auto-Submit: Carlos IL Reviewed-by: Joe DeBlasio Commit-Queue: Joe DeBlasio Cr-Commit-Position: refs/heads/main@{#951772} --- .../variations/fieldtrial_testing_config.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json index 656617add19215..10a44134f30248 100644 --- a/testing/variations/fieldtrial_testing_config.json +++ b/testing/variations/fieldtrial_testing_config.json @@ -1888,6 +1888,22 @@ ] } ], + "CertificateTransparencyAndroid": [ + { + "platforms": [ + "android" + ], + "experiments": [ + { + "name": "Enabled", + "enable_features": [ + "CertificateTransparencyAndroid", + "CertificateTransparencyComponentUpdater" + ] + } + ] + } + ], "CertificateTransparencyComponentUpdater": [ { "platforms": [ From c11809b8c0b9fe16955456110c9ec3723b5ff7b5 Mon Sep 17 00:00:00 2001 From: Mason Freed Date: Wed, 15 Dec 2021 01:51:28 +0000 Subject: [PATCH 50/77] Enable ForceSynchronousHTMLParsing by default This feature is enabled at 100% (with 1% stable holdback) on all channels since M96. No unresolved problems have been reported, and overall metrics are improved [1]. This CL enables the ForceSynchronousHTMLParsing feature by default, and removes the LoaderDataPipeTuning feature entirely, replacing it with newly- hard-coded values from the launched experiment. [1] https://docs.google.com/document/d/13GewLNZ50nqs0OI7-rzofOXtjuAlD0R4PMLTUsr73dg/edit#heading=h.ctbltu75kgzp This part is cool: Fixed: 901056 Fixed: 461877 Fixed: 761992 Fixed: 789124 Fixed: 995660 Fixed: 1038534 Fixed: 1041006 Fixed: 1128608 Fixed: 1130290 Fixed: 1149988 Fixed: 1231037 -> That's 85 stars worth of bugs, as of 12-13-21. Not sure this is "fixed" by this CL, but it should at least address comment #3: Bug: 1087177 Change-Id: Icbf01ef6665362ae23f28657e5574ca705b82717 Cq-Do-Not-Cancel-Tryjobs: true Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2798812 Reviewed-by: Nate Chapin Reviewed-by: John Abd-El-Malek Reviewed-by: Kentaro Hara Reviewed-by: Yutaka Hirano Commit-Queue: Mason Freed Cr-Commit-Position: refs/heads/main@{#951773} --- services/network/public/cpp/features.cc | 33 ++++++++++--------- third_party/blink/common/features.cc | 2 +- .../loader/fetch/response_body_loader_test.cc | 11 +------ .../toggleEvent-expected.txt | 13 -------- 4 files changed, 20 insertions(+), 39 deletions(-) delete mode 100644 third_party/blink/web_tests/platform/fuchsia/external/wpt/html/semantics/interactive-elements/the-details-element/toggleEvent-expected.txt diff --git a/services/network/public/cpp/features.cc b/services/network/public/cpp/features.cc index 4aaf592cb9d5d1..f1291623833240 100644 --- a/services/network/public/cpp/features.cc +++ b/services/network/public/cpp/features.cc @@ -195,41 +195,44 @@ const base::Feature kSCTAuditingRetryAndPersistReports{ // This feature is used for tuning several loading-related data pipe // parameters. See crbug.com/1041006. const base::Feature kLoaderDataPipeTuningFeature{ - "LoaderDataPipeTuning", base::FEATURE_DISABLED_BY_DEFAULT}; + "LoaderDataPipeTuning", base::FEATURE_ENABLED_BY_DEFAULT}; namespace { -// The default buffer size of DataPipe which is used to send the content body. -static constexpr uint32_t kDataPipeDefaultAllocationSize = 512 * 1024; -constexpr base::FeatureParam kDataPipeAllocationSize{ - &kLoaderDataPipeTuningFeature, "allocation_size_bytes", - base::saturated_cast(kDataPipeDefaultAllocationSize)}; +// The default Mojo ring buffer size, used to send the content body. +static constexpr uint32_t kDefaultDataPipeAllocationSize = 512 * 1024; +// The larger ring buffer size, used primarily for network::URLLoader loads. +// This value was optimized via Finch: see crbug.com/1041006. +static constexpr uint32_t kLargerDataPipeAllocationSize = 2 * 1024 * 1024; // The maximal number of bytes consumed in a loading task. When there are more // bytes in the data pipe, they will be consumed in following tasks. Setting too // small of a number will generate many tasks but setting a too large of a -// number will lead to thread janks. -static constexpr uint32_t kMaxNumConsumedBytesInTask = 64 * 1024; -constexpr base::FeatureParam kLoaderChunkSize{ - &kLoaderDataPipeTuningFeature, "loader_chunk_size", - base::saturated_cast(kMaxNumConsumedBytesInTask)}; +// number will lead to thread janks. This value was optimized via Finch: +// see crbug.com/1041006. +static constexpr uint32_t kDefaultMaxNumConsumedBytesInTask = 64 * 1024; +static constexpr uint32_t kLargerMaxNumConsumedBytesInTask = 1024 * 1024; } // namespace // static uint32_t GetDataPipeDefaultAllocationSize(DataPipeAllocationSize option) { // For low-memory devices, always use the (smaller) default buffer size. if (base::SysInfo::AmountOfPhysicalMemoryMB() <= 512) - return kDataPipeDefaultAllocationSize; + return kDefaultDataPipeAllocationSize; + if (!base::FeatureList::IsEnabled(features::kLoaderDataPipeTuningFeature)) + return kDefaultDataPipeAllocationSize; switch (option) { case DataPipeAllocationSize::kDefaultSizeOnly: - return kDataPipeDefaultAllocationSize; + return kDefaultDataPipeAllocationSize; case DataPipeAllocationSize::kLargerSizeIfPossible: - return base::saturated_cast(kDataPipeAllocationSize.Get()); + return kLargerDataPipeAllocationSize; } } // static uint32_t GetLoaderChunkSize() { - return base::saturated_cast(kLoaderChunkSize.Get()); + if (!base::FeatureList::IsEnabled(features::kLoaderDataPipeTuningFeature)) + return kDefaultMaxNumConsumedBytesInTask; + return kLargerMaxNumConsumedBytesInTask; } // Check disk cache to see if the queued requests (especially those don't need diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc index ca187e793302c3..5ede3827d50fdf 100644 --- a/third_party/blink/common/features.cc +++ b/third_party/blink/common/features.cc @@ -123,7 +123,7 @@ const base::Feature kJSONModules{"JSONModules", base::FEATURE_ENABLED_BY_DEFAULT}; const base::Feature kForceSynchronousHTMLParsing{ - "ForceSynchronousHTMLParsing", base::FEATURE_DISABLED_BY_DEFAULT}; + "ForceSynchronousHTMLParsing", base::FEATURE_ENABLED_BY_DEFAULT}; // Enable EditingNG by default. This feature is for a kill switch. const base::Feature kEditingNG{"EditingNG", base::FEATURE_ENABLED_BY_DEFAULT}; diff --git a/third_party/blink/renderer/platform/loader/fetch/response_body_loader_test.cc b/third_party/blink/renderer/platform/loader/fetch/response_body_loader_test.cc index 621ad4ce6054d7..73f09c3bd95e92 100644 --- a/third_party/blink/renderer/platform/loader/fetch/response_body_loader_test.cc +++ b/third_party/blink/renderer/platform/loader/fetch/response_body_loader_test.cc @@ -43,15 +43,6 @@ class ResponseBodyLoaderTest : public testing::Test { using PublicState = BytesConsumer::PublicState; using Result = BytesConsumer::Result; - static constexpr uint32_t kMaxNumConsumedBytesInTaskForTesting = 512 * 1024; - ResponseBodyLoaderTest() { - base::FieldTrialParams params; - params["loader_chunk_size"] = - base::NumberToString(kMaxNumConsumedBytesInTaskForTesting); - scoped_feature_list_.InitAndEnableFeatureWithParameters( - network::features::kLoaderDataPipeTuningFeature, params); - } - class TestClient final : public GarbageCollected, public ResponseBodyLoaderClient { public: @@ -357,7 +348,7 @@ TEST_F(ResponseBodyLoaderTest, Suspend) { TEST_F(ResponseBodyLoaderTest, ReadTooBigBuffer) { auto task_runner = base::MakeRefCounted(); auto* consumer = MakeGarbageCollected(task_runner); - constexpr auto kMax = kMaxNumConsumedBytesInTaskForTesting; + const uint32_t kMax = network::features::GetLoaderChunkSize(); consumer->Add(Command(Command::kData, std::string(kMax - 1, 'a').data())); consumer->Add(Command(Command::kData, std::string(2, 'b').data())); diff --git a/third_party/blink/web_tests/platform/fuchsia/external/wpt/html/semantics/interactive-elements/the-details-element/toggleEvent-expected.txt b/third_party/blink/web_tests/platform/fuchsia/external/wpt/html/semantics/interactive-elements/the-details-element/toggleEvent-expected.txt deleted file mode 100644 index 5c808aa0a050a4..00000000000000 --- a/third_party/blink/web_tests/platform/fuchsia/external/wpt/html/semantics/interactive-elements/the-details-element/toggleEvent-expected.txt +++ /dev/null @@ -1,13 +0,0 @@ -This is a testharness.js-based test. -PASS Adding open to 'details' should fire a toggle event at the 'details' element -PASS Removing open from 'details' should fire a toggle event at the 'details' element -PASS Adding open to 'details' (display:none) should fire a toggle event at the 'details' element -PASS Adding open from 'details' (no children) should fire a toggle event at the 'details' element -PASS Calling open twice on 'details' fires only one toggle event -PASS Calling setAttribute('open', '') to 'details' should fire a toggle event at the 'details' element -PASS Calling removeAttribute('open') to 'details' should fire a toggle event at the 'details' element -FAIL Setting open=true to opened 'details' element should not fire a toggle event at the 'details' element assert_true: expected true got false -PASS Setting open=false to closed 'details' element should not fire a toggle event at the 'details' element -PASS Adding open to 'details' (not in the document) should fire a toggle event at the 'details' element -Harness: the test ran to completion. - From d31979411057c5e1f1c36a33608d4aadc2e57d80 Mon Sep 17 00:00:00 2001 From: Sinan Sahin Date: Wed, 15 Dec 2021 01:52:15 +0000 Subject: [PATCH 51/77] [GMNext] Move OmniboxTheme to chrome/browser/ui/theme and rename Bug: 1114183, 1233725 Change-Id: I8d5713f6deffbd54c6f6e8be1600a71f06fb490f Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3321424 Reviewed-by: Sky Malice Reviewed-by: Filip Gorski Commit-Queue: Sinan Sahin Cr-Commit-Position: refs/heads/main@{#951774} --- .../features/toolbar/CustomTabToolbar.java | 9 +- ...opdownItemViewInfoListManagerUnitTest.java | 23 ++-- .../ClipboardSuggestionProcessorTest.java | 2 +- chrome/browser/ui/android/omnibox/BUILD.gn | 1 - .../browser/omnibox/LocationBarMediator.java | 15 +-- .../omnibox/LocationBarMediatorTest.java | 29 ++--- .../browser/omnibox/UrlBarCoordinator.java | 8 +- .../browser/omnibox/UrlBarMediator.java | 18 +-- .../browser/omnibox/UrlBarProperties.java | 6 +- .../browser/omnibox/UrlBarViewBinder.java | 16 +-- .../styles/OmniboxResourceProvider.java | 99 +++++++++-------- .../styles/OmniboxResourceProviderTest.java | 103 ++++++++++-------- .../browser/omnibox/styles/OmniboxTheme.java | 24 ---- .../suggestions/AutocompleteCoordinator.java | 8 +- .../suggestions/AutocompleteMediator.java | 10 +- .../DropdownItemViewInfoListManager.java | 18 +-- .../OmniboxSuggestionsDropdown.java | 10 +- .../SuggestionCommonProperties.java | 5 +- .../suggestions/SuggestionListProperties.java | 6 +- .../suggestions/SuggestionListViewBinder.java | 4 +- .../base/BaseSuggestionViewBinder.java | 6 +- .../BaseSuggestionViewBinderUnitTest.java | 4 +- .../basic/SuggestionViewViewBinder.java | 14 ++- .../editurl/EditUrlSuggestionViewBinder.java | 4 +- .../suggestions/header/HeaderViewBinder.java | 4 +- .../MostVisitedTilesProcessor.java | 7 +- .../tail/TailSuggestionViewBinder.java | 4 +- chrome/browser/ui/android/theme/BUILD.gn | 1 + .../browser/ui/theme/BrandedColorScheme.java | 31 ++++++ .../browser/toolbar/LocationBarModel.java | 16 +-- 30 files changed, 270 insertions(+), 235 deletions(-) delete mode 100644 chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxTheme.java create mode 100644 chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/ui/theme/BrandedColorScheme.java diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java index 4ef5eeec691f5d..6cee71cc23f772 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java @@ -55,7 +55,6 @@ import org.chromium.chrome.browser.omnibox.UrlBarCoordinator; import org.chromium.chrome.browser.omnibox.UrlBarCoordinator.SelectionState; import org.chromium.chrome.browser.omnibox.UrlBarData; -import org.chromium.chrome.browser.omnibox.styles.OmniboxTheme; import org.chromium.chrome.browser.page_info.ChromePageInfo; import org.chromium.chrome.browser.page_info.ChromePageInfoHighlight; import org.chromium.chrome.browser.tab.Tab; @@ -66,6 +65,7 @@ import org.chromium.chrome.browser.toolbar.top.ToolbarLayout; import org.chromium.chrome.browser.toolbar.top.ToolbarPhone; import org.chromium.chrome.browser.ui.native_page.NativePage; +import org.chromium.chrome.browser.ui.theme.BrandedColorScheme; import org.chromium.components.browser_ui.styles.ChromeColors; import org.chromium.components.browser_ui.styles.SemanticColorUtils; import org.chromium.components.browser_ui.widget.TintedDrawable; @@ -976,9 +976,10 @@ private void updateUrlBar() { private void updateUseDarkColors() { updateButtonsTint(); - @OmniboxTheme - int omniboxTheme = mUseDarkColors ? OmniboxTheme.LIGHT_THEME : OmniboxTheme.DARK_THEME; - if (mUrlCoordinator.setOmniboxTheme(omniboxTheme)) { + @BrandedColorScheme + int brandedColorScheme = mUseDarkColors ? BrandedColorScheme.LIGHT_BRANDED_THEME + : BrandedColorScheme.DARK_BRANDED_THEME; + if (mUrlCoordinator.setBrandedColorScheme(brandedColorScheme)) { // Update the URL to make it use the new color scheme. updateUrlBar(); } diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListManagerUnitTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListManagerUnitTest.java index e4a8a58341493a..d41f512286788a 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListManagerUnitTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListManagerUnitTest.java @@ -26,7 +26,7 @@ import org.chromium.base.test.BaseJUnit4ClassRunner; import org.chromium.base.test.util.Batch; -import org.chromium.chrome.browser.omnibox.styles.OmniboxTheme; +import org.chromium.chrome.browser.ui.theme.BrandedColorScheme; import org.chromium.components.omnibox.AutocompleteResult.GroupDetails; import org.chromium.ui.modelutil.ListObservable.ListObserver; import org.chromium.ui.modelutil.MVCListAdapter.ModelList; @@ -89,16 +89,17 @@ private void verifyModelEquals(List expected) { /** * Verify that PropertyModels of all suggestions on managed list reflect the expected values. */ - private void verifyPropertyValues(int layoutDirection, @OmniboxTheme int omniboxTheme) { + private void verifyPropertyValues( + int layoutDirection, @BrandedColorScheme int brandedColorScheme) { for (int index = 0; index < mSuggestionModels.size(); index++) { Assert.assertEquals("Unexpected layout direction for suggestion at position " + index, layoutDirection, mSuggestionModels.get(index).model.get( SuggestionCommonProperties.LAYOUT_DIRECTION)); Assert.assertEquals("Unexpected visual theme for suggestion at position " + index, - omniboxTheme, + brandedColorScheme, mSuggestionModels.get(index).model.get( - SuggestionCommonProperties.OMNIBOX_THEME)); + SuggestionCommonProperties.COLOR_SCHEME)); } } @@ -348,16 +349,16 @@ public void updateSuggestionsList_uiChangesArePropagatedToSuggestions() { mManager.setSourceViewInfoList(list, new SparseArray()); verifyModelEquals(list); - verifyPropertyValues(View.LAYOUT_DIRECTION_INHERIT, OmniboxTheme.LIGHT_THEME); + verifyPropertyValues(View.LAYOUT_DIRECTION_INHERIT, BrandedColorScheme.LIGHT_BRANDED_THEME); mManager.setLayoutDirection(View.LAYOUT_DIRECTION_RTL); - verifyPropertyValues(View.LAYOUT_DIRECTION_RTL, OmniboxTheme.LIGHT_THEME); + verifyPropertyValues(View.LAYOUT_DIRECTION_RTL, BrandedColorScheme.LIGHT_BRANDED_THEME); - mManager.setOmniboxTheme(OmniboxTheme.DARK_THEME); - verifyPropertyValues(View.LAYOUT_DIRECTION_RTL, OmniboxTheme.DARK_THEME); + mManager.setBrandedColorScheme(BrandedColorScheme.DARK_BRANDED_THEME); + verifyPropertyValues(View.LAYOUT_DIRECTION_RTL, BrandedColorScheme.DARK_BRANDED_THEME); - mManager.setOmniboxTheme(OmniboxTheme.INCOGNITO); - verifyPropertyValues(View.LAYOUT_DIRECTION_RTL, OmniboxTheme.INCOGNITO); + mManager.setBrandedColorScheme(BrandedColorScheme.INCOGNITO); + verifyPropertyValues(View.LAYOUT_DIRECTION_RTL, BrandedColorScheme.INCOGNITO); // Finally, set the new list and confirm that the values are still applied. list = Arrays.asList(new DropdownItemViewInfo(mHeaderProcessor, @@ -370,6 +371,6 @@ public void updateSuggestionsList_uiChangesArePropagatedToSuggestions() { new PropertyModel(SuggestionCommonProperties.ALL_KEYS), 2)); mManager.setSourceViewInfoList(list, new SparseArray()); verifyModelEquals(list); - verifyPropertyValues(View.LAYOUT_DIRECTION_RTL, OmniboxTheme.INCOGNITO); + verifyPropertyValues(View.LAYOUT_DIRECTION_RTL, BrandedColorScheme.INCOGNITO); } } diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/clipboard/ClipboardSuggestionProcessorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/clipboard/ClipboardSuggestionProcessorTest.java index 402721ea3940a8..caa35a862c5845 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/clipboard/ClipboardSuggestionProcessorTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/clipboard/ClipboardSuggestionProcessorTest.java @@ -132,7 +132,7 @@ private void createClipboardSuggestion(int type, GURL url, byte[] clipboardImage mModel = mProcessor.createModel(); mProcessor.populateModel(mSuggestion, mModel, 0); SuggestionViewViewBinder.bind(mModel, mRootView, SuggestionViewProperties.TEXT_LINE_1_TEXT); - SuggestionViewViewBinder.bind(mModel, mRootView, SuggestionCommonProperties.OMNIBOX_THEME); + SuggestionViewViewBinder.bind(mModel, mRootView, SuggestionCommonProperties.COLOR_SCHEME); SuggestionViewViewBinder.bind( mModel, mRootView, SuggestionViewProperties.IS_SEARCH_SUGGESTION); SuggestionViewViewBinder.bind(mModel, mRootView, SuggestionViewProperties.TEXT_LINE_2_TEXT); diff --git a/chrome/browser/ui/android/omnibox/BUILD.gn b/chrome/browser/ui/android/omnibox/BUILD.gn index 743edd573b6925..092d928b8abb38 100644 --- a/chrome/browser/ui/android/omnibox/BUILD.gn +++ b/chrome/browser/ui/android/omnibox/BUILD.gn @@ -54,7 +54,6 @@ android_library("java") { "java/src/org/chromium/chrome/browser/omnibox/status/StatusView.java", "java/src/org/chromium/chrome/browser/omnibox/status/StatusViewBinder.java", "java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProvider.java", - "java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxTheme.java", "java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java", "java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java", "java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteDelegate.java", diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java index 3c4f67f4b545d2..609ecb56d7bd67 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java @@ -45,7 +45,6 @@ import org.chromium.chrome.browser.omnibox.geo.GeolocationHeader; import org.chromium.chrome.browser.omnibox.status.StatusCoordinator; import org.chromium.chrome.browser.omnibox.styles.OmniboxResourceProvider; -import org.chromium.chrome.browser.omnibox.styles.OmniboxTheme; import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteCoordinator; import org.chromium.chrome.browser.omnibox.voice.AssistantVoiceSearchService; import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler; @@ -57,6 +56,7 @@ import org.chromium.chrome.browser.signin.services.IdentityServicesProvider; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.ui.native_page.NativePage; +import org.chromium.chrome.browser.ui.theme.BrandedColorScheme; import org.chromium.chrome.browser.util.ChromeAccessibilityUtil; import org.chromium.chrome.browser.util.KeyNavigationUtil; import org.chromium.components.browser_ui.styles.ChromeColors; @@ -927,24 +927,25 @@ private void focusCurrentTab() { * Update visuals to use a correct color scheme depending on the primary color. */ @VisibleForTesting - /* package */ void updateOmniboxTheme() { + /* package */ void updateBrandedColorScheme() { // TODO(crbug.com/1114183): Unify light and dark color logic in chrome and make it clear // whether the foreground or background color is dark. final boolean useDarkForegroundColors = !ColorUtils.shouldUseLightForegroundOnBackground(getPrimaryBackgroundColor()); - final @OmniboxTheme int omniboxTheme = OmniboxResourceProvider.getOmniboxTheme( - mContext, mLocationBarDataProvider.isIncognito(), getPrimaryBackgroundColor()); + final @BrandedColorScheme int brandedColorScheme = + OmniboxResourceProvider.getBrandedColorScheme(mContext, + mLocationBarDataProvider.isIncognito(), getPrimaryBackgroundColor()); mLocationBarLayout.setDeleteButtonTint( ChromeColors.getPrimaryIconTint(mContext, !useDarkForegroundColors)); // If the URL changed colors and is not focused, update the URL to account for the new // color scheme. - if (mUrlCoordinator.setOmniboxTheme(omniboxTheme) && !isUrlBarFocused()) { + if (mUrlCoordinator.setBrandedColorScheme(brandedColorScheme) && !isUrlBarFocused()) { updateUrl(); } mStatusCoordinator.setUseDarkForegroundColors(useDarkForegroundColors); if (mAutocompleteCoordinator != null) { - mAutocompleteCoordinator.updateVisualsForState(omniboxTheme); + mAutocompleteCoordinator.updateVisualsForState(brandedColorScheme); } } @@ -1172,7 +1173,7 @@ public void onNtpStartedLoading() { public void onPrimaryColorChanged() { updateAssistantVoiceSearchDrawableAndColors(); updateLensButtonColors(); - updateOmniboxTheme(); + updateBrandedColorScheme(); } @Override diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediatorTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediatorTest.java index af0ce86face190..4635ff07a672ea 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediatorTest.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediatorTest.java @@ -69,7 +69,6 @@ import org.chromium.chrome.browser.omnibox.UrlBarCoordinator.SelectionState; import org.chromium.chrome.browser.omnibox.geo.GeolocationHeader; import org.chromium.chrome.browser.omnibox.status.StatusCoordinator; -import org.chromium.chrome.browser.omnibox.styles.OmniboxTheme; import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteCoordinator; import org.chromium.chrome.browser.omnibox.voice.AssistantVoiceSearchService; import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler; @@ -81,6 +80,7 @@ import org.chromium.chrome.browser.profiles.ProfileJni; import org.chromium.chrome.browser.signin.services.IdentityServicesProvider; import org.chromium.chrome.browser.tab.Tab; +import org.chromium.chrome.browser.ui.theme.BrandedColorScheme; import org.chromium.chrome.browser.util.ChromeAccessibilityUtil; import org.chromium.chrome.test.util.browser.Features; import org.chromium.chrome.test.util.browser.signin.AccountManagerTestRule; @@ -663,11 +663,12 @@ public void testUpdateColors_lightBrandedColor() { .getPrimaryColor(); doReturn(false).when(mLocationBarDataProvider).isIncognito(); - mMediator.updateOmniboxTheme(); + mMediator.updateBrandedColorScheme(); verify(mLocationBarLayout).setDeleteButtonTint(any(ColorStateList.class)); verify(mStatusCoordinator).setUseDarkForegroundColors(true); - verify(mAutocompleteCoordinator).updateVisualsForState(OmniboxTheme.LIGHT_THEME); + verify(mAutocompleteCoordinator) + .updateVisualsForState(BrandedColorScheme.LIGHT_BRANDED_THEME); } @Test @@ -675,11 +676,12 @@ public void testUpdateColors_darkBrandedColor() { doReturn(Color.BLACK).when(mLocationBarDataProvider).getPrimaryColor(); doReturn(false).when(mLocationBarDataProvider).isIncognito(); - mMediator.updateOmniboxTheme(); + mMediator.updateBrandedColorScheme(); verify(mLocationBarLayout).setDeleteButtonTint(any(ColorStateList.class)); verify(mStatusCoordinator).setUseDarkForegroundColors(false); - verify(mAutocompleteCoordinator).updateVisualsForState(OmniboxTheme.DARK_THEME); + verify(mAutocompleteCoordinator) + .updateVisualsForState(BrandedColorScheme.DARK_BRANDED_THEME); } @Test @@ -688,11 +690,11 @@ public void testUpdateColors_incognito() { doReturn(primaryColor).when(mLocationBarDataProvider).getPrimaryColor(); doReturn(true).when(mLocationBarDataProvider).isIncognito(); - mMediator.updateOmniboxTheme(); + mMediator.updateBrandedColorScheme(); verify(mLocationBarLayout).setDeleteButtonTint(any(ColorStateList.class)); verify(mStatusCoordinator).setUseDarkForegroundColors(false); - verify(mAutocompleteCoordinator).updateVisualsForState(OmniboxTheme.INCOGNITO); + verify(mAutocompleteCoordinator).updateVisualsForState(BrandedColorScheme.INCOGNITO); } @Test @@ -701,28 +703,29 @@ public void testUpdateColors_default() { doReturn(primaryColor).when(mLocationBarDataProvider).getPrimaryColor(); doReturn(false).when(mLocationBarDataProvider).isIncognito(); - mMediator.updateOmniboxTheme(); + mMediator.updateBrandedColorScheme(); verify(mLocationBarLayout).setDeleteButtonTint(any(ColorStateList.class)); verify(mStatusCoordinator).setUseDarkForegroundColors(true); - verify(mAutocompleteCoordinator).updateVisualsForState(OmniboxTheme.DEFAULT); + verify(mAutocompleteCoordinator).updateVisualsForState(BrandedColorScheme.APP_DEFAULT); } @Test - public void testUpdateColors_setOmniboxTheme() { + public void testUpdateColors_setColorScheme() { String url = "https://www.google.com"; UrlBarData urlBarData = UrlBarData.forUrl(url); doReturn(urlBarData).when(mLocationBarDataProvider).getUrlBarData(); doReturn(url).when(mLocationBarDataProvider).getCurrentUrl(); - doReturn(true).when(mUrlCoordinator).setOmniboxTheme(anyInt()); + doReturn(true).when(mUrlCoordinator).setBrandedColorScheme(anyInt()); - mMediator.updateOmniboxTheme(); + mMediator.updateBrandedColorScheme(); verify(mLocationBarLayout).setDeleteButtonTint(anyObject()); verify(mUrlCoordinator) .setUrlBarData( urlBarData, UrlBar.ScrollType.SCROLL_TO_TLD, SelectionState.SELECT_ALL); verify(mStatusCoordinator).setUseDarkForegroundColors(false); - verify(mAutocompleteCoordinator).updateVisualsForState(OmniboxTheme.DARK_THEME); + verify(mAutocompleteCoordinator) + .updateVisualsForState(BrandedColorScheme.DARK_BRANDED_THEME); } @Test diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java index 5dc80036aff328..7b8e242c95321b 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java @@ -18,7 +18,7 @@ import org.chromium.chrome.browser.omnibox.UrlBar.ScrollType; import org.chromium.chrome.browser.omnibox.UrlBar.UrlBarDelegate; import org.chromium.chrome.browser.omnibox.UrlBar.UrlTextChangeListener; -import org.chromium.chrome.browser.omnibox.styles.OmniboxTheme; +import org.chromium.chrome.browser.ui.theme.BrandedColorScheme; import org.chromium.ui.KeyboardVisibilityDelegate; import org.chromium.ui.base.WindowDelegate; import org.chromium.ui.modelutil.PropertyModel; @@ -122,9 +122,9 @@ public void setAutocompleteText(String userText, String autocompleteText) { mMediator.setAutocompleteText(userText, autocompleteText); } - /** @see UrlBarMediator#setOmniboxTheme(int) */ - public boolean setOmniboxTheme(@OmniboxTheme int omniboxTheme) { - return mMediator.setOmniboxTheme(omniboxTheme); + /** @see UrlBarMediator#setBrandedColorScheme(int) */ + public boolean setBrandedColorScheme(@BrandedColorScheme int brandedColorScheme) { + return mMediator.setBrandedColorScheme(brandedColorScheme); } /** @see UrlBarMediator#setIncognitoColorsEnabled(boolean) */ diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediator.java index 7a32254496652b..31bb1db62c3abc 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediator.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediator.java @@ -24,7 +24,7 @@ import org.chromium.chrome.browser.omnibox.UrlBarCoordinator.SelectionState; import org.chromium.chrome.browser.omnibox.UrlBarProperties.AutocompleteText; import org.chromium.chrome.browser.omnibox.UrlBarProperties.UrlBarTextState; -import org.chromium.chrome.browser.omnibox.styles.OmniboxTheme; +import org.chromium.chrome.browser.ui.theme.BrandedColorScheme; import org.chromium.components.omnibox.OmniboxUrlEmphasizer.UrlEmphasisSpan; import org.chromium.ui.modelutil.PropertyModel; @@ -73,7 +73,7 @@ public UrlBarMediator( mModel.set(UrlBarProperties.TEXT_CONTEXT_MENU_DELEGATE, this); mModel.set(UrlBarProperties.URL_TEXT_CHANGE_LISTENER, this); mModel.set(UrlBarProperties.TEXT_CHANGED_LISTENER, this); - setOmniboxTheme(OmniboxTheme.DEFAULT); + setBrandedColorScheme(BrandedColorScheme.APP_DEFAULT); } public void destroy() { @@ -224,17 +224,17 @@ private void onUrlFocusChange(boolean focus) { } /** - * Sets the omnibox theme. + * Sets the color scheme. * - * @param omniboxTheme The {@link @OmniboxTheme}. + * @param brandedColorScheme The {@link @BrandedColorScheme}. * @return Whether this resulted in a change from the previous value. */ - public boolean setOmniboxTheme(@OmniboxTheme int omniboxTheme) { + public boolean setBrandedColorScheme(@BrandedColorScheme int brandedColorScheme) { // TODO(bauerb): Make clients observe the property instead of checking the return value. - @OmniboxTheme - int previousValue = mModel.get(UrlBarProperties.OMNIBOX_THEME); - mModel.set(UrlBarProperties.OMNIBOX_THEME, omniboxTheme); - return previousValue != omniboxTheme; + @BrandedColorScheme + int previousValue = mModel.get(UrlBarProperties.BRANDED_COLOR_SCHEME); + mModel.set(UrlBarProperties.BRANDED_COLOR_SCHEME, brandedColorScheme); + return previousValue != brandedColorScheme; } /** diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarProperties.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarProperties.java index d057f393074977..297a714a0181bb 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarProperties.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarProperties.java @@ -125,10 +125,10 @@ public String toString() { new WritableObjectPropertyKey<>(); /** - * Specifies the omnibox theme. It can be light or dark because of a publisher defined color, + * Specifies the color scheme. It can be light or dark because of a publisher defined color, * incognito, or the default theme that follows dynamic colors. */ - public static final WritableIntPropertyKey OMNIBOX_THEME = new WritableIntPropertyKey(); + public static final WritableIntPropertyKey BRANDED_COLOR_SCHEME = new WritableIntPropertyKey(); /** * Specifies whether incognito colors should be used in the view, meaning baseline dark theme @@ -145,5 +145,5 @@ public String toString() { new PropertyKey[] {ACTION_MODE_CALLBACK, ALLOW_FOCUS, AUTOCOMPLETE_TEXT, DELEGATE, FOCUS_CHANGE_CALLBACK, SHOW_CURSOR, TEXT_CONTEXT_MENU_DELEGATE, TEXT_STATE, URL_DIRECTION_LISTENER, URL_TEXT_CHANGE_LISTENER, TEXT_CHANGED_LISTENER, - OMNIBOX_THEME, INCOGNITO_COLORS_ENABLED, WINDOW_DELEGATE}; + BRANDED_COLOR_SCHEME, INCOGNITO_COLORS_ENABLED, WINDOW_DELEGATE}; } diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarViewBinder.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarViewBinder.java index 1c853bbc4d94d1..f481a0fbe8526c 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarViewBinder.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarViewBinder.java @@ -19,7 +19,7 @@ import org.chromium.chrome.browser.omnibox.UrlBarProperties.AutocompleteText; import org.chromium.chrome.browser.omnibox.UrlBarProperties.UrlBarTextState; import org.chromium.chrome.browser.omnibox.styles.OmniboxResourceProvider; -import org.chromium.chrome.browser.omnibox.styles.OmniboxTheme; +import org.chromium.chrome.browser.ui.theme.BrandedColorScheme; import org.chromium.ui.modelutil.PropertyKey; import org.chromium.ui.modelutil.PropertyModel; @@ -72,8 +72,8 @@ public static void bind(PropertyModel model, UrlBar view, PropertyKey propertyKe view.setSelection(view.getText().length()); } } - } else if (UrlBarProperties.OMNIBOX_THEME.equals(propertyKey)) { - updateTextColors(view, model.get(UrlBarProperties.OMNIBOX_THEME)); + } else if (UrlBarProperties.BRANDED_COLOR_SCHEME.equals(propertyKey)) { + updateTextColors(view, model.get(UrlBarProperties.BRANDED_COLOR_SCHEME)); } else if (UrlBarProperties.INCOGNITO_COLORS_ENABLED.equals(propertyKey)) { final boolean incognitoColorsEnabled = model.get(UrlBarProperties.INCOGNITO_COLORS_ENABLED); @@ -92,12 +92,12 @@ public static void bind(PropertyModel model, UrlBar view, PropertyKey propertyKe } } - private static void updateTextColors(UrlBar view, @OmniboxTheme int omniboxTheme) { - final @ColorInt int textColor = - OmniboxResourceProvider.getUrlBarPrimaryTextColor(view.getContext(), omniboxTheme); + private static void updateTextColors(UrlBar view, @BrandedColorScheme int brandedColorScheme) { + final @ColorInt int textColor = OmniboxResourceProvider.getUrlBarPrimaryTextColor( + view.getContext(), brandedColorScheme); - final @ColorInt int hintColor = - OmniboxResourceProvider.getUrlBarHintTextColor(view.getContext(), omniboxTheme); + final @ColorInt int hintColor = OmniboxResourceProvider.getUrlBarHintTextColor( + view.getContext(), brandedColorScheme); view.setTextColor(textColor); setHintTextColor(view, hintColor); diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProvider.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProvider.java index 2c25e66bb7679f..5da39d5a1c6cb7 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProvider.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProvider.java @@ -20,6 +20,7 @@ import org.chromium.chrome.browser.night_mode.NightModeUtils; import org.chromium.chrome.browser.omnibox.R; import org.chromium.chrome.browser.theme.ThemeUtils; +import org.chromium.chrome.browser.ui.theme.BrandedColorScheme; import org.chromium.ui.util.ColorUtils; /** Provides resources specific to Omnibox. */ @@ -27,64 +28,65 @@ public class OmniboxResourceProvider { private static final String TAG = "OmniboxResourceProvider"; /** @return Whether the mode is dark (dark theme or incognito). */ - public static boolean isDarkMode(@OmniboxTheme int omniboxTheme) { - return omniboxTheme == OmniboxTheme.DARK_THEME || omniboxTheme == OmniboxTheme.INCOGNITO; + public static boolean isDarkMode(@BrandedColorScheme int brandedColorScheme) { + return brandedColorScheme == BrandedColorScheme.DARK_BRANDED_THEME + || brandedColorScheme == BrandedColorScheme.INCOGNITO; } /** - * Returns a drawable for a given attribute depending on a {@link OmniboxTheme} + * Returns a drawable for a given attribute depending on a {@link BrandedColorScheme} * * @param context The {@link Context} used to retrieve resources. - * @param omniboxTheme {@link OmniboxTheme} to use. + * @param brandedColorScheme {@link BrandedColorScheme} to use. * @param attributeResId A resource ID of an attribute to resolve. * @return A background drawable resource ID providing ripple effect. */ public static Drawable resolveAttributeToDrawable( - Context context, @OmniboxTheme int omniboxTheme, int attributeResId) { - Context wrappedContext = maybeWrapContext(context, omniboxTheme); + Context context, @BrandedColorScheme int brandedColorScheme, int attributeResId) { + Context wrappedContext = maybeWrapContext(context, brandedColorScheme); @DrawableRes int resourceId = resolveAttributeToDrawableRes(wrappedContext, attributeResId); return ContextCompat.getDrawable(wrappedContext, resourceId); } /** - * Returns the OmniboxTheme based on the incognito state and the background color. + * Returns the ColorScheme based on the incognito state and the background color. * * @param context The {@link Context}. * @param isIncognito Whether incognito mode is enabled. * @param primaryBackgroundColor The primary background color of the omnibox. - * @return The {@link OmniboxTheme}. + * @return The {@link BrandedColorScheme}. */ - public static @OmniboxTheme int getOmniboxTheme( + public static @BrandedColorScheme int getBrandedColorScheme( Context context, boolean isIncognito, @ColorInt int primaryBackgroundColor) { - if (isIncognito) return OmniboxTheme.INCOGNITO; + if (isIncognito) return BrandedColorScheme.INCOGNITO; if (ThemeUtils.isUsingDefaultToolbarColor(context, isIncognito, primaryBackgroundColor)) { - return OmniboxTheme.DEFAULT; + return BrandedColorScheme.APP_DEFAULT; } return ColorUtils.shouldUseLightForegroundOnBackground(primaryBackgroundColor) - ? OmniboxTheme.DARK_THEME - : OmniboxTheme.LIGHT_THEME; + ? BrandedColorScheme.DARK_BRANDED_THEME + : BrandedColorScheme.LIGHT_BRANDED_THEME; } /** * Returns the primary text color for the url bar. * * @param context The context to retrieve the resources from. - * @param omniboxTheme The {@link OmniboxTheme}. + * @param brandedColorScheme The {@link BrandedColorScheme}. * @return Primary url bar text color. */ public static @ColorInt int getUrlBarPrimaryTextColor( - Context context, @OmniboxTheme int omniboxTheme) { + Context context, @BrandedColorScheme int brandedColorScheme) { final Resources resources = context.getResources(); @ColorInt int color; - if (omniboxTheme == OmniboxTheme.LIGHT_THEME) { + if (brandedColorScheme == BrandedColorScheme.LIGHT_BRANDED_THEME) { color = resources.getColor(R.color.branded_url_text_on_light_bg); - } else if (omniboxTheme == OmniboxTheme.DARK_THEME) { + } else if (brandedColorScheme == BrandedColorScheme.DARK_BRANDED_THEME) { color = resources.getColor(R.color.branded_url_text_on_dark_bg); - } else if (omniboxTheme == OmniboxTheme.INCOGNITO) { + } else if (brandedColorScheme == BrandedColorScheme.INCOGNITO) { color = resources.getColor(R.color.url_bar_primary_text_incognito); } else { color = MaterialColors.getColor(context, R.attr.colorOnSurface, TAG); @@ -96,19 +98,19 @@ public static Drawable resolveAttributeToDrawable( * Returns the secondary text color for the url bar. * * @param context The context to retrieve the resources from. - * @param omniboxTheme The {@link OmniboxTheme}. + * @param brandedColorScheme The {@link BrandedColorScheme}. * @return Secondary url bar text color. */ public static @ColorInt int getUrlBarSecondaryTextColor( - Context context, @OmniboxTheme int omniboxTheme) { + Context context, @BrandedColorScheme int brandedColorScheme) { final Resources resources = context.getResources(); @ColorInt int color; - if (omniboxTheme == OmniboxTheme.LIGHT_THEME) { + if (brandedColorScheme == BrandedColorScheme.LIGHT_BRANDED_THEME) { color = resources.getColor(R.color.branded_url_text_variant_on_light_bg); - } else if (omniboxTheme == OmniboxTheme.DARK_THEME) { + } else if (brandedColorScheme == BrandedColorScheme.DARK_BRANDED_THEME) { color = resources.getColor(R.color.branded_url_text_variant_on_dark_bg); - } else if (omniboxTheme == OmniboxTheme.INCOGNITO) { + } else if (brandedColorScheme == BrandedColorScheme.INCOGNITO) { color = resources.getColor(R.color.url_bar_secondary_text_incognito); } else { color = MaterialColors.getColor(context, R.attr.colorOnSurfaceVariant, TAG); @@ -120,29 +122,30 @@ public static Drawable resolveAttributeToDrawable( * Returns the hint text color for the url bar. * * @param context The context to retrieve the resources from. - * @param omniboxTheme The {@link OmniboxTheme}. + * @param brandedColorScheme The {@link BrandedColorScheme}. * @return The url bar hint text color. */ public static @ColorInt int getUrlBarHintTextColor( - Context context, @OmniboxTheme int omniboxTheme) { - return getUrlBarSecondaryTextColor(context, omniboxTheme); + Context context, @BrandedColorScheme int brandedColorScheme) { + return getUrlBarSecondaryTextColor(context, brandedColorScheme); } /** * Returns the danger semantic color. * * @param context The context to retrieve the resources from. - * @param omniboxTheme The {@link OmniboxTheme}. + * @param brandedColorScheme The {@link BrandedColorScheme}. * @return The danger semantic color to be used on the url bar. */ public static @ColorInt int getUrlBarDangerColor( - Context context, @OmniboxTheme int omniboxTheme) { + Context context, @BrandedColorScheme int brandedColorScheme) { // Danger color has semantic meaning and it doesn't change with dynamic colors. @ColorRes int colorId = R.color.default_red; - if (omniboxTheme == OmniboxTheme.DARK_THEME || omniboxTheme == OmniboxTheme.INCOGNITO) { + if (brandedColorScheme == BrandedColorScheme.DARK_BRANDED_THEME + || brandedColorScheme == BrandedColorScheme.INCOGNITO) { colorId = R.color.default_red_light; - } else if (omniboxTheme == OmniboxTheme.LIGHT_THEME) { + } else if (brandedColorScheme == BrandedColorScheme.LIGHT_BRANDED_THEME) { colorId = R.color.default_red_dark; } return context.getResources().getColor(colorId); @@ -152,17 +155,18 @@ public static Drawable resolveAttributeToDrawable( * Returns the secure semantic color. * * @param context The context to retrieve the resources from. - * @param omniboxTheme The {@link OmniboxTheme}. + * @param brandedColorScheme The {@link BrandedColorScheme}. * @return The secure semantic color to be used on the url bar. */ public static @ColorInt int getUrlBarSecureColor( - Context context, @OmniboxTheme int omniboxTheme) { + Context context, @BrandedColorScheme int brandedColorScheme) { // Secure color has semantic meaning and it doesn't change with dynamic colors. @ColorRes int colorId = R.color.default_green; - if (omniboxTheme == OmniboxTheme.DARK_THEME || omniboxTheme == OmniboxTheme.INCOGNITO) { + if (brandedColorScheme == BrandedColorScheme.DARK_BRANDED_THEME + || brandedColorScheme == BrandedColorScheme.INCOGNITO) { colorId = R.color.default_green_light; - } else if (omniboxTheme == OmniboxTheme.LIGHT_THEME) { + } else if (brandedColorScheme == BrandedColorScheme.LIGHT_BRANDED_THEME) { colorId = R.color.default_green_dark; } return context.getResources().getColor(colorId); @@ -172,14 +176,14 @@ public static Drawable resolveAttributeToDrawable( * Returns the primary text color for the suggestions. * * @param context The context to retrieve the resources from. - * @param omniboxTheme The {@link OmniboxTheme}. + * @param brandedColorScheme The {@link BrandedColorScheme}. * @return Primary suggestion text color. */ public static @ColorInt int getSuggestionPrimaryTextColor( - Context context, @OmniboxTheme int omniboxTheme) { + Context context, @BrandedColorScheme int brandedColorScheme) { // Suggestions are only shown when the omnibox is focused, hence LIGHT_THEME and DARK_THEME // are ignored as they don't change the result. - return omniboxTheme == OmniboxTheme.INCOGNITO + return brandedColorScheme == BrandedColorScheme.INCOGNITO ? ApiCompatibilityUtils.getColor( context.getResources(), R.color.default_text_color_light) : MaterialColors.getColor(context, R.attr.colorOnSurface, TAG); @@ -189,14 +193,14 @@ public static Drawable resolveAttributeToDrawable( * Returns the secondary text color for the suggestions. * * @param context The context to retrieve the resources from. - * @param omniboxTheme The {@link OmniboxTheme}. + * @param brandedColorScheme The {@link BrandedColorScheme}. * @return Secondary suggestion text color. */ public static @ColorInt int getSuggestionSecondaryTextColor( - Context context, @OmniboxTheme int omniboxTheme) { + Context context, @BrandedColorScheme int brandedColorScheme) { // Suggestions are only shown when the omnibox is focused, hence LIGHT_THEME and DARK_THEME // are ignored as they don't change the result. - return omniboxTheme == OmniboxTheme.INCOGNITO + return brandedColorScheme == BrandedColorScheme.INCOGNITO ? ApiCompatibilityUtils.getColor( context.getResources(), R.color.default_text_color_secondary_light) : MaterialColors.getColor(context, R.attr.colorOnSurfaceVariant, TAG); @@ -206,14 +210,14 @@ public static Drawable resolveAttributeToDrawable( * Returns the URL text color for the suggestions. * * @param context The context to retrieve the resources from. - * @param omniboxTheme The {@link OmniboxTheme}. + * @param brandedColorScheme The {@link BrandedColorScheme}. * @return URL suggestion text color. */ public static @ColorInt int getSuggestionUrlTextColor( - Context context, @OmniboxTheme int omniboxTheme) { + Context context, @BrandedColorScheme int brandedColorScheme) { // Suggestions are only shown when the omnibox is focused, hence LIGHT_THEME and DARK_THEME // are ignored as they don't change the result. - final @ColorRes int colorId = omniboxTheme == OmniboxTheme.INCOGNITO + final @ColorRes int colorId = brandedColorScheme == BrandedColorScheme.INCOGNITO ? R.color.suggestion_url_color_incognito : R.color.suggestion_url_color; return ApiCompatibilityUtils.getColor(context.getResources(), colorId); @@ -223,12 +227,13 @@ public static Drawable resolveAttributeToDrawable( * Wraps the context if necessary to force dark resources for incognito. * * @param context The {@link Context} to be wrapped. - * @param omniboxTheme Current omnibox theme. - * @return Context with resources appropriate to the {@link OmniboxTheme}. + * @param brandedColorScheme Current color scheme. + * @return Context with resources appropriate to the {@link BrandedColorScheme}. */ - private static Context maybeWrapContext(Context context, @OmniboxTheme int omniboxTheme) { + private static Context maybeWrapContext( + Context context, @BrandedColorScheme int brandedColorScheme) { // Only wraps the context in case of incognito. - if (omniboxTheme == OmniboxTheme.INCOGNITO) { + if (brandedColorScheme == BrandedColorScheme.INCOGNITO) { return NightModeUtils.wrapContextWithNightModeConfig( context, R.style.Theme_Chromium_TabbedMode, /*nightMode=*/true); } diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProviderTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProviderTest.java index e2dcdfad585e6d..77eba05c7b1049 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProviderTest.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProviderTest.java @@ -23,6 +23,7 @@ import org.robolectric.annotation.Config; import org.chromium.chrome.R; +import org.chromium.chrome.browser.ui.theme.BrandedColorScheme; import org.chromium.components.browser_ui.styles.ChromeColors; import org.chromium.testing.local.LocalRobolectricTestRunner; @@ -48,38 +49,40 @@ public void setUp() { @Test public void isDarkMode() { - Assert.assertTrue(OmniboxResourceProvider.isDarkMode(OmniboxTheme.DARK_THEME)); - Assert.assertTrue(OmniboxResourceProvider.isDarkMode(OmniboxTheme.INCOGNITO)); - Assert.assertFalse(OmniboxResourceProvider.isDarkMode(OmniboxTheme.LIGHT_THEME)); + Assert.assertTrue( + OmniboxResourceProvider.isDarkMode(BrandedColorScheme.DARK_BRANDED_THEME)); + Assert.assertTrue(OmniboxResourceProvider.isDarkMode(BrandedColorScheme.INCOGNITO)); + Assert.assertFalse( + OmniboxResourceProvider.isDarkMode(BrandedColorScheme.LIGHT_BRANDED_THEME)); } @Test public void resolveAttributeToDrawable() { Drawable drawableLight = OmniboxResourceProvider.resolveAttributeToDrawable( - mActivity, OmniboxTheme.LIGHT_THEME, R.attr.selectableItemBackground); + mActivity, BrandedColorScheme.LIGHT_BRANDED_THEME, R.attr.selectableItemBackground); Assert.assertNotNull(drawableLight); Drawable drawableDark = OmniboxResourceProvider.resolveAttributeToDrawable( - mActivity, OmniboxTheme.DARK_THEME, R.attr.selectableItemBackground); + mActivity, BrandedColorScheme.DARK_BRANDED_THEME, R.attr.selectableItemBackground); Assert.assertNotNull(drawableDark); } @Test - public void getOmniboxTheme_incognito() { - assertEquals("Omnibox theme should be INCOGNITO.", OmniboxTheme.INCOGNITO, - OmniboxResourceProvider.getOmniboxTheme(mActivity, true, mDefaultColor)); - assertEquals("Omnibox theme should be INCOGNITO.", OmniboxTheme.INCOGNITO, - OmniboxResourceProvider.getOmniboxTheme(mActivity, true, Color.RED)); + public void getColorScheme_incognito() { + assertEquals("Color scheme should be INCOGNITO.", BrandedColorScheme.INCOGNITO, + OmniboxResourceProvider.getBrandedColorScheme(mActivity, true, mDefaultColor)); + assertEquals("Color scheme should be INCOGNITO.", BrandedColorScheme.INCOGNITO, + OmniboxResourceProvider.getBrandedColorScheme(mActivity, true, Color.RED)); } @Test - public void getOmniboxTheme_nonIncognito() { - assertEquals("Omnibox theme should be DEFAULT.", OmniboxTheme.DEFAULT, - OmniboxResourceProvider.getOmniboxTheme(mActivity, false, mDefaultColor)); - assertEquals("Omnibox theme should be DARK_THEME.", OmniboxTheme.DARK_THEME, - OmniboxResourceProvider.getOmniboxTheme(mActivity, false, Color.BLACK)); - assertEquals("Omnibox theme should be LIGHT_THEME.", OmniboxTheme.LIGHT_THEME, - OmniboxResourceProvider.getOmniboxTheme( + public void getColorScheme_nonIncognito() { + assertEquals("Color scheme should be DEFAULT.", BrandedColorScheme.APP_DEFAULT, + OmniboxResourceProvider.getBrandedColorScheme(mActivity, false, mDefaultColor)); + assertEquals("Color scheme should be DARK_THEME.", BrandedColorScheme.DARK_BRANDED_THEME, + OmniboxResourceProvider.getBrandedColorScheme(mActivity, false, Color.BLACK)); + assertEquals("Color scheme should be LIGHT_THEME.", BrandedColorScheme.LIGHT_BRANDED_THEME, + OmniboxResourceProvider.getBrandedColorScheme( mActivity, false, Color.parseColor("#eaecf0" /*Light grey color*/))); } @@ -93,15 +96,16 @@ public void getUrlBarPrimaryTextColor() { assertEquals("Wrong url bar primary text color for LIGHT_THEME.", darkTextColor, OmniboxResourceProvider.getUrlBarPrimaryTextColor( - mActivity, OmniboxTheme.LIGHT_THEME)); + mActivity, BrandedColorScheme.LIGHT_BRANDED_THEME)); assertEquals("Wrong url bar primary text color for DARK_THEME.", lightTextColor, OmniboxResourceProvider.getUrlBarPrimaryTextColor( - mActivity, OmniboxTheme.DARK_THEME)); + mActivity, BrandedColorScheme.DARK_BRANDED_THEME)); assertEquals("Wrong url bar primary text color for INCOGNITO.", incognitoColor, OmniboxResourceProvider.getUrlBarPrimaryTextColor( - mActivity, OmniboxTheme.INCOGNITO)); + mActivity, BrandedColorScheme.INCOGNITO)); assertEquals("Wrong url bar primary text color for DEFAULT.", defaultColor, - OmniboxResourceProvider.getUrlBarPrimaryTextColor(mActivity, OmniboxTheme.DEFAULT)); + OmniboxResourceProvider.getUrlBarPrimaryTextColor( + mActivity, BrandedColorScheme.APP_DEFAULT)); } @Test @@ -115,16 +119,16 @@ public void getUrlBarSecondaryTextColor() { assertEquals("Wrong url bar secondary text color for LIGHT_THEME.", darkTextColor, OmniboxResourceProvider.getUrlBarSecondaryTextColor( - mActivity, OmniboxTheme.LIGHT_THEME)); + mActivity, BrandedColorScheme.LIGHT_BRANDED_THEME)); assertEquals("Wrong url bar secondary text color for DARK_THEME.", lightTextColor, OmniboxResourceProvider.getUrlBarSecondaryTextColor( - mActivity, OmniboxTheme.DARK_THEME)); + mActivity, BrandedColorScheme.DARK_BRANDED_THEME)); assertEquals("Wrong url bar secondary text color for INCOGNITO.", incognitoColor, OmniboxResourceProvider.getUrlBarSecondaryTextColor( - mActivity, OmniboxTheme.INCOGNITO)); + mActivity, BrandedColorScheme.INCOGNITO)); assertEquals("Wrong url bar secondary text color for DEFAULT.", defaultColor, OmniboxResourceProvider.getUrlBarSecondaryTextColor( - mActivity, OmniboxTheme.DEFAULT)); + mActivity, BrandedColorScheme.APP_DEFAULT)); } @Test @@ -134,14 +138,18 @@ public void getUrlBarDangerColor() { final int redOnLight = resources.getColor(R.color.default_red_dark); assertEquals("Danger color for DARK_THEME should be the lighter red.", redOnDark, - OmniboxResourceProvider.getUrlBarDangerColor(mActivity, OmniboxTheme.DARK_THEME)); + OmniboxResourceProvider.getUrlBarDangerColor( + mActivity, BrandedColorScheme.DARK_BRANDED_THEME)); assertEquals("Danger color for LIGHT_THEME should be the darker red.", redOnLight, - OmniboxResourceProvider.getUrlBarDangerColor(mActivity, OmniboxTheme.LIGHT_THEME)); + OmniboxResourceProvider.getUrlBarDangerColor( + mActivity, BrandedColorScheme.LIGHT_BRANDED_THEME)); assertEquals("Danger color for DEFAULT should be the darker red when we're in light theme.", redOnLight, - OmniboxResourceProvider.getUrlBarDangerColor(mActivity, OmniboxTheme.DEFAULT)); + OmniboxResourceProvider.getUrlBarDangerColor( + mActivity, BrandedColorScheme.APP_DEFAULT)); assertEquals("Danger color for INCOGNITO should be the lighter red.", redOnDark, - OmniboxResourceProvider.getUrlBarDangerColor(mActivity, OmniboxTheme.INCOGNITO)); + OmniboxResourceProvider.getUrlBarDangerColor( + mActivity, BrandedColorScheme.INCOGNITO)); } @Test @@ -151,15 +159,19 @@ public void getUrlBarSecureColor() { final int greenOnLight = resources.getColor(R.color.default_green_dark); assertEquals("Secure color for DARK_THEME should be the lighter green.", greenOnDark, - OmniboxResourceProvider.getUrlBarSecureColor(mActivity, OmniboxTheme.DARK_THEME)); + OmniboxResourceProvider.getUrlBarSecureColor( + mActivity, BrandedColorScheme.DARK_BRANDED_THEME)); assertEquals("Secure color for LIGHT_THEME should be the darker green.", greenOnLight, - OmniboxResourceProvider.getUrlBarSecureColor(mActivity, OmniboxTheme.LIGHT_THEME)); + OmniboxResourceProvider.getUrlBarSecureColor( + mActivity, BrandedColorScheme.LIGHT_BRANDED_THEME)); assertEquals( "Secure color for DEFAULT should be the darker green when we're in light theme.", greenOnLight, - OmniboxResourceProvider.getUrlBarSecureColor(mActivity, OmniboxTheme.DEFAULT)); + OmniboxResourceProvider.getUrlBarSecureColor( + mActivity, BrandedColorScheme.APP_DEFAULT)); assertEquals("Secure color for INCOGNITO should be the lighter green.", greenOnDark, - OmniboxResourceProvider.getUrlBarSecureColor(mActivity, OmniboxTheme.INCOGNITO)); + OmniboxResourceProvider.getUrlBarSecureColor( + mActivity, BrandedColorScheme.INCOGNITO)); } @Test @@ -170,16 +182,16 @@ public void getSuggestionPrimaryTextColor() { assertEquals("Wrong suggestion primary text color for LIGHT_THEME.", defaultColor, OmniboxResourceProvider.getSuggestionPrimaryTextColor( - mActivity, OmniboxTheme.LIGHT_THEME)); + mActivity, BrandedColorScheme.LIGHT_BRANDED_THEME)); assertEquals("Wrong suggestion primary text color for DARK_THEME.", defaultColor, OmniboxResourceProvider.getSuggestionPrimaryTextColor( - mActivity, OmniboxTheme.DARK_THEME)); + mActivity, BrandedColorScheme.DARK_BRANDED_THEME)); assertEquals("Wrong suggestion primary text color for INCOGNITO.", incognitoColor, OmniboxResourceProvider.getSuggestionPrimaryTextColor( - mActivity, OmniboxTheme.INCOGNITO)); + mActivity, BrandedColorScheme.INCOGNITO)); assertEquals("Wrong suggestion primary text color for DEFAULT.", defaultColor, OmniboxResourceProvider.getSuggestionPrimaryTextColor( - mActivity, OmniboxTheme.DEFAULT)); + mActivity, BrandedColorScheme.APP_DEFAULT)); } @Test @@ -191,16 +203,16 @@ public void getSuggestionSecondaryTextColor() { assertEquals("Wrong suggestion secondary text color for LIGHT_THEME.", defaultColor, OmniboxResourceProvider.getSuggestionSecondaryTextColor( - mActivity, OmniboxTheme.LIGHT_THEME)); + mActivity, BrandedColorScheme.LIGHT_BRANDED_THEME)); assertEquals("Wrong suggestion secondary text color for DARK_THEME.", defaultColor, OmniboxResourceProvider.getSuggestionSecondaryTextColor( - mActivity, OmniboxTheme.DARK_THEME)); + mActivity, BrandedColorScheme.DARK_BRANDED_THEME)); assertEquals("Wrong suggestion secondary text color for INCOGNITO.", incognitoColor, OmniboxResourceProvider.getSuggestionSecondaryTextColor( - mActivity, OmniboxTheme.INCOGNITO)); + mActivity, BrandedColorScheme.INCOGNITO)); assertEquals("Wrong suggestion secondary text color for DEFAULT.", defaultColor, OmniboxResourceProvider.getSuggestionSecondaryTextColor( - mActivity, OmniboxTheme.DEFAULT)); + mActivity, BrandedColorScheme.APP_DEFAULT)); } @Test @@ -211,14 +223,15 @@ public void getSuggestionUrlTextColor() { assertEquals("Wrong suggestion url text color for LIGHT_THEME.", defaultColor, OmniboxResourceProvider.getSuggestionUrlTextColor( - mActivity, OmniboxTheme.LIGHT_THEME)); + mActivity, BrandedColorScheme.LIGHT_BRANDED_THEME)); assertEquals("Wrong suggestion url text color for DARK_THEME.", defaultColor, OmniboxResourceProvider.getSuggestionUrlTextColor( - mActivity, OmniboxTheme.DARK_THEME)); + mActivity, BrandedColorScheme.DARK_BRANDED_THEME)); assertEquals("Wrong suggestion url text color for INCOGNITO.", incognitoColor, OmniboxResourceProvider.getSuggestionUrlTextColor( - mActivity, OmniboxTheme.INCOGNITO)); + mActivity, BrandedColorScheme.INCOGNITO)); assertEquals("Wrong suggestion url text color for DEFAULT.", defaultColor, - OmniboxResourceProvider.getSuggestionUrlTextColor(mActivity, OmniboxTheme.DEFAULT)); + OmniboxResourceProvider.getSuggestionUrlTextColor( + mActivity, BrandedColorScheme.APP_DEFAULT)); } } diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxTheme.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxTheme.java deleted file mode 100644 index 78921e47e99f21..00000000000000 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxTheme.java +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2020 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package org.chromium.chrome.browser.omnibox.styles; - -import androidx.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -@IntDef({OmniboxTheme.LIGHT_THEME, OmniboxTheme.DARK_THEME, OmniboxTheme.INCOGNITO, - OmniboxTheme.DEFAULT}) -@Retention(RetentionPolicy.SOURCE) -public @interface OmniboxTheme { - /* Light branded color as defined by the website, unrelated to the app/OS dark theme setting. */ - int LIGHT_THEME = 0; - /* Dark branded color as defined by the website, unrelated to the app/OS dark theme setting. */ - int DARK_THEME = 1; - /* Incognito theme. */ - int INCOGNITO = 2; - /* Default theme with potentially dynamic colors that can be light or dark. */ - int DEFAULT = 3; -} diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java index 3b5b2f8a3f1ca5..d986081c734327 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java @@ -26,7 +26,6 @@ import org.chromium.chrome.browser.omnibox.UrlBar.UrlTextChangeListener; import org.chromium.chrome.browser.omnibox.UrlBarEditingTextStateProvider; import org.chromium.chrome.browser.omnibox.UrlFocusChangeListener; -import org.chromium.chrome.browser.omnibox.styles.OmniboxTheme; import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteController.OnSuggestionsReceivedListener; import org.chromium.chrome.browser.omnibox.suggestions.SuggestionListViewBinder.SuggestionListViewHolder; import org.chromium.chrome.browser.omnibox.suggestions.answer.AnswerSuggestionViewBinder; @@ -49,6 +48,7 @@ import org.chromium.chrome.browser.share.ShareDelegate; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tabmodel.TabWindowManager; +import org.chromium.chrome.browser.ui.theme.BrandedColorScheme; import org.chromium.chrome.browser.util.KeyNavigationUtil; import org.chromium.components.omnibox.AutocompleteMatch; import org.chromium.ui.ViewProvider; @@ -298,10 +298,10 @@ public void updateSuggestionListLayoutDirection() { /** * Update the visuals of the autocomplete UI. - * @param omniboxTheme The {@link @OmniboxTheme}. + * @param brandedColorScheme The {@link @BrandedColorScheme}. */ - public void updateVisualsForState(@OmniboxTheme int omniboxTheme) { - mMediator.updateVisualsForState(omniboxTheme); + public void updateVisualsForState(@BrandedColorScheme int colorScheme) { + mMediator.updateVisualsForState(colorScheme); } /** diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java index 568a0f96b17cf2..eed7e0d0fc0774 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java @@ -29,7 +29,6 @@ import org.chromium.chrome.browser.omnibox.OmniboxSuggestionType; import org.chromium.chrome.browser.omnibox.R; import org.chromium.chrome.browser.omnibox.UrlBarEditingTextStateProvider; -import org.chromium.chrome.browser.omnibox.styles.OmniboxTheme; import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteController.OnSuggestionsReceivedListener; import org.chromium.chrome.browser.omnibox.suggestions.SuggestionsMetrics.RefineActionUsage; import org.chromium.chrome.browser.omnibox.suggestions.basic.BasicSuggestionProcessor.BookmarkState; @@ -42,6 +41,7 @@ import org.chromium.chrome.browser.tabmodel.TabModel; import org.chromium.chrome.browser.tabmodel.TabModelUtils; import org.chromium.chrome.browser.tabmodel.TabWindowManager; +import org.chromium.chrome.browser.ui.theme.BrandedColorScheme; import org.chromium.components.metrics.OmniboxEventProtos.OmniboxEventProto.PageClassification; import org.chromium.components.omnibox.AutocompleteMatch; import org.chromium.components.omnibox.AutocompleteResult; @@ -248,11 +248,11 @@ void setLayoutDirection(int layoutDirection) { /** * Specifies the visual state to be used by the suggestions. - * @param omniboxTheme The {@link @OmniboxTheme}. + * @param brandedColorScheme The {@link @BrandedColorScheme}. */ - void updateVisualsForState(@OmniboxTheme int omniboxTheme) { - mDropdownViewInfoListManager.setOmniboxTheme(omniboxTheme); - mListPropertyModel.set(SuggestionListProperties.OMNIBOX_THEME, omniboxTheme); + void updateVisualsForState(@BrandedColorScheme int brandedColorScheme) { + mDropdownViewInfoListManager.setBrandedColorScheme(brandedColorScheme); + mListPropertyModel.set(SuggestionListProperties.COLOR_SCHEME, brandedColorScheme); } /** diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListManager.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListManager.java index cb0e31c9c2d99a..1e7bd8ab3b2570 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListManager.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListManager.java @@ -10,7 +10,7 @@ import androidx.annotation.NonNull; -import org.chromium.chrome.browser.omnibox.styles.OmniboxTheme; +import org.chromium.chrome.browser.ui.theme.BrandedColorScheme; import org.chromium.components.omnibox.AutocompleteResult; import org.chromium.ui.modelutil.MVCListAdapter.ListItem; import org.chromium.ui.modelutil.MVCListAdapter.ModelList; @@ -25,13 +25,13 @@ class DropdownItemViewInfoListManager { private final ModelList mManagedModel; private final SparseBooleanArray mGroupsCollapsedState; private int mLayoutDirection; - private @OmniboxTheme int mOmniboxTheme; + private @BrandedColorScheme int mBrandedColorScheme; private List mSourceViewInfoList; DropdownItemViewInfoListManager(@NonNull ModelList managedModel) { assert managedModel != null : "Must specify a non-null model."; mLayoutDirection = View.LAYOUT_DIRECTION_INHERIT; - mOmniboxTheme = OmniboxTheme.LIGHT_THEME; + mBrandedColorScheme = BrandedColorScheme.LIGHT_BRANDED_THEME; mSourceViewInfoList = Collections.emptyList(); mGroupsCollapsedState = new SparseBooleanArray(); mManagedModel = managedModel; @@ -58,15 +58,15 @@ void setLayoutDirection(int layoutDirection) { /** * Specifies the visual theme to be used by the suggestions. - * @param omniboxTheme Specifies which {@link OmniboxTheme} should be used. + * @param brandedColorScheme Specifies which {@link BrandedColorScheme} should be used. */ - void setOmniboxTheme(@OmniboxTheme int omniboxTheme) { - if (mOmniboxTheme == omniboxTheme) return; + void setBrandedColorScheme(@BrandedColorScheme int brandedColorScheme) { + if (mBrandedColorScheme == brandedColorScheme) return; - mOmniboxTheme = omniboxTheme; + mBrandedColorScheme = brandedColorScheme; for (int i = 0; i < mSourceViewInfoList.size(); i++) { PropertyModel model = mSourceViewInfoList.get(i).model; - model.set(SuggestionCommonProperties.OMNIBOX_THEME, omniboxTheme); + model.set(SuggestionCommonProperties.COLOR_SCHEME, brandedColorScheme); } } @@ -129,7 +129,7 @@ void setSourceViewInfoList(@NonNull List sourceList, final DropdownItemViewInfo item = mSourceViewInfoList.get(i); final PropertyModel model = item.model; model.set(SuggestionCommonProperties.LAYOUT_DIRECTION, mLayoutDirection); - model.set(SuggestionCommonProperties.OMNIBOX_THEME, mOmniboxTheme); + model.set(SuggestionCommonProperties.COLOR_SCHEME, mBrandedColorScheme); final boolean groupIsDefaultCollapsed = getGroupCollapsedState(item.groupId); if (!groupIsDefaultCollapsed || isGroupHeaderWithId(item, item.groupId)) { diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdown.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdown.java index 641a4939eebc05..8c05fd558daf29 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdown.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdown.java @@ -14,7 +14,6 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; -import android.view.View.MeasureSpec; import android.view.ViewGroup; import android.view.ViewParent; import android.view.ViewTreeObserver.OnGlobalLayoutListener; @@ -32,7 +31,7 @@ import org.chromium.base.task.PostTask; import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.omnibox.R; -import org.chromium.chrome.browser.omnibox.styles.OmniboxTheme; +import org.chromium.chrome.browser.ui.theme.BrandedColorScheme; import org.chromium.chrome.browser.util.KeyNavigationUtil; import org.chromium.components.browser_ui.styles.ChromeColors; import org.chromium.content_public.browser.UiThreadTaskTraits; @@ -247,10 +246,11 @@ public void hide() { /** * Update the suggestion popup background to reflect the current state. - * @param omniboxTheme The {@link @OmniboxTheme}. + * @param brandedColorScheme The {@link @BrandedColorScheme}. */ - public void refreshPopupBackground(@OmniboxTheme int omniboxTheme) { - int color = omniboxTheme == OmniboxTheme.INCOGNITO ? mIncognitoBgColor : mStandardBgColor; + public void refreshPopupBackground(@BrandedColorScheme int brandedColorScheme) { + int color = brandedColorScheme == BrandedColorScheme.INCOGNITO ? mIncognitoBgColor + : mStandardBgColor; if (!isHardwareAccelerated()) { // When HW acceleration is disabled, changing mSuggestionList' items somehow erases // mOmniboxResultsContainer' background from the area not covered by diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionCommonProperties.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionCommonProperties.java index dc7918dd9fea65..1cf01f1eeab829 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionCommonProperties.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionCommonProperties.java @@ -12,10 +12,9 @@ */ public class SuggestionCommonProperties { /** Whether dark colors should be applied to text, icons. */ - public static final WritableIntPropertyKey OMNIBOX_THEME = new WritableIntPropertyKey(); + public static final WritableIntPropertyKey COLOR_SCHEME = new WritableIntPropertyKey(); /** The layout direction to be applied to the entire suggestion view. */ public static final WritableIntPropertyKey LAYOUT_DIRECTION = new WritableIntPropertyKey(); - public static final PropertyKey[] ALL_KEYS = - new PropertyKey[] {OMNIBOX_THEME, LAYOUT_DIRECTION}; + public static final PropertyKey[] ALL_KEYS = new PropertyKey[] {COLOR_SCHEME, LAYOUT_DIRECTION}; } diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionListProperties.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionListProperties.java index cb75241fdde127..1f26fa94116b76 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionListProperties.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionListProperties.java @@ -29,15 +29,15 @@ public class SuggestionListProperties { new WritableObjectPropertyKey<>(true); /** - * Specifies the omnibox theme. It can be light or dark because of a publisher defined color, + * Specifies the color scheme. It can be light or dark because of a publisher defined color, * incognito, or the default theme that follows dynamic colors. */ - public static final WritableIntPropertyKey OMNIBOX_THEME = new WritableIntPropertyKey(); + public static final WritableIntPropertyKey COLOR_SCHEME = new WritableIntPropertyKey(); /** Observer that will receive notifications and callbacks from Suggestion List. */ public static final WritableObjectPropertyKey OBSERVER = new WritableObjectPropertyKey<>(); public static final PropertyKey[] ALL_KEYS = - new PropertyKey[] {VISIBLE, EMBEDDER, SUGGESTION_MODELS, OMNIBOX_THEME, OBSERVER}; + new PropertyKey[] {VISIBLE, EMBEDDER, SUGGESTION_MODELS, COLOR_SCHEME, OBSERVER}; } diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionListViewBinder.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionListViewBinder.java index fd50d86c0407be..963dd13d43d99e 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionListViewBinder.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionListViewBinder.java @@ -63,8 +63,8 @@ public void onItemRangeChanged(ListObservable source, int index, view.dropdown.resetSelection(); } }); - } else if (SuggestionListProperties.OMNIBOX_THEME.equals(propertyKey)) { - view.dropdown.refreshPopupBackground(model.get(SuggestionListProperties.OMNIBOX_THEME)); + } else if (SuggestionListProperties.COLOR_SCHEME.equals(propertyKey)) { + view.dropdown.refreshPopupBackground(model.get(SuggestionListProperties.COLOR_SCHEME)); } } } diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewBinder.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewBinder.java index a81af962162495..becea4ebc463d4 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewBinder.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewBinder.java @@ -57,7 +57,7 @@ public void bind(PropertyModel model, BaseSuggestionView view, PropertyKey pr ViewCompat.setLayoutDirection( view, model.get(SuggestionCommonProperties.LAYOUT_DIRECTION)); updateContentViewPadding(model, view.getDecoratedSuggestionView()); - } else if (SuggestionCommonProperties.OMNIBOX_THEME == propertyKey) { + } else if (SuggestionCommonProperties.COLOR_SCHEME == propertyKey) { updateColorScheme(model, view); } else if (BaseSuggestionViewProperties.ACTIONS == propertyKey) { bindActionButtons(model, view, model.get(BaseSuggestionViewProperties.ACTIONS)); @@ -147,7 +147,7 @@ private static void updateColorScheme( /** @return Whether currently used color scheme is considered to be dark. */ private static boolean useDarkColors(PropertyModel model) { return !OmniboxResourceProvider.isDarkMode( - model.get(SuggestionCommonProperties.OMNIBOX_THEME)); + model.get(SuggestionCommonProperties.COLOR_SCHEME)); } /** Update attributes of decorated suggestion icon. */ @@ -207,7 +207,7 @@ private static void updateContentViewPadding( */ private static Drawable getSelectableBackgroundDrawable(View view, PropertyModel model) { return OmniboxResourceProvider.resolveAttributeToDrawable(view.getContext(), - model.get(SuggestionCommonProperties.OMNIBOX_THEME), + model.get(SuggestionCommonProperties.COLOR_SCHEME), R.attr.selectableItemBackground); } diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewBinderUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewBinderUnitTest.java index 7ebe859cdd1583..2f6dbc2c3d4d12 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewBinderUnitTest.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewBinderUnitTest.java @@ -29,9 +29,9 @@ import org.robolectric.annotation.Config; import org.chromium.chrome.R; -import org.chromium.chrome.browser.omnibox.styles.OmniboxTheme; import org.chromium.chrome.browser.omnibox.suggestions.SuggestionCommonProperties; import org.chromium.chrome.browser.omnibox.suggestions.base.BaseSuggestionViewProperties.Action; +import org.chromium.chrome.browser.ui.theme.BrandedColorScheme; import org.chromium.testing.local.LocalRobolectricTestRunner; import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModelChangeProcessor; @@ -224,7 +224,7 @@ public void actionIcon_dontCrashWhenRecycling() { Assert.assertNull(mModel.get(BaseSuggestionViewProperties.ACTIONS)); mBaseView.setActionButtonsCount(1); // Change in color scheme happening ahead of setting action could cause a crash. - mModel.set(SuggestionCommonProperties.OMNIBOX_THEME, OmniboxTheme.LIGHT_THEME); + mModel.set(SuggestionCommonProperties.COLOR_SCHEME, BrandedColorScheme.LIGHT_BRANDED_THEME); } @Test diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionViewViewBinder.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionViewViewBinder.java index fa9bc9ab3db771..e2084343fbd289 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionViewViewBinder.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionViewViewBinder.java @@ -13,9 +13,9 @@ import org.chromium.chrome.browser.omnibox.R; import org.chromium.chrome.browser.omnibox.styles.OmniboxResourceProvider; -import org.chromium.chrome.browser.omnibox.styles.OmniboxTheme; import org.chromium.chrome.browser.omnibox.suggestions.SuggestionCommonProperties; import org.chromium.chrome.browser.omnibox.suggestions.base.SuggestionSpannable; +import org.chromium.chrome.browser.ui.theme.BrandedColorScheme; import org.chromium.ui.modelutil.PropertyKey; import org.chromium.ui.modelutil.PropertyModel; @@ -26,7 +26,7 @@ public static void bind(PropertyModel model, View view, PropertyKey propertyKey) if (propertyKey == SuggestionViewProperties.TEXT_LINE_1_TEXT) { TextView tv = view.findViewById(R.id.line_1); tv.setText(model.get(SuggestionViewProperties.TEXT_LINE_1_TEXT)); - } else if (propertyKey == SuggestionCommonProperties.OMNIBOX_THEME) { + } else if (propertyKey == SuggestionCommonProperties.COLOR_SCHEME) { updateSuggestionTextColor(view, model); } else if (propertyKey == SuggestionViewProperties.IS_SEARCH_SUGGESTION) { updateSuggestionTextColor(view, model); @@ -54,18 +54,20 @@ public static void bind(PropertyModel model, View view, PropertyKey propertyKey) private static void updateSuggestionTextColor(View view, PropertyModel model) { final boolean isSearch = model.get(SuggestionViewProperties.IS_SEARCH_SUGGESTION); - final @OmniboxTheme int omniboxTheme = model.get(SuggestionCommonProperties.OMNIBOX_THEME); + final @BrandedColorScheme int brandedColorScheme = + model.get(SuggestionCommonProperties.COLOR_SCHEME); final TextView line1 = view.findViewById(R.id.line_1); final TextView line2 = view.findViewById(R.id.line_2); final Context context = view.getContext(); final @ColorInt int color1 = - OmniboxResourceProvider.getSuggestionPrimaryTextColor(context, omniboxTheme); + OmniboxResourceProvider.getSuggestionPrimaryTextColor(context, brandedColorScheme); line1.setTextColor(color1); final @ColorInt int color2 = isSearch - ? OmniboxResourceProvider.getSuggestionSecondaryTextColor(context, omniboxTheme) - : OmniboxResourceProvider.getSuggestionUrlTextColor(context, omniboxTheme); + ? OmniboxResourceProvider.getSuggestionSecondaryTextColor( + context, brandedColorScheme) + : OmniboxResourceProvider.getSuggestionUrlTextColor(context, brandedColorScheme); line2.setTextColor(color2); } } diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionViewBinder.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionViewBinder.java index e8922bd2d02c12..887ce1e1a9d4e7 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionViewBinder.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionViewBinder.java @@ -28,9 +28,9 @@ public EditUrlSuggestionViewBinder() { public void bind(PropertyModel model, EditUrlSuggestionView view, PropertyKey propertyKey) { mBinder.bind(model, view.getBaseSuggestionView(), propertyKey); - if (SuggestionCommonProperties.OMNIBOX_THEME == propertyKey) { + if (SuggestionCommonProperties.COLOR_SCHEME == propertyKey) { Drawable drawable = OmniboxResourceProvider.resolveAttributeToDrawable( - view.getContext(), model.get(SuggestionCommonProperties.OMNIBOX_THEME), + view.getContext(), model.get(SuggestionCommonProperties.COLOR_SCHEME), android.R.attr.listDivider); view.getDivider().setBackground(drawable); } diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/header/HeaderViewBinder.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/header/HeaderViewBinder.java index 0f1bb60660a300..6c21881c3385e4 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/header/HeaderViewBinder.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/header/HeaderViewBinder.java @@ -21,9 +21,9 @@ public class HeaderViewBinder { public static void bind(PropertyModel model, HeaderView view, PropertyKey propertyKey) { if (HeaderViewProperties.TITLE == propertyKey) { view.getTextView().setText(model.get(HeaderViewProperties.TITLE)); - } else if (propertyKey == SuggestionCommonProperties.OMNIBOX_THEME) { + } else if (propertyKey == SuggestionCommonProperties.COLOR_SCHEME) { final boolean useDarkColors = !OmniboxResourceProvider.isDarkMode( - model.get(SuggestionCommonProperties.OMNIBOX_THEME)); + model.get(SuggestionCommonProperties.COLOR_SCHEME)); TextViewCompat.setTextAppearance(view.getTextView(), ChromeColors.getTextMediumThickSecondaryStyle(!useDarkColors)); ApiCompatibilityUtils.setImageTintList(view.getIconView(), diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesProcessor.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesProcessor.java index 27a9dbc90bd8af..22efd4f3fb2d4c 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesProcessor.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesProcessor.java @@ -22,12 +22,12 @@ import org.chromium.chrome.browser.omnibox.OmniboxSuggestionType; import org.chromium.chrome.browser.omnibox.R; import org.chromium.chrome.browser.omnibox.styles.OmniboxResourceProvider; -import org.chromium.chrome.browser.omnibox.styles.OmniboxTheme; import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionUiType; import org.chromium.chrome.browser.omnibox.suggestions.SuggestionHost; import org.chromium.chrome.browser.omnibox.suggestions.carousel.BaseCarouselSuggestionProcessor; import org.chromium.chrome.browser.omnibox.suggestions.carousel.BaseCarouselSuggestionView; import org.chromium.chrome.browser.omnibox.suggestions.carousel.BaseCarouselSuggestionViewProperties; +import org.chromium.chrome.browser.ui.theme.BrandedColorScheme; import org.chromium.components.browser_ui.widget.RoundedIconGenerator; import org.chromium.components.browser_ui.widget.tile.TileView; import org.chromium.components.browser_ui.widget.tile.TileViewBinder; @@ -220,8 +220,9 @@ private static TileView buildTile(ViewGroup parent) { .inflate(R.layout.suggestions_tile_view, parent, false); tile.setClickable(true); - Drawable background = OmniboxResourceProvider.resolveAttributeToDrawable( - parent.getContext(), OmniboxTheme.LIGHT_THEME, R.attr.selectableItemBackground); + Drawable background = + OmniboxResourceProvider.resolveAttributeToDrawable(parent.getContext(), + BrandedColorScheme.LIGHT_BRANDED_THEME, R.attr.selectableItemBackground); tile.setBackgroundDrawable(background); return tile; } diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionViewBinder.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionViewBinder.java index d7aeb540dedc31..a0ad5d61397522 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionViewBinder.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionViewBinder.java @@ -21,9 +21,9 @@ public static void bind(PropertyModel model, TailSuggestionView view, PropertyKe view.setTailText(model.get(TailSuggestionViewProperties.TEXT)); } else if (propertyKey == TailSuggestionViewProperties.FILL_INTO_EDIT) { view.setFullText(model.get(TailSuggestionViewProperties.FILL_INTO_EDIT)); - } else if (propertyKey == SuggestionCommonProperties.OMNIBOX_THEME) { + } else if (propertyKey == SuggestionCommonProperties.COLOR_SCHEME) { final @ColorInt int color = OmniboxResourceProvider.getSuggestionPrimaryTextColor( - view.getContext(), model.get(SuggestionCommonProperties.OMNIBOX_THEME)); + view.getContext(), model.get(SuggestionCommonProperties.COLOR_SCHEME)); view.setTextColor(color); } } diff --git a/chrome/browser/ui/android/theme/BUILD.gn b/chrome/browser/ui/android/theme/BUILD.gn index 4fac4fb43815bd..36e61e785c0f23 100644 --- a/chrome/browser/ui/android/theme/BUILD.gn +++ b/chrome/browser/ui/android/theme/BUILD.gn @@ -9,6 +9,7 @@ android_library("java") { "java/src/org/chromium/chrome/browser/theme/ThemeColorProvider.java", "java/src/org/chromium/chrome/browser/theme/ThemeUtils.java", "java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.java", + "java/src/org/chromium/chrome/browser/ui/theme/BrandedColorScheme.java", ] deps = [ ":java_resources", diff --git a/chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/ui/theme/BrandedColorScheme.java b/chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/ui/theme/BrandedColorScheme.java new file mode 100644 index 00000000000000..eaada423c4c3e1 --- /dev/null +++ b/chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/ui/theme/BrandedColorScheme.java @@ -0,0 +1,31 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.ui.theme; + +import androidx.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@IntDef({BrandedColorScheme.LIGHT_BRANDED_THEME, BrandedColorScheme.DARK_BRANDED_THEME, + BrandedColorScheme.INCOGNITO, BrandedColorScheme.APP_DEFAULT}) +@Retention(RetentionPolicy.SOURCE) +public @interface BrandedColorScheme { + /** + * Light branded color as defined by the website, unrelated to the app/OS dark theme setting. + */ + int LIGHT_BRANDED_THEME = 0; + /** + * Dark branded color as defined by the website, unrelated to the app/OS dark theme setting. + */ + int DARK_BRANDED_THEME = 1; + /** Incognito theme. */ + int INCOGNITO = 2; + /** + * Default theme with potentially dynamic colors that can be light or dark depending on user + * or system settings. + */ + int APP_DEFAULT = 3; +} diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java index cd596de6836831..d0632e7176bcf2 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java @@ -28,7 +28,6 @@ import org.chromium.chrome.browser.omnibox.SearchEngineLogoUtils; import org.chromium.chrome.browser.omnibox.UrlBarData; import org.chromium.chrome.browser.omnibox.styles.OmniboxResourceProvider; -import org.chromium.chrome.browser.omnibox.styles.OmniboxTheme; import org.chromium.chrome.browser.paint_preview.TabbedPaintPreview; import org.chromium.chrome.browser.preferences.ChromePreferenceKeys; import org.chromium.chrome.browser.profiles.Profile; @@ -36,6 +35,7 @@ import org.chromium.chrome.browser.tab.TrustedCdn; import org.chromium.chrome.browser.theme.ThemeUtils; import org.chromium.chrome.browser.ui.native_page.NativePage; +import org.chromium.chrome.browser.ui.theme.BrandedColorScheme; import org.chromium.chrome.features.start_surface.StartSurfaceConfiguration; import org.chromium.components.browser_ui.styles.ChromeColors; import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils; @@ -306,16 +306,18 @@ && shouldEmphasizeUrl()) { ChromeAutocompleteSchemeClassifier chromeAutocompleteSchemeClassifier = new ChromeAutocompleteSchemeClassifier(getProfile()); - final @OmniboxTheme int omniboxTheme = OmniboxResourceProvider.getOmniboxTheme( - mContext, isIncognito(), getPrimaryColor()); + final @BrandedColorScheme int brandedColorScheme = + OmniboxResourceProvider.getBrandedColorScheme( + mContext, isIncognito(), getPrimaryColor()); final @ColorInt int nonEmphasizedColor = - OmniboxResourceProvider.getUrlBarSecondaryTextColor(mContext, omniboxTheme); + OmniboxResourceProvider.getUrlBarSecondaryTextColor( + mContext, brandedColorScheme); final @ColorInt int emphasizedColor = - OmniboxResourceProvider.getUrlBarPrimaryTextColor(mContext, omniboxTheme); + OmniboxResourceProvider.getUrlBarPrimaryTextColor(mContext, brandedColorScheme); final @ColorInt int dangerColor = - OmniboxResourceProvider.getUrlBarDangerColor(mContext, omniboxTheme); + OmniboxResourceProvider.getUrlBarDangerColor(mContext, brandedColorScheme); final @ColorInt int secureColor = - OmniboxResourceProvider.getUrlBarSecureColor(mContext, omniboxTheme); + OmniboxResourceProvider.getUrlBarSecureColor(mContext, brandedColorScheme); OmniboxUrlEmphasizer.emphasizeUrl(spannableDisplayText, chromeAutocompleteSchemeClassifier, getSecurityLevel(), isInternalPage, shouldEmphasizeHttpsScheme(), nonEmphasizedColor, emphasizedColor, dangerColor, From 129a6893313a3a4e46ec20538a1069fd03663256 Mon Sep 17 00:00:00 2001 From: Gordon Seto Date: Wed, 15 Dec 2021 01:53:38 +0000 Subject: [PATCH 52/77] [CrOS Bluetooth] Handle Bluetooth power state toggling during pairing. Update to handle Bluetooth being disabled and re-enabled while pairing is in progress. Screenshot: https://screenshot.googleplex.com/9YRfgp6Cby9mmwX.png Video: https://drive.google.com/file/d/1YdlQQ_cRqma2w6mBiMsU5Gc-3J06pjUl/view?usp=sharing Fixed: b/209060012 Change-Id: I38a235d5be912d6ba2822ac591adaf7f362e1967 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3339181 Reviewed-by: Kyle Horimoto Commit-Queue: Gordon Seto Cr-Commit-Position: refs/heads/main@{#951775} --- chrome/app/chromeos_strings.grdp | 3 + ...AIRING_PAIRING_BLUETOOTH_DISABLED.png.sha1 | 1 + ...luetooth_shared_load_time_data_provider.cc | 1 + ...ooth_pairing_device_selection_page_test.js | 16 +++ .../bluetooth/bluetooth_pairing_ui_test.js | 107 +++++++++++++++++- .../bluetooth/fake_bluetooth_config.js | 25 ++++ .../bluetooth/fake_device_pairing_handler.js | 16 +++ ...uetooth_pairing_device_selection_page.html | 10 +- ...bluetooth_pairing_device_selection_page.js | 12 +- .../bluetooth/bluetooth_pairing_ui.html | 1 + .../bluetooth/bluetooth_pairing_ui.js | 51 ++++++++- 11 files changed, 235 insertions(+), 8 deletions(-) create mode 100644 chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRING_PAIRING_BLUETOOTH_DISABLED.png.sha1 diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp index 6c61f4d19cd5a5..c5ed57cdf4e05e 100644 --- a/chrome/app/chromeos_strings.grdp +++ b/chrome/app/chromeos_strings.grdp @@ -821,6 +821,9 @@ No devices found nearby + + Bluetooth is turned off. To see available devices, turn Bluetooth on. + Make sure your Bluetooth device is in pairing mode and nearby. Only pair with devices you trust. <a target="_blank" href="$1">Learn more</a> diff --git a/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRING_PAIRING_BLUETOOTH_DISABLED.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRING_PAIRING_BLUETOOTH_DISABLED.png.sha1 new file mode 100644 index 00000000000000..9980daaf4c7197 --- /dev/null +++ b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRING_PAIRING_BLUETOOTH_DISABLED.png.sha1 @@ -0,0 +1 @@ +b2f997a99de8e23846769f3e573edff6f883d7fa \ No newline at end of file diff --git a/chrome/browser/ui/webui/chromeos/bluetooth_shared_load_time_data_provider.cc b/chrome/browser/ui/webui/chromeos/bluetooth_shared_load_time_data_provider.cc index b7739d7a5d9073..7885bbd56e6275 100644 --- a/chrome/browser/ui/webui/chromeos/bluetooth_shared_load_time_data_provider.cc +++ b/chrome/browser/ui/webui/chromeos/bluetooth_shared_load_time_data_provider.cc @@ -26,6 +26,7 @@ void AddLocalizedStrings(content::WebUIDataSource* html_source) { IDS_BLUETOOTH_PAIRING_PAIRING_AVAILABLE_DEVICES}, {"bluetoothNoAvailableDevices", IDS_BLUETOOTH_PAIRING_PAIRING_NO_AVAILABLE_DEVICES}, + {"bluetoothDisabled", IDS_BLUETOOTH_PAIRING_PAIRING_BLUETOOTH_DISABLED}, {"bluetoothAccept", IDS_BLUETOOTH_PAIRING_ACCEPT_PASSKEY}, {"bluetoothEnterKey", IDS_BLUETOOTH_PAIRING_ENTER_KEY}, {"bluetoothPair", IDS_BLUETOOTH_PAIRING_PAIR}, diff --git a/chrome/test/data/webui/cr_components/chromeos/bluetooth/bluetooth_pairing_device_selection_page_test.js b/chrome/test/data/webui/cr_components/chromeos/bluetooth/bluetooth_pairing_device_selection_page_test.js index ca199d07e467c4..b1c095b569397e 100644 --- a/chrome/test/data/webui/cr_components/chromeos/bluetooth/bluetooth_pairing_device_selection_page_test.js +++ b/chrome/test/data/webui/cr_components/chromeos/bluetooth/bluetooth_pairing_device_selection_page_test.js @@ -36,6 +36,7 @@ suite('CrComponentsBluetoothPairingDeviceSelectionPageTest', function() { deviceSelectionPage = /** @type {?SettingsBluetoothPairingDeviceSelectionPageElement} */ ( document.createElement('bluetooth-pairing-device-selection-page')); + deviceSelectionPage.isBluetoothEnabled = true; document.body.appendChild(deviceSelectionPage); flush(); @@ -65,6 +66,9 @@ suite('CrComponentsBluetoothPairingDeviceSelectionPageTest', function() { const getDeviceListItems = () => deviceSelectionPage.shadowRoot.querySelectorAll( 'bluetooth-pairing-device-item'); + const getBasePage = () => + deviceSelectionPage.shadowRoot.querySelector('bluetooth-base-page'); + assertTrue(getBasePage().showScanProgress); const learnMoreLink = deviceSelectionPage.shadowRoot.querySelector('localized-link'); @@ -116,5 +120,17 @@ suite('CrComponentsBluetoothPairingDeviceSelectionPageTest', function() { await flushAsync(); assertEquals( getDeviceListItems()[0].deviceItemState, DeviceItemState.PAIRING); + + // Disable Bluetooth. + deviceSelectionPage.isBluetoothEnabled = false; + await flushAsync(); + + // Scanning progress should be hidden and the 'Bluetooth disabled' message + // shown. + assertFalse(getBasePage().showScanProgress); + assertFalse(!!getDeviceList()); + assertEquals( + deviceSelectionPage.i18n('bluetoothDisabled'), + getDeviceListTitle().textContent.trim()); }); }); diff --git a/chrome/test/data/webui/cr_components/chromeos/bluetooth/bluetooth_pairing_ui_test.js b/chrome/test/data/webui/cr_components/chromeos/bluetooth/bluetooth_pairing_ui_test.js index 971b4108c368d8..904afbfafe2c95 100644 --- a/chrome/test/data/webui/cr_components/chromeos/bluetooth/bluetooth_pairing_ui_test.js +++ b/chrome/test/data/webui/cr_components/chromeos/bluetooth/bluetooth_pairing_ui_test.js @@ -33,6 +33,8 @@ suite('CrComponentsBluetoothPairingUiTest', function() { setup(async function() { bluetoothConfig = new FakeBluetoothConfig(); + bluetoothConfig.setSystemState( + chromeos.bluetoothConfig.mojom.BluetoothSystemState.kEnabled); setBluetoothConfigForTesting(bluetoothConfig); }); @@ -264,6 +266,7 @@ suite('CrComponentsBluetoothPairingUiTest', function() { assertTrue(!!getSpinnerPage()); assertFalse(!!getDeviceSelectionPage()); await flushTasks(); + await waitAfterNextRender(bluetoothPairingUi); // Once we begin pairing we should still be in the spinner page. assertTrue(!!getSpinnerPage()); @@ -638,7 +641,7 @@ suite('CrComponentsBluetoothPairingUiTest', function() { // Simulate pressing 'Confirm'. let event = new CustomEvent('confirm-code'); getConfirmCodePage().dispatchEvent(event); - await flushTasks(); + await waitAfterNextRender(bluetoothPairingUi); // Spinner should be shown. assertTrue(!!getSpinnerPage()); @@ -669,6 +672,7 @@ suite('CrComponentsBluetoothPairingUiTest', function() { await simulateCancelation(); deviceHandler.completePairDevice(/*success=*/ false); await flushTasks(); + await waitAfterNextRender(bluetoothPairingUi); // The device selection page should be shown. assertTrue(!!getDeviceSelectionPage()); @@ -720,6 +724,9 @@ suite('CrComponentsBluetoothPairingUiTest', function() { /*opt_audioCapability=*/ mojom.AudioOutputCapability.kCapableOfAudioOutput, /*opt_deviceType=*/ mojom.DeviceType.kMouse); + bluetoothConfig.appendToDiscoveredDeviceList([device.deviceProperties]); + await flushTasks(); + const pairingCode = '123456'; // Try pairing. @@ -762,4 +769,102 @@ suite('CrComponentsBluetoothPairingUiTest', function() { assertTrue(!!getDeviceSelectionPage()); assertFalse(!!getDeviceSelectionPage().failedPairingDeviceId); }); + + test('Disable Bluetooth during pairing', async function() { + await init(); + assertTrue(!!getDeviceSelectionPage()); + assertTrue(getDeviceSelectionPage().isBluetoothEnabled); + + const deviceId = '123456'; + const device = createDefaultBluetoothDevice( + deviceId, + /*publicName=*/ 'BeatsX', + /*connectionState=*/ + chromeos.bluetoothConfig.mojom.DeviceConnectionState.kConnected, + /*opt_nickname=*/ 'device1', + /*opt_audioCapability=*/ + mojom.AudioOutputCapability.kCapableOfAudioOutput, + /*opt_deviceType=*/ mojom.DeviceType.kMouse); + bluetoothConfig.appendToDiscoveredDeviceList([device.deviceProperties]); + await flushTasks(); + + // Disable Bluetooth. + bluetoothConfig.setSystemState( + chromeos.bluetoothConfig.mojom.BluetoothSystemState.kDisabled); + await flushTasks(); + + // This should propagate to the device selection page. + assertFalse(getDeviceSelectionPage().isBluetoothEnabled); + + // Re-enable and select the device. + bluetoothConfig.setSystemState( + chromeos.bluetoothConfig.mojom.BluetoothSystemState.kEnabled); + await flushTasks(); + await waitAfterNextRender(bluetoothPairingUi); + + assertTrue(getDeviceSelectionPage().isBluetoothEnabled); + await selectDevice(device.deviceProperties); + await flushTasks(); + + const pairingCode = '123456'; + let deviceHandler = bluetoothConfig.getLastCreatedPairingHandler(); + deviceHandler.requireAuthentication( + PairingAuthType.CONFIRM_PASSKEY, pairingCode); + await flushTasks(); + + // Confirmation code page should be shown. + assertTrue(!!getConfirmCodePage()); + assertEquals(getConfirmCodePage().code, pairingCode); + + // Disable Bluetooth. + bluetoothConfig.setSystemState( + chromeos.bluetoothConfig.mojom.BluetoothSystemState.kDisabled); + await flushTasks(); + + // We should be back to the device selection page again. + assertFalse(!!getConfirmCodePage()); + assertTrue(!!getDeviceSelectionPage()); + assertFalse(getDeviceSelectionPage().isBluetoothEnabled); + + // Re-enable. + bluetoothConfig.setSystemState( + chromeos.bluetoothConfig.mojom.BluetoothSystemState.kEnabled); + await flushTasks(); + await waitAfterNextRender(bluetoothPairingUi); + + assertTrue(getDeviceSelectionPage().isBluetoothEnabled); + + // Error text shouldn't be showing because this pairing failed due to + // Bluetooth disabling. + assertEquals(getDeviceSelectionPage().failedPairingDeviceId, ''); + + // Select the device. + await selectDevice(device.deviceProperties); + await flushTasks(); + + // Simulate pairing failing. + deviceHandler = bluetoothConfig.getLastCreatedPairingHandler(); + deviceHandler.completePairDevice(/*success=*/ false); + await flushTasks(); + await waitAfterNextRender(bluetoothPairingUi); + + // Error text should be showing. + assertTrue(!!getDeviceSelectionPage()); + assertEquals(getDeviceSelectionPage().failedPairingDeviceId, deviceId); + + // Disable and re-enable. + bluetoothConfig.setSystemState( + chromeos.bluetoothConfig.mojom.BluetoothSystemState.kDisabled); + await flushTasks(); + + assertFalse(getDeviceSelectionPage().isBluetoothEnabled); + bluetoothConfig.setSystemState( + chromeos.bluetoothConfig.mojom.BluetoothSystemState.kEnabled); + await flushTasks(); + + assertTrue(getDeviceSelectionPage().isBluetoothEnabled); + + // Error text should no longer be showing. + assertEquals(getDeviceSelectionPage().failedPairingDeviceId, ''); + }); }); diff --git a/chrome/test/data/webui/cr_components/chromeos/bluetooth/fake_bluetooth_config.js b/chrome/test/data/webui/cr_components/chromeos/bluetooth/fake_bluetooth_config.js index 9ba121073263d5..65204deec2c91d 100644 --- a/chrome/test/data/webui/cr_components/chromeos/bluetooth/fake_bluetooth_config.js +++ b/chrome/test/data/webui/cr_components/chromeos/bluetooth/fake_bluetooth_config.js @@ -106,6 +106,8 @@ export class FakeBluetoothConfig { this.pendingForgetRequest_ = null; /** + * The last pairing handler created. If defined, indicates discovery in is + * progress. If null, indicates no discovery is occurring. * @private {?FakeDevicePairingHandler} */ this.lastPairingHandler_ = null; @@ -236,6 +238,21 @@ export class FakeBluetoothConfig { */ (Object.assign({}, this.systemProperties_)); this.notifyObserversPropertiesUpdated_(); + + // If discovery is in progress and Bluetooth is no longer enabled, stop + // discovery and any pairing that may be occurring. + if (!this.lastPairingHandler_) { + return; + } + + if (systemState === + chromeos.bluetoothConfig.mojom.BluetoothSystemState.kEnabled) { + return; + } + + this.lastPairingHandler_.rejectPairDevice(); + this.lastPairingHandler_ = null; + this.notifyDiscoveryStopped_(); } /** @@ -442,6 +459,14 @@ export class FakeBluetoothConfig { devicePairingHandlerReciever.$.bindNewPipeAndPassRemote()); } + /** + * @private + * Notifies the last delegate that device discovery has stopped. + */ + notifyDiscoveryStopped_() { + this.lastDiscoveryDelegate_.onBluetoothDiscoveryStopped(); + } + /** * @return {?FakeDevicePairingHandler} */ diff --git a/chrome/test/data/webui/cr_components/chromeos/bluetooth/fake_device_pairing_handler.js b/chrome/test/data/webui/cr_components/chromeos/bluetooth/fake_device_pairing_handler.js index e8fe62229ce55d..4ff6cbe17ef4aa 100644 --- a/chrome/test/data/webui/cr_components/chromeos/bluetooth/fake_device_pairing_handler.js +++ b/chrome/test/data/webui/cr_components/chromeos/bluetooth/fake_device_pairing_handler.js @@ -28,6 +28,11 @@ export class FakeDevicePairingHandler { */ this.pairDeviceCallback_ = null; + /** + * @private {?function()} + */ + this.pairDeviceRejectCallback_ = null; + /** @private {number} */ this.pairDeviceCalledCount_ = 0; @@ -47,6 +52,7 @@ export class FakeDevicePairingHandler { this.devicePairingDelegate_ = delegate; let promise = new Promise((resolve, reject) => { this.pairDeviceCallback_ = resolve; + this.pairDeviceRejectCallback_ = reject; }); return promise; } @@ -145,6 +151,16 @@ export class FakeDevicePairingHandler { }); } + /** + * Simulates pairing failing due to an exception, such as the Mojo pipe + * disconnecting. + */ + rejectPairDevice() { + if (this.pairDeviceRejectCallback_) { + this.pairDeviceRejectCallback_(); + } + } + /** * @return {number} */ diff --git a/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_selection_page.html b/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_selection_page.html index 39ec00caf04823..618dba864850b1 100644 --- a/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_selection_page.html +++ b/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_selection_page.html @@ -14,14 +14,18 @@ margin: 20px 0 8px 0; } -
-
[[getDeviceListTitle_(devices.*)]]
-