diff --git a/DEPS b/DEPS index 26416479ba34f5..cf7a1c985a9b02 100644 --- a/DEPS +++ b/DEPS @@ -239,15 +239,15 @@ 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': 'd26057a2c0e1e35a2f3a498769a27d8f8b6b1e62', + 'skia_revision': 'f74c7893fc17685e34089af7c81446e23cfab4ad', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. - 'v8_revision': '8a05d7add33e40e73aac9fc43b4d9cce04adeaca', + 'v8_revision': 'b5bf62dafe52d10d65bfd2e378d658b5b3b04a5e', # 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': '42bd4fc29aea6c0c1722d3604b0eadf7fb1e9aca', + 'angle_revision': 'fefd7ae66ad94d8fcb16ef9f5f7304c1b4b9fbf8', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling SwiftShader # and whatever else without interference from each other. @@ -306,7 +306,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling catapult # and whatever else without interference from each other. - 'catapult_revision': 'e3f9ae73db5135ad998108113af7ef82a47efc51', + 'catapult_revision': '563885e399ba18672cd7d009a0c0f0a996d71f6b', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libFuzzer # and whatever else without interference from each other. @@ -314,7 +314,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling devtools-frontend # and whatever else without interference from each other. - 'devtools_frontend_revision': 'a1121f966ce5590b46706befdf60e3350a5cf2d1', + 'devtools_frontend_revision': '5fec567a800a98035a5c7aa02eebf93d3690e484', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libprotobuf-mutator # and whatever else without interference from each other. @@ -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': '4682ae0034e7929dbea9b064ff39f6953eec406b', + 'dawn_revision': 'f296710f64b6625d204262c09463401def70cea6', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. @@ -716,7 +716,7 @@ deps = { 'packages': [ { 'package': 'chromium/rts/model/linux-amd64', - 'version': 'vdD8aHXAya55_zVdy0rcmEfQix9y1GLancfERg2yKc8C', + 'version': 'h4OErwvGMzWmTzk59jLdAUp9o00HkQ6vKZlwtDCstocC', }, ], 'dep_type': 'cipd', @@ -727,7 +727,7 @@ deps = { 'packages': [ { 'package': 'chromium/rts/model/mac-amd64', - 'version': 'yVc-vTWsYEzs3l22SOOsPdO_AWBNR3HP71lX3Q4VQQYC', + 'version': 'ST_XL0WVEj5Xxvj-kAcMuyZsvgGw_VNlvbupF6-aFXUC', }, ], 'dep_type': 'cipd', @@ -738,7 +738,7 @@ deps = { 'packages': [ { 'package': 'chromium/rts/model/windows-amd64', - 'version': 'tI4WdYbciNIAG3pe4wvAouTF40XTOBKIAMEZ0h_TQT8C', + 'version': 'K9BP5YhVbtP-iXTbJU0dbwRi9gaMI_w2x0OtulD3R_oC', }, ], 'dep_type': 'cipd', @@ -799,7 +799,7 @@ deps = { 'packages': [ { 'package': 'chromium/third_party/androidx', - 'version': 'juajBfBlqvumlCoLLOxvOPlX6cPvfNhDi8XyKP4yjTEC', + 'version': 'Cd5XvW44KjLwajSIYoTsODMnpNKBR--BNLPfpIDKGeAC', }, ], 'condition': 'checkout_android', @@ -1018,7 +1018,7 @@ deps = { # Tools used when building Chrome for Chrome OS. This affects both the Simple # Chrome workflow, as well as the chromeos-chrome ebuild. 'src/third_party/chromite': { - 'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'fff14ebdcbb0fd75801af82d2765758efa1ec579', + 'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '7f13e5c5ff2def048f60931a4f09bdcc0c7c3965', 'condition': 'checkout_chromeos', }, @@ -1033,7 +1033,7 @@ deps = { # For Linux and Chromium OS. 'src/third_party/cros_system_api': { - 'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + '738cc02fc3beb133d085f871e2534416baf5afc7', + 'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + '188fb4eba05af428a48c7ce173a3026215241850', 'condition': 'checkout_linux', }, @@ -1421,7 +1421,7 @@ deps = { }, 'src/third_party/perfetto': - Var('android_git') + '/platform/external/perfetto.git' + '@' + '1357bd132733ccc23a295739e4bc432d7a53d120', + Var('android_git') + '/platform/external/perfetto.git' + '@' + 'f9abf9948a180a56a3595ec54ff9f2f5c2c9947c', 'src/third_party/perl': { 'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3', @@ -1642,7 +1642,7 @@ deps = { Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '216e2ff413d3ed5e7695626bb55ae41575818352', 'src/third_party/webrtc': - Var('webrtc_git') + '/src.git' + '@' + '699d1a242ecd3ca819293cfa96f5dcf8f5e1b91a', + Var('webrtc_git') + '/src.git' + '@' + '63b97de330fe3d4775b9b4df8ad15c7593d58fc0', 'src/third_party/libgifcodec': Var('skia_git') + '/libgifcodec' + '@'+ Var('libgifcodec_revision'), @@ -1700,7 +1700,7 @@ deps = { Var('chromium_git') + '/v8/v8.git' + '@' + Var('v8_revision'), 'src-internal': { - 'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@d49909553bd8838195664ca9df5b36229c2582da', + 'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@c258c12674403e7c685e3462d136b29560e6b4d8', 'condition': 'checkout_src_internal', }, @@ -1719,7 +1719,7 @@ deps = { 'packages': [ { 'package': 'chromeos_internal/apps/help_app/app', - 'version': 'uwAu1UD0oDj17zqx0Af0wZnVecasHn1pBC4V6A3JBRwC', + 'version': 'RcDM8Mq_LrCGiuxfQto-gkDVFbqCRy6FE8lzPeHsNEwC', }, ], 'condition': 'checkout_chromeos and checkout_src_internal', @@ -1730,7 +1730,7 @@ deps = { 'packages': [ { 'package': 'chromeos_internal/apps/media_app/app', - 'version': 'Hm4ipUPiXd-gzWDEbyraNGipQikDIbxi1Xc70s6BwWQC', + 'version': 'OEszP2fR0fctn9Qr3fnIElqnNLPxHyhTUfWfaiiLVckC', }, ], 'condition': 'checkout_chromeos and checkout_src_internal', diff --git a/android_webview/browser/aw_contents.cc b/android_webview/browser/aw_contents.cc index 0cb2f1c607daf0..d824169e051486 100644 --- a/android_webview/browser/aw_contents.cc +++ b/android_webview/browser/aw_contents.cc @@ -79,6 +79,7 @@ #include "content/public/browser/favicon_status.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/navigation_handle.h" +#include "content/public/browser/page.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" @@ -1512,21 +1513,20 @@ void AwContents::RenderViewHostChanged(content::RenderViewHost* old_host, new_host->GetWidget()->GetFrameSinkId()); } -void AwContents::DidFinishNavigation( - content::NavigationHandle* navigation_handle) { - // If this request was blocked in any way, broadcast an error. - net::Error error_code = navigation_handle->GetNetErrorCode(); - - bool navigation_successful_and_scheme_http_or_https = - ((error_code == net::OK) && - navigation_handle->GetURL().SchemeIsHTTPOrHTTPS()); - if (navigation_successful_and_scheme_http_or_https != scheme_http_or_https_) { - scheme_http_or_https_ = navigation_successful_and_scheme_http_or_https; +void AwContents::PrimaryPageChanged(content::Page& page) { + std::string scheme = page.GetMainDocument().GetLastCommittedURL().scheme(); + if (scheme_ != scheme) { + scheme_ = scheme; AwBrowserProcess::GetInstance() ->visibility_metrics_logger() ->ClientVisibilityChanged(this); } +} +void AwContents::DidFinishNavigation( + content::NavigationHandle* navigation_handle) { + // If this request was blocked in any way, broadcast an error. + net::Error error_code = navigation_handle->GetNetErrorCode(); if (!net::IsRequestBlockedError(error_code) && error_code != net::ERR_ABORTED) { return; @@ -1582,7 +1582,8 @@ VisibilityMetricsLogger::VisibilityInfo AwContents::GetVisibilityInfo() { return VisibilityMetricsLogger::VisibilityInfo{ browser_view_renderer_.attached_to_window(), browser_view_renderer_.view_visible(), - browser_view_renderer_.window_visible(), scheme_http_or_https_}; + browser_view_renderer_.window_visible(), + VisibilityMetricsLogger::SchemeStringToEnum(scheme_)}; } void AwContents::RendererUnresponsive( diff --git a/android_webview/browser/aw_contents.h b/android_webview/browser/aw_contents.h index 2c8885fe9c8978..7e19b9e14e491d 100644 --- a/android_webview/browser/aw_contents.h +++ b/android_webview/browser/aw_contents.h @@ -389,6 +389,7 @@ class AwContents : public FindHelper::Listener, // content::WebContentsObserver overrides void RenderViewHostChanged(content::RenderViewHost* old_host, content::RenderViewHost* new_host) override; + void PrimaryPageChanged(content::Page& page) override; void DidFinishNavigation( content::NavigationHandle* navigation_handle) override; @@ -428,7 +429,7 @@ class AwContents : public FindHelper::Listener, std::unique_ptr js_communication_host_; bool view_tree_force_dark_state_ = false; - bool scheme_http_or_https_ = false; + std::string scheme_; // GURL is supplied by the content layer as requesting frame. // Callback is supplied by the content layer, and is invoked with the result diff --git a/android_webview/browser/metrics/visibility_metrics_logger.cc b/android_webview/browser/metrics/visibility_metrics_logger.cc index 417bdbebb9204a..8784c5abdb92ba 100644 --- a/android_webview/browser/metrics/visibility_metrics_logger.cc +++ b/android_webview/browser/metrics/visibility_metrics_logger.cc @@ -11,6 +11,7 @@ #include "base/metrics/histogram_base.h" #include "base/time/time.h" #include "content/public/browser/browser_thread.h" +#include "content/public/common/url_constants.h" using content::BrowserThread; @@ -41,20 +42,20 @@ base::HistogramBase* GetPerWebViewVisibilityHistogram() { return histogram; } -base::HistogramBase* GetGlobalOpenWebVisibilityHistogram() { +void LogGlobalVisibleScheme(VisibilityMetricsLogger::Scheme scheme, + int32_t seconds) { static base::HistogramBase* histogram(CreateHistogramForDurationTracking( - "Android.WebView.WebViewOpenWebVisible.Global", - static_cast( - VisibilityMetricsLogger::WebViewOpenWebVisibility::kMaxValue))); - return histogram; + "Android.WebView.VisibleScheme.Global", + static_cast(VisibilityMetricsLogger::Scheme::kMaxValue))); + histogram->AddCount(static_cast(scheme), seconds); } -base::HistogramBase* GetPerWebViewOpenWebVisibilityHistogram() { +void LogPerWebViewVisibleScheme(VisibilityMetricsLogger::Scheme scheme, + int32_t seconds) { static base::HistogramBase* histogram(CreateHistogramForDurationTracking( - "Android.WebView.WebViewOpenWebVisible.PerWebView", - static_cast( - VisibilityMetricsLogger::WebViewOpenWebVisibility::kMaxValue))); - return histogram; + "Android.WebView.VisibleScheme.PerWebView", + static_cast(VisibilityMetricsLogger::Scheme::kMaxValue))); + histogram->AddCount(static_cast(scheme), seconds); } base::HistogramBase* GetOpenWebVisibileScreenPortionHistogram() { @@ -67,6 +68,36 @@ base::HistogramBase* GetOpenWebVisibileScreenPortionHistogram() { } // anonymous namespace +// static +VisibilityMetricsLogger::Scheme VisibilityMetricsLogger::SchemeStringToEnum( + const std::string& scheme) { + if (scheme.empty()) + return Scheme::kEmpty; + if (scheme == url::kHttpScheme) + return Scheme::kHttp; + if (scheme == url::kHttpsScheme) + return Scheme::kHttps; + if (scheme == url::kFileScheme) + return Scheme::kFile; + if (scheme == url::kFtpScheme) + return Scheme::kFtp; + if (scheme == url::kDataScheme) + return Scheme::kData; + if (scheme == url::kJavaScriptScheme) + return Scheme::kJavaScript; + if (scheme == url::kAboutScheme) + return Scheme::kAbout; + if (scheme == content::kChromeUIScheme) + return Scheme::kChrome; + if (scheme == url::kBlobScheme) + return Scheme::kBlob; + if (scheme == url::kContentScheme) + return Scheme::kContent; + if (scheme == "intent") + return Scheme::kIntent; + return Scheme::kUnknown; +} + VisibilityMetricsLogger::VisibilityMetricsLogger() { DCHECK_CURRENTLY_ON(BrowserThread::UI); last_update_time_ = base::TimeTicks::Now(); @@ -125,29 +156,26 @@ void VisibilityMetricsLogger::UpdateOpenWebScreenArea(int pixels, void VisibilityMetricsLogger::UpdateDurations() { base::TimeTicks update_time = base::TimeTicks::Now(); base::TimeDelta delta = update_time - last_update_time_; - if (visible_client_count_ > 0) { - visible_duration_tracker_.any_webview_tracked_duration_ += delta; + if (all_clients_visible_count_ > 0) { + all_clients_tracker_.any_webview_tracked_duration_ += delta; } else { - visible_duration_tracker_.no_webview_tracked_duration_ += delta; + all_clients_tracker_.no_webview_tracked_duration_ += delta; } + all_clients_tracker_.per_webview_duration_ += + delta * all_clients_visible_count_; + all_clients_tracker_.per_webview_untracked_duration_ += + delta * (client_visibility_.size() - all_clients_visible_count_); - if (visible_webcontent_client_count_ > 0) { - webcontent_visible_tracker_.any_webview_tracked_duration_ += delta; - } else { - webcontent_visible_tracker_.no_webview_tracked_duration_ += delta; + for (size_t i = 0; i < base::size(per_scheme_visible_counts_); i++) { + if (!per_scheme_visible_counts_[i]) + continue; + per_scheme_trackers_[i].any_webview_tracked_duration_ += delta; + per_scheme_trackers_[i].per_webview_duration_ += + delta * per_scheme_visible_counts_[i]; } - visible_duration_tracker_.per_webview_duration_ += - delta * visible_client_count_; - visible_duration_tracker_.per_webview_untracked_duration_ += - delta * (client_visibility_.size() - visible_client_count_); - - webcontent_visible_tracker_.per_webview_duration_ += - delta * visible_webcontent_client_count_; - webcontent_visible_tracker_.per_webview_untracked_duration_ += - delta * (client_visibility_.size() - visible_webcontent_client_count_); - - if (visible_webcontent_client_count_ > 0) { + if (per_scheme_visible_counts_[static_cast(Scheme::kHttp)] > 0 || + per_scheme_visible_counts_[static_cast(Scheme::kHttps)] > 0) { open_web_screen_portion_tracked_duration_[static_cast( current_open_web_screen_portion_)] += delta; } @@ -159,13 +187,9 @@ bool VisibilityMetricsLogger::VisibilityInfo::IsVisible() const { return view_attached && view_visible && window_visible; } -bool VisibilityMetricsLogger::VisibilityInfo::ContainsOpenWebContent() const { - return scheme_http_or_https; -} - bool VisibilityMetricsLogger::VisibilityInfo::IsDisplayingOpenWebContent() const { - return IsVisible() && ContainsOpenWebContent(); + return IsVisible() && (scheme == Scheme::kHttp || scheme == Scheme::kHttps); } void VisibilityMetricsLogger::ProcessClientUpdate(Client* client, @@ -173,26 +197,25 @@ void VisibilityMetricsLogger::ProcessClientUpdate(Client* client, VisibilityInfo curr_info = client_visibility_[client]; bool was_visible = curr_info.IsVisible(); bool is_visible = info.IsVisible(); - bool was_visible_web = curr_info.IsDisplayingOpenWebContent(); - bool is_visible_web = info.IsDisplayingOpenWebContent(); + Scheme old_scheme = curr_info.scheme; + Scheme new_scheme = info.scheme; client_visibility_[client] = info; - DCHECK(!was_visible || visible_client_count_ > 0); + DCHECK(!was_visible || all_clients_visible_count_ > 0); - bool any_client_was_visible = visible_client_count_ > 0; + bool any_client_was_visible = all_clients_visible_count_ > 0; if (!was_visible && is_visible) { - ++visible_client_count_; + ++all_clients_visible_count_; } else if (was_visible && !is_visible) { - --visible_client_count_; + --all_clients_visible_count_; } - if (!was_visible_web && is_visible_web) { - ++visible_webcontent_client_count_; - } else if (was_visible_web && !is_visible_web) { - --visible_webcontent_client_count_; - } + if (was_visible) + per_scheme_visible_counts_[static_cast(old_scheme)]--; + if (is_visible) + per_scheme_visible_counts_[static_cast(new_scheme)]++; - bool any_client_is_visible = visible_client_count_ > 0; + bool any_client_is_visible = all_clients_visible_count_ > 0; if (on_visibility_changed_callback_ && any_client_was_visible != any_client_is_visible) { on_visibility_changed_callback_.Run(any_client_is_visible); @@ -209,7 +232,7 @@ void VisibilityMetricsLogger::RecordMetrics() { DCHECK_CURRENTLY_ON(BrowserThread::UI); UpdateDurations(); RecordVisibilityMetrics(); - RecordOpenWebDisplayMetrics(); + RecordVisibleSchemeMetrics(); RecordScreenPortionMetrics(); } @@ -220,21 +243,21 @@ void VisibilityMetricsLogger::RecordVisibilityMetrics() { int32_t total_no_webview_visible_seconds; any_webview_visible_seconds = - visible_duration_tracker_.any_webview_tracked_duration_.InSeconds(); - visible_duration_tracker_.any_webview_tracked_duration_ -= + all_clients_tracker_.any_webview_tracked_duration_.InSeconds(); + all_clients_tracker_.any_webview_tracked_duration_ -= base::Seconds(any_webview_visible_seconds); no_webview_visible_seconds = - visible_duration_tracker_.no_webview_tracked_duration_.InSeconds(); - visible_duration_tracker_.no_webview_tracked_duration_ -= + all_clients_tracker_.no_webview_tracked_duration_.InSeconds(); + all_clients_tracker_.no_webview_tracked_duration_ -= base::Seconds(no_webview_visible_seconds); total_webview_visible_seconds = - visible_duration_tracker_.per_webview_duration_.InSeconds(); - visible_duration_tracker_.per_webview_duration_ -= + all_clients_tracker_.per_webview_duration_.InSeconds(); + all_clients_tracker_.per_webview_duration_ -= base::Seconds(total_webview_visible_seconds); total_no_webview_visible_seconds = - visible_duration_tracker_.per_webview_untracked_duration_.InSeconds(); - visible_duration_tracker_.per_webview_untracked_duration_ -= + all_clients_tracker_.per_webview_untracked_duration_.InSeconds(); + all_clients_tracker_.per_webview_untracked_duration_ -= base::Seconds(total_no_webview_visible_seconds); if (any_webview_visible_seconds) { @@ -257,50 +280,24 @@ void VisibilityMetricsLogger::RecordVisibilityMetrics() { } } -void VisibilityMetricsLogger::RecordOpenWebDisplayMetrics() { - int32_t any_webcontent_visible_seconds; - int32_t no_webcontent_visible_seconds; - int32_t total_webcontent_visible_seconds; - int32_t total_not_webcontent_or_not_visible_seconds; - - any_webcontent_visible_seconds = - webcontent_visible_tracker_.any_webview_tracked_duration_.InSeconds(); - webcontent_visible_tracker_.any_webview_tracked_duration_ -= - base::Seconds(any_webcontent_visible_seconds); - no_webcontent_visible_seconds = - webcontent_visible_tracker_.no_webview_tracked_duration_.InSeconds(); - webcontent_visible_tracker_.no_webview_tracked_duration_ -= - base::Seconds(no_webcontent_visible_seconds); - - total_webcontent_visible_seconds = - webcontent_visible_tracker_.per_webview_duration_.InSeconds(); - webcontent_visible_tracker_.per_webview_duration_ -= - base::Seconds(total_webcontent_visible_seconds); - total_not_webcontent_or_not_visible_seconds = - webcontent_visible_tracker_.per_webview_untracked_duration_.InSeconds(); - webcontent_visible_tracker_.per_webview_untracked_duration_ -= - base::Seconds(total_not_webcontent_or_not_visible_seconds); - - if (any_webcontent_visible_seconds) { - GetGlobalOpenWebVisibilityHistogram()->AddCount( - static_cast(WebViewOpenWebVisibility::kDisplayOpenWebContent), - any_webcontent_visible_seconds); - } - if (no_webcontent_visible_seconds) { - GetGlobalOpenWebVisibilityHistogram()->AddCount( - static_cast(WebViewOpenWebVisibility::kNotDisplayOpenWebContent), - no_webcontent_visible_seconds); - } - - if (total_webcontent_visible_seconds) { - GetPerWebViewOpenWebVisibilityHistogram()->AddCount( - static_cast(WebViewOpenWebVisibility::kDisplayOpenWebContent), - total_webcontent_visible_seconds); - } - if (total_not_webcontent_or_not_visible_seconds) { - GetPerWebViewOpenWebVisibilityHistogram()->AddCount( - static_cast(WebViewOpenWebVisibility::kNotDisplayOpenWebContent), - total_not_webcontent_or_not_visible_seconds); +void VisibilityMetricsLogger::RecordVisibleSchemeMetrics() { + for (size_t i = 0; i < base::size(per_scheme_trackers_); i++) { + Scheme scheme = static_cast(i); + auto& tracker = per_scheme_trackers_[i]; + + int32_t any_webview_seconds = + tracker.any_webview_tracked_duration_.InSeconds(); + if (any_webview_seconds) { + tracker.any_webview_tracked_duration_ -= + base::Seconds(any_webview_seconds); + LogGlobalVisibleScheme(scheme, any_webview_seconds); + } + + int32_t per_webview_seconds = tracker.per_webview_duration_.InSeconds(); + if (per_webview_seconds) { + tracker.per_webview_duration_ -= base::Seconds(per_webview_seconds); + LogPerWebViewVisibleScheme(scheme, per_webview_seconds); + } } } diff --git a/android_webview/browser/metrics/visibility_metrics_logger.h b/android_webview/browser/metrics/visibility_metrics_logger.h index 925e23739f6dbe..c0b9936157e9d2 100644 --- a/android_webview/browser/metrics/visibility_metrics_logger.h +++ b/android_webview/browser/metrics/visibility_metrics_logger.h @@ -6,6 +6,7 @@ #define ANDROID_WEBVIEW_BROWSER_METRICS_VISIBILITY_METRICS_LOGGER_H_ #include +#include #include "base/callback.h" #include "base/time/time.h" @@ -14,14 +15,35 @@ namespace android_webview { class VisibilityMetricsLogger { public: + // These values are persisted to logs and must match the WebViewUrlScheme enum + // defined in enums.xml. Entries should not be renumbered and numeric values + // should never be reused. + enum class Scheme { + kEmpty = 0, + kUnknown = 1, + kHttp = 2, + kHttps = 3, + kFile = 4, + kFtp = 5, + kData = 6, + kJavaScript = 7, + kAbout = 8, + kChrome = 9, + kBlob = 10, + kContent = 11, + kIntent = 12, + kMaxValue = kIntent, + }; + + static Scheme SchemeStringToEnum(const std::string& scheme); + struct VisibilityInfo { bool view_attached = false; bool view_visible = false; bool window_visible = false; - bool scheme_http_or_https = false; + Scheme scheme = Scheme::kEmpty; bool IsVisible() const; - bool ContainsOpenWebContent() const; bool IsDisplayingOpenWebContent() const; }; @@ -51,14 +73,6 @@ class VisibilityMetricsLogger { kMaxValue = kExactlyZeroPercent, }; - // These values are persisted to logs. Entries should not be renumbered and - // numeric values should never be reused. - enum class WebViewOpenWebVisibility { - kDisplayOpenWebContent = 0, - kNotDisplayOpenWebContent = 1, - kMaxValue = kNotDisplayOpenWebContent - }; - class Client { public: virtual VisibilityInfo GetVisibilityInfo() = 0; @@ -88,13 +102,14 @@ class VisibilityMetricsLogger { void UpdateDurations(); void ProcessClientUpdate(Client* client, const VisibilityInfo& info); void RecordVisibilityMetrics(); - void RecordOpenWebDisplayMetrics(); + void RecordVisibleSchemeMetrics(); void RecordScreenPortionMetrics(); - // Counter for visible clients - size_t visible_client_count_ = 0; - // Counter for visible web clients - size_t visible_webcontent_client_count_ = 0; + // Counts the number of visible clients. + size_t all_clients_visible_count_ = 0; + // Counts the number of visible clients per scheme. + size_t per_scheme_visible_counts_[static_cast(Scheme::kMaxValue) + + 1] = {}; struct WebViewDurationTracker { // Duration any WebView meets the tracking criteria @@ -108,8 +123,9 @@ class VisibilityMetricsLogger { base::TimeDelta per_webview_untracked_duration_ = base::Seconds(0); }; - WebViewDurationTracker visible_duration_tracker_; - WebViewDurationTracker webcontent_visible_tracker_; + WebViewDurationTracker all_clients_tracker_; + WebViewDurationTracker + per_scheme_trackers_[static_cast(Scheme::kMaxValue) + 1] = {}; base::TimeTicks last_update_time_; std::map client_visibility_; diff --git a/android_webview/browser/metrics/visibility_metrics_logger_unittest.cc b/android_webview/browser/metrics/visibility_metrics_logger_unittest.cc index b8f6c98215f876..259d37c8739e0b 100644 --- a/android_webview/browser/metrics/visibility_metrics_logger_unittest.cc +++ b/android_webview/browser/metrics/visibility_metrics_logger_unittest.cc @@ -40,8 +40,8 @@ class TestClient : public VisibilityMetricsLogger::Client { logger_->ClientVisibilityChanged(this); } - void SetSchemeHttpOrHttps(bool scheme_http_or_https) { - visibility_info_.scheme_http_or_https = scheme_http_or_https; + void SetScheme(VisibilityMetricsLogger::Scheme scheme) { + visibility_info_.scheme = scheme; logger_->ClientVisibilityChanged(this); } @@ -119,7 +119,7 @@ TEST_F(VisibilityMetricsLoggerTest, TestSingleVisibleClient) { client->SetViewVisible(true); client->SetViewAttached(true); client->SetWindowVisible(true); - client->SetSchemeHttpOrHttps(true); + client->SetScheme(VisibilityMetricsLogger::Scheme::kHttp); task_environment().FastForwardBy(base::Seconds(10)); client->SetWindowVisible(false); @@ -131,21 +131,16 @@ TEST_F(VisibilityMetricsLoggerTest, TestSingleVisibleClient) { histogram_tester.ExpectBucketCount( "Android.WebView.Visibility.Global", VisibilityMetricsLogger::Visibility::kNotVisible, 40); - - histogram_tester.ExpectBucketCount( - "Android.WebView.WebViewOpenWebVisible.Global", - VisibilityMetricsLogger::WebViewOpenWebVisibility::kDisplayOpenWebContent, - 10); - histogram_tester.ExpectBucketCount( - "Android.WebView.WebViewOpenWebVisible.Global", - VisibilityMetricsLogger::WebViewOpenWebVisibility:: - kNotDisplayOpenWebContent, - 40); + histogram_tester.ExpectBucketCount("Android.WebView.VisibleScheme.Global", + VisibilityMetricsLogger::Scheme::kHttp, + 10); + histogram_tester.ExpectBucketCount("Android.WebView.VisibleScheme.Global", + VisibilityMetricsLogger::Scheme::kData, 0); client->SetViewVisible(true); client->SetViewAttached(true); client->SetWindowVisible(true); - client->SetSchemeHttpOrHttps(false); + client->SetScheme(VisibilityMetricsLogger::Scheme::kData); task_environment().FastForwardBy(base::Seconds(90)); logger()->RecordMetrics(); @@ -155,15 +150,12 @@ TEST_F(VisibilityMetricsLoggerTest, TestSingleVisibleClient) { histogram_tester.ExpectBucketCount( "Android.WebView.Visibility.Global", VisibilityMetricsLogger::Visibility::kNotVisible, 40); - histogram_tester.ExpectBucketCount( - "Android.WebView.WebViewOpenWebVisible.Global", - VisibilityMetricsLogger::WebViewOpenWebVisibility::kDisplayOpenWebContent, - 10); - histogram_tester.ExpectBucketCount( - "Android.WebView.WebViewOpenWebVisible.Global", - VisibilityMetricsLogger::WebViewOpenWebVisibility:: - kNotDisplayOpenWebContent, - 130); + histogram_tester.ExpectBucketCount("Android.WebView.VisibleScheme.Global", + VisibilityMetricsLogger::Scheme::kHttp, + 10); + histogram_tester.ExpectBucketCount("Android.WebView.VisibleScheme.Global", + VisibilityMetricsLogger::Scheme::kData, + 90); client.reset(); } @@ -267,22 +259,22 @@ TEST_F(VisibilityMetricsLoggerTest, TestTwoVisibleClients) { TEST_F(VisibilityMetricsLoggerTest, TestTwoVisibleWebContentClients) { // t=0: client1 created // t=10: client2 created - // t=40: client1 visible, recording scheduled for t+60s - // t=50: client1 navigates to open web content - // t=60: client2 visible - // t=70: client1 invisible - // t=80: client2 invisible - // t=100: clients deleted. + // t=40: client1 visible with empty scheme + // t=50: client1 navigates to http scheme + // t=60: client2 visible and navigates to http scheme + // t=70: client2 invisible + // t=80: client1 invisible + // t=100: clients deleted - // Time with any client visible: 80 - 40 = 40 - // Time with no visible client: 100 - 40 = 60 - // Time x visible clients: (60-40) * 1 + (70-60) * 2 + (80-70) * 1 = 50 - // Time x hidden clients: 100 + 90 - 50 = 140 + // Any client visible: 40 + // No client visible: 60 + // Per client visible: 40 (client1) + 10 (client2) = 50 + // Per client existing but invisible: 100 (client1) + 90 (client2) - 50 = 140 - // Time with any client displaying open web content: 70 - 50 = 20 - // Time with no client displaying open web content: 100 - 20 = 80 - // Time x clients displaying open web content: (70-50) * 1 = 50 - // Time x clients not displaying open web content: 100 + 90 - 20 = 170 + // Any client visible with empty scheme: 10 + // Any client visible with http scheme: 30 + // Per client visible with empty scheme: 10 + // Per client visible with http scheme: 30 (client1) + 10 (client2) = 40 base::HistogramTester histogram_tester; std::unique_ptr client1 = std::make_unique(logger()); @@ -291,29 +283,23 @@ TEST_F(VisibilityMetricsLoggerTest, TestTwoVisibleWebContentClients) { std::unique_ptr client2 = std::make_unique(logger()); task_environment().FastForwardBy(base::Seconds(30)); - // This queues delayed recording after 60 seconds (test-defined) client1->SetViewVisible(true); client1->SetViewAttached(true); client1->SetWindowVisible(true); task_environment().FastForwardBy(base::Seconds(10)); - // No additional task is queued - client1->SetSchemeHttpOrHttps(true); + client1->SetScheme(VisibilityMetricsLogger::Scheme::kHttp); task_environment().FastForwardBy(base::Seconds(10)); - // No additional task is queued client2->SetViewVisible(true); client2->SetViewAttached(true); client2->SetWindowVisible(true); + client2->SetScheme(VisibilityMetricsLogger::Scheme::kHttp); task_environment().FastForwardBy(base::Seconds(10)); - // This does not cause metrics to be recorded because one client remains - // visible. client1->SetWindowVisible(false); task_environment().FastForwardBy(base::Seconds(10)); - // The last client becoming invisible triggers immediate recording and the - // cancellation of the queued task. client2->SetWindowVisible(false); task_environment().FastForwardBy(base::Seconds(20)); @@ -334,24 +320,18 @@ TEST_F(VisibilityMetricsLoggerTest, TestTwoVisibleWebContentClients) { "Android.WebView.Visibility.PerWebView", VisibilityMetricsLogger::Visibility::kNotVisible, 140); - histogram_tester.ExpectBucketCount( - "Android.WebView.WebViewOpenWebVisible.Global", - VisibilityMetricsLogger::WebViewOpenWebVisibility::kDisplayOpenWebContent, - 20); - histogram_tester.ExpectBucketCount( - "Android.WebView.WebViewOpenWebVisible.Global", - VisibilityMetricsLogger::WebViewOpenWebVisibility:: - kNotDisplayOpenWebContent, - 80); - histogram_tester.ExpectBucketCount( - "Android.WebView.WebViewOpenWebVisible.PerWebView", - VisibilityMetricsLogger::WebViewOpenWebVisibility::kDisplayOpenWebContent, - 20); - histogram_tester.ExpectBucketCount( - "Android.WebView.WebViewOpenWebVisible.PerWebView", - VisibilityMetricsLogger::WebViewOpenWebVisibility:: - kNotDisplayOpenWebContent, - 170); + histogram_tester.ExpectBucketCount("Android.WebView.VisibleScheme.Global", + VisibilityMetricsLogger::Scheme::kEmpty, + 10); + histogram_tester.ExpectBucketCount("Android.WebView.VisibleScheme.Global", + VisibilityMetricsLogger::Scheme::kHttp, + 30); + histogram_tester.ExpectBucketCount("Android.WebView.VisibleScheme.PerWebView", + VisibilityMetricsLogger::Scheme::kEmpty, + 10); + histogram_tester.ExpectBucketCount("Android.WebView.VisibleScheme.PerWebView", + VisibilityMetricsLogger::Scheme::kHttp, + 40); } TEST_F(VisibilityMetricsLoggerTest, TestScreenPortion) { @@ -383,7 +363,7 @@ TEST_F(VisibilityMetricsLoggerTest, TestScreenPortion) { client->SetViewVisible(true); client->SetViewAttached(true); client->SetWindowVisible(true); - client->SetSchemeHttpOrHttps(true); + client->SetScheme(VisibilityMetricsLogger::Scheme::kHttp); // If pixels is 0 then time spent is logged under kExactlyZeroPercent, // otherwise the screen portion is calculated as percentage / 10. logger()->UpdateOpenWebScreenArea(/*pixels=*/0, /*percentage=*/0); @@ -404,7 +384,7 @@ TEST_F(VisibilityMetricsLoggerTest, TestScreenPortion) { client->SetViewVisible(true); task_environment().FastForwardBy(base::Seconds(25)); - client->SetSchemeHttpOrHttps(false); + client->SetScheme(VisibilityMetricsLogger::Scheme::kData); task_environment().FastForwardBy(base::Seconds(5)); logger()->RecordMetrics(); @@ -416,10 +396,9 @@ TEST_F(VisibilityMetricsLoggerTest, TestScreenPortion) { histogram_tester.ExpectBucketCount( "Android.WebView.Visibility.Global", VisibilityMetricsLogger::Visibility::kVisible, 80); - histogram_tester.ExpectBucketCount( - "Android.WebView.WebViewOpenWebVisible.Global", - VisibilityMetricsLogger::WebViewOpenWebVisibility::kDisplayOpenWebContent, - 75); + histogram_tester.ExpectBucketCount("Android.WebView.VisibleScheme.Global", + VisibilityMetricsLogger::Scheme::kHttp, + 75); histogram_tester.ExpectBucketCount( "Android.WebView.WebViewOpenWebVisible.ScreenPortion2", VisibilityMetricsLogger::WebViewOpenWebScreenPortion::kExactlyZeroPercent, diff --git a/ash/app_list/views/apps_container_view.cc b/ash/app_list/views/apps_container_view.cc index 52e1102686bbda..8a65e8f1d3f50a 100644 --- a/ash/app_list/views/apps_container_view.cc +++ b/ash/app_list/views/apps_container_view.cc @@ -54,6 +54,7 @@ #include "ui/views/focus/focus_manager.h" #include "ui/views/layout/box_layout.h" #include "ui/views/layout/flex_layout.h" +#include "ui/views/view_class_properties.h" #include "ui/views/view_utils.h" namespace ash { @@ -131,6 +132,9 @@ constexpr int kSortUiControlPreferredSize = 20; // The number of columns available for the ContinueSectionView. constexpr int kContinueColumnCount = 4; +// The vertical spacing between recent apps and continue section view. +constexpr int kRecentAppsTopMargin = 16; + // The vertical spacing above and below the separator. constexpr int kSeparatorVerticalInset = 16; @@ -261,6 +265,7 @@ class AppsContainerView::ContinueContainer : public views::View { ContinueContainer(AppsContainerView* apps_container, AppListViewDelegate* view_delegate) { SetPaintToLayer(ui::LAYER_NOT_DRAWN); + SetLayoutManager(std::make_unique()) ->SetOrientation(views::LayoutOrientation::kVertical); @@ -278,16 +283,16 @@ class AppsContainerView::ContinueContainer : public views::View { separator_ = AddChildView(std::make_unique()); separator_->SetColor(ColorProvider::Get()->GetContentLayerColor( ColorProvider::ContentLayerType::kSeparatorColor)); - separator_->SetBorder( - views::CreateEmptyBorder(gfx::Insets(kSeparatorVerticalInset, 0))); separator_->SetPreferredSize( - gfx::Size(kSeparatorWidth, - kSeparatorVerticalInset * 2 + views::Separator::kThickness)); + gfx::Size(kSeparatorWidth, views::Separator::kThickness)); + separator_->SetProperty(views::kMarginsKey, + gfx::Insets(kSeparatorVerticalInset, 0)); separator_->SetPaintToLayer(); separator_->layer()->SetFillsBoundsOpaquely(false); separator_->SetProperty(views::kCrossAxisAlignmentKey, views::LayoutAlignment::kCenter); + UpdateRecentAppsMargins(); UpdateSeparatorVisibility(); } @@ -295,6 +300,9 @@ class AppsContainerView::ContinueContainer : public views::View { void ChildVisibilityChanged(views::View* child) override { if (child == recent_apps_ || child == continue_section_) UpdateSeparatorVisibility(); + + if (child == continue_section_) + UpdateRecentAppsMargins(); } void OnThemeChanged() override { @@ -308,6 +316,16 @@ class AppsContainerView::ContinueContainer : public views::View { views::View* separator() { return separator_; } private: + void UpdateRecentAppsMargins() { + if (!recent_apps_ || !continue_section_) + return; + // Remove recent apps top margin if continue section is hidden. + recent_apps_->SetProperty( + views::kMarginsKey, + gfx::Insets(continue_section_->GetVisible() ? kRecentAppsTopMargin : 0, + 0, 0, 0)); + } + void UpdateSeparatorVisibility() { if (!separator_ || !recent_apps_ || !continue_section_) return; @@ -809,22 +827,24 @@ void AppsContainerView::Layout() { GetContentsBounds(), contents_view_->GetSearchBoxSize(AppListState::kStateApps)); gfx::Rect grid_rect = rect; - grid_rect.Inset(margins.left(), kGridFadeoutZoneHeight - grid_insets.top(), - margins.right(), margins.bottom()); + grid_rect.Inset(margins.left(), kGridFadeoutZoneHeight, margins.right(), + margins.bottom()); // The grid rect insets are added to calculated margins. Given that the // grid bounds rect should include insets, they have to be removed from // added margins. - grid_rect.Inset(-grid_insets.left(), 0, -grid_insets.right(), - -grid_insets.bottom()); + grid_rect.Inset(-grid_insets); scrollable_container_->SetBoundsRect(grid_rect); + bool first_page_offset_changed = false; if (features::IsProductivityLauncherEnabled()) { + const int continue_container_height = + continue_container_->GetPreferredSize().height(); continue_container_->SetBoundsRect( - gfx::Rect(0, 0, grid_rect.width(), - continue_container_->GetPreferredSize().height())); + gfx::Rect(0, 0, grid_rect.width(), continue_container_height)); // Setting this offset prevents the app items in the grid from overlapping // with the continue section. - apps_grid_view_->set_first_page_offset( - continue_container_->bounds().height()); + first_page_offset_changed = + continue_container_height != apps_grid_view_->first_page_offset(); + apps_grid_view_->set_first_page_offset(continue_container_height); } // Make sure that UpdateTopLevelGridDimensions() happens after setting the @@ -832,8 +852,15 @@ void AppsContainerView::Layout() { // shown in the grid. UpdateTopLevelGridDimensions(); - apps_grid_view_->SetBoundsRect( - gfx::Rect(0, 0, grid_rect.width(), grid_rect.height())); + const gfx::Rect apps_grid_bounds(grid_rect.size()); + if (apps_grid_view_->bounds() != apps_grid_bounds) { + apps_grid_view_->SetBoundsRect(apps_grid_bounds); + } else if (first_page_offset_changed) { + // Apps grid layout depends on the continue container bounds, so explicitly + // call layout to ensure apps grid view gets laid out even if its bounds do + // not change. + apps_grid_view_->Layout(); + } // Record the distance of y position between suggestion chip container // and apps grid view to avoid duplicate calculation of apps grid view's @@ -1036,11 +1063,12 @@ int AppsContainerView::GetMinTopMarginForAppsGrid( const int suggestion_chip_container_size = features::IsProductivityLauncherEnabled() ? 0 - : kSuggestionChipContainerHeight; + : kSuggestionChipContainerHeight + kSuggestionChipContainerTopMargin; + // NOTE: Use the fadeout zone height as min top margin to match the apps grid // view's bottom margin. return search_box_size.height() + kGridFadeoutZoneHeight + - kSuggestionChipContainerTopMargin + suggestion_chip_container_size; + suggestion_chip_container_size; } int AppsContainerView::GetIdealHorizontalMargin() const { diff --git a/ash/app_list/views/continue_section_view.cc b/ash/app_list/views/continue_section_view.cc index a4dcf4f272938e..478e65aa24c32e 100644 --- a/ash/app_list/views/continue_section_view.cc +++ b/ash/app_list/views/continue_section_view.cc @@ -31,11 +31,6 @@ namespace ash { namespace { -// Continue File Section view paddings. This view encloses the header and the -// suggested tasks container. -constexpr int kSectionVerticalPadding = 16; -constexpr int kSectionHorizontalPadding = 20; - // Header paddings in dips. constexpr int kHeaderVerticalSpacing = 4; constexpr int kHeaderHorizontalPadding = 12; @@ -61,8 +56,7 @@ ContinueSectionView::ContinueSectionView(AppListViewDelegate* view_delegate, AppListModelProvider::Get()->AddObserver(this); auto* layout = SetLayoutManager(std::make_unique( - views::BoxLayout::Orientation::kVertical, - gfx::Insets(kSectionVerticalPadding, kSectionHorizontalPadding), + views::BoxLayout::Orientation::kVertical, gfx::Insets(), kHeaderVerticalSpacing)); layout->set_main_axis_alignment( tablet_mode ? views::BoxLayout::MainAxisAlignment::kCenter diff --git a/ash/app_list/views/continue_section_view_unittest.cc b/ash/app_list/views/continue_section_view_unittest.cc index 0d4381f3466150..ff50472ae3ffbe 100644 --- a/ash/app_list/views/continue_section_view_unittest.cc +++ b/ash/app_list/views/continue_section_view_unittest.cc @@ -652,7 +652,7 @@ TEST_F(ContinueSectionViewTabletModeTest, // Set the display width so only 2 continue section tasks fit into available // space. - UpdateDisplay("800x600"); + UpdateDisplay("600x800"); EnsureLauncherShown(); VerifyResultViewsUpdated(); diff --git a/ash/app_list/views/paged_apps_grid_view.cc b/ash/app_list/views/paged_apps_grid_view.cc index c8ddb6ec491da1..5c59683d5e8a84 100644 --- a/ash/app_list/views/paged_apps_grid_view.cc +++ b/ash/app_list/views/paged_apps_grid_view.cc @@ -589,7 +589,8 @@ void PagedAppsGridView::UpdateBorder() { if (IsInFolder()) return; - SetBorder(views::CreateEmptyBorder(gfx::Insets(GetFadeoutMaskHeight(), 0))); + if (!features::IsProductivityLauncherEnabled()) + SetBorder(views::CreateEmptyBorder(gfx::Insets(GetFadeoutMaskHeight(), 0))); } void PagedAppsGridView::MaybeStartCardifiedView() { @@ -1221,8 +1222,9 @@ gfx::Rect PagedAppsGridView::BackgroundCardBounds(int new_page_index) { ? first_page_vertical_tile_padding_ : vertical_tile_padding_; const gfx::Size background_card_size = - grid_size + - gfx::Size(2 * horizontal_tile_padding_, 2 * vertical_tile_padding); + grid_size + gfx::Size(2 * horizontal_tile_padding_, + 2 * std::max(kMinVerticalPaddingBetweenTiles, + vertical_tile_padding)); // Add a padding on the sides to make space for pagination preview, but make // sure the padding doesn't exceed the tile padding (otherwise the background @@ -1233,13 +1235,13 @@ gfx::Rect PagedAppsGridView::BackgroundCardBounds(int new_page_index) { (GetContentsBounds().width() - background_card_size.width()) / 2 + extra_padding_for_cardified_state; - const int y_offset = (new_page_index == 0 ? first_page_offset_ : 0); + int y_offset = std::max(new_page_index == 0 ? first_page_offset_ : 0, + GetFadeoutMaskHeight()); // The vertical padding should account for the fadeout mask. - const int vertical_padding = y_offset + - (GetContentsBounds().height() - y_offset - - background_card_size.height()) / - 2 + - GetFadeoutMaskHeight(); + const int vertical_padding = + y_offset + (GetContentsBounds().height() - y_offset - + background_card_size.height()) / + 2; const int padding_between_pages = GetPaddingBetweenPages(); // The space that each page occupies in the items container. This is the size // of the grid without outer padding plus the padding between pages. @@ -1314,6 +1316,10 @@ void PagedAppsGridView::UpdateTilePadding() { if (cardified_state_) { content_size = gfx::ScaleToRoundedSize(content_size, kCardifiedScale) - gfx::Size(2 * kCardifiedHorizontalPadding, 0); + content_size.set_width( + std::max(content_size.width(), cols() * tile_size.width())); + content_size.set_height( + std::max(content_size.height(), max_rows_ * tile_size.height())); } const auto calculate_tile_padding = [](int content_size, int num_tiles, diff --git a/ash/app_list/views/paged_apps_grid_view.h b/ash/app_list/views/paged_apps_grid_view.h index 8796b500e117ed..bd9c03093e2dbc 100644 --- a/ash/app_list/views/paged_apps_grid_view.h +++ b/ash/app_list/views/paged_apps_grid_view.h @@ -150,6 +150,7 @@ class ASH_EXPORT PagedAppsGridView : public AppsGridView, // Gets the PaginationModel used for the grid view. PaginationModel* pagination_model() { return &pagination_model_; } + int first_page_offset() const { return first_page_offset_; } void set_first_page_offset(int offset) { first_page_offset_ = offset; } // Calculates the maximum number of rows on the first page. Relies on tile diff --git a/ash/login/ui/fingerprint_auth_factor_model.cc b/ash/login/ui/fingerprint_auth_factor_model.cc index f668717185f312..b31ecb858695e2 100644 --- a/ash/login/ui/fingerprint_auth_factor_model.cc +++ b/ash/login/ui/fingerprint_auth_factor_model.cc @@ -93,9 +93,7 @@ int FingerprintAuthFactorModel::GetLabelId() const { case FingerprintState::AVAILABLE_WITH_TOUCH_SENSOR_WARNING: return IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_TOUCH_SENSOR; case FingerprintState::DISABLED_FROM_ATTEMPTS: - // TODO(crbug.com/1233614): Update this string: "Too many attempts" -> - // "Too many fingerprint attempts". - return IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_DISABLED_FROM_ATTEMPTS; + return IDS_FINGERPRINT_LABEL_NOT_AUTHENTICATED; case FingerprintState::DISABLED_FROM_TIMEOUT: return can_use_pin_ ? IDS_AUTH_FACTOR_LABEL_PASSWORD_OR_PIN_REQUIRED : IDS_AUTH_FACTOR_LABEL_PASSWORD_REQUIRED; diff --git a/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc b/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc index e45a0e57463fe9..5d67876eeeec24 100644 --- a/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc +++ b/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc @@ -8,6 +8,7 @@ #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" #include "base/metrics/sparse_histogram.h" + namespace { const char kEngagementFlowInitialMetric[] = @@ -19,6 +20,11 @@ const char kTotalUxPairTimeInitialMetric[] = "Bluetooth.ChromeOS.FastPair.TotalUxPairTime.InitialPairingProtocol"; const char kTotalUxPairTimeSubsequentMetric[] = "Bluetooth.ChromeOS.FastPair.TotalUxPairTime.SubsequentPairingProtocol"; +const char kRetroactiveEngagementFlowMetric[] = + "Bluetooth.ChromeOS.FastPair.RetroactiveEngagementFunnel.Steps"; +const char kPairingMethodMetric[] = "Bluetooth.ChromeOS.FastPair.PairingMethod"; +const char kRetroactivePairingResultMetric[] = + "Bluetooth.ChromeOS.FastPair.RetroactivePairing.Result"; } // namespace @@ -56,5 +62,27 @@ void AttemptRecordingTotalUxPairTime(const Device& device, } } +void AttemptRecordingFastPairRetroactiveEngagementFlow( + const Device& device, + FastPairRetroactiveEngagementFlowEvent event) { + switch (device.protocol) { + case Protocol::kFastPairInitial: + case Protocol::kFastPairSubsequent: + break; + case Protocol::kFastPairRetroactive: + base::UmaHistogramSparse(kRetroactiveEngagementFlowMetric, + static_cast(event)); + break; + } +} + +void RecordPairingMethod(PairingMethod method) { + base::UmaHistogramEnumeration(kPairingMethodMetric, method); +} + +void RecordRetroactivePairingResult(bool success) { + base::UmaHistogramBoolean(kRetroactivePairingResultMetric, success); +} + } // namespace quick_pair } // namespace ash diff --git a/ash/quick_pair/common/fast_pair/fast_pair_metrics.h b/ash/quick_pair/common/fast_pair/fast_pair_metrics.h index b0ead28a5742c6..f59ab1ce612594 100644 --- a/ash/quick_pair/common/fast_pair/fast_pair_metrics.h +++ b/ash/quick_pair/common/fast_pair/fast_pair_metrics.h @@ -27,6 +27,33 @@ enum COMPONENT_EXPORT(QUICK_PAIR_COMMON) FastPairEngagementFlowEvent { kErrorUiSettingsPressed = 1212, }; +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. The numbers here correspond to the +// ordering of the flow. This enum should be kept in sync with the +// FastPairRetroactiveEngagementFlowEvent enum in +// src/tools/metrics/histograms/enums.xml. +enum COMPONENT_EXPORT(QUICK_PAIR_COMMON) + FastPairRetroactiveEngagementFlowEvent { + kAssociateAccountUiShown = 1, + kAssociateAccountUiDismissedByUser = 11, + kAssociateAccountUiDismissed = 12, + kAssociateAccountLearnMorePressed = 13, + kAssociateAccountSavePressed = 14, + kAssociateAccountSavePressedAfterLearnMorePressed = 131, + kAssociateAccountDismissedByUserAfterLearnMorePressed = 132, + kAssociateAccountDismissedAfterLearnMorePressed = 133, + }; + +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. This enum should be kept in sync +// with the FastPairPairingMethod enum in +// src/tools/metrics/histograms/enums.xml. +enum class COMPONENT_EXPORT(QUICK_PAIR_COMMON) PairingMethod { + kFastPair = 0, + kSystemPairingUi = 1, + kMaxValue = kSystemPairingUi, +}; + COMPONENT_EXPORT(QUICK_PAIR_COMMON) void AttemptRecordingFastPairEngagementFlow(const Device& device, FastPairEngagementFlowEvent event); @@ -35,6 +62,17 @@ COMPONENT_EXPORT(QUICK_PAIR_COMMON) void AttemptRecordingTotalUxPairTime(const Device& device, base::TimeDelta total_pair_time); +COMPONENT_EXPORT(QUICK_PAIR_COMMON) +void AttemptRecordingFastPairRetroactiveEngagementFlow( + const Device& device, + FastPairRetroactiveEngagementFlowEvent event); + +COMPONENT_EXPORT(QUICK_PAIR_COMMON) +void RecordPairingMethod(PairingMethod method); + +COMPONENT_EXPORT(QUICK_PAIR_COMMON) +void RecordRetroactivePairingResult(bool success); + } // namespace quick_pair } // namespace ash diff --git a/ash/quick_pair/keyed_service/quick_pair_mediator.cc b/ash/quick_pair/keyed_service/quick_pair_mediator.cc index 519f3136e63ea8..f6cf807569b1b3 100644 --- a/ash/quick_pair/keyed_service/quick_pair_mediator.cc +++ b/ash/quick_pair/keyed_service/quick_pair_mediator.cc @@ -84,7 +84,8 @@ Mediator::Mediator( fast_pair_bluetooth_config_delegate_( std::make_unique()) { metrics_logger_ = std::make_unique( - scanner_broker_.get(), pairer_broker_.get(), ui_broker_.get()); + scanner_broker_.get(), pairer_broker_.get(), ui_broker_.get(), + retroactive_pairing_detector_.get()); battery_update_message_handler_ = std::make_unique( message_stream_lookup_.get()); diff --git a/ash/quick_pair/keyed_service/quick_pair_metrics_logger.cc b/ash/quick_pair/keyed_service/quick_pair_metrics_logger.cc index 736742ee3a550c..8eb0540ea23637 100644 --- a/ash/quick_pair/keyed_service/quick_pair_metrics_logger.cc +++ b/ash/quick_pair/keyed_service/quick_pair_metrics_logger.cc @@ -7,29 +7,77 @@ #include "ash/quick_pair/common/device.h" #include "ash/quick_pair/common/fast_pair/fast_pair_feature_usage_metrics_logger.h" #include "ash/quick_pair/common/fast_pair/fast_pair_metrics.h" +#include "base/containers/contains.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" namespace ash { namespace quick_pair { -QuickPairMetricsLogger::QuickPairMetricsLogger(ScannerBroker* scanner_broker, - PairerBroker* pairer_broker, - UIBroker* ui_broker) +QuickPairMetricsLogger::QuickPairMetricsLogger( + ScannerBroker* scanner_broker, + PairerBroker* pairer_broker, + UIBroker* ui_broker, + RetroactivePairingDetector* retroactive_pairing_detector) : feature_usage_metrics_logger_( std::make_unique()) { + device::BluetoothAdapterFactory::Get()->GetAdapter(base::BindOnce( + &QuickPairMetricsLogger::OnGetAdapter, weak_ptr_factory_.GetWeakPtr())); + scanner_broker_observation_.Observe(scanner_broker); + retroactive_pairing_detector_observation_.Observe( + retroactive_pairing_detector); pairer_broker_observation_.Observe(pairer_broker); ui_broker_observation_.Observe(ui_broker); } QuickPairMetricsLogger::~QuickPairMetricsLogger() = default; +void QuickPairMetricsLogger::OnGetAdapter( + scoped_refptr adapter) { + adapter_ = adapter; + adapter_observation_.Observe(adapter_.get()); +} + +void QuickPairMetricsLogger::DevicePairedChanged( + device::BluetoothAdapter* adapter, + device::BluetoothDevice* device, + bool new_paired_status) { + // This event fires whenever a device pairing has changed with the adapter. + // If the |new_paired_status| is false, it means a device was unpaired with + // the adapter, so we early return since it would not be a device that has + // been paired alternatively. If the device that was paired to that fires this + // event is a device we just paired to with Fast Pair, then we early return + // since it also wouldn't be one that was alternatively pair to. We want to + // only continue our check here if we have a newly paired device that was + // paired with classic Bluetooth pairing. + const std::string& classic_address = device->GetAddress(); + if (!new_paired_status || + base::Contains(fast_pair_addresses_, classic_address)) { + return; + } + + RecordPairingMethod(PairingMethod::kSystemPairingUi); +} + void QuickPairMetricsLogger::OnDevicePaired(scoped_refptr device) { AttemptRecordingFastPairEngagementFlow( *device, FastPairEngagementFlowEvent::kPairingSucceeded); feature_usage_metrics_logger_->RecordUsage(/*success=*/true); + base::TimeDelta total_pair_time = base::TimeTicks::Now() - device_pairing_start_timestamps_[device]; AttemptRecordingTotalUxPairTime(*device, total_pair_time); + + RecordPairingMethod(PairingMethod::kFastPair); + + // The classic address is assigned to the Device during the + // initial Fast Pair pairing protocol during the key exchange, and if it + // doesn't exist, then it wasn't properly paired during initial Fast Pair + // pairing. We want to save the addresses here in the event that the + // Bluetooth adapter pairing event fires, so we can detect when a device + // was paired solely via classic bluetooth, instead of Fast Pair. + if (device->classic_address()) + fast_pair_addresses_.insert(device->classic_address().value()); } void QuickPairMetricsLogger::OnPairFailure(scoped_refptr device, @@ -82,17 +130,85 @@ void QuickPairMetricsLogger::OnDeviceFound(scoped_refptr device) { *device, FastPairEngagementFlowEvent::kDiscoveryUiShown); } +void QuickPairMetricsLogger::OnRetroactivePairFound( + scoped_refptr device) { + AttemptRecordingFastPairRetroactiveEngagementFlow( + *device, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiShown); +} + +void QuickPairMetricsLogger::OnAssociateAccountAction( + scoped_refptr device, + AssociateAccountAction action) { + switch (action) { + case AssociateAccountAction::kAssoicateAccount: + if (base::Contains(learn_more_devices_, device)) { + AttemptRecordingFastPairRetroactiveEngagementFlow( + *device, FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountSavePressedAfterLearnMorePressed); + learn_more_devices_.erase(device); + break; + } + + AttemptRecordingFastPairRetroactiveEngagementFlow( + *device, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountSavePressed); + break; + case AssociateAccountAction::kLearnMore: + // We need to record whether or not the Associate Account UI for this + // device has had the Learn More button pressed because since the + // Learn More button is not a terminal state, we need to record + // if the subsequent terminal states were reached after the user + // has learned more about saving their accounts. So we will check + // this map when the user dismisses or saves their account in order + // to capture whether or not the user elected to learn more beforehand. + learn_more_devices_.insert(device); + + AttemptRecordingFastPairRetroactiveEngagementFlow( + *device, FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountLearnMorePressed); + break; + case AssociateAccountAction::kDismissedByUser: + if (base::Contains(learn_more_devices_, device)) { + AttemptRecordingFastPairRetroactiveEngagementFlow( + *device, FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountDismissedByUserAfterLearnMorePressed); + learn_more_devices_.erase(device); + break; + } + + AttemptRecordingFastPairRetroactiveEngagementFlow( + *device, FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountUiDismissedByUser); + break; + case AssociateAccountAction::kDismissed: + if (base::Contains(learn_more_devices_, device)) { + AttemptRecordingFastPairRetroactiveEngagementFlow( + *device, FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountDismissedAfterLearnMorePressed); + learn_more_devices_.erase(device); + break; + } + + AttemptRecordingFastPairRetroactiveEngagementFlow( + *device, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiDismissed); + break; + } +} + void QuickPairMetricsLogger::OnAccountKeyWrite( scoped_refptr device, - absl::optional error) {} + absl::optional error) { + if (device->protocol != Protocol::kFastPairRetroactive) + return; + + RecordRetroactivePairingResult(/*success=*/!error.has_value()); +} void QuickPairMetricsLogger::OnCompanionAppAction(scoped_refptr device, CompanionAppAction action) {} -void QuickPairMetricsLogger::OnAssociateAccountAction( - scoped_refptr device, - AssociateAccountAction action) {} - void QuickPairMetricsLogger::OnDeviceLost(scoped_refptr device) {} } // namespace quick_pair diff --git a/ash/quick_pair/keyed_service/quick_pair_metrics_logger.h b/ash/quick_pair/keyed_service/quick_pair_metrics_logger.h index c7f28954668e42..e0f6ea588db80a 100644 --- a/ash/quick_pair/keyed_service/quick_pair_metrics_logger.h +++ b/ash/quick_pair/keyed_service/quick_pair_metrics_logger.h @@ -6,12 +6,16 @@ #define ASH_QUICK_PAIR_KEYED_SERVICE_QUICK_PAIR_METRICS_LOGGER_H_ #include "ash/quick_pair/pairing/pairer_broker.h" +#include "ash/quick_pair/pairing/retroactive_pairing_detector.h" #include "ash/quick_pair/scanning/scanner_broker.h" #include "ash/quick_pair/ui/ui_broker.h" #include "base/containers/flat_map.h" +#include "base/containers/flat_set.h" #include "base/memory/scoped_refptr.h" +#include "base/memory/weak_ptr.h" #include "base/scoped_observation.h" #include "base/time/time.h" +#include "device/bluetooth/bluetooth_adapter.h" namespace ash { namespace quick_pair { @@ -22,11 +26,15 @@ class FastPairFeatureUsageMetricsLogger; // Observes pairing, scanning and UI events and logs corresponding metrics. class QuickPairMetricsLogger : public PairerBroker::Observer, public ScannerBroker::Observer, - public UIBroker::Observer { + public UIBroker::Observer, + public RetroactivePairingDetector::Observer, + public device::BluetoothAdapter::Observer { public: - QuickPairMetricsLogger(ScannerBroker* scanner_broker, - PairerBroker* pairer_broker, - UIBroker* ui_broker); + QuickPairMetricsLogger( + ScannerBroker* scanner_broker, + PairerBroker* pairer_broker, + UIBroker* ui_broker, + RetroactivePairingDetector* retroactive_pairing_detector); QuickPairMetricsLogger(const QuickPairMetricsLogger&) = delete; QuickPairMetricsLogger& operator=(const QuickPairMetricsLogger&) = delete; ~QuickPairMetricsLogger() override; @@ -53,20 +61,52 @@ class QuickPairMetricsLogger : public PairerBroker::Observer, void OnDeviceFound(scoped_refptr device) override; void OnDeviceLost(scoped_refptr device) override; + // RetroactivePairingDetector::Observer + void OnRetroactivePairFound(scoped_refptr device) override; + + // device::BluetoothAdapter::Observer + void DevicePairedChanged(device::BluetoothAdapter* adapter, + device::BluetoothDevice* device, + bool new_paired_status) override; + + // Internal method called by BluetoothAdapterFactory to provide the adapter + // object. + void OnGetAdapter(scoped_refptr adapter); + // Map of devices to the time at which a pairing was initiated. This is used // to calculate the time between the user electing to pair the device and // the pairing entering a terminal state (success or failure). base::flat_map, base::TimeTicks> device_pairing_start_timestamps_; + // Set of devices of which on the Associate Account UI shown, the Learn More + // button was pressed. We need this map to know which + // |FastPairRetroactiveEngagementFlowEvent| event to log at the subsequent + // AssociateAccount action events that will follow, since the LearnMore + // event is not a terminal state. + base::flat_set> learn_more_devices_; + + // The classic pairing addresses of Fast Pair devices that we have already + // paired to. + base::flat_set fast_pair_addresses_; + + scoped_refptr adapter_; std::unique_ptr feature_usage_metrics_logger_; + + base::ScopedObservation + adapter_observation_{this}; base::ScopedObservation scanner_broker_observation_{this}; base::ScopedObservation pairer_broker_observation_{this}; + base::ScopedObservation + retroactive_pairing_detector_observation_{this}; base::ScopedObservation ui_broker_observation_{ this}; + base::WeakPtrFactory weak_ptr_factory_{this}; }; } // namespace quick_pair diff --git a/ash/quick_pair/keyed_service/quick_pair_metrics_logger_unittest.cc b/ash/quick_pair/keyed_service/quick_pair_metrics_logger_unittest.cc index 9c4735a6bda6ea..57cf0e2b05b278 100644 --- a/ash/quick_pair/keyed_service/quick_pair_metrics_logger_unittest.cc +++ b/ash/quick_pair/keyed_service/quick_pair_metrics_logger_unittest.cc @@ -6,13 +6,17 @@ #include +#include "ash/quick_pair/common/account_key_failure.h" +#include "ash/quick_pair/common/constants.h" #include "ash/quick_pair/common/device.h" #include "ash/quick_pair/common/fast_pair/fast_pair_metrics.h" #include "ash/quick_pair/common/logging.h" #include "ash/quick_pair/common/pair_failure.h" #include "ash/quick_pair/common/protocol.h" +#include "ash/quick_pair/pairing/fake_retroactive_pairing_detector.h" #include "ash/quick_pair/pairing/mock_pairer_broker.h" #include "ash/quick_pair/pairing/pairer_broker.h" +#include "ash/quick_pair/pairing/retroactive_pairing_detector.h" #include "ash/quick_pair/scanning/mock_scanner_broker.h" #include "ash/quick_pair/scanning/scanner_broker.h" #include "ash/quick_pair/ui/mock_ui_broker.h" @@ -22,6 +26,9 @@ #include "base/test/metrics/histogram_tester.h" #include "base/test/task_environment.h" #include "base/time/time.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" +#include "device/bluetooth/test/mock_bluetooth_adapter.h" +#include "device/bluetooth/test/mock_bluetooth_device.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/abseil-cpp/absl/types/optional.h" @@ -34,10 +41,48 @@ constexpr char kFastPairEngagementFlowMetricInitial[] = constexpr char kFastPairEngagementFlowMetricSubsequent[] = "Bluetooth.ChromeOS.FastPair.EngagementFunnel.Steps." "SubsequentPairingProtocol"; +const char kFastPairRetroactiveEngagementFlowMetric[] = + "Bluetooth.ChromeOS.FastPair.RetroactiveEngagementFunnel.Steps"; constexpr char kFastPairPairTimeMetricInitial[] = "Bluetooth.ChromeOS.FastPair.TotalUxPairTime.InitialPairingProtocol"; constexpr char kFastPairPairTimeMetricSubsequent[] = "Bluetooth.ChromeOS.FastPair.TotalUxPairTime.SubsequentPairingProtocol"; +const char kPairingMethodMetric[] = "Bluetooth.ChromeOS.FastPair.PairingMethod"; +const char kRetroactivePairingResultMetric[] = + "Bluetooth.ChromeOS.FastPair.RetroactivePairing.Result"; + +constexpr char kTestDeviceAddress[] = "11:12:13:14:15:16"; +constexpr char kTestBleDeviceName[] = "Test Device Name"; +constexpr char kValidModelId[] = "718c17"; + +std::unique_ptr> +CreateTestBluetoothDevice(std::string address) { + return std::make_unique>( + /*adapter=*/nullptr, /*bluetooth_class=*/0, kTestBleDeviceName, address, + /*paired=*/true, /*connected=*/false); +} + +class FakeMetricBluetoothAdapter + : public testing::NiceMock { + public: + device::BluetoothDevice* GetDevice(const std::string& address) override { + for (const auto& it : mock_devices_) { + if (it->GetAddress() == address) + return it.get(); + } + + return nullptr; + } + + void NotifyDevicePairedChanged(device::BluetoothDevice* device, + bool new_paired_status) { + device::BluetoothAdapter::NotifyDevicePairedChanged(device, + new_paired_status); + } + + private: + ~FakeMetricBluetoothAdapter() = default; +}; } // namespace @@ -47,10 +92,19 @@ namespace quick_pair { class QuickPairMetricsLoggerTest : public testing::Test { public: void SetUp() override { + adapter_ = base::MakeRefCounted(); + device::BluetoothAdapterFactory::SetAdapterForTesting(adapter_); + scanner_broker_ = std::make_unique(); mock_scanner_broker_ = static_cast(scanner_broker_.get()); + retroactive_pairing_detector_ = + std::make_unique(); + fake_retroactive_pairing_detector_ = + static_cast( + retroactive_pairing_detector_.get()); + pairer_broker_ = std::make_unique(); mock_pairer_broker_ = static_cast(pairer_broker_.get()); @@ -61,9 +115,12 @@ class QuickPairMetricsLoggerTest : public testing::Test { kTestMetadataId, kTestAddress, Protocol::kFastPairInitial); subsequent_device_ = base::MakeRefCounted( kTestMetadataId, kTestAddress, Protocol::kFastPairSubsequent); + retroactive_device_ = base::MakeRefCounted( + kTestMetadataId, kTestAddress, Protocol::kFastPairRetroactive); metrics_logger_ = std::make_unique( - scanner_broker_.get(), pairer_broker_.get(), ui_broker_.get()); + scanner_broker_.get(), pairer_broker_.get(), ui_broker_.get(), + retroactive_pairing_detector_.get()); } void SimulateDiscoveryUiShown(Protocol protocol) { @@ -129,9 +186,11 @@ class QuickPairMetricsLoggerTest : public testing::Test { void SimulatePairingSucceeded(Protocol protocol) { switch (protocol) { case Protocol::kFastPairInitial: + initial_device_->set_classic_address(kTestAddress); mock_pairer_broker_->NotifyDevicePaired(initial_device_); break; case Protocol::kFastPairSubsequent: + subsequent_device_->set_classic_address(kTestAddress); mock_pairer_broker_->NotifyDevicePaired(subsequent_device_); break; case Protocol::kFastPairRetroactive: @@ -169,19 +228,96 @@ class QuickPairMetricsLoggerTest : public testing::Test { } } + void SimulateAssociateAccountUiShown() { + fake_retroactive_pairing_detector_->NotifyRetroactivePairFound( + retroactive_device_); + } + + void SimulateAssociateAccountUiDismissed() { + mock_ui_broker_->NotifyAssociateAccountAction( + retroactive_device_, AssociateAccountAction::kDismissed); + } + + void SimulateAssociateAccountUiDismissedByUser() { + mock_ui_broker_->NotifyAssociateAccountAction( + retroactive_device_, AssociateAccountAction::kDismissedByUser); + } + + void SimulateAssociateAccountUiSavePressed() { + mock_ui_broker_->NotifyAssociateAccountAction( + retroactive_device_, AssociateAccountAction::kAssoicateAccount); + } + + void SimulateAssociateAccountUiLearnMorePressed() { + mock_ui_broker_->NotifyAssociateAccountAction( + retroactive_device_, AssociateAccountAction::kLearnMore); + } + + void SimulateAccountKeyWritten(Protocol protocol) { + switch (protocol) { + case Protocol::kFastPairInitial: + mock_pairer_broker_->NotifyAccountKeyWrite(initial_device_, + absl::nullopt); + break; + case Protocol::kFastPairSubsequent: + break; + case Protocol::kFastPairRetroactive: + mock_pairer_broker_->NotifyAccountKeyWrite(retroactive_device_, + absl::nullopt); + break; + } + } + + void SimulateAccountKeyFailure(Protocol protocol) { + switch (protocol) { + case Protocol::kFastPairInitial: + mock_pairer_broker_->NotifyAccountKeyWrite( + initial_device_, AccountKeyFailure::kAccountKeyCharacteristicWrite); + break; + case Protocol::kFastPairSubsequent: + break; + case Protocol::kFastPairRetroactive: + mock_pairer_broker_->NotifyAccountKeyWrite( + retroactive_device_, + AccountKeyFailure::kAccountKeyCharacteristicWrite); + break; + } + } + + void PairFastPairDeviceWithFastPair(std::string address) { + auto fp_device = base::MakeRefCounted(kValidModelId, address, + Protocol::kFastPairInitial); + fp_device->set_classic_address(address); + mock_pairer_broker_->NotifyDevicePaired(fp_device); + } + + void PairFastPairDeviceWithClassicBluetooth(bool new_paired_status, + std::string classic_address) { + std::unique_ptr> + bluetooth_device = CreateTestBluetoothDevice(classic_address); + bluetooth_device->AddUUID(ash::quick_pair::kFastPairBluetoothUuid); + auto* bt_device_ptr = bluetooth_device.get(); + adapter_->AddMockDevice(std::move(bluetooth_device)); + adapter_->NotifyDevicePairedChanged(bt_device_ptr, new_paired_status); + } + base::HistogramTester& histogram_tester() { return histogram_tester_; } protected: base::HistogramTester histogram_tester_; base::test::SingleThreadTaskEnvironment task_environment_; + scoped_refptr adapter_; scoped_refptr initial_device_; scoped_refptr subsequent_device_; + scoped_refptr retroactive_device_; MockScannerBroker* mock_scanner_broker_ = nullptr; MockPairerBroker* mock_pairer_broker_ = nullptr; MockUIBroker* mock_ui_broker_ = nullptr; + FakeRetroactivePairingDetector* fake_retroactive_pairing_detector_ = nullptr; std::unique_ptr scanner_broker_; + std::unique_ptr retroactive_pairing_detector_; std::unique_ptr pairer_broker_; std::unique_ptr ui_broker_; std::unique_ptr metrics_logger_; @@ -669,5 +805,444 @@ TEST_F(QuickPairMetricsLoggerTest, LogPairTime_Subsequent) { histogram_tester().ExpectTotalCount(kFastPairPairTimeMetricSubsequent, 1); } +TEST_F(QuickPairMetricsLoggerTest, LogAssociateAccountShown) { + SimulateAssociateAccountUiShown(); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiShown), + 1); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiDismissed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountUiDismissedByUser), + 0); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountSavePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountLearnMorePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountSavePressedAfterLearnMorePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountDismissedByUserAfterLearnMorePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountDismissedAfterLearnMorePressed), + 0); +} + +TEST_F(QuickPairMetricsLoggerTest, LogAssociateAccountDismissed) { + SimulateAssociateAccountUiDismissed(); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiShown), + 0); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiDismissed), + 1); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountUiDismissedByUser), + 0); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountSavePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountLearnMorePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountSavePressedAfterLearnMorePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountDismissedByUserAfterLearnMorePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountDismissedAfterLearnMorePressed), + 0); +} + +TEST_F(QuickPairMetricsLoggerTest, LogAssociateAccountDismissedByUser) { + SimulateAssociateAccountUiDismissedByUser(); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiShown), + 0); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiDismissed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountUiDismissedByUser), + 1); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountSavePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountLearnMorePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountSavePressedAfterLearnMorePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountDismissedByUserAfterLearnMorePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountDismissedAfterLearnMorePressed), + 0); +} + +TEST_F(QuickPairMetricsLoggerTest, LogAssociateAccountSavePressed) { + SimulateAssociateAccountUiSavePressed(); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiShown), + 0); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiDismissed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountUiDismissedByUser), + 0); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountSavePressed), + 1); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountLearnMorePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountSavePressedAfterLearnMorePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountDismissedByUserAfterLearnMorePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountDismissedAfterLearnMorePressed), + 0); +} + +TEST_F(QuickPairMetricsLoggerTest, LogAssociateAccountLearnMorePressed) { + SimulateAssociateAccountUiLearnMorePressed(); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiShown), + 0); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiDismissed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountUiDismissedByUser), + 0); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountSavePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountLearnMorePressed), + 1); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountSavePressedAfterLearnMorePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountDismissedByUserAfterLearnMorePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountDismissedAfterLearnMorePressed), + 0); +} + +TEST_F(QuickPairMetricsLoggerTest, + LogAssociateAccountLearnMorePressed_SavePressed) { + SimulateAssociateAccountUiLearnMorePressed(); + base::RunLoop().RunUntilIdle(); + SimulateAssociateAccountUiSavePressed(); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiShown), + 0); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiDismissed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountUiDismissedByUser), + 0); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountSavePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountLearnMorePressed), + 1); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountSavePressedAfterLearnMorePressed), + 1); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountDismissedByUserAfterLearnMorePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountDismissedAfterLearnMorePressed), + 0); +} + +TEST_F(QuickPairMetricsLoggerTest, + LogAssociateAccountLearnMorePressed_Dismissed) { + SimulateAssociateAccountUiLearnMorePressed(); + base::RunLoop().RunUntilIdle(); + SimulateAssociateAccountUiDismissed(); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiShown), + 0); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiDismissed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountUiDismissedByUser), + 0); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountSavePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountLearnMorePressed), + 1); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountSavePressedAfterLearnMorePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountDismissedByUserAfterLearnMorePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountDismissedAfterLearnMorePressed), + 1); +} + +TEST_F(QuickPairMetricsLoggerTest, + LogAssociateAccountLearnMorePressed_DismissedByUser) { + SimulateAssociateAccountUiLearnMorePressed(); + base::RunLoop().RunUntilIdle(); + SimulateAssociateAccountUiDismissedByUser(); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiShown), + 0); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiDismissed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountUiDismissedByUser), + 0); + EXPECT_EQ( + histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent::kAssociateAccountSavePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountLearnMorePressed), + 1); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountSavePressedAfterLearnMorePressed), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountDismissedByUserAfterLearnMorePressed), + 1); + EXPECT_EQ(histogram_tester().GetBucketCount( + kFastPairRetroactiveEngagementFlowMetric, + FastPairRetroactiveEngagementFlowEvent:: + kAssociateAccountDismissedAfterLearnMorePressed), + 0); +} + +TEST_F(QuickPairMetricsLoggerTest, DevicedPaired_FastPair) { + EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric, + PairingMethod::kFastPair), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric, + PairingMethod::kSystemPairingUi), + 0); + PairFastPairDeviceWithFastPair(kTestDeviceAddress); + PairFastPairDeviceWithClassicBluetooth( + /*new_paired_status=*/true, kTestDeviceAddress); + EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric, + PairingMethod::kFastPair), + 1); + EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric, + PairingMethod::kSystemPairingUi), + 0); +} + +TEST_F(QuickPairMetricsLoggerTest, DeviceUnpaired) { + EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric, + PairingMethod::kFastPair), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric, + PairingMethod::kSystemPairingUi), + 0); + PairFastPairDeviceWithClassicBluetooth( + /*new_paired_status=*/false, kTestDeviceAddress); + EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric, + PairingMethod::kFastPair), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric, + PairingMethod::kSystemPairingUi), + 0); +} + +TEST_F(QuickPairMetricsLoggerTest, DevicePaired) { + EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric, + PairingMethod::kFastPair), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric, + PairingMethod::kSystemPairingUi), + 0); + PairFastPairDeviceWithClassicBluetooth( + /*new_paired_status=*/true, kTestDeviceAddress); + EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric, + PairingMethod::kFastPair), + 0); + EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric, + PairingMethod::kSystemPairingUi), + 1); +} + +TEST_F(QuickPairMetricsLoggerTest, WriteAccountKey_Initial) { + histogram_tester().ExpectTotalCount(kRetroactivePairingResultMetric, 0); + SimulateAccountKeyWritten(Protocol::kFastPairInitial); + histogram_tester().ExpectTotalCount(kRetroactivePairingResultMetric, 0); +} + +TEST_F(QuickPairMetricsLoggerTest, WriteAccountKey_Retroactive) { + histogram_tester().ExpectTotalCount(kRetroactivePairingResultMetric, 0); + SimulateAccountKeyWritten(Protocol::kFastPairRetroactive); + histogram_tester().ExpectTotalCount(kRetroactivePairingResultMetric, 1); +} + +TEST_F(QuickPairMetricsLoggerTest, WriteAccountKeyFailure_Retroactive) { + histogram_tester().ExpectTotalCount(kRetroactivePairingResultMetric, 0); + SimulateAccountKeyFailure(Protocol::kFastPairRetroactive); + histogram_tester().ExpectTotalCount(kRetroactivePairingResultMetric, 1); +} + } // namespace quick_pair } // namespace ash diff --git a/ash/system/message_center/unified_message_center_bubble.cc b/ash/system/message_center/unified_message_center_bubble.cc index 5a6b32c1d384e6..5224e313ccacfd 100644 --- a/ash/system/message_center/unified_message_center_bubble.cc +++ b/ash/system/message_center/unified_message_center_bubble.cc @@ -127,6 +127,11 @@ UnifiedMessageCenterBubble::~UnifiedMessageCenterBubble() { CHECK(!views::WidgetObserver::IsInObserverList()); } +gfx::Rect UnifiedMessageCenterBubble::GetBoundsInScreen() const { + DCHECK(bubble_view_); + return bubble_view_->GetBoundsInScreen(); +} + void UnifiedMessageCenterBubble::CollapseMessageCenter() { if (message_center_view_->collapsed()) return; diff --git a/ash/system/message_center/unified_message_center_bubble.h b/ash/system/message_center/unified_message_center_bubble.h index 14da351c2a2287..f50660219287b5 100644 --- a/ash/system/message_center/unified_message_center_bubble.h +++ b/ash/system/message_center/unified_message_center_bubble.h @@ -39,6 +39,9 @@ class ASH_EXPORT UnifiedMessageCenterBubble ~UnifiedMessageCenterBubble() override; + // Return the bounds of the bubble in the screen. + gfx::Rect GetBoundsInScreen() const; + // We need the code to show the bubble explicitly separated from the // contructor. This is to prevent trigerring the TrayEventFilter from within // the constructor. Doing so can cause a crash when the TrayEventFilter tries diff --git a/ash/system/message_center/unified_message_center_bubble_unittest.cc b/ash/system/message_center/unified_message_center_bubble_unittest.cc index 51fc252d5fe56c..d11a129373f1a3 100644 --- a/ash/system/message_center/unified_message_center_bubble_unittest.cc +++ b/ash/system/message_center/unified_message_center_bubble_unittest.cc @@ -6,6 +6,7 @@ #include +#include "ash/constants/ash_features.h" #include "ash/constants/ash_pref_names.h" #include "ash/session/session_controller_impl.h" #include "ash/shell.h" @@ -16,7 +17,9 @@ #include "ash/system/unified/unified_system_tray_controller.h" #include "ash/system/unified/unified_system_tray_view.h" #include "ash/test/ash_test_base.h" +#include "ash/wm/tablet_mode/tablet_mode_controller.h" #include "base/strings/stringprintf.h" +#include "base/test/scoped_feature_list.h" #include "components/prefs/pref_service.h" #include "ui/events/keycodes/keyboard_codes_posix.h" #include "ui/message_center/message_center.h" @@ -396,4 +399,79 @@ TEST_F(UnifiedMessageCenterBubbleTest, FocusCycleWithNoNotifications) { GetFirstQuickSettingsFocusable()); } +// Tests with NotificationsRefresh enabled and disabled. +class ParameterizedMessageCenterBubbleTest + : public UnifiedMessageCenterBubbleTest, + public testing::WithParamInterface { + public: + ParameterizedMessageCenterBubbleTest() = default; + + ParameterizedMessageCenterBubbleTest( + const ParameterizedMessageCenterBubbleTest&) = delete; + ParameterizedMessageCenterBubbleTest& operator=( + const ParameterizedMessageCenterBubbleTest&) = delete; + + ~ParameterizedMessageCenterBubbleTest() override = default; + + // AshTestBase: + void SetUp() override { + scoped_feature_list_ = std::make_unique(); + scoped_feature_list_->InitWithFeatureState(features::kNotificationsRefresh, + IsNotificationsRefreshEnabled()); + + UnifiedMessageCenterBubbleTest::SetUp(); + } + + bool IsNotificationsRefreshEnabled() const { return GetParam(); } + + private: + std::unique_ptr scoped_feature_list_; +}; + +INSTANTIATE_TEST_SUITE_P(All, + ParameterizedMessageCenterBubbleTest, + testing::Bool() /* IsNotificationsRefreshEnabled() */); + +TEST_P(ParameterizedMessageCenterBubbleTest, BubbleBounds) { + // Set display size where the message center is not collapsed. + UpdateDisplay("0+0-1280×1024"); + + // Ensure message center is not collapsed. + GetPrimaryUnifiedSystemTray()->ShowBubble(); + ASSERT_FALSE(GetMessageCenterBubble()->IsMessageCenterCollapsed()); + + // Add enough notifications so that the scroll bar is visible. + while (!GetMessageCenterBubble()->message_center_view()->IsScrollBarVisible()) + AddNotification(); + + // The message center bubble should be positioned above the system tray + // bubble. + GetPrimaryUnifiedSystemTray()->ShowBubble(); + EXPECT_LT(GetMessageCenterBubble()->GetBoundsInScreen().bottom(), + GetSystemTrayBubble()->GetBoundsInScreen().y()); + GetPrimaryUnifiedSystemTray()->CloseBubble(); + + // Go into overview mode, check bounds again. + EnterOverview(); + GetPrimaryUnifiedSystemTray()->ShowBubble(); + EXPECT_LT(GetMessageCenterBubble()->GetBoundsInScreen().bottom(), + GetSystemTrayBubble()->GetBoundsInScreen().y()); + GetPrimaryUnifiedSystemTray()->CloseBubble(); + ExitOverview(); + + // Go into tablet mode, check bounds again. + Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true); + GetPrimaryUnifiedSystemTray()->ShowBubble(); + EXPECT_LT(GetMessageCenterBubble()->GetBoundsInScreen().bottom(), + GetSystemTrayBubble()->GetBoundsInScreen().y()); + GetPrimaryUnifiedSystemTray()->CloseBubble(); + + // Go into overview mode inside tablet mode, check bounds again. + EnterOverview(); + GetPrimaryUnifiedSystemTray()->ShowBubble(); + EXPECT_LT(GetMessageCenterBubble()->GetBoundsInScreen().bottom(), + GetSystemTrayBubble()->GetBoundsInScreen().y()); + GetPrimaryUnifiedSystemTray()->CloseBubble(); +} + } // namespace ash diff --git a/ash/system/message_center/unified_message_center_view.cc b/ash/system/message_center/unified_message_center_view.cc index 72db4a46e40b75..23dac8d1c901e3 100644 --- a/ash/system/message_center/unified_message_center_view.cc +++ b/ash/system/message_center/unified_message_center_view.cc @@ -129,8 +129,12 @@ void UnifiedMessageCenterView::Init() { void UnifiedMessageCenterView::SetMaxHeight(int max_height) { int max_scroller_height = max_height; - if (notification_bar_->GetVisible()) + if (notification_bar_->GetVisible()) { max_scroller_height -= kStackedNotificationBarHeight; + if (is_notifications_refresh_enabled_) + max_scroller_height -= + 2 * kNotificationBarVerticalPadding + kMessageCenterBottomPadding; + } scroller_->ClipHeightTo(0, max_scroller_height); } @@ -183,6 +187,10 @@ bool UnifiedMessageCenterView::IsNotificationBarVisible() const { return notification_bar_->GetVisible(); } +bool UnifiedMessageCenterView::IsScrollBarVisible() const { + return scroll_bar_->GetVisible(); +} + void UnifiedMessageCenterView::OnNotificationSlidOut() { if (notification_bar_->GetVisible()) { notification_bar_->Update( diff --git a/ash/system/message_center/unified_message_center_view.h b/ash/system/message_center/unified_message_center_view.h index 52d330149e94d9..4729718645fdd7 100644 --- a/ash/system/message_center/unified_message_center_view.h +++ b/ash/system/message_center/unified_message_center_view.h @@ -130,6 +130,9 @@ class ASH_EXPORT UnifiedMessageCenterView // Returns true if the notification bar is visible. bool IsNotificationBarVisible() const; + // Returns true if the scroll bar is visible. + bool IsScrollBarVisible() const; + // views::View: void AddedToWidget() override; void RemovedFromWidget() override; diff --git a/ash/system/message_center/unified_message_list_view.cc b/ash/system/message_center/unified_message_list_view.cc index bb5674f9ed4107..0f5f737463a2a3 100644 --- a/ash/system/message_center/unified_message_list_view.cc +++ b/ash/system/message_center/unified_message_list_view.cc @@ -946,7 +946,10 @@ void UnifiedMessageListView::StartAnimation() { kExpandOrCollapseAnimationSmoothnessHistogramName); DCHECK(expand_or_collapsing_container_); animation_duration = - expand_or_collapsing_container_->GetBoundsAnimationDuration(); + expand_or_collapsing_container_ + ? expand_or_collapsing_container_->GetBoundsAnimationDuration() + : base::Milliseconds( + kLargeImageExpandAndCollapseAnimationDuration); break; } diff --git a/ash/webui/resources/BUILD.gn b/ash/webui/resources/BUILD.gn index 60aff1464f5682..427f9cc608f81a 100644 --- a/ash/webui/resources/BUILD.gn +++ b/ash/webui/resources/BUILD.gn @@ -76,6 +76,11 @@ ash_generated_grit("firmware_update_app_resources") { deps = [ "//ash/webui/firmware_update_ui/resources:build_grd" ] } +ash_generated_grit("system_extensions_internals_resources") { + source = "$root_gen_dir/ash/webui/system_extensions_internals_ui/ash_system_extensions_internals_resources.grd" + deps = [ "//ash/webui/system_extensions_internals_ui:build_grd" ] +} + if (!is_official_build) { # Resources used by chrome://demo-mode-app ash_generated_grit("demo_mode_app_resources") { diff --git a/ash/webui/shimless_rma/resources/onboarding_enter_rsu_wp_disable_code_page.html b/ash/webui/shimless_rma/resources/onboarding_enter_rsu_wp_disable_code_page.html index b373475a9719ff..ef85af925265e5 100644 --- a/ash/webui/shimless_rma/resources/onboarding_enter_rsu_wp_disable_code_page.html +++ b/ash/webui/shimless_rma/resources/onboarding_enter_rsu_wp_disable_code_page.html @@ -2,6 +2,26 @@ #dialogBody { overflow-wrap: anywhere; } + + cr-input { + --cr-input-error-display: none; + } + + #inputValidationLabel { + color: var(--google-grey-600); + } + + :host([rsu-code-invalid_]) #inputValidationLabel { + color: red; + } + + #rsuCodeLengthLabel { + float: right; + } + + #inputContainer { + width: 275px; + } @@ -10,13 +30,20 @@

[[i18n('rsuCodePageTitleText')]]

- - +
+ + +
+ [[i18n('rsuCodeLabelText')]] + [[rsuCodeLengthLabel_]] +
+
diff --git a/ash/webui/shimless_rma/resources/onboarding_enter_rsu_wp_disable_code_page.js b/ash/webui/shimless_rma/resources/onboarding_enter_rsu_wp_disable_code_page.js index ec917166574227..d1d57ae8487ff1 100644 --- a/ash/webui/shimless_rma/resources/onboarding_enter_rsu_wp_disable_code_page.js +++ b/ash/webui/shimless_rma/resources/onboarding_enter_rsu_wp_disable_code_page.js @@ -83,6 +83,26 @@ export class OnboardingEnterRsuWpDisableCodePage extends value: '', computed: 'computeRsuChallengeLinkText_(rsuHwid_, rsuChallenge_)', }, + + /** @protected */ + rsuCodeValidationRegex_: { + type: String, + value: '.{1,8}', + readOnly: true, + }, + + /** @protected {boolean} */ + rsuCodeInvalid_: { + type: Boolean, + value: false, + reflectToAttribute: true, + }, + + /** @protected */ + rsuCodeLengthLabel_: { + type: String, + computed: 'computeRsuCodeLengthLabel_(rsuCode_)', + }, }; } @@ -202,6 +222,14 @@ export class OnboardingEnterRsuWpDisableCodePage extends closeDialog_() { this.shadowRoot.querySelector('#rsuChallengeDialog').close(); } + + /** + * @return {string} + * @private + */ + computeRsuCodeLengthLabel_() { + return this.rsuCode_.length + '/8'; + } } customElements.define( diff --git a/ash/webui/shimless_rma/shimless_rma.cc b/ash/webui/shimless_rma/shimless_rma.cc index c42fe40ead3336..2ac3c051d3a1cc 100644 --- a/ash/webui/shimless_rma/shimless_rma.cc +++ b/ash/webui/shimless_rma/shimless_rma.cc @@ -140,7 +140,6 @@ void AddShimlessRmaStrings(content::WebUIDataSource* html_source) { {"rsuChallengeDialogTitleText", IDS_SHIMLESS_RMA_RSU_CHALLENGE_DIALOG_TITLE}, {"rsuCodeLabelText", IDS_SHIMLESS_RMA_RSU_CODE_LABEL}, - {"rsuCodePlaceHolderText", IDS_SHIMLESS_RMA_RSU_CODE_PLACEHOLDER}, {"rsuChallengeDialogDoneButtonLabel", IDS_SHIMLESS_RMA_RSU_CHALLENGE_DIALOG_DONE_BUTTON}, // Manual WP disable complete diff --git a/ash/webui/system_extensions_internals_ui/BUILD.gn b/ash/webui/system_extensions_internals_ui/BUILD.gn new file mode 100644 index 00000000000000..d260625ad12671 --- /dev/null +++ b/ash/webui/system_extensions_internals_ui/BUILD.gn @@ -0,0 +1,55 @@ +# 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("//build/config/chromeos/ui_mode.gni") +import("//chrome/test/base/js2gtest.gni") +import("//third_party/closure_compiler/compile_js.gni") +import("//ui/webui/resources/tools/generate_grd.gni") + +assert(is_chromeos_ash, "System Extensions Internals is ash-chrome only") + +static_library("system_extensions_internals_ui") { + sources = [ + "system_extensions_internals_ui.cc", + "system_extensions_internals_ui.h", + "url_constants.cc", + "url_constants.h", + ] + + deps = [ + "//ash/webui/resources:system_extensions_internals_resources", + "//content/public/browser", + "//ui/webui", + ] +} + +js_type_check("closure_compile") { + deps = [ ":system_extensions_internals" ] + closure_flags = default_closure_args + mojom_js_args +} + +js_library("system_extensions_internals") { + sources = [ "resources/index.js" ] +} + +js2gtest("browser_tests_js") { + test_type = "mojo_lite_webui" + + sources = [ "test/system_extensions_internals_ui_browsertest.js" ] + + defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ] +} + +grd_prefix = "ash_system_extensions_internals" + +generate_grd("build_grd") { + input_files_base_dir = rebase_path("resources", "//") + input_files = [ + "index.html", + "index.js", + ] + + grd_prefix = grd_prefix + out_grd = "$target_gen_dir/${grd_prefix}_resources.grd" +} diff --git a/ash/webui/system_extensions_internals_ui/DEPS b/ash/webui/system_extensions_internals_ui/DEPS new file mode 100644 index 00000000000000..e1a02eff17a9d9 --- /dev/null +++ b/ash/webui/system_extensions_internals_ui/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + # Do not add chrome here. Use a delegate instead. + "+ash/grit/ash_system_extensions_internals_resources.h", + "+ui/webui", +] diff --git a/ash/webui/system_extensions_internals_ui/OWNERS b/ash/webui/system_extensions_internals_ui/OWNERS new file mode 100644 index 00000000000000..47d56c8504b5a8 --- /dev/null +++ b/ash/webui/system_extensions_internals_ui/OWNERS @@ -0,0 +1 @@ +file://ash/webui/system_apps/PLATFORM_OWNERS diff --git a/ash/webui/system_extensions_internals_ui/resources/index.html b/ash/webui/system_extensions_internals_ui/resources/index.html new file mode 100644 index 00000000000000..1268eca5b3da04 --- /dev/null +++ b/ash/webui/system_extensions_internals_ui/resources/index.html @@ -0,0 +1,16 @@ + + + + + System Extensions Internals + +

Choose a directory with a System Extension to sideload it. The System Extension directory must be in Downloads for Chrome OS to be able to find it.

+ + +

System Extension installed

+
+ + + diff --git a/ash/webui/system_extensions_internals_ui/resources/index.js b/ash/webui/system_extensions_internals_ui/resources/index.js new file mode 100644 index 00000000000000..1441a844b3899f --- /dev/null +++ b/ash/webui/system_extensions_internals_ui/resources/index.js @@ -0,0 +1,10 @@ +// 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. + +const chooseDirButton = document.querySelector('#choose-directory'); +const resultDialog = document.querySelector('#result-dialog'); + +chooseDirButton.addEventListener('click', async event => { + resultDialog.showModal(); +}); diff --git a/ash/webui/system_extensions_internals_ui/system_extensions_internals_ui.cc b/ash/webui/system_extensions_internals_ui/system_extensions_internals_ui.cc new file mode 100644 index 00000000000000..69458872940086 --- /dev/null +++ b/ash/webui/system_extensions_internals_ui/system_extensions_internals_ui.cc @@ -0,0 +1,38 @@ +// 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 "ash/webui/system_extensions_internals_ui/system_extensions_internals_ui.h" + +#include "ash/grit/ash_system_extensions_internals_resources.h" +#include "ash/grit/ash_system_extensions_internals_resources_map.h" +#include "ash/webui/system_extensions_internals_ui/url_constants.h" +#include "base/memory/ptr_util.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_ui.h" +#include "content/public/browser/web_ui_data_source.h" +#include "services/network/public/mojom/content_security_policy.mojom.h" +#include "ui/webui/webui_allowlist.h" + +namespace ash { + +SystemExtensionsInternalsUI::SystemExtensionsInternalsUI(content::WebUI* web_ui) + : ui::MojoWebUIController(web_ui) { + auto data_source = base::WrapUnique( + content::WebUIDataSource::Create(kChromeUISystemExtensionsInternalsHost)); + + data_source->AddResourcePath("", + IDR_ASH_SYSTEM_EXTENSIONS_INTERNALS_INDEX_HTML); + data_source->AddResourcePaths( + base::make_span(kAshSystemExtensionsInternalsResources, + kAshSystemExtensionsInternalsResourcesSize)); + + auto* browser_context = web_ui->GetWebContents()->GetBrowserContext(); + content::WebUIDataSource::Add(browser_context, data_source.release()); +} + +SystemExtensionsInternalsUI::~SystemExtensionsInternalsUI() = default; + +WEB_UI_CONTROLLER_TYPE_IMPL(SystemExtensionsInternalsUI) + +} // namespace ash diff --git a/ash/webui/system_extensions_internals_ui/system_extensions_internals_ui.h b/ash/webui/system_extensions_internals_ui/system_extensions_internals_ui.h new file mode 100644 index 00000000000000..c1dcb1c2514ff0 --- /dev/null +++ b/ash/webui/system_extensions_internals_ui/system_extensions_internals_ui.h @@ -0,0 +1,27 @@ +// 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 ASH_WEBUI_SYSTEM_EXTENSIONS_INTERNALS_UI_SYSTEM_EXTENSIONS_INTERNALS_UI_H_ +#define ASH_WEBUI_SYSTEM_EXTENSIONS_INTERNALS_UI_SYSTEM_EXTENSIONS_INTERNALS_UI_H_ + +#include "ui/webui/mojo_web_ui_controller.h" + +namespace ash { + +// WebUIController for chrome://system-extensions-internals/. +class SystemExtensionsInternalsUI : public ui::MojoWebUIController { + public: + explicit SystemExtensionsInternalsUI(content::WebUI* web_ui); + SystemExtensionsInternalsUI(const SystemExtensionsInternalsUI&) = delete; + SystemExtensionsInternalsUI& operator=(const SystemExtensionsInternalsUI&) = + delete; + ~SystemExtensionsInternalsUI() override; + + private: + WEB_UI_CONTROLLER_TYPE_DECL(); +}; + +} // namespace ash + +#endif // ASH_WEBUI_SYSTEM_EXTENSIONS_INTERNALS_UI_SYSTEM_EXTENSIONS_INTERNALS_UI_H_ diff --git a/ash/webui/system_extensions_internals_ui/test/system_extensions_internals_ui_browsertest.js b/ash/webui/system_extensions_internals_ui/test/system_extensions_internals_ui_browsertest.js new file mode 100644 index 00000000000000..e9f06587d0bef8 --- /dev/null +++ b/ash/webui/system_extensions_internals_ui/test/system_extensions_internals_ui_browsertest.js @@ -0,0 +1,52 @@ +// 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. + +/** + * @fileoverview Test suite for chrome://system-extensions-internals/ + */ + +GEN('#include "ash/constants/ash_features.h"'); +GEN('#include "content/public/test/browser_test.h"'); + +const HOST_ORIGIN = 'chrome://system-extensions-internals'; + +// TODO:(crbug.com/1262025): We should avoid using `var`. +// +// js2gtest fixtures require var here (https://crbug.com/1033337). +// eslint-disable-next-line no-var +var SystemExtensionsInternalsUIBrowserTest = class extends testing.Test { + /** @override */ + get browsePreload() { + return HOST_ORIGIN; + } + + /** @override */ + get featureList() { + return { + enabled: [ + 'ash::features::kSystemExtensions', + ] + }; + } + + /** @override */ + get runAccessibilityChecks() { + return false; + } + + /** @override */ + get isAsync() { + return true; + } +}; + +// Tests that chrome://system-extensions-internals loads successfully. +TEST_F( + 'SystemExtensionsInternalsUIBrowserTest', 'HasChromeSchemeURL', + async () => { + const header = document.querySelector('title'); + assertEquals(header.innerText, 'System Extensions Internals'); + assertEquals(document.location.origin, HOST_ORIGIN); + testDone(); + }); diff --git a/ash/webui/system_extensions_internals_ui/url_constants.cc b/ash/webui/system_extensions_internals_ui/url_constants.cc new file mode 100644 index 00000000000000..4838257065b27a --- /dev/null +++ b/ash/webui/system_extensions_internals_ui/url_constants.cc @@ -0,0 +1,10 @@ +// 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 "ash/webui/system_extensions_internals_ui/url_constants.h" + +namespace ash { +const char kChromeUISystemExtensionsInternalsHost[] = + "system-extensions-internals"; +} // namespace ash diff --git a/ash/webui/system_extensions_internals_ui/url_constants.h b/ash/webui/system_extensions_internals_ui/url_constants.h new file mode 100644 index 00000000000000..bf293e97b5afc9 --- /dev/null +++ b/ash/webui/system_extensions_internals_ui/url_constants.h @@ -0,0 +1,12 @@ +// 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 ASH_WEBUI_SYSTEM_EXTENSIONS_INTERNALS_UI_URL_CONSTANTS_H_ +#define ASH_WEBUI_SYSTEM_EXTENSIONS_INTERNALS_UI_URL_CONSTANTS_H_ + +namespace ash { +extern const char kChromeUISystemExtensionsInternalsHost[]; +} // namespace ash + +#endif // ASH_WEBUI_SYSTEM_EXTENSIONS_INTERNALS_UI_URL_CONSTANTS_H_ diff --git a/ash/wm/desks/desk.cc b/ash/wm/desks/desk.cc index ae04b632404e4d..06ea15fc673e92 100644 --- a/ash/wm/desks/desk.cc +++ b/ash/wm/desks/desk.cc @@ -498,9 +498,6 @@ void Desk::MoveWindowToDesk(aura::Window* window, DCHECK(target_root); DCHECK(base::Contains(windows_, window)); DCHECK(this != target_desk); - // The desks bar should not be allowed to move individually to another desk. - // Only as part of `MoveWindowsToDesk()` when the desk is removed. - DCHECK_NE(window->GetId(), kShellWindowId_DesksBarWindow); { ScopedWindowPositionerDisabler window_positioner_disabler; diff --git a/ash/wm/desks/desk_animation_impl.cc b/ash/wm/desks/desk_animation_impl.cc index 73a507cced057e..ca8bace7c85a82 100644 --- a/ash/wm/desks/desk_animation_impl.cc +++ b/ash/wm/desks/desk_animation_impl.cc @@ -11,11 +11,13 @@ #include "ash/wm/desks/desks_controller.h" #include "ash/wm/desks/desks_histogram_enums.h" #include "ash/wm/desks/desks_util.h" +#include "ash/wm/haptics_util.h" #include "ash/wm/overview/overview_controller.h" #include "ash/wm/splitview/split_view_utils.h" #include "ash/wm/window_util.h" #include "base/bind.h" #include "base/metrics/histogram_macros.h" +#include "ui/events/devices/haptic_touchpad_effects.h" namespace ash { @@ -156,6 +158,10 @@ bool DeskActivationAnimation::UpdateSwipeAnimation(float scroll_delta_x) { presentation_time_recorder_->RequestNext(); + auto* first_animator = desk_switch_animators_.front().get(); + DCHECK(first_animator); + const bool old_reached_edge = first_animator->reached_edge(); + // If any of the displays need a new screenshot while scrolling, take the // ending desk screenshot for all of them to keep them in sync. absl::optional ending_desk_index; @@ -167,15 +173,25 @@ bool DeskActivationAnimation::UpdateSwipeAnimation(float scroll_delta_x) { } // See if the animator of the first display has visibly changed desks. If so, - // update |visible_desk_changes_| for metrics collection purposes. - auto* first_animator = desk_switch_animators_.front().get(); - DCHECK(first_animator); + // update `visible_desk_changes_` for metrics collection purposes. Also fire a + // haptic event if we have reached the edge, or the visible desk has changed. if (first_animator->starting_desk_screenshot_taken() && first_animator->ending_desk_screenshot_taken()) { const int old_visible_desk_index = visible_desk_index_; visible_desk_index_ = first_animator->GetIndexOfMostVisibleDeskScreenshot(); - if (visible_desk_index_ != old_visible_desk_index) + if (visible_desk_index_ != old_visible_desk_index) { ++visible_desk_changes_; + haptics_util::PlayHapticTouchpadEffect( + ui::HapticTouchpadEffect::kTick, + ui::HapticTouchpadEffectStrength::kMedium); + } + + const bool reached_edge = first_animator->reached_edge(); + if (reached_edge && !old_reached_edge) { + haptics_util::PlayHapticTouchpadEffect( + ui::HapticTouchpadEffect::kKnock, + ui::HapticTouchpadEffectStrength::kMedium); + } } // No screenshot needed. diff --git a/ash/wm/desks/desk_animation_impl_unittest.cc b/ash/wm/desks/desk_animation_impl_unittest.cc index 901c6e40ed0cb9..0c288edb605373 100644 --- a/ash/wm/desks/desk_animation_impl_unittest.cc +++ b/ash/wm/desks/desk_animation_impl_unittest.cc @@ -9,6 +9,7 @@ #include "ash/wm/desks/desks_constants.h" #include "ash/wm/desks/desks_controller.h" #include "ash/wm/desks/desks_histogram_enums.h" +#include "ash/wm/desks/desks_test_util.h" #include "ash/wm/desks/root_window_desk_switch_animator_test_api.h" #include "base/barrier_closure.h" #include "base/test/scoped_feature_list.h" @@ -18,19 +19,6 @@ namespace ash { -namespace { - -void WaitEndingScreenshotTaken(DeskActivationAnimation* animation) { - base::RunLoop run_loop; - auto* desk_switch_animator = - animation->GetDeskSwitchAnimatorAtIndexForTesting(0); - RootWindowDeskSwitchAnimatorTestApi(desk_switch_animator) - .SetOnEndingScreenshotTakenCallback(run_loop.QuitClosure()); - run_loop.Run(); -} - -} // namespace - using DeskActivationAnimationTest = AshTestBase; // Tests that there is no crash when ending a swipe animation before the @@ -102,17 +90,17 @@ TEST_F(DeskActivationAnimationTest, VisibleDeskChangeCount) { animation.set_skip_notify_controller_on_animation_finished_for_testing(true); animation.Launch(); - WaitEndingScreenshotTaken(&animation); + WaitUntilEndingScreenshotTaken(&animation); EXPECT_EQ(0, animation.visible_desk_changes()); // Swipe enough so that our third and fourth desk screenshots are taken, and // then swipe so that the fourth desk is fully shown. There should be 3 // visible desk changes in total. animation.UpdateSwipeAnimation(-kTouchpadSwipeLengthForDeskChange); - WaitEndingScreenshotTaken(&animation); + WaitUntilEndingScreenshotTaken(&animation); animation.UpdateSwipeAnimation(-kTouchpadSwipeLengthForDeskChange); - WaitEndingScreenshotTaken(&animation); + WaitUntilEndingScreenshotTaken(&animation); animation.UpdateSwipeAnimation(-3 * kTouchpadSwipeLengthForDeskChange); EXPECT_EQ(3, animation.visible_desk_changes()); @@ -146,7 +134,7 @@ TEST_F(DeskActivationAnimationTest, CloseWindowDuringAnimation) { animation.Launch(); window.reset(); - WaitEndingScreenshotTaken(&animation); + WaitUntilEndingScreenshotTaken(&animation); } // Tests that if a fast swipe is detected, we will still wait for the ending @@ -185,7 +173,7 @@ TEST_F(DeskActivationAnimationTest, AnimatingAfterFastSwipe) { animation.EndSwipeAnimation(); ASSERT_FALSE(desk_switch_animator->ending_desk_screenshot_taken()); - WaitEndingScreenshotTaken(&animation); + WaitUntilEndingScreenshotTaken(&animation); // Tests that there is an animation after the ending screenshots have been // taken. diff --git a/ash/wm/desks/desks_bar_view.cc b/ash/wm/desks/desks_bar_view.cc index b5c475225734ef..5923d3636f2ac3 100644 --- a/ash/wm/desks/desks_bar_view.cc +++ b/ash/wm/desks/desks_bar_view.cc @@ -31,6 +31,7 @@ #include "ash/wm/desks/templates/desks_templates_presenter.h" #include "ash/wm/desks/templates/desks_templates_util.h" #include "ash/wm/desks/zero_state_button.h" +#include "ash/wm/haptics_util.h" #include "ash/wm/overview/overview_controller.h" #include "ash/wm/overview/overview_grid.h" #include "ash/wm/overview/overview_highlight_controller.h" @@ -43,6 +44,7 @@ #include "ui/aura/window.h" #include "ui/base/l10n/l10n_util.h" #include "ui/events/devices/device_data_manager.h" +#include "ui/events/devices/haptic_touchpad_effects.h" #include "ui/events/devices/input_device.h" #include "ui/events/event_observer.h" #include "ui/events/types/event_type.h" @@ -537,7 +539,7 @@ void DesksBarView::HandleLongPressEvent(DeskMiniView* mini_view, // Initialize and start drag. gfx::PointF location = event.target()->GetScreenLocationF(event); InitDragDesk(mini_view, location); - StartDragDesk(mini_view, location); + StartDragDesk(mini_view, location, event.IsMouseEvent()); } void DesksBarView::HandleDragEvent(DeskMiniView* mini_view, @@ -553,7 +555,7 @@ void DesksBarView::HandleDragEvent(DeskMiniView* mini_view, // continue drag. switch (drag_proxy_->state()) { case DeskDragProxy::State::kInitialized: - StartDragDesk(mini_view, location); + StartDragDesk(mini_view, location, event.IsMouseEvent()); break; case DeskDragProxy::State::kStarted: ContinueDragDesk(mini_view, location); @@ -606,7 +608,8 @@ void DesksBarView::InitDragDesk(DeskMiniView* mini_view, } void DesksBarView::StartDragDesk(DeskMiniView* mini_view, - const gfx::PointF& location_in_screen) { + const gfx::PointF& location_in_screen, + bool is_mouse_dragging) { DCHECK(drag_view_); DCHECK(drag_proxy_); DCHECK_EQ(mini_view, drag_view_); @@ -620,6 +623,13 @@ void DesksBarView::StartDragDesk(DeskMiniView* mini_view, drag_proxy_->InitAndScaleAndMoveToX(location_in_screen.x()); Shell::Get()->cursor_manager()->SetCursor(ui::mojom::CursorType::kGrabbing); + + // Fire a haptic event if necessary. + if (is_mouse_dragging) { + haptics_util::PlayHapticTouchpadEffect( + ui::HapticTouchpadEffect::kTick, + ui::HapticTouchpadEffectStrength::kMedium); + } } void DesksBarView::ContinueDragDesk(DeskMiniView* mini_view, diff --git a/ash/wm/desks/desks_bar_view.h b/ash/wm/desks/desks_bar_view.h index a2e34f9e1d9b17..7a892de9eed883 100644 --- a/ash/wm/desks/desks_bar_view.h +++ b/ash/wm/desks/desks_bar_view.h @@ -136,9 +136,11 @@ class ASH_EXPORT DesksBarView : public views::View, // Finalize any unfinished drag & drop. Initialize a new drag proxy. void InitDragDesk(DeskMiniView* mini_view, const gfx::PointF& location_in_screen); - // Start to drag. Scale up the drag proxy. + // Start to drag. Scale up the drag proxy. `is_mouse_dragging` is true when + // triggered by mouse/trackpad, false when triggered by touch. void StartDragDesk(DeskMiniView* mini_view, - const gfx::PointF& location_in_screen); + const gfx::PointF& location_in_screen, + bool is_mouse_dragging); // Reorder desks according to the drag proxy's location. void ContinueDragDesk(DeskMiniView* mini_view, const gfx::PointF& location_in_screen); diff --git a/ash/wm/desks/desks_controller.cc b/ash/wm/desks/desks_controller.cc index 6413733f5c801b..70aae8b307e780 100644 --- a/ash/wm/desks/desks_controller.cc +++ b/ash/wm/desks/desks_controller.cc @@ -118,8 +118,11 @@ constexpr int kDeskDefaultNameIds[] = { void AppendWindowsToOverview(const std::vector& windows) { DCHECK(Shell::Get()->overview_controller()->InOverviewSession()); + // TODO(dandersson): See if we can remove this code and just let + // OverviewSession do its thing when the windows are moved to the new desk. auto* overview_session = Shell::Get()->overview_controller()->overview_session(); + overview_session->set_auto_add_windows_enabled(false); for (auto* window : Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk)) { if (!base::Contains(windows, window) || @@ -129,6 +132,7 @@ void AppendWindowsToOverview(const std::vector& windows) { overview_session->AppendItem(window, /*reposition=*/true, /*animate=*/true); } + overview_session->set_auto_add_windows_enabled(true); } // Removes all the items that currently exist in overview. @@ -560,6 +564,13 @@ void DesksController::ActivateDesk(const Desk* desk, DesksSwitchSource source) { return; } + if (source == DesksSwitchSource::kLaunchTemplate) { + // Desk switch due to launching a template will immediately activate the new + // desk without animation. + ActivateDeskInternal(desk, /*update_window_activation=*/true); + return; + } + // When switching desks we want to update window activation when leaving // overview or if nothing was active prior to switching desks. This will // ensure that after switching desks, we will try to focus a candidate window. @@ -973,13 +984,35 @@ void DesksController::CreateAndActivateNewDeskForTemplate( desk->SetName(desk_name, /*set_by_user=*/true); // Force update user prefs because `SetName()` does not trigger it. desks_restore_util::UpdatePrimaryUserDeskNamesPrefs(); + + // We're staying in overview mode, so move desks bar window and the save + // template button to the new desk. They would otherwise disappear when the + // new desk is activated. + DCHECK(active_desk_); + + // Since we're going to move certain windows from the currently active desk, + // this is going to implicitly modify that list. We therefore grab a copy of + // it to avoid issues with concurrent iteration and modification of the list. + auto active_desk_windows = active_desk_->windows(); + for (aura::Window* window : active_desk_windows) { + if (window->GetId() == kShellWindowId_DesksBarWindow || + window->GetId() == kShellWindowId_SaveDeskAsTemplateWindow) { + aura::Window* destination_container = + desk->GetDeskContainerForRoot(window->GetRootWindow()); + destination_container->AddChild(window); + } + } + + if (auto* session = Shell::Get()->overview_controller()->overview_session()) { + session->HideDesksTemplatesGrids(); + for (auto& grid : session->grid_list()) + grid->RemoveAllItemsForDesksTemplatesLaunch(); + } + ActivateDesk(desk, DesksSwitchSource::kLaunchTemplate); - DCHECK(animation_); - animation_->set_finished_callback(base::BindOnce( - [](base::OnceCallback passed_callback) { - std::move(passed_callback).Run(/*success=*/true); - }, - std::move(callback))); + DCHECK(!animation_); + + std::move(callback).Run(/*success=*/true); } bool DesksController::OnSingleInstanceAppLaunchingFromTemplate( @@ -1018,7 +1051,7 @@ bool DesksController::OnSingleInstanceAppLaunchingFromTemplate( existing_app_instance_window)) { DCHECK(src_desk); DCHECK_NE(src_desk, active_desk_); - DCHECK(!Shell::Get()->overview_controller()->InOverviewSession()); + base::AutoReset in_progress(&are_desks_being_modified_, true); src_desk->MoveWindowToDesk(existing_app_instance_window, active_desk_, existing_app_instance_window->GetRootWindow(), diff --git a/ash/wm/desks/desks_test_util.cc b/ash/wm/desks/desks_test_util.cc index 65737921485cf6..1be77386b66a6d 100644 --- a/ash/wm/desks/desks_test_util.cc +++ b/ash/wm/desks/desks_test_util.cc @@ -7,6 +7,7 @@ #include "ash/shell.h" #include "ash/wm/desks/desk.h" #include "ash/wm/desks/desk_animation_base.h" +#include "ash/wm/desks/desk_animation_impl.h" #include "ash/wm/desks/desks_histogram_enums.h" #include "ash/wm/desks/root_window_desk_switch_animator_test_api.h" #include "ash/wm/gestures/wm_gesture_handler.h" @@ -148,4 +149,13 @@ void ScrollToSwitchDesks(bool scroll_left, } } +void WaitUntilEndingScreenshotTaken(DeskActivationAnimation* animation) { + base::RunLoop run_loop; + auto* desk_switch_animator = + animation->GetDeskSwitchAnimatorAtIndexForTesting(0); + RootWindowDeskSwitchAnimatorTestApi(desk_switch_animator) + .SetOnEndingScreenshotTakenCallback(run_loop.QuitClosure()); + run_loop.Run(); +} + } // namespace ash diff --git a/ash/wm/desks/desks_test_util.h b/ash/wm/desks/desks_test_util.h index fc0dead2e5829d..e50e6ef42a98a1 100644 --- a/ash/wm/desks/desks_test_util.h +++ b/ash/wm/desks/desks_test_util.h @@ -16,6 +16,8 @@ class EventGenerator; namespace ash { +class DeskActivationAnimation; + constexpr int kNumFingersForHighlight = 3; constexpr int kNumFingersForDesksSwitch = 4; @@ -69,6 +71,9 @@ const Desk* GetNextDesk(); void ScrollToSwitchDesks(bool scroll_left, ui::test::EventGenerator* event_generator); +// Wait until `animation`'s ending screenshot has been taken. +void WaitUntilEndingScreenshotTaken(DeskActivationAnimation* animation); + } // namespace ash #endif // ASH_WM_DESKS_DESKS_TEST_UTIL_H_ diff --git a/ash/wm/desks/root_window_desk_switch_animator.cc b/ash/wm/desks/root_window_desk_switch_animator.cc index d65c2b154bbb71..5279b145fe8142 100644 --- a/ash/wm/desks/root_window_desk_switch_animator.cc +++ b/ash/wm/desks/root_window_desk_switch_animator.cc @@ -271,6 +271,13 @@ absl::optional RootWindowDeskSwitchAnimator::UpdateSwipeAnimation( // edge padding (i.e. translation of (-190, 0)). gfx::RectF transformed_animation_layer_bounds(animation_layer->bounds()); transform.TransformRect(&transformed_animation_layer_bounds); + + // `reached_edge_` becomes true if the user has scrolled `animation_layer` to + // its limits. + reached_edge_ = + transformed_animation_layer_bounds.x() == 0 || + transformed_animation_layer_bounds.right() == root_window_size_.width(); + transformed_animation_layer_bounds.Inset(edge_padding_width_dp_, 0); const bool moving_left = scroll_delta_x < 0.f; diff --git a/ash/wm/desks/root_window_desk_switch_animator.h b/ash/wm/desks/root_window_desk_switch_animator.h index ac5f566b0bdaf3..8f292eef5e2c8e 100644 --- a/ash/wm/desks/root_window_desk_switch_animator.h +++ b/ash/wm/desks/root_window_desk_switch_animator.h @@ -220,6 +220,7 @@ class ASH_EXPORT RootWindowDeskSwitchAnimator return ending_desk_screenshot_taken_; } bool animation_finished() const { return animation_finished_; } + bool reached_edge() const { return reached_edge_; } // Begins phase (1) of the animation by taking a screenshot of the starting // desk content. Delegate::OnStartingDeskScreenshotTaken() will be called once @@ -362,6 +363,10 @@ class ASH_EXPORT RootWindowDeskSwitchAnimator // True when phase (3) finishes. bool animation_finished_ = false; + // True if during a continuous swipe, the user went all the way left or right + // and swiping in that direction will no longer update the UI. + bool reached_edge_ = false; + // True while setting a new transform for chaining. If a animation is active, // calling SetTransform will trigger OnImplicitAnimationsCompleted. In these // cases we do not want to notify our delegate that the animation is finished. diff --git a/ash/wm/desks/templates/desks_templates_unittest.cc b/ash/wm/desks/templates/desks_templates_unittest.cc index 9382eeab44fd27..cb4c752126d013 100644 --- a/ash/wm/desks/templates/desks_templates_unittest.cc +++ b/ash/wm/desks/templates/desks_templates_unittest.cc @@ -726,36 +726,29 @@ TEST_F(DesksTemplatesTest, LaunchTemplate) { ASSERT_EQ(1ul, GetAllEntries().size()); // Click on the grid item to launch the template. - { - DeskSwitchAnimationWaiter waiter; - ClickOnView(GetItemViewFromTemplatesGrid(/*grid_item_index=*/0)); - WaitForDesksTemplatesUI(); - waiter.Wait(); - } + ClickOnView(GetItemViewFromTemplatesGrid(/*grid_item_index=*/0)); + WaitForDesksTemplatesUI(); // Verify that we have created and activated a new desk. EXPECT_EQ(2ul, desks_controller->desks().size()); EXPECT_EQ(1, desks_controller->GetActiveDeskIndex()); - // Launching a template creates and activates a new desk, which also results - // in exiting overview mode, so we check to make sure overview is closed. - EXPECT_FALSE(InOverviewSession()); + // Launching a template creates and activates a new desk without exiting + // overview mode, so we check that we're still in overview. + EXPECT_TRUE(InOverviewSession()); // This section tests clicking on the "Use template" button to launch the // template. + ToggleOverview(); OpenOverviewAndShowTemplatesGrid(); - { - DeskSwitchAnimationWaiter waiter; - DesksTemplatesItemView* item_view = GetItemViewFromTemplatesGrid( - /*grid_item_index=*/0); - ClickOnView(DesksTemplatesItemViewTestApi(item_view).launch_button()); - WaitForDesksTemplatesUI(); - waiter.Wait(); - } + DesksTemplatesItemView* item_view = GetItemViewFromTemplatesGrid( + /*grid_item_index=*/0); + ClickOnView(DesksTemplatesItemViewTestApi(item_view).launch_button()); + WaitForDesksTemplatesUI(); EXPECT_EQ(3ul, desks_controller->desks().size()); EXPECT_EQ(2, desks_controller->GetActiveDeskIndex()); - EXPECT_FALSE(InOverviewSession()); + EXPECT_TRUE(InOverviewSession()); } // Tests that the order of DesksTemplatesItemView is in order. @@ -1308,16 +1301,13 @@ TEST_F(DesksTemplatesTest, LaunchTemplateWithMinimizedOverviewWindow) { OpenOverviewAndSaveTemplate(Shell::Get()->GetPrimaryRootWindow()); ASSERT_EQ(1ul, GetAllEntries().size()); - // Click on the grid item to launch the template. We should exit overview and - // there should be no crash. - DeskSwitchAnimationWaiter waiter; + // Click on the grid item to launch the template. We should remain in overview + // and there should be no crash. ClickOnView(GetItemViewFromTemplatesGrid(/*grid_item_index=*/0)); - // Launching a template fetches it from the desk model asynchronously. Make - // sure the async call is done before waiting. + // Launching a template fetches it from the desk model asynchronously. WaitForDesksTemplatesUI(); - waiter.Wait(); - EXPECT_FALSE(InOverviewSession()); + EXPECT_TRUE(InOverviewSession()); } // Tests that there is no crash if we launch a template after deleting the @@ -1339,16 +1329,12 @@ TEST_F(DesksTemplatesTest, LaunchTemplateAfterClosingActiveDesk) { // a template" button was not moved when the active desk was removed. RemoveDesk(desks_controller->active_desk()); - // Click on the grid item to launch the template. We should exit overview and - // there should be no crash. - DeskSwitchAnimationWaiter waiter; + // Click on the grid item to launch the template. There should be no crash. ClickOnView(GetItemViewFromTemplatesGrid(/*grid_item_index=*/0)); - // Launching a template fetches it from the desk model asynchronously. Make - // sure the async call is done before waiting. + // Launching a template fetches it from the desk model asynchronously. WaitForDesksTemplatesUI(); - waiter.Wait(); - EXPECT_FALSE(InOverviewSession()); + EXPECT_TRUE(InOverviewSession()); } // Tests that if we open the desks templates grid a second time during an @@ -1717,10 +1703,8 @@ TEST_F(DesksTemplatesTest, LaunchTemplateRecordsMetric) { ASSERT_EQ(1ul, GetAllEntries().size()); // Click on the grid item to launch the template. - DeskSwitchAnimationWaiter waiter; ClickOnView(GetItemViewFromTemplatesGrid(/*grid_item_index=*/0)); WaitForDesksTemplatesUI(); - waiter.Wait(); // Verify that we have created and activated a new desk. EXPECT_EQ(2ul, desks_controller->desks().size()); diff --git a/ash/wm/haptics_util_unittest.cc b/ash/wm/haptics_util_unittest.cc index aae53843f4f49a..3ae71b515fbf05 100644 --- a/ash/wm/haptics_util_unittest.cc +++ b/ash/wm/haptics_util_unittest.cc @@ -8,8 +8,12 @@ #include "ash/shell.h" #include "ash/test/ash_test_base.h" +#include "ash/wm/desks/desk_animation_impl.h" +#include "ash/wm/desks/desks_constants.h" #include "ash/wm/desks/desks_controller.h" +#include "ash/wm/desks/desks_histogram_enums.h" #include "ash/wm/desks/desks_test_util.h" +#include "ash/wm/desks/root_window_desk_switch_animator_test_api.h" #include "ash/wm/gestures/wm_gesture_handler.h" #include "ash/wm/overview/overview_controller.h" #include "ash/wm/overview/overview_item.h" @@ -305,4 +309,75 @@ TEST_F(HapticsUtilTest, HapticFeedbackForDeskSwitchingOffLimits) { HapticTouchpadEffectStrength::kMedium)); } +// Tests that haptics are sent when doing a continuous touchpad gesture to +// switch desks. They are expected to be sent if we hit the edge, or when the +// visible desk changes. +TEST_F(HapticsUtilTest, HapticFeedbackForContinuousDesksSwitching) { + auto input_controller = std::make_unique(); + haptics_util::SetInputControllerForTesting(input_controller.get()); + + // Add three desks for a total of four. + auto* desks_controller = DesksController::Get(); + desks_controller->NewDesk(DesksCreationRemovalSource::kButton); + desks_controller->NewDesk(DesksCreationRemovalSource::kButton); + desks_controller->NewDesk(DesksCreationRemovalSource::kButton); + + // Create a standalone animation object. This is the same object that gets + // created when swiping with 4 fingers, but mocking 4 fingers swipes is harder + // to control in a test with all the async operations and touchpad unit + // conversions. + DeskActivationAnimation animation(desks_controller, 0, 1, + DesksSwitchSource::kDeskSwitchTouchpad, + /*update_window_activation=*/false); + animation.set_skip_notify_controller_on_animation_finished_for_testing(true); + animation.Launch(); + + // Wait for the ending screenshot to be taken. + WaitUntilEndingScreenshotTaken(&animation); + + EXPECT_EQ(0, input_controller->GetSendHapticCount( + HapticTouchpadEffect::kKnock, + HapticTouchpadEffectStrength::kMedium)); + EXPECT_EQ(0, input_controller->GetSendHapticCount( + HapticTouchpadEffect::kTick, + HapticTouchpadEffectStrength::kMedium)); + + // Swipe enough so that our third and fourth desk screenshots are taken, and + // then swipe so that the fourth desk is fully shown. There should be 3 + // visible desk changes in total, which means 3 tick haptic events sent. + animation.UpdateSwipeAnimation(-kTouchpadSwipeLengthForDeskChange); + WaitUntilEndingScreenshotTaken(&animation); + + animation.UpdateSwipeAnimation(-kTouchpadSwipeLengthForDeskChange); + WaitUntilEndingScreenshotTaken(&animation); + + animation.UpdateSwipeAnimation(-kTouchpadSwipeLengthForDeskChange); + EXPECT_EQ(3, input_controller->GetSendHapticCount( + HapticTouchpadEffect::kTick, + HapticTouchpadEffectStrength::kMedium)); + + // Try doing a full swipe to the right. Test that a knock haptic event is sent + // because we are at the edge. + animation.UpdateSwipeAnimation(-kTouchpadSwipeLengthForDeskChange); + EXPECT_EQ(1, input_controller->GetSendHapticCount( + HapticTouchpadEffect::kKnock, + HapticTouchpadEffectStrength::kMedium)); + + // Swipe 3 times to the left. We move from the fourth desk as the visible desk + // to the first desk, so there should be three more tick haptic events. + animation.UpdateSwipeAnimation(kTouchpadSwipeLengthForDeskChange); + animation.UpdateSwipeAnimation(kTouchpadSwipeLengthForDeskChange); + animation.UpdateSwipeAnimation(kTouchpadSwipeLengthForDeskChange); + EXPECT_EQ(6, input_controller->GetSendHapticCount( + HapticTouchpadEffect::kTick, + HapticTouchpadEffectStrength::kMedium)); + + // Swipe to the left while at the first desk. Tests that another haptic event + // is sent because we are at the edge. + animation.UpdateSwipeAnimation(kTouchpadSwipeLengthForDeskChange); + EXPECT_EQ(2, input_controller->GetSendHapticCount( + HapticTouchpadEffect::kKnock, + HapticTouchpadEffectStrength::kMedium)); +} + } // namespace ash diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc index c28ecac9eb17c1..55833a94bed543 100644 --- a/ash/wm/overview/overview_grid.cc +++ b/ash/wm/overview/overview_grid.cc @@ -766,6 +766,12 @@ void OverviewGrid::RemoveItem(OverviewItem* overview_item, } } +void OverviewGrid::RemoveAllItemsForDesksTemplatesLaunch() { + for (auto& item : window_list_) + item->RestoreWindow(/*reset_tranform=*/true); + window_list_.clear(); +} + void OverviewGrid::AddDropTargetForDraggingFromThisGrid( OverviewItem* dragged_item) { DCHECK(!drop_target_widget_); @@ -1416,6 +1422,16 @@ bool OverviewGrid::IntersectsWithDesksBar(const gfx::Point& screen_location, return dragged_item_over_bar; } +int OverviewGrid::GetDeskIndexFromScreenLocation( + const gfx::Point& screen_location) { + auto* desks_controller = DesksController::Get(); + for (auto* mini_view : desks_bar_view_->mini_views()) { + if (mini_view->IsPointOnMiniView(screen_location)) + return desks_controller->GetDeskIndex(mini_view->desk()); + } + return -1; +} + bool OverviewGrid::MaybeDropItemOnDeskMiniViewOrNewDeskButton( const gfx::Point& screen_location, OverviewItem* drag_item) { diff --git a/ash/wm/overview/overview_grid.h b/ash/wm/overview/overview_grid.h index 491673f0165d1a..b3ca253200ec0f 100644 --- a/ash/wm/overview/overview_grid.h +++ b/ash/wm/overview/overview_grid.h @@ -141,6 +141,12 @@ class ASH_EXPORT OverviewGrid : public SplitViewObserver, bool item_destroying, bool reposition); + // Removes all overview items and restores the respective windows. This is + // used when launching a desks template. While this will empty the grid, it + // will *not* invoke `OverviewSession::OnGridEmpty()` since the grid is about + // to get filled with new windows. + void RemoveAllItemsForDesksTemplatesLaunch(); + // Adds a drop target for |dragged_item|, at the index immediately following // |dragged_item|. Repositions all items except |dragged_item|, so that the // drop target takes the place of |dragged_item|. Does not animate the @@ -286,6 +292,10 @@ class ASH_EXPORT OverviewGrid : public SplitViewObserver, bool update_desks_bar_drag_details, bool for_drop); + // Returns the desk index of the provided screen location if it belongs to + // any, otherwise `-1` will be returned. + int GetDeskIndexFromScreenLocation(const gfx::Point& screen_location); + // Updates the drag details for DesksBarView to end the drag and move the // window of |drag_item| to another desk if it was dropped on a mini_view of // a desk that is different than that of the active desk or if dropped on the diff --git a/ash/wm/overview/overview_item.cc b/ash/wm/overview/overview_item.cc index fff7468a24a5a3..e6bb04b4c53388 100644 --- a/ash/wm/overview/overview_item.cc +++ b/ash/wm/overview/overview_item.cc @@ -187,7 +187,8 @@ OverviewItem::OverviewItem(aura::Window* window, : root_window_(window->GetRootWindow()), transform_window_(this, window), overview_session_(overview_session), - overview_grid_(overview_grid) { + overview_grid_(overview_grid), + animation_disabler_(window) { CreateItemWidget(); window->AddObserver(this); WindowState::Get(window)->AddObserver(this); diff --git a/ash/wm/overview/overview_item.h b/ash/wm/overview/overview_item.h index 88028bfdf8cfb2..a24e2b9598c662 100644 --- a/ash/wm/overview/overview_item.h +++ b/ash/wm/overview/overview_item.h @@ -8,6 +8,7 @@ #include #include "ash/ash_export.h" +#include "ash/scoped_animation_disabler.h" #include "ash/wm/overview/overview_session.h" #include "ash/wm/overview/scoped_overview_transform_window.h" #include "ash/wm/window_state_observer.h" @@ -445,6 +446,10 @@ class ASH_EXPORT OverviewItem : public aura::WindowObserver, std::unique_ptr item_widget_event_blocker_; + // Disable animations on the contained window while it is being managed by the + // overview item. + ScopedAnimationDisabler animation_disabler_; + base::WeakPtrFactory weak_ptr_factory_{this}; }; diff --git a/ash/wm/overview/overview_session.cc b/ash/wm/overview/overview_session.cc index f98e745578354b..60cc7052100c93 100644 --- a/ash/wm/overview/overview_session.cc +++ b/ash/wm/overview/overview_session.cc @@ -32,6 +32,7 @@ #include "ash/wm/desks/templates/desks_templates_dialog_controller.h" #include "ash/wm/desks/templates/desks_templates_presenter.h" #include "ash/wm/desks/templates/desks_templates_util.h" +#include "ash/wm/haptics_util.h" #include "ash/wm/mru_window_tracker.h" #include "ash/wm/overview/overview_controller.h" #include "ash/wm/overview/overview_delegate.h" @@ -56,10 +57,12 @@ #include "ui/base/l10n/l10n_util.h" #include "ui/compositor/layer.h" #include "ui/compositor/scoped_layer_animation_settings.h" +#include "ui/events/devices/haptic_touchpad_effects.h" #include "ui/events/event.h" #include "ui/views/accessibility/view_accessibility.h" #include "ui/views/widget/widget.h" #include "ui/wm/core/coordinate_conversion.h" +#include "ui/wm/core/window_util.h" namespace ash { @@ -257,6 +260,8 @@ void OverviewSession::Init(const WindowList& windows, Shell::Get()->accessibility_controller()->TriggerAccessibilityAlert( AccessibilityAlert::WINDOW_OVERVIEW_MODE_ENTERED); + desks_controller_observation_.Observe(DesksController::Get()); + ignore_activations_ = false; } @@ -268,6 +273,12 @@ void OverviewSession::Shutdown() { // began. See OverviewController::OnSelectionEnded(). DCHECK(is_shutting_down_); + desks_controller_observation_.Reset(); + if (observing_desk_) { + for (auto* root : Shell::GetAllRootWindows()) + observing_desk_->GetDeskContainerForRoot(root)->RemoveObserver(this); + } + Shell::Get()->RemovePreTargetHandler(this); Shell::Get()->RemoveShellObserver(this); @@ -549,6 +560,7 @@ void OverviewSession::InitiateDrag(OverviewItem* item, ->IsDividerAnimating()) { return; } + highlight_controller_->SetFocusHighlightVisibility(false); window_drag_controller_ = std::make_unique( this, item, is_touch_dragging); @@ -558,6 +570,13 @@ void OverviewSession::InitiateDrag(OverviewItem* item, grid->OnSelectorItemDragStarted(item); grid->UpdateSaveDeskAsTemplateButton(); } + + // Fire a haptic event if necessary. + if (!is_touch_dragging) { + haptics_util::PlayHapticTouchpadEffect( + ui::HapticTouchpadEffect::kTick, + ui::HapticTouchpadEffectStrength::kMedium); + } } void OverviewSession::Drag(OverviewItem* item, @@ -964,6 +983,35 @@ void OverviewSession::ShowDesksTemplatesGrids(bool was_zero_state) { UpdateNoWindowsWidgetOnEachGrid(); } +void OverviewSession::HideDesksTemplatesGrids() { + // Before hiding the templates grid, we need to explicitly activate the focus + // window. Otherwise, some other window may get activated as the templates + // grid is hidden, and this could in turn lead to exiting overview mode. + wm::ActivateWindow(GetOverviewFocusWindow()); + + for (auto& grid : grid_list_) + grid->HideDesksTemplatesGrid(/*exit_overview=*/false); +} + +void OverviewSession::OnDeskAdded(const Desk* desk) {} +void OverviewSession::OnDeskRemoved(const Desk* desk) {} +void OverviewSession::OnDeskReordered(int old_index, int new_index) {} + +void OverviewSession::OnDeskActivationChanged(const Desk* activated, + const Desk* deactivated) { + observing_desk_ = activated; + + for (auto* root : Shell::GetAllRootWindows()) { + activated->GetDeskContainerForRoot(root)->AddObserver(this); + deactivated->GetDeskContainerForRoot(root)->RemoveObserver(this); + } +} + +void OverviewSession::OnDeskSwitchAnimationLaunching() {} +void OverviewSession::OnDeskSwitchAnimationFinished() {} +void OverviewSession::OnDeskNameChanged(const Desk* desk, + const std::u16string& new_name) {} + void OverviewSession::OnDisplayAdded(const display::Display& display) { if (EndOverview(OverviewEndAction::kDisplayAdded)) return; @@ -994,6 +1042,25 @@ void OverviewSession::OnWindowDestroying(aura::Window* window) { active_window_before_overview_ = nullptr; } +void OverviewSession::OnWindowAdded(aura::Window* new_window) { + if (!auto_add_windows_enabled_) + return; + + // We track if we are in the process of adding an item to avoid recursively + // adding items. + if (is_adding_new_item_) + return; + base::AutoReset adding_new_item_resetter(&is_adding_new_item_, true); + + // Avoid adding overview items for certain windows. + if (!WindowState::Get(new_window) || + window_util::ShouldExcludeForOverview(new_window)) { + return; + } + + AppendItem(new_window, /*reposition=*/true, /*animate*/ true); +} + void OverviewSession::OnKeyEvent(ui::KeyEvent* event) { // If app list is open when overview is active (it can happen in clamshell // mode, when we snap an overview window to one side of the screen and then diff --git a/ash/wm/overview/overview_session.h b/ash/wm/overview/overview_session.h index 9f3e7fe70e7c63..30512b7cab6c9c 100644 --- a/ash/wm/overview/overview_session.h +++ b/ash/wm/overview/overview_session.h @@ -15,6 +15,7 @@ #include "ash/public/cpp/shelf_types.h" #include "ash/public/cpp/tablet_mode_observer.h" #include "ash/shell_observer.h" +#include "ash/wm/desks/desks_controller.h" #include "ash/wm/overview/overview_types.h" #include "ash/wm/overview/scoped_overview_hide_windows.h" #include "ash/wm/splitview/split_view_controller.h" @@ -57,7 +58,8 @@ class ASH_EXPORT OverviewSession : public display::DisplayObserver, public ui::EventHandler, public ShellObserver, public SplitViewObserver, - public TabletModeObserver { + public TabletModeObserver, + public DesksController::Observer { public: using WindowList = std::vector; @@ -279,6 +281,18 @@ class ASH_EXPORT OverviewSession : public display::DisplayObserver, // Shows the desks templates grids on all displays. If `was_zero_state` is // true then we will expand the desks bars. void ShowDesksTemplatesGrids(bool was_zero_state); + void HideDesksTemplatesGrids(); + + // DesksController::Observer: + void OnDeskAdded(const Desk* desk) override; + void OnDeskRemoved(const Desk* desk) override; + void OnDeskReordered(int old_index, int new_index) override; + void OnDeskActivationChanged(const Desk* activated, + const Desk* deactivated) override; + void OnDeskSwitchAnimationLaunching() override; + void OnDeskSwitchAnimationFinished() override; + void OnDeskNameChanged(const Desk* desk, + const std::u16string& new_name) override; // display::DisplayObserver: void OnDisplayAdded(const display::Display& display) override; @@ -287,6 +301,7 @@ class ASH_EXPORT OverviewSession : public display::DisplayObserver, // aura::WindowObserver: void OnWindowDestroying(aura::Window* window) override; + void OnWindowAdded(aura::Window* new_window) override; // ui::EventHandler: void OnKeyEvent(ui::KeyEvent* event) override; @@ -338,6 +353,10 @@ class ASH_EXPORT OverviewSession : public display::DisplayObserver, return desks_templates_presenter_.get(); } + void set_auto_add_windows_enabled(bool enabled) { + auto_add_windows_enabled_ = enabled; + } + private: friend class DesksAcceleratorsTest; friend class OverviewTestBase; @@ -448,8 +467,22 @@ class ASH_EXPORT OverviewSession : public display::DisplayObserver, // Boolean to indicate whether chromeVox is enabled or not. bool chromevox_enabled_; + // When non-null, windows changes on this desk are observed. + const Desk* observing_desk_ = nullptr; + + // This is true *while* an overview item is being dynamically added. It is + // used to avoid recursively adding overview items. + bool is_adding_new_item_ = false; + + // When true, windows added to the observed desk are automatically added to + // the overview session. + bool auto_add_windows_enabled_ = true; + base::ScopedObservation tablet_mode_observation_{this}; + + base::ScopedObservation + desks_controller_observation_{this}; }; } // namespace ash diff --git a/ash/wm/overview/overview_session_unittest.cc b/ash/wm/overview/overview_session_unittest.cc index 622c2dae5e0178..ad65fbb761d886 100644 --- a/ash/wm/overview/overview_session_unittest.cc +++ b/ash/wm/overview/overview_session_unittest.cc @@ -438,7 +438,6 @@ TEST_P(OverviewSessionTest, MinimizeDuringOverview) { WMEvent minimize_event(WM_EVENT_MINIMIZE); window_state->OnWMEvent(&minimize_event); EXPECT_FALSE(window->IsVisible()); - EXPECT_EQ(0.f, window->layer()->GetTargetOpacity()); EXPECT_EQ(WindowStateType::kMinimized, WindowState::Get(window.get())->GetStateType()); ToggleOverview(); diff --git a/ash/wm/overview/overview_window_drag_controller.cc b/ash/wm/overview/overview_window_drag_controller.cc index 92397209970fcc..bd5b892fdf62c8 100644 --- a/ash/wm/overview/overview_window_drag_controller.cc +++ b/ash/wm/overview/overview_window_drag_controller.cc @@ -16,6 +16,7 @@ #include "ash/wm/desks/desk_preview_view.h" #include "ash/wm/desks/desks_bar_view.h" #include "ash/wm/desks/desks_util.h" +#include "ash/wm/haptics_util.h" #include "ash/wm/overview/overview_constants.h" #include "ash/wm/overview/overview_controller.h" #include "ash/wm/overview/overview_grid.h" @@ -37,6 +38,7 @@ #include "ui/aura/window_observer.h" #include "ui/compositor/layer.h" #include "ui/display/display.h" +#include "ui/events/devices/haptic_touchpad_effects.h" #include "ui/wm/core/coordinate_conversion.h" namespace ash { @@ -552,11 +554,27 @@ void OverviewWindowDragController::ContinueNormalDrag( // Update the mini views borders by checking if |location_in_screen| // intersects. Only update the borders if the dragged item is not visible // on all desks. - overview_grid->IntersectsWithDesksBar( + bool intersects_with_desks_bar = overview_grid->IntersectsWithDesksBar( gfx::ToRoundedPoint(location_in_screen), /*update_desks_bar_drag_details=*/ !DraggedItemIsVisibleOnAllDesks(item_), /*for_drop=*/false); + // Fire a haptic event if necessary. + if (intersects_with_desks_bar && !is_touch_dragging_) { + const int desk_index = overview_grid->GetDeskIndexFromScreenLocation( + gfx::ToRoundedPoint(location_in_screen)); + if (last_desk_index_ != desk_index) { + last_desk_index_ = desk_index; + if (desk_index != -1) { + haptics_util::PlayHapticTouchpadEffect( + ui::HapticTouchpadEffect::kTick, + ui::HapticTouchpadEffectStrength::kMedium); + } + } + } else { + last_desk_index_ = -1; + } + float value = 0.f; if (centerpoint.y() < desks_bar_data.desks_bar_bounds.y() || centerpoint.y() > desks_bar_data.desks_bar_bounds.bottom()) { diff --git a/ash/wm/overview/overview_window_drag_controller.h b/ash/wm/overview/overview_window_drag_controller.h index 6f72f3ac4e0963..ecd2bfcf16c800 100644 --- a/ash/wm/overview/overview_window_drag_controller.h +++ b/ash/wm/overview/overview_window_drag_controller.h @@ -232,6 +232,12 @@ class ASH_EXPORT OverviewWindowDragController { // Set to true once the bounds of |item_| change. bool did_move_ = false; + // The last desk index that the screen location points to during drag. Please + // note that this would be set to `-1` when it does not intersect with desks + // bar. It is used to keep track of haptic feedback since we do not want + // duplicate event for the same desk during drag. + int last_desk_index_ = -1; + // Records the presentation time of window drag operation in overview mode. std::unique_ptr presentation_time_recorder_; diff --git a/base/android/build_info.cc b/base/android/build_info.cc index be9143007a9354..6bc369571d389b 100644 --- a/base/android/build_info.cc +++ b/base/android/build_info.cc @@ -79,7 +79,8 @@ BuildInfo::BuildInfo(const std::vector& params) target_sdk_version_(GetIntParam(params, 21)), is_debug_android_(GetIntParam(params, 22)), is_tv_(GetIntParam(params, 23)), - version_incremental_(StrDupParam(params, 24)) {} + version_incremental_(StrDupParam(params, 24)), + hardware_(StrDupParam(params, 25)) {} // static BuildInfo* BuildInfo::GetInstance() { diff --git a/base/android/build_info.h b/base/android/build_info.h index f8e2c73cb60ca5..6717e8010047fd 100644 --- a/base/android/build_info.h +++ b/base/android/build_info.h @@ -138,6 +138,8 @@ class BASE_EXPORT BuildInfo { const char* version_incremental() const { return version_incremental_; } + const char* hardware() const { return hardware_; } + private: friend struct BuildInfoSingletonTraits; @@ -173,6 +175,7 @@ class BASE_EXPORT BuildInfo { const bool is_debug_android_; const bool is_tv_; const char* const version_incremental_; + const char* const hardware_; }; } // namespace android diff --git a/base/android/java/src/org/chromium/base/BuildInfo.java b/base/android/java/src/org/chromium/base/BuildInfo.java index 198c970fd347b4..4d286ce4706085 100644 --- a/base/android/java/src/org/chromium/base/BuildInfo.java +++ b/base/android/java/src/org/chromium/base/BuildInfo.java @@ -91,6 +91,7 @@ private static String[] getAll() { isDebugAndroid() ? "1" : "0", buildInfo.isTV ? "1" : "0", Build.VERSION.INCREMENTAL, + Build.HARDWARE, }; } diff --git a/base/json/json_file_value_serializer.h b/base/json/json_file_value_serializer.h index 1e1910b0a4420b..e2479aa401e428 100644 --- a/base/json/json_file_value_serializer.h +++ b/base/json/json_file_value_serializer.h @@ -12,6 +12,7 @@ #include "base/base_export.h" #include "base/files/file_path.h" +#include "base/json/json_reader.h" #include "base/values.h" class BASE_EXPORT JSONFileValueSerializer : public base::ValueSerializer { @@ -54,8 +55,9 @@ class BASE_EXPORT JSONFileValueDeserializer : public base::ValueDeserializer { // |json_file_path_| is the path of a file that will be source of the // deserialization. |options| is a bitmask of JSONParserOptions. - explicit JSONFileValueDeserializer(const base::FilePath& json_file_path, - int options = 0); + explicit JSONFileValueDeserializer( + const base::FilePath& json_file_path, + int options = base::JSON_PARSE_CHROMIUM_EXTENSIONS); JSONFileValueDeserializer(const JSONFileValueDeserializer&) = delete; JSONFileValueDeserializer& operator=(const JSONFileValueDeserializer&) = diff --git a/base/json/json_parser.cc b/base/json/json_parser.cc index d5c84aad76f050..fdff416aae652d 100644 --- a/base/json/json_parser.cc +++ b/base/json/json_parser.cc @@ -10,6 +10,7 @@ #include "base/check_op.h" #include "base/json/json_reader.h" +#include "base/metrics/histogram_functions.h" #include "base/notreached.h" #include "base/numerics/safe_conversions.h" #include "base/ranges/algorithm.h" @@ -77,6 +78,20 @@ bool UnprefixedHexStringToInt(StringPiece input, int* output) { return HexStringToInt(input, output); } +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. +enum class ChromiumJsonExtension { + kCComment, + kCppComment, + kXEscape, + kVerticalTabEscape, + kControlCharacter, + kMaxValue = kControlCharacter, +}; + +const char kExtensionHistogramName[] = + "Security.JSONParser.ChromiumExtensionUsage"; + } // namespace // This is U+FFFD. @@ -327,7 +342,16 @@ bool JSONParser::EatComment() { if (!comment_start) return false; + const bool comments_allowed = options_ & JSON_ALLOW_COMMENTS; + if (comment_start == "//") { + UmaHistogramEnumeration(kExtensionHistogramName, + ChromiumJsonExtension::kCppComment); + if (!comments_allowed) { + ReportError(JSON_UNEXPECTED_TOKEN, 0); + return false; + } + ConsumeChars(2); // Single line comment, read to newline. while (absl::optional c = PeekChar()) { @@ -336,6 +360,13 @@ bool JSONParser::EatComment() { ConsumeChar(); } } else if (comment_start == "/*") { + UmaHistogramEnumeration(kExtensionHistogramName, + ChromiumJsonExtension::kCComment); + if (!comments_allowed) { + ReportError(JSON_UNEXPECTED_TOKEN, 0); + return false; + } + ConsumeChars(2); char previous_char = '\0'; // Block comment, read until end marker. @@ -527,6 +558,19 @@ bool JSONParser::ConsumeStringRaw(StringBuilder* out) { return true; } if (next_char != '\\') { + // Per Section 7, "All Unicode characters may be placed within the + // quotation marks, except for the characters that MUST be escaped: + // quotation mark, reverse solidus, and the control characters (U+0000 + // through U+001F)". + if (next_char <= 0x1F) { + UmaHistogramEnumeration(kExtensionHistogramName, + ChromiumJsonExtension::kControlCharacter); + if (!(options_ & JSON_ALLOW_CONTROL_CHARS)) { + ReportError(JSON_UNSUPPORTED_ENCODING, -1); + return false; + } + } + // If this character is not an escape sequence, track any line breaks and // copy next_char to the StringBuilder. The JSON spec forbids unescaped // ASCII control characters within a string, including '\r' and '\n', but @@ -561,6 +605,13 @@ bool JSONParser::ConsumeStringRaw(StringBuilder* out) { case 'x': { // UTF-8 sequence. // UTF-8 \x escape sequences are not allowed in the spec, but they // are supported here for backwards-compatiblity with the old parser. + UmaHistogramEnumeration(kExtensionHistogramName, + ChromiumJsonExtension::kXEscape); + if (!(options_ & JSON_ALLOW_X_ESCAPES)) { + ReportError(JSON_INVALID_ESCAPE, -1); + return false; + } + escape_sequence = ConsumeChars(2); if (!escape_sequence) { ReportError(JSON_INVALID_ESCAPE, -3); @@ -612,6 +663,12 @@ bool JSONParser::ConsumeStringRaw(StringBuilder* out) { string.Append('\t'); break; case 'v': // Not listed as valid escape sequence in the RFC. + UmaHistogramEnumeration(kExtensionHistogramName, + ChromiumJsonExtension::kVerticalTabEscape); + if (!(options_ & JSON_ALLOW_VERT_TAB)) { + ReportError(JSON_INVALID_ESCAPE, -1); + return false; + } string.Append('\v'); break; // All other escape squences are illegal. diff --git a/base/json/json_parser_unittest.cc b/base/json/json_parser_unittest.cc index 9c1ea22d3aa981..8fbf0a098a719c 100644 --- a/base/json/json_parser_unittest.cc +++ b/base/json/json_parser_unittest.cc @@ -277,7 +277,7 @@ TEST_F(JSONParserTest, ErrorMessages) { } { - JSONParser parser(JSON_PARSE_RFC); + JSONParser parser(JSON_PARSE_RFC | JSON_ALLOW_X_ESCAPES); absl::optional value = parser.Parse("[\"xxx\\xq\"]"); EXPECT_FALSE(value); EXPECT_EQ(JSONParser::FormatErrorMessage(1, 7, JSONParser::kInvalidEscape), diff --git a/base/json/json_reader.h b/base/json/json_reader.h index b2821805fb4a4f..0e33c3b971eaf9 100644 --- a/base/json/json_reader.h +++ b/base/json/json_reader.h @@ -23,8 +23,6 @@ // Configurable (see the JSONParserOptions type) deviations from the RFC: // - Allow trailing commas: "[1,2,]". // - Replace invalid Unicode with U+FFFD REPLACEMENT CHARACTER. -// -// Non-configurable deviations from the RFC: // - Allow "// etc\n" and "/* etc */" C-style comments. // - Allow ASCII control characters, including literal (not escaped) NUL bytes // and new lines, within a JSON string. @@ -50,8 +48,7 @@ namespace base { enum JSONParserOptions { - // Parses the input strictly according to RFC 8259, except for where noted - // above. + // Parses the input strictly according to RFC 8259. JSON_PARSE_RFC = 0, // Allows commas to exist after the last element in structures. @@ -62,6 +59,26 @@ enum JSONParserOptions { // not set, invalid code points trigger a hard error and parsing // fails. JSON_REPLACE_INVALID_CHARACTERS = 1 << 1, + + // Allows both C (/* */) and C++ (//) style comments. + JSON_ALLOW_COMMENTS = 1 << 2, + + // Permits unescaped ASCII control characters (such as unescaped \r and \n) + // in the range [0x00,0x1F]. + JSON_ALLOW_CONTROL_CHARS = 1 << 3, + + // Permits \\v vertical tab escapes. + JSON_ALLOW_VERT_TAB = 1 << 4, + + // Permits \\xNN escapes as described above. + JSON_ALLOW_X_ESCAPES = 1 << 5, + + // This parser historically accepted, without configuration flags, + // non-standard JSON extensions. This flag enables that traditional parsing + // behavior. + JSON_PARSE_CHROMIUM_EXTENSIONS = JSON_ALLOW_COMMENTS | + JSON_ALLOW_CONTROL_CHARS | + JSON_ALLOW_VERT_TAB | JSON_ALLOW_X_ESCAPES, }; class BASE_EXPORT JSONReader { @@ -94,7 +111,7 @@ class BASE_EXPORT JSONReader { // If |json| is not a properly formed JSON string, returns absl::nullopt. static absl::optional Read( StringPiece json, - int options = JSON_PARSE_RFC, + int options = JSON_PARSE_CHROMIUM_EXTENSIONS, size_t max_depth = internal::kAbsoluteMaxDepth); // Deprecated. Use the Read() method above. @@ -104,7 +121,7 @@ class BASE_EXPORT JSONReader { // convert to a FooValue at the same time. static std::unique_ptr ReadDeprecated( StringPiece json, - int options = JSON_PARSE_RFC, + int options = JSON_PARSE_CHROMIUM_EXTENSIONS, size_t max_depth = internal::kAbsoluteMaxDepth); // Reads and parses |json| like Read(). Returns a ValueWithError, which on @@ -112,7 +129,7 @@ class BASE_EXPORT JSONReader { // the error location if appropriate. static ValueWithError ReadAndReturnValueWithError( StringPiece json, - int options = JSON_PARSE_RFC); + int options = JSON_PARSE_CHROMIUM_EXTENSIONS); }; } // namespace base diff --git a/base/json/json_reader_unittest.cc b/base/json/json_reader_unittest.cc index 816c8d3a6639dc..9dbe3b6e1daddb 100644 --- a/base/json/json_reader_unittest.cc +++ b/base/json/json_reader_unittest.cc @@ -377,7 +377,7 @@ TEST(JSONReaderTest, CompleteDictionary) { absl::optional root2 = JSONReader::Read( "{\"number\":9.87654321, \"null\":null , \"\\x53\" : \"str\", }", - JSON_ALLOW_TRAILING_COMMAS); + JSON_PARSE_CHROMIUM_EXTENSIONS | JSON_ALLOW_TRAILING_COMMAS); ASSERT_TRUE(root2); ASSERT_TRUE(root2->is_dict()); EXPECT_EQ(*dict_val, *root2); @@ -389,7 +389,7 @@ TEST(JSONReaderTest, CompleteDictionary) { " \"null\":null,\n" " \"\\x53\":\"str\",\n" "}\n", - JSON_ALLOW_TRAILING_COMMAS); + JSON_PARSE_CHROMIUM_EXTENSIONS | JSON_ALLOW_TRAILING_COMMAS); ASSERT_TRUE(root2); ASSERT_TRUE(root2->is_dict()); EXPECT_EQ(*dict_val, *root2); @@ -400,7 +400,7 @@ TEST(JSONReaderTest, CompleteDictionary) { " \"null\":null,\r\n" " \"\\x53\":\"str\",\r\n" "}\r\n", - JSON_ALLOW_TRAILING_COMMAS); + JSON_PARSE_CHROMIUM_EXTENSIONS | JSON_ALLOW_TRAILING_COMMAS); ASSERT_TRUE(root2); ASSERT_TRUE(root2->is_dict()); EXPECT_EQ(*dict_val, *root2); @@ -1014,11 +1014,54 @@ TEST(JSONReaderTest, LineColumnCounting) { SCOPED_TRACE(StringPrintf("case %u: \"%s\"", i, test_case.input)); JSONReader::ValueWithError root = JSONReader::ReadAndReturnValueWithError( - test_case.input, JSON_PARSE_RFC); + test_case.input, JSON_PARSE_RFC | JSON_ALLOW_CONTROL_CHARS); EXPECT_FALSE(root.value); EXPECT_EQ(test_case.error_line, root.error_line); EXPECT_EQ(test_case.error_column, root.error_column); } } +TEST(JSONReaderTest, ChromiumExtensions) { + // All of these cases should parse with JSON_PARSE_CHROMIUM_EXTENSIONS but + // fail with JSON_PARSE_RFC. + const struct { + // The JSON input. + const char* input; + // What JSON_* option permits this extension. + int option; + } kCases[] = { + {"{ /* comment */ \"foo\": 3 }", JSON_ALLOW_COMMENTS}, + {"{ // comment\n \"foo\": 3 }", JSON_ALLOW_COMMENTS}, + {"[\"\\xAB\"]", JSON_ALLOW_X_ESCAPES}, + {"[\"\b\"]", JSON_ALLOW_CONTROL_CHARS}, + {"[\"\f\"]", JSON_ALLOW_CONTROL_CHARS}, + {"[\"\n\"]", JSON_ALLOW_CONTROL_CHARS}, + {"[\"\r\"]", JSON_ALLOW_CONTROL_CHARS}, + {"[\"\t\"]", JSON_ALLOW_CONTROL_CHARS}, + {"[\"\v\"]", JSON_ALLOW_CONTROL_CHARS}, + {"[\"\\v\"]", JSON_ALLOW_VERT_TAB}, + }; + + for (size_t i = 0; i < base::size(kCases); ++i) { + SCOPED_TRACE(testing::Message() << "case " << i); + const auto& test_case = kCases[i]; + + JSONReader::ValueWithError result = JSONReader::ReadAndReturnValueWithError( + test_case.input, JSON_PARSE_RFC); + EXPECT_FALSE(result.value); + + result = JSONReader::ReadAndReturnValueWithError( + test_case.input, JSON_PARSE_RFC | test_case.option); + EXPECT_TRUE(result.value); + + result = JSONReader::ReadAndReturnValueWithError( + test_case.input, JSON_PARSE_CHROMIUM_EXTENSIONS); + EXPECT_TRUE(result.value); + + result = JSONReader::ReadAndReturnValueWithError( + test_case.input, JSON_PARSE_CHROMIUM_EXTENSIONS & ~test_case.option); + EXPECT_FALSE(result.value); + } +} + } // namespace base diff --git a/base/json/json_string_value_serializer.h b/base/json/json_string_value_serializer.h index 9faa0aed81ac11..27be85279c55a5 100644 --- a/base/json/json_string_value_serializer.h +++ b/base/json/json_string_value_serializer.h @@ -9,6 +9,7 @@ #include #include "base/base_export.h" +#include "base/json/json_reader.h" #include "base/memory/raw_ptr.h" #include "base/strings/string_piece.h" #include "base/values.h" @@ -51,8 +52,9 @@ class BASE_EXPORT JSONStringValueDeserializer : public base::ValueDeserializer { // This retains a reference to the contents of |json_string|, so the data // must outlive the JSONStringValueDeserializer. |options| is a bitmask of // JSONParserOptions. - explicit JSONStringValueDeserializer(const base::StringPiece& json_string, - int options = 0); + explicit JSONStringValueDeserializer( + const base::StringPiece& json_string, + int options = base::JSON_PARSE_CHROMIUM_EXTENSIONS); JSONStringValueDeserializer(const JSONStringValueDeserializer&) = delete; JSONStringValueDeserializer& operator=(const JSONStringValueDeserializer&) = diff --git a/base/test/values_test_util.cc b/base/test/values_test_util.cc index cd8be33e0e17d0..8f82229f4dde3b 100644 --- a/base/test/values_test_util.cc +++ b/base/test/values_test_util.cc @@ -219,8 +219,8 @@ void IsJsonMatcher::DescribeNegationTo(std::ostream* os) const { } Value ParseJson(StringPiece json) { - JSONReader::ValueWithError result = - JSONReader::ReadAndReturnValueWithError(json, JSON_ALLOW_TRAILING_COMMAS); + JSONReader::ValueWithError result = JSONReader::ReadAndReturnValueWithError( + json, JSON_PARSE_CHROMIUM_EXTENSIONS | JSON_ALLOW_TRAILING_COMMAS); if (!result.value) { ADD_FAILURE() << "Failed to parse \"" << json << "\": " << result.error_message; diff --git a/base/trace_event/trace_event_unittest.cc b/base/trace_event/trace_event_unittest.cc index 9b6bb44ba1dd7b..3712c086751405 100644 --- a/base/trace_event/trace_event_unittest.cc +++ b/base/trace_event/trace_event_unittest.cc @@ -208,8 +208,8 @@ void TraceEventTestFixture::OnTraceDataCollected( trace_buffer_.AddFragment(events_str->data()); trace_buffer_.Finish(); - absl::optional root = - base::JSONReader::Read(json_output_.json_output, JSON_PARSE_RFC); + absl::optional root = base::JSONReader::Read( + json_output_.json_output, JSON_PARSE_RFC | JSON_ALLOW_CONTROL_CHARS); if (!root.has_value()) { LOG(ERROR) << json_output_.json_output; diff --git a/build/config/chromeos/ui_mode.gni b/build/config/chromeos/ui_mode.gni index aff59afa6664cd..12d18e83b9b19d 100644 --- a/build/config/chromeos/ui_mode.gni +++ b/build/config/chromeos/ui_mode.gni @@ -2,6 +2,8 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import("//build/config/chromeos/args.gni") + declare_args() { # Deprecated, use is_lacros. # @@ -16,15 +18,21 @@ declare_args() { # is_ash = chromeos_product == "ash" chromeos_is_browser_only = false - # Setting this to true when building LaCrOS-chrome will cause it to - # *also* build ash-chrome in a subdirectory using an alternate toolchain. + # Setting this to true when building linux Lacros-chrome will cause it to + # *also* build linux ash-chrome in a subdirectory using an alternate + # toolchain. # Don't set this unless you're sure you want it, because it'll double # your build time. also_build_ash_chrome = false - # Setting this to true when building ash-chrome will cause it to - # *also* build lacros-chrome in a subdirectory using an alternate toolchain. + # Setting this to true when building linux ash-chrome will cause it to + # *also* build linux Lacros-chrome in a subdirectory using an alternate toolchain. also_build_lacros_chrome = false + + # Setting this when building ash-chrome will cause it to + # *also* build Lacros-chrome in a subdirectory using an alternate toolchain. + # You can set this to either "amd64" or "arm". + also_build_lacros_chrome_for_architecture = "" } # is_chromeos_{ash,lacros} is used to specify that it is specific to either @@ -38,3 +46,15 @@ is_chromeos_lacros = is_chromeos && chromeos_is_browser_only # also_build_ash_chrome and also_build_lacros_chrome cannot be both true. assert(!(also_build_ash_chrome && also_build_lacros_chrome)) + +# Can't set both also_build_lacros_chrome and +# also_build_lacros_chrome_for_architecture. +assert(!(also_build_lacros_chrome == true && + also_build_lacros_chrome_for_architecture != "")) + +# also_build_lacros_chrome_for_architecture is for device only. +assert(is_chromeos_device || also_build_lacros_chrome_for_architecture == "") + +# also_build_lacros_chrome_for_architecture is for ash build only. +assert(!chromeos_is_browser_only || + also_build_lacros_chrome_for_architecture == "") diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn index 7a1825e1db1db4..e45fa2c80b97c1 100644 --- a/build/config/compiler/BUILD.gn +++ b/build/config/compiler/BUILD.gn @@ -129,8 +129,8 @@ declare_args() { # TODO(gbiv): We disable optimizations by default on most platforms because # the space overhead is too great. We should use some mixture of profiles and # optimization settings to better tune the size increase. - thin_lto_enable_optimizations = - (is_chromeos_ash || is_android || is_win || is_linux) && is_official_build + thin_lto_enable_optimizations = (is_chromeos_ash || is_android || is_win || + is_linux || is_mac) && is_official_build # Initialize all local variables with a pattern. This flag will fill # uninitialized floating-point types (and 32-bit pointers) with 0xFF and the @@ -654,10 +654,11 @@ config("compiler") { if (!is_debug && use_thin_lto && is_a_target_toolchain) { assert(use_lld, "LTO is only supported with lld") - cflags += [ - "-flto=thin", - "-fsplit-lto-unit", - ] + cflags += [ "-flto=thin" ] + if (!is_mac) { + # TODO(lgrey): Enable unit splitting for Mac when supported. + cflags += [ "-fsplit-lto-unit" ] + } # Limit the size of the ThinLTO cache to the lesser of 10% of # available disk space, 40GB and 100000 files. diff --git a/build/config/compiler/compiler.gni b/build/config/compiler/compiler.gni index a41df193c6a2c4..3520e4288b40d3 100644 --- a/build/config/compiler/compiler.gni +++ b/build/config/compiler/compiler.gni @@ -74,7 +74,8 @@ declare_args() { use_thin_lto = is_cfi || (is_clang && is_official_build && chrome_pgo_phase != 1 && - (is_linux || is_win || (is_android && target_os != "chromeos") || + (is_linux || is_win || is_mac || + (is_android && target_os != "chromeos") || ((is_chromeos_ash || is_chromeos_lacros) && is_chromeos_device))) # If true, use Goma for ThinLTO code generation where applicable. diff --git a/build/toolchain/cros/BUILD.gn b/build/toolchain/cros/BUILD.gn index 4cff006a05e599..15bdd9c13cdbca 100644 --- a/build/toolchain/cros/BUILD.gn +++ b/build/toolchain/cros/BUILD.gn @@ -174,3 +174,60 @@ cros_toolchain("v8_snapshot") { sysroot = cros_v8_snapshot_sysroot } } + +# This toolchain is used when we want to build Lacros using alternate toolchain. +# To use this, you need to set gn arg 'also_build_lacros_chrome_for_architecture'. +# See build/config/chromeos/ui_mode.gni +if (also_build_lacros_chrome_for_architecture != "") { + cros_toolchain("lacros_clang") { + # These are args for the template. + ar = cros_target_ar + cc = cros_target_cc + cxx = cros_target_cxx + ld = cros_target_ld + + if (cros_target_nm != "") { + nm = cros_target_nm + } + if (cros_target_readelf != "") { + readelf = cros_target_readelf + } + extra_cflags = cros_target_extra_cflags + extra_cppflags = cros_target_extra_cppflags + extra_cxxflags = cros_target_extra_cxxflags + extra_ldflags = cros_target_extra_ldflags + + toolchain_args = { + if (also_build_lacros_chrome_for_architecture == "amd64") { + forward_variables_from( + read_file("//build/args/chromeos/amd64-generic-crostoolchain.gni", + "scope"), + "*") + cros_v8_snapshot_sysroot = "//build/linux/debian_sid_amd64-sysroot" + } else if (also_build_lacros_chrome_for_architecture == "arm") { + forward_variables_from( + read_file("//build/args/chromeos/arm-generic-crostoolchain.gni", + "scope"), + "*") + cros_v8_snapshot_sysroot = "//build/linux/debian_sid_i386-sysroot" + } else { + assert(false, + "also_build_lacros_chrome_for_architecture is not " + + "one of the supported architecture.") + } + current_os = "chromeos" + target_os = "chromeos" + current_cpu = current_cpu + also_build_lacros_chrome_for_architecture = "" + chromeos_is_browser_only = true + use_clang_coverage = false + cros_host_sysroot = "//build/linux/debian_sid_amd64-sysroot" + cc_wrapper = "" + needs_gomacc_path_arg = true + clang_use_chrome_plugins = false + is_clang = is_clang + use_debug_fission = use_debug_fission + use_gold = use_gold + } + } +} diff --git a/cc/test/test_options_provider.cc b/cc/test/test_options_provider.cc index d9f07cd41bcd9a..82d5a8c29b44e1 100644 --- a/cc/test/test_options_provider.cc +++ b/cc/test/test_options_provider.cc @@ -22,12 +22,19 @@ class TestOptionsProvider::DiscardableManager return true; } + // SkStrikeServer::DiscardableHandleManager::isHandleDeleted implementation. + bool isHandleDeleted(SkDiscardableHandleId) override { return false; } + // SkStrikeClient::DiscardableHandleManager implementation. bool deleteHandle(SkDiscardableHandleId handle_id) override { CHECK_LT(handle_id, next_handle_id_); return false; } + // SkStrikeClient::DiscardableHandleManager implementation. + void notifyCacheMiss(SkStrikeClient::CacheMissType type, + int fontSize) override {} + private: SkDiscardableHandleId next_handle_id_ = 1u; }; diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn index 9e2c48f5bc7337..3a50c88e6cb48f 100644 --- a/chrome/BUILD.gn +++ b/chrome/BUILD.gn @@ -161,6 +161,10 @@ if (!is_android && !is_mac) { data_deps += [ "//sandbox/linux:chrome_sandbox" ] } + if (also_build_lacros_chrome_for_architecture != "") { + data_deps += [ "//chrome:chrome(//build/toolchain/cros:lacros_clang)" ] + } + if (is_win) { sources += [ "app/chrome_exe.rc", diff --git a/chrome/VERSION b/chrome/VERSION index cf17d4421570c2..aaa50afa9765e2 100644 --- a/chrome/VERSION +++ b/chrome/VERSION @@ -1,4 +1,4 @@ MAJOR=99 MINOR=0 -BUILD=4766 +BUILD=4767 PATCH=0 diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni index 5cb50a7a62ca02..220effcbfb9eee 100644 --- a/chrome/android/chrome_java_resources.gni +++ b/chrome/android/chrome_java_resources.gni @@ -449,7 +449,6 @@ chrome_java_resources = [ "java/res/drawable-xxxhdpi/unionpay_card.png", "java/res/drawable-xxxhdpi/verify_checkmark.png", "java/res/drawable/accessibility_tab_switcher_divider.xml", - "java/res/drawable/account_picker_background.xml", "java/res/drawable/adaptive_toolbar_preference_header.xml", "java/res/drawable/arrow_down.xml", "java/res/drawable/arrow_up.xml", diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java index ff79bb2fe03784..a33674d0bfabda 100644 --- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java +++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java @@ -280,7 +280,7 @@ public StartSurfaceCoordinator(@NonNull Activity activity, new StartSurfaceMediator(controller, mTabModelSelector, mPropertyModel, mIsStartSurfaceEnabled ? this::initializeSecondaryTasksSurface : null, mIsStartSurfaceEnabled, mActivity, mBrowserControlsManager, - this::isActivityFinishingOrDestroyed, excludeMVTiles, + this::isActivityFinishingOrDestroyed, excludeMVTiles, excludeQueryTiles, startSurfaceOneshotSupplier, hadWarmStart, jankTracker); // Show feed loading image. diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java index df97897535acc1..c5a97e2e171a2b 100644 --- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java +++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java @@ -15,6 +15,7 @@ import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.MORE_TABS_CLICK_LISTENER; import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.MV_TILES_CONTAINER_TOP_MARGIN; import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.MV_TILES_VISIBLE; +import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.QUERY_TILES_VISIBLE; import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.RESET_TASK_SURFACE_HEADER_SCROLL_POSITION; import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.TAB_SWITCHER_TITLE_TOP_MARGIN; import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.TASKS_SURFACE_BODY_TOP_MARGIN; @@ -98,6 +99,7 @@ interface ActivityStateChecker { private final boolean mIsStartSurfaceEnabled; private final ObserverList mStateObservers = new ObserverList<>(); private final boolean mHadWarmStart; + private final boolean mExcludeQueryTiles; // Boolean histogram used to record whether cached // ChromePreferenceKeys.FEED_ARTICLES_LIST_VISIBLE is consistent with @@ -164,8 +166,8 @@ interface ActivityStateChecker { boolean isStartSurfaceEnabled, Context context, BrowserControlsStateProvider browserControlsStateProvider, ActivityStateChecker activityStateChecker, boolean excludeMVTiles, - OneshotSupplier startSurfaceSupplier, boolean hadWarmStart, - JankTracker jankTracker) { + boolean excludeQueryTiles, OneshotSupplier startSurfaceSupplier, + boolean hadWarmStart, JankTracker jankTracker) { mController = controller; mTabModelSelector = tabModelSelector; mPropertyModel = propertyModel; @@ -175,6 +177,7 @@ interface ActivityStateChecker { mBrowserControlsStateProvider = browserControlsStateProvider; mActivityStateChecker = activityStateChecker; mExcludeMVTiles = excludeMVTiles; + mExcludeQueryTiles = excludeQueryTiles; mStartSurfaceSupplier = startSurfaceSupplier; mHadWarmStart = hadWarmStart; mJankTracker = jankTracker; @@ -346,6 +349,7 @@ void setSecondaryTasksSurfacePropertyModel(PropertyModel propertyModel) { // Secondary tasks surface is used for more Tabs or incognito mode single pane, where MV // tiles and voice recognition button should be invisible. mSecondaryTasksSurfacePropertyModel.set(MV_TILES_VISIBLE, false); + mSecondaryTasksSurfacePropertyModel.set(QUERY_TILES_VISIBLE, false); mSecondaryTasksSurfacePropertyModel.set(IS_VOICE_RECOGNITION_BUTTON_VISIBLE, false); mSecondaryTasksSurfacePropertyModel.set(IS_LENS_BUTTON_VISIBLE, false); } @@ -491,6 +495,8 @@ private void setOverviewStateInternal() { setTabCarouselVisibility( hasNormalTab && !mIsIncognito && !mHideTabCarouselForNewSurface); setMVTilesVisibility(!mIsIncognito && !mHideMVForNewSurface); + // TODO(qinmin): show query tiles when flag is enabled. + setQueryTilesVisibility(false); setFakeBoxVisibility(!mIsIncognito); setSecondaryTasksSurfaceVisibility(mIsIncognito, /* skipUpdateController = */ false); @@ -506,6 +512,7 @@ private void setOverviewStateInternal() { } else if (mStartSurfaceState == StartSurfaceState.SHOWN_TABSWITCHER) { setTabCarouselVisibility(false); setMVTilesVisibility(false); + setQueryTilesVisibility(false); setFakeBoxVisibility(false); setSecondaryTasksSurfaceVisibility( /* isVisible= */ true, /* skipUpdateController = */ false); @@ -875,6 +882,11 @@ private void setMVTilesVisibility(boolean isVisible) { mPropertyModel.set(MV_TILES_VISIBLE, isVisible); } + private void setQueryTilesVisibility(boolean isVisible) { + if (mExcludeQueryTiles || isVisible == mPropertyModel.get(QUERY_TILES_VISIBLE)) return; + mPropertyModel.set(QUERY_TILES_VISIBLE, isVisible); + } + private void setFakeBoxVisibility(boolean isVisible) { if (mPropertyModel == null) return; mPropertyModel.set(IS_FAKE_SEARCH_BOX_VISIBLE, isVisible); diff --git a/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java b/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java index daf212db051473..f4f373029fbebc 100644 --- a/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java +++ b/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java @@ -1200,7 +1200,8 @@ private StartSurfaceMediator createStartSurfaceMediatorWithoutInit( isStartSurfaceEnabled ? mSecondaryTasksSurfaceInitializer : null, isStartSurfaceEnabled, ContextUtils.getApplicationContext(), mBrowserControlsStateProvider, mActivityStateChecker, excludeMVTiles, - mStartSurfaceSupplier, hadWarmStart, new DummyJankTracker()); + true /* excludeQueryTiles */, mStartSurfaceSupplier, hadWarmStart, + new DummyJankTracker()); return mediator; } } diff --git a/chrome/android/features/tab_ui/BUILD.gn b/chrome/android/features/tab_ui/BUILD.gn index 314a3fe3cfae96..2c70d093f21e38 100644 --- a/chrome/android/features/tab_ui/BUILD.gn +++ b/chrome/android/features/tab_ui/BUILD.gn @@ -68,6 +68,7 @@ android_resources("java_resources") { "java/res/layout/new_tab_tile_card_item.xml", "java/res/layout/price_card.xml", "java/res/layout/price_tracking_dialog_layout.xml", + "java/res/layout/query_tiles_layout.xml", "java/res/layout/selectable_tab_grid_card_item.xml", "java/res/layout/selectable_tab_list_card_item.xml", "java/res/layout/single_tab_view_layout.xml", diff --git a/chrome/android/features/tab_ui/java/res/layout/query_tiles_layout.xml b/chrome/android/features/tab_ui/java/res/layout/query_tiles_layout.xml new file mode 100644 index 00000000000000..f07d489d6adc09 --- /dev/null +++ b/chrome/android/features/tab_ui/java/res/layout/query_tiles_layout.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/chrome/android/features/tab_ui/java/res/layout/tasks_view_layout.xml b/chrome/android/features/tab_ui/java/res/layout/tasks_view_layout.xml index fa62d8070497e0..3a40bb6e7f2c85 100644 --- a/chrome/android/features/tab_ui/java/res/layout/tasks_view_layout.xml +++ b/chrome/android/features/tab_ui/java/res/layout/tasks_view_layout.xml @@ -33,6 +33,16 @@ app:layout_scrollFlags="scroll"> + + + MORE_TABS_CLICK_LISTENER = new PropertyModel.WritableObjectPropertyKey<>(); public static final PropertyModel.WritableBooleanPropertyKey MV_TILES_VISIBLE = IS_VISIBLE; + public static final PropertyModel.WritableBooleanPropertyKey QUERY_TILES_VISIBLE = + new PropertyModel.WritableBooleanPropertyKey(); public static final PropertyModel .WritableObjectPropertyKey VOICE_SEARCH_BUTTON_CLICK_LISTENER = new PropertyModel.WritableObjectPropertyKey<>(); @@ -87,7 +89,7 @@ private TasksSurfaceProperties() {} INCOGNITO_COOKIE_CONTROLS_TOGGLE_ENFORCEMENT, INCOGNITO_COOKIE_CONTROLS_MANAGER, INCOGNITO_LEARN_MORE_CLICK_LISTENER, FAKE_SEARCH_BOX_CLICK_LISTENER, FAKE_SEARCH_BOX_TEXT_WATCHER, LENS_BUTTON_CLICK_LISTENER, MORE_TABS_CLICK_LISTENER, - MV_TILES_VISIBLE, VOICE_SEARCH_BUTTON_CLICK_LISTENER, TASKS_SURFACE_BODY_TOP_MARGIN, - MV_TILES_CONTAINER_TOP_MARGIN, TAB_SWITCHER_TITLE_TOP_MARGIN, - RESET_TASK_SURFACE_HEADER_SCROLL_POSITION}; + MV_TILES_VISIBLE, QUERY_TILES_VISIBLE, VOICE_SEARCH_BUTTON_CLICK_LISTENER, + TASKS_SURFACE_BODY_TOP_MARGIN, MV_TILES_CONTAINER_TOP_MARGIN, + TAB_SWITCHER_TITLE_TOP_MARGIN, RESET_TASK_SURFACE_HEADER_SCROLL_POSITION}; } diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksView.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksView.java index 75874030ff05c3..3b17beb4730a63 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksView.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksView.java @@ -164,6 +164,13 @@ void setMostVisitedVisibility(int visibility) { findViewById(R.id.mv_tiles_container).setVisibility(visibility); } + /** + * Set the visibility of the Most Visited Tiles. + */ + void setQueryTilesVisibility(int visibility) { + findViewById(R.id.query_tiles_container).setVisibility(visibility); + } + /** * Set the {@link android.view.View.OnClickListener} for More Tabs. */ diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksViewBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksViewBinder.java index 857d0a40e7a58d..0f75b7aa68ef94 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksViewBinder.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksViewBinder.java @@ -24,6 +24,7 @@ import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.MORE_TABS_CLICK_LISTENER; import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.MV_TILES_CONTAINER_TOP_MARGIN; import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.MV_TILES_VISIBLE; +import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.QUERY_TILES_VISIBLE; import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.RESET_TASK_SURFACE_HEADER_SCROLL_POSITION; import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.TAB_SWITCHER_TITLE_TOP_MARGIN; import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.TASKS_SURFACE_BODY_TOP_MARGIN; @@ -91,6 +92,8 @@ public static void bind(PropertyModel model, TasksView view, PropertyKey propert view.setMoreTabsOnClickListener(model.get(MORE_TABS_CLICK_LISTENER)); } else if (propertyKey == MV_TILES_VISIBLE) { view.setMostVisitedVisibility(model.get(MV_TILES_VISIBLE) ? View.VISIBLE : View.GONE); + } else if (propertyKey == QUERY_TILES_VISIBLE) { + view.setQueryTilesVisibility(model.get(QUERY_TILES_VISIBLE) ? View.VISIBLE : View.GONE); } else if (propertyKey == VOICE_SEARCH_BUTTON_CLICK_LISTENER) { view.getSearchBoxCoordinator().addVoiceSearchButtonClickListener( model.get(VOICE_SEARCH_BUTTON_CLICK_LISTENER)); diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java index 38f5764c02cb59..ba9546db965c9c 100644 --- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java +++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java @@ -159,14 +159,10 @@ private void maybeUpdateSignInPromo() { // blocking the UI thread for several seconds if the accounts cache is not populated // yet. if (isVisible()) { - SigninPromoController.OnDismissListener dismissListener = null; - if (ChromeFeatureList.isEnabled(ChromeFeatureList.FEED_SIGNIN_PROMO_DISMISS)) { - dismissListener = this::onDismissPromo; - } mSigninPromoController.setUpSyncPromoView(mProfileDataCache, mCoordinator.getSigninPromoView().findViewById( R.id.signin_promo_view_container), - dismissListener); + this::onDismissPromo); } } diff --git a/chrome/android/java/res/values/drawables.xml b/chrome/android/java/res/values/drawables.xml index 90764a09511211..8569b73d402aa6 100644 --- a/chrome/android/java/res/values/drawables.xml +++ b/chrome/android/java/res/values/drawables.xml @@ -4,6 +4,7 @@ found in the LICENSE file. --> + @drawable/rounded_rectangle_surface_1 @drawable/modern_toolbar_text_box_background @drawable/badge_update_dark @drawable/ic_error_grey800_24dp_filled diff --git a/chrome/android/java/res/values/styles.xml b/chrome/android/java/res/values/styles.xml index 247fa6fb0f22d1..48045abbdf5da6 100644 --- a/chrome/android/java/res/values/styles.xml +++ b/chrome/android/java/res/values/styles.xml @@ -119,7 +119,7 @@ TODO(https://crbug.com/819142): Remove textAppearance when all TextViews have text style explicitly specified. --> "; LensSidePanelView::LensSidePanelView(content::BrowserContext* browser_context, base::RepeatingClosure close_callback, @@ -87,7 +94,12 @@ LensSidePanelView::LensSidePanelView(content::BrowserContext* browser_context, SetCrossAxisAlignment(views::LayoutAlignment::kStretch); CreateAndInstallHeader(close_callback, launch_callback); separator_ = AddChildView(std::make_unique()); + loading_indicator_web_view_ = AddChildView(CreateWebView(this, browser_context)); + loading_indicator_web_view_->GetWebContents()->GetController().LoadURL( + GURL(kStaticGhostCardDataURL), content::Referrer(), ui::PAGE_TRANSITION_FROM_API, + std::string()); web_view_ = AddChildView(CreateWebView(this, browser_context)); + web_view_->SetVisible(false); } content::WebContents* LensSidePanelView::GetWebContents() { @@ -99,13 +111,16 @@ void LensSidePanelView::OnThemeChanged() { const auto* color_provider = GetColorProvider(); separator_->SetColor(color_provider->GetColor(ui::kColorMenuSeparator)); - const SkColor color = color_provider->GetColor(ui::kColorIcon); // kGoogleLensFullLogoIcon is rectangular. We should create a tiled image so // that the coordinates and scale are correct. The vector icon should have its - // own fill color. - gfx::ImageSkia image = gfx::ImageSkiaOperations::CreateTiledImage( - gfx::CreateVectorIcon(kGoogleLensFullLogoIcon, color), 0, 0, - kGoogleLensLogoWidth, kGoogleLensLogoHeight); + // own fill color. The same applies to the dark mode icon. + const SkColor color = color_provider->GetColor(ui::kColorIcon); + const gfx::VectorIcon& icon = GetNativeTheme()->ShouldUseDarkColors() + ? kGoogleLensFullLogoDarkIcon + : kGoogleLensFullLogoIcon; + const gfx::ImageSkia image = gfx::ImageSkiaOperations::CreateTiledImage( + gfx::CreateVectorIcon(icon, color), 0, 0, kGoogleLensLogoWidth, + kGoogleLensLogoHeight); branding_->SetImage(image); } @@ -162,6 +177,11 @@ void LensSidePanelView::CreateAndInstallHeader( AddChildView(std::move(header)); } +void LensSidePanelView::SetContentVisible(bool visible) { + web_view_->SetVisible(visible); + loading_indicator_web_view_->SetVisible(!visible); +} + LensSidePanelView::~LensSidePanelView() = default; } // namespace lens diff --git a/chrome/browser/ui/views/lens/lens_side_panel_view.h b/chrome/browser/ui/views/lens/lens_side_panel_view.h index 501669e0687516..8f3e42de158687 100644 --- a/chrome/browser/ui/views/lens/lens_side_panel_view.h +++ b/chrome/browser/ui/views/lens/lens_side_panel_view.h @@ -36,12 +36,15 @@ class LensSidePanelView : public views::FlexLayoutView { // views::FlexLayoutView: void OnThemeChanged() override; + void SetContentVisible(bool visible); + private: void CreateAndInstallHeader(base::RepeatingClosure close_callback, base::RepeatingClosure launch_callback); raw_ptr branding_; raw_ptr separator_; + raw_ptr loading_indicator_web_view_; raw_ptr web_view_; raw_ptr close_button_; raw_ptr launch_button_; diff --git a/chrome/browser/ui/views/media_router/cast_toolbar_button.cc b/chrome/browser/ui/views/media_router/cast_toolbar_button.cc index 84b41b4d0abcd0..18990a8f7ff7e2 100644 --- a/chrome/browser/ui/views/media_router/cast_toolbar_button.cc +++ b/chrome/browser/ui/views/media_router/cast_toolbar_button.cc @@ -117,8 +117,7 @@ void CastToolbarButton::OnIssuesCleared() { } void CastToolbarButton::OnRoutesUpdated( - const std::vector& routes, - const std::vector& joinable_route_ids) { + const std::vector& routes) { has_local_display_route_ = std::find_if(routes.begin(), routes.end(), [](const media_router::MediaRoute& route) { diff --git a/chrome/browser/ui/views/media_router/cast_toolbar_button.h b/chrome/browser/ui/views/media_router/cast_toolbar_button.h index 74b19e06c3ed24..be2bef2a0be03b 100644 --- a/chrome/browser/ui/views/media_router/cast_toolbar_button.h +++ b/chrome/browser/ui/views/media_router/cast_toolbar_button.h @@ -53,9 +53,8 @@ class CastToolbarButton : public ToolbarButton, void OnIssuesCleared() override; // media_router::MediaRoutesObserver: - void OnRoutesUpdated(const std::vector& routes, - const std::vector& - joinable_route_ids) override; + void OnRoutesUpdated( + const std::vector& routes) override; // ToolbarButton: bool OnMousePressed(const ui::MouseEvent& event) override; diff --git a/chrome/browser/ui/views/media_router/cast_toolbar_button_unittest.cc b/chrome/browser/ui/views/media_router/cast_toolbar_button_unittest.cc index 2aa7a5217f8e19..5629287123a495 100644 --- a/chrome/browser/ui/views/media_router/cast_toolbar_button_unittest.cc +++ b/chrome/browser/ui/views/media_router/cast_toolbar_button_unittest.cc @@ -187,15 +187,15 @@ TEST_F(CastToolbarButtonTest, UpdateRoutes) { button_->UpdateIcon(); EXPECT_TRUE(gfx::test::AreImagesEqual(idle_icon_, GetIcon())); - button_->OnRoutesUpdated(local_display_route_list_, {}); + button_->OnRoutesUpdated(local_display_route_list_); EXPECT_TRUE(gfx::test::AreImagesEqual(active_icon_, GetIcon())); // The idle icon should be shown when we only have non-local and/or // non-display routes. - button_->OnRoutesUpdated(non_local_display_route_list_, {}); + button_->OnRoutesUpdated(non_local_display_route_list_); EXPECT_TRUE(gfx::test::AreImagesEqual(idle_icon_, GetIcon())); - button_->OnRoutesUpdated({}, {}); + button_->OnRoutesUpdated({}); EXPECT_TRUE(gfx::test::AreImagesEqual(idle_icon_, GetIcon())); } diff --git a/chrome/browser/ui/views/media_router/media_router_ui_browsertest.cc b/chrome/browser/ui/views/media_router/media_router_ui_browsertest.cc index b41fd0f75bc1ab..02c1a38ea83a02 100644 --- a/chrome/browser/ui/views/media_router/media_router_ui_browsertest.cc +++ b/chrome/browser/ui/views/media_router/media_router_ui_browsertest.cc @@ -200,10 +200,9 @@ IN_PROC_BROWSER_TEST_F(MediaRouterUIBrowserTest, action_controller_->OnIssuesCleared(); EXPECT_FALSE(ToolbarIconExists()); - action_controller_->OnRoutesUpdated(routes_, std::vector()); + action_controller_->OnRoutesUpdated(routes_); EXPECT_TRUE(ToolbarIconExists()); - action_controller_->OnRoutesUpdated(std::vector(), - std::vector()); + action_controller_->OnRoutesUpdated(std::vector()); EXPECT_FALSE(ToolbarIconExists()); SetAlwaysShowActionPref(true); @@ -214,7 +213,7 @@ IN_PROC_BROWSER_TEST_F(MediaRouterUIBrowserTest, IN_PROC_BROWSER_TEST_F(MediaRouterUIBrowserTest, EphemeralToolbarIconWithMultipleWindows) { - action_controller_->OnRoutesUpdated(routes_, std::vector()); + action_controller_->OnRoutesUpdated(routes_); EXPECT_TRUE(ToolbarIconExists()); // Opening and closing a window shouldn't affect the state of the ephemeral @@ -222,10 +221,9 @@ IN_PROC_BROWSER_TEST_F(MediaRouterUIBrowserTest, // also work. Browser* browser2 = CreateBrowser(browser()->profile()); EXPECT_TRUE(ToolbarIconExists()); - action_controller_->OnRoutesUpdated(std::vector(), - std::vector()); + action_controller_->OnRoutesUpdated(std::vector()); EXPECT_FALSE(ToolbarIconExists()); - action_controller_->OnRoutesUpdated(routes_, std::vector()); + action_controller_->OnRoutesUpdated(routes_); EXPECT_TRUE(ToolbarIconExists()); browser2->window()->Close(); EXPECT_TRUE(ToolbarIconExists()); diff --git a/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.cc b/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.cc index 5205716ee2d62c..104c5239e25fb4 100644 --- a/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.cc +++ b/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.cc @@ -358,7 +358,7 @@ void OmniboxPopupContentsView::UpdatePopupAppearance() { } // Fix-up any matches due to tail suggestions, before display below. - edit_model_->autocomplete_controller()->InlineTailPrefixes(); + edit_model_->autocomplete_controller()->SetTailSuggestContentPrefixes(); // Update the match cached by each row, in the process of doing so make sure // we have enough row views. diff --git a/chrome/browser/ui/views/page_info/page_info_history_controller.cc b/chrome/browser/ui/views/page_info/page_info_history_controller.cc index bdc20d1ba03a35..acd2b193dce033 100644 --- a/chrome/browser/ui/views/page_info/page_info_history_controller.cc +++ b/chrome/browser/ui/views/page_info/page_info_history_controller.cc @@ -50,17 +50,14 @@ void PageInfoHistoryController::UpdateRow(base::Time last_visit) { std::unique_ptr PageInfoHistoryController::CreateHistoryButton( std::u16string last_visit) { - // TODO(crbug.com/1275042): Use correct icons and strings (title, tooltip). - // TODO(crbug.com/1275042): Add on click handler to open the history webpage. - auto history_button = std::make_unique( + // TODO(crbug.com/1275042): Use correct icons and strings (tooltip). + return std::make_unique( base::BindRepeating(&PageInfoHistoryController::OpenHistoryPage, weak_factory_.GetWeakPtr()), - PageInfoViewFactory::GetSiteSettingsIcon(), IDS_PAGE_INFO_COOKIES, - last_visit, 0, + PageInfoViewFactory::GetSiteSettingsIcon(), IDS_PAGE_INFO_HISTORY, + last_visit, PageInfoViewFactory::VIEW_ID_PAGE_INFO_HISTORY_BUTTON, /*tooltip_text=*/std::u16string(), std::u16string(), PageInfoViewFactory::GetLaunchIcon()); - history_button->SetTitleText(u"History"); - return history_button; } void PageInfoHistoryController::OpenHistoryPage() { diff --git a/chrome/browser/ui/views/page_info/page_info_view_factory.h b/chrome/browser/ui/views/page_info/page_info_view_factory.h index dfc2362c4477b5..da2d5152e3f205 100644 --- a/chrome/browser/ui/views/page_info/page_info_view_factory.h +++ b/chrome/browser/ui/views/page_info/page_info_view_factory.h @@ -55,6 +55,7 @@ class PageInfoViewFactory { VIEW_ID_PAGE_INFO_CURRENT_VIEW, VIEW_ID_PAGE_INFO_RESET_PERMISSIONS_BUTTON, VIEW_ID_PAGE_INFO_ABOUT_THIS_SITE_BUTTON, + VIEW_ID_PAGE_INFO_HISTORY_BUTTON }; // Creates a separator view with padding on top and bottom. Use with flex diff --git a/chrome/browser/ui/views/payments/payment_request_row_view.cc b/chrome/browser/ui/views/payments/payment_request_row_view.cc index 0ff097247d1142..84d9c78093e49c 100644 --- a/chrome/browser/ui/views/payments/payment_request_row_view.cc +++ b/chrome/browser/ui/views/payments/payment_request_row_view.cc @@ -14,7 +14,10 @@ #include "ui/color/color_provider.h" #include "ui/views/background.h" #include "ui/views/border.h" +#include "ui/views/controls/focus_ring.h" #include "ui/views/controls/label.h" +#include "ui/views/layout/table_layout.h" +#include "ui/views/view_class_properties.h" #include "ui/views/view_utils.h" #include "ui/views/widget/widget.h" @@ -168,6 +171,11 @@ void PaymentRequestRowView::OnFocus() { SchedulePaint(); } View::OnFocus(); + views::FocusRing* focus_ring = views::FocusRing::Get(this); + views::TableLayout* layout = + static_cast(GetLayoutManager()); + if (focus_ring && layout) + layout->SetChildViewIgnoredByLayout(focus_ring, true); } void PaymentRequestRowView::OnBlur() { diff --git a/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc b/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc index 688f330c8448e3..f49e84de237b43 100644 --- a/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc +++ b/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc @@ -59,8 +59,9 @@ #include "ui/views/controls/label.h" #include "ui/views/controls/styled_label.h" #include "ui/views/layout/box_layout.h" -#include "ui/views/layout/fill_layout.h" -#include "ui/views/layout/grid_layout.h" +#include "ui/views/layout/box_layout_view.h" +#include "ui/views/layout/table_layout.h" +#include "ui/views/layout/table_layout_view.h" #include "ui/views/view.h" namespace payments { @@ -132,8 +133,33 @@ std::unique_ptr CreatePaymentSheetRow( std::unique_ptr trailing_button, bool clickable, bool extra_trailing_inset, - views::GridLayout::Alignment vertical_alignment = - views::GridLayout::LEADING) { + views::LayoutAlignment vertical_alignment = + views::LayoutAlignment::kStart) { + constexpr int kNameColumnWidth = 112; + constexpr int kPaddingAfterName = 32; + constexpr int kPaddingColumnsWidth = 25; + + auto table_layout = std::make_unique(); + table_layout + // A column for the section name. + ->AddColumn(views::LayoutAlignment::kStart, vertical_alignment, + views::TableLayout::kFixedSize, + views::TableLayout::ColumnSize::kFixed, kNameColumnWidth, 0) + .AddPaddingColumn(views::TableLayout::kFixedSize, kPaddingAfterName) + // A column for the content. + .AddColumn(views::LayoutAlignment::kStretch, vertical_alignment, 1.0, + views::TableLayout::ColumnSize::kUsePreferred, 0, 0) + // A column for the extra content. + .AddColumn(views::LayoutAlignment::kEnd, views::LayoutAlignment::kCenter, + views::TableLayout::kFixedSize, + views::TableLayout::ColumnSize::kUsePreferred, 0, 0) + .AddPaddingColumn(views::TableLayout::kFixedSize, kPaddingColumnsWidth) + // A column for the trailing_button. + .AddColumn(views::LayoutAlignment::kEnd, views::LayoutAlignment::kCenter, + views::TableLayout::kFixedSize, + views::TableLayout::ColumnSize::kUsePreferred, 0, 0) + .AddRows(1, views::TableLayout::kFixedSize); + const int trailing_inset = extra_trailing_inset ? kPaymentRequestRowHorizontalInsets + kPaymentRequestRowExtraRightInset @@ -141,66 +167,28 @@ std::unique_ptr CreatePaymentSheetRow( const gfx::Insets row_insets( kPaymentRequestRowVerticalInsets, kPaymentRequestRowHorizontalInsets, kPaymentRequestRowVerticalInsets, trailing_inset); - std::unique_ptr row = - std::make_unique(std::move(callback), clickable, - row_insets); - views::GridLayout* layout = - row->SetLayoutManager(std::make_unique()); - - views::ColumnSet* columns = layout->AddColumnSet(0); - // A column for the section name. - constexpr int kNameColumnWidth = 112; - columns->AddColumn(views::GridLayout::LEADING, vertical_alignment, - views::GridLayout::kFixedSize, - views::GridLayout::ColumnSize::kFixed, kNameColumnWidth, - 0); - - constexpr int kPaddingAfterName = 32; - columns->AddPaddingColumn(views::GridLayout::kFixedSize, kPaddingAfterName); - - // A column for the content. - columns->AddColumn(views::GridLayout::FILL, vertical_alignment, 1.0, - views::GridLayout::ColumnSize::kUsePreferred, 0, 0); - // A column for the extra content. - columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER, - views::GridLayout::kFixedSize, - views::GridLayout::ColumnSize::kUsePreferred, 0, 0); - - constexpr int kPaddingColumnsWidth = 25; - columns->AddPaddingColumn(views::GridLayout::kFixedSize, - kPaddingColumnsWidth); - // A column for the trailing_button. - columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER, - views::GridLayout::kFixedSize, - views::GridLayout::ColumnSize::kUsePreferred, 0, 0); - - layout->StartRow(views::GridLayout::kFixedSize, 0); - std::unique_ptr name_label = CreateMediumLabel(section_name); - name_label->SetMultiLine(true); - name_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); - layout->AddView(std::move(name_label)); - - if (content_view) { - content_view->SetCanProcessEventsWithinSubtree(false); - layout->AddView(std::move(content_view)); - } else { - layout->SkipColumns(1); - } - - if (extra_content_view) { - extra_content_view->SetCanProcessEventsWithinSubtree(false); - layout->AddView(std::move(extra_content_view)); - } else { - layout->SkipColumns(1); - } - layout->AddView(std::move(trailing_button)); - - row->SetAccessibleName( - l10n_util::GetStringFUTF16(IDS_PAYMENTS_ROW_ACCESSIBLE_NAME_FORMAT, - section_name, accessible_content)); - - return row; + return views::Builder() + .SetLayoutManager(std::move(table_layout)) + .SetCallback(std::move(callback)) + .SetClickable(clickable) + .SetRowInsets(row_insets) + .SetAccessibleName( + l10n_util::GetStringFUTF16(IDS_PAYMENTS_ROW_ACCESSIBLE_NAME_FORMAT, + section_name, accessible_content)) + .AddChildren( + views::Builder(CreateMediumLabel(section_name)) + .SetMultiLine(true) + .SetHorizontalAlignment(gfx::ALIGN_LEFT), + content_view ? views::Builder(std::move(content_view)) + .SetCanProcessEventsWithinSubtree(false) + : views::Builder(), + extra_content_view + ? views::Builder(std::move(extra_content_view)) + .SetCanProcessEventsWithinSubtree(false) + : views::Builder(), + views::Builder(std::move(trailing_button))) + .Build(); } std::unique_ptr CreateInlineCurrencyAmountItem( @@ -208,39 +196,26 @@ std::unique_ptr CreateInlineCurrencyAmountItem( const std::u16string& amount, bool hint_color, bool bold) { - std::unique_ptr item_amount_line = - std::make_unique(); - views::GridLayout* item_amount_layout = - item_amount_line->SetLayoutManager(std::make_unique()); - views::ColumnSet* item_amount_columns = item_amount_layout->AddColumnSet(0); - item_amount_columns->AddColumn( - views::GridLayout::LEADING, views::GridLayout::LEADING, - views::GridLayout::kFixedSize, - views::GridLayout::ColumnSize::kUsePreferred, 0, 0); - item_amount_columns->AddColumn( - views::GridLayout::TRAILING, views::GridLayout::LEADING, 1.0, - views::GridLayout::ColumnSize::kUsePreferred, 0, 0); - DCHECK(!bold || !hint_color); - std::unique_ptr currency_label; - if (bold) - currency_label = CreateBoldLabel(currency); - else if (hint_color) - currency_label = CreateHintLabel(currency); - else - currency_label = std::make_unique(currency); - - std::unique_ptr amount_label = - bold ? CreateBoldLabel(amount) : std::make_unique(amount); - amount_label->SetMultiLine(true); - amount_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); - amount_label->SetAllowCharacterBreak(true); - - item_amount_layout->StartRow(views::GridLayout::kFixedSize, 0); - item_amount_layout->AddView(std::move(currency_label)); - item_amount_layout->AddView(std::move(amount_label)); - - return item_amount_line; + return views::Builder() + .AddColumn(views::LayoutAlignment::kStart, views::LayoutAlignment::kStart, + views::TableLayout::kFixedSize, + views::TableLayout::ColumnSize::kUsePreferred, 0, 0) + .AddColumn(views::LayoutAlignment::kEnd, views::LayoutAlignment::kStart, + 1.0, views::TableLayout::ColumnSize::kUsePreferred, 0, 0) + .AddRows(1, views::TableLayout::kFixedSize, 0) + .AddChildren((bold ? views::Builder(CreateBoldLabel(u"")) + : (hint_color ? views::Builder( + CreateHintLabel(u"")) + : views::Builder())) + .SetText(currency), + (bold ? views::Builder(CreateBoldLabel(u"")) + : views::Builder()) + .SetText(amount) + .SetMultiLine(true) + .SetHorizontalAlignment(gfx::ALIGN_LEFT) + .SetAllowCharacterBreak(true)) + .Build(); } // A class used to build Payment Sheet Rows. Construct an instance of it, chain @@ -351,7 +326,7 @@ class PaymentSheetRowBuilder { views::Button::PressedCallback(), section_name_, accessible_content_, std::move(content_view), nullptr, std::move(button), /*clickable=*/false, - /*extra_trailing_inset=*/false, views::GridLayout::CENTER); + /*extra_trailing_inset=*/false, views::LayoutAlignment::kCenter); } views::Button::PressedCallback GetPressedCallback() const { @@ -424,38 +399,32 @@ void PaymentSheetViewController::FillContentView(views::View* content_view) { if (!spec()) return; - views::GridLayout* layout = - content_view->SetLayoutManager(std::make_unique()); - views::ColumnSet* columns = layout->AddColumnSet(0); - columns->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER, 1.0, - views::GridLayout::ColumnSize::kUsePreferred, 0, 0); + auto builder = views::Builder(content_view) + .SetLayoutManager(std::make_unique( + views::BoxLayout::Orientation::kVertical)); if (!spec()->retry_error_message().empty()) { - std::unique_ptr warning_view = - CreateWarningView(spec()->retry_error_message(), true /* show_icon */); - layout->StartRow(views::GridLayout::kFixedSize, 0); - layout->AddView(std::move(warning_view)); + builder.AddChild(views::Builder(CreateWarningView( + spec()->retry_error_message(), true /* show_icon */))); } // The shipping address and contact info rows are optional. std::unique_ptr summary_row = CreatePaymentSheetSummaryRow(); if (!summary_row) - return; + return std::move(builder).BuildChildren(); PaymentRequestRowView* previous_row = summary_row.get(); - layout->StartRow(views::GridLayout::kFixedSize, 0); - layout->AddView(std::move(summary_row)); + builder.AddChild(views::Builder(std::move(summary_row))); if (state()->ShouldShowShippingSection()) { std::unique_ptr shipping_row = CreateShippingRow(); if (!shipping_row) - return; + return std::move(builder).BuildChildren(); shipping_row->set_previous_row(previous_row->AsWeakPtr()); previous_row = shipping_row.get(); - layout->StartRow(views::GridLayout::kFixedSize, 0); - layout->AddView(std::move(shipping_row)); + builder.AddChild(views::Builder(std::move(shipping_row))); // It's possible for requestShipping to be true and for there to be no // shipping options yet (they will come in updateWith). // TODO(crbug.com/707353): Put a better placeholder row, instead of no row. @@ -464,26 +433,25 @@ void PaymentSheetViewController::FillContentView(views::View* content_view) { if (shipping_option_row) { shipping_option_row->set_previous_row(previous_row->AsWeakPtr()); previous_row = shipping_option_row.get(); - layout->StartRow(views::GridLayout::kFixedSize, 0); - layout->AddView(std::move(shipping_option_row)); + builder.AddChild( + views::Builder(std::move(shipping_option_row))); } } std::unique_ptr payment_method_row = CreatePaymentMethodRow(); payment_method_row->set_previous_row(previous_row->AsWeakPtr()); previous_row = payment_method_row.get(); - layout->StartRow(views::GridLayout::kFixedSize, 0); - layout->AddView(std::move(payment_method_row)); + builder.AddChild(views::Builder(std::move(payment_method_row))); if (state()->ShouldShowContactSection()) { std::unique_ptr contact_info_row = CreateContactInfoRow(); contact_info_row->set_previous_row(previous_row->AsWeakPtr()); previous_row = contact_info_row.get(); - layout->StartRow(views::GridLayout::kFixedSize, 0); - layout->AddView(std::move(contact_info_row)); + builder.AddChild(views::Builder(std::move(contact_info_row))); } - layout->StartRow(views::GridLayout::kFixedSize, 0); - layout->AddView(CreateDataSourceRow()); + builder.AddChild(views::Builder(CreateDataSourceRow())); + + std::move(builder).BuildChildren(); } // Adds the product logo to the footer. @@ -509,16 +477,16 @@ PaymentSheetViewController::CreatePaymentSheetSummaryRow() { if (!spec()) return nullptr; - std::unique_ptr inline_summary = std::make_unique(); - views::GridLayout* layout = - inline_summary->SetLayoutManager(std::make_unique()); - views::ColumnSet* columns = layout->AddColumnSet(0); - columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING, - 1.0, views::GridLayout::ColumnSize::kUsePreferred, 0, 0); constexpr int kItemSummaryPriceFixedWidth = 96; - columns->AddColumn(views::GridLayout::FILL, views::GridLayout::LEADING, - views::GridLayout::kFixedSize, - views::GridLayout::ColumnSize::kFixed, + auto view_builder = + views::Builder() + .AddColumn(views::LayoutAlignment::kStart, + views::LayoutAlignment::kStart, 1.0, + views::TableLayout::ColumnSize::kUsePreferred, 0, 0) + .AddColumn(views::LayoutAlignment::kStretch, + views::LayoutAlignment::kStart, + views::TableLayout::kFixedSize, + views::TableLayout::ColumnSize::kFixed, kItemSummaryPriceFixedWidth, kItemSummaryPriceFixedWidth); const std::vector& items = @@ -536,48 +504,49 @@ PaymentSheetViewController::CreatePaymentSheetSummaryRow() { ? items.size() : kMaxNumberOfItemsShown; for (size_t i = 0; i < items.size() && i < displayed_items; ++i) { - layout->StartRow(views::GridLayout::kFixedSize, 0); - std::unique_ptr summary = - std::make_unique(base::UTF8ToUTF16((*items[i])->label)); - summary->SetHorizontalAlignment(gfx::ALIGN_LEFT); - layout->AddView(std::move(summary)); - - layout->AddView(CreateInlineCurrencyAmountItem( - is_mixed_currency ? base::UTF8ToUTF16(spec()->GetFormattedCurrencyCode( - (*items[i])->amount)) - : std::u16string(), - spec()->GetFormattedCurrencyAmount((*items[i])->amount), true, false)); + view_builder.AddRows(1, views::TableLayout::kFixedSize, 0) + .AddChildren( + views::Builder() + .SetText(base::UTF8ToUTF16((*items[i])->label)) + .SetHorizontalAlignment(gfx::ALIGN_LEFT), + views::Builder(CreateInlineCurrencyAmountItem( + is_mixed_currency + ? base::UTF8ToUTF16( + spec()->GetFormattedCurrencyCode((*items[i])->amount)) + : std::u16string(), + spec()->GetFormattedCurrencyAmount((*items[i])->amount), true, + false))); } size_t hidden_item_count = items.size() - displayed_items; if (hidden_item_count > 0) { - layout->StartRow(views::GridLayout::kFixedSize, 0); - std::unique_ptr label = - CreateHintLabel(l10n_util::GetPluralStringFUTF16( - IDS_PAYMENT_REQUEST_ORDER_SUMMARY_MORE_ITEMS, hidden_item_count)); - layout->AddView(std::move(label)); - if (is_mixed_currency) { - std::unique_ptr multiple_currency_label = - CreateHintLabel(l10n_util::GetStringUTF16( - IDS_PAYMENT_REQUEST_ORDER_SUMMARY_MULTIPLE_CURRENCY_INDICATOR)); - layout->AddView(std::move(multiple_currency_label)); - } + view_builder.AddRows(1, views::TableLayout::kFixedSize, 0) + .AddChildren( + views::Builder( + CreateHintLabel(l10n_util::GetPluralStringFUTF16( + IDS_PAYMENT_REQUEST_ORDER_SUMMARY_MORE_ITEMS, + hidden_item_count))), + is_mixed_currency + ? views::Builder< + views::View>(CreateHintLabel(l10n_util::GetStringUTF16( + IDS_PAYMENT_REQUEST_ORDER_SUMMARY_MULTIPLE_CURRENCY_INDICATOR))) + : views::Builder()); } - layout->StartRow(views::GridLayout::kFixedSize, 0); PaymentApp* selected_app = state()->selected_app(); const mojom::PaymentItemPtr& total = spec()->GetTotal(selected_app); std::u16string total_label_text = base::UTF8ToUTF16(total->label); - std::unique_ptr total_label = CreateBoldLabel(total_label_text); - layout->AddView(std::move(total_label)); - std::u16string total_currency_code = base::UTF8ToUTF16(spec()->GetFormattedCurrencyCode( spec()->GetTotal(state()->selected_app())->amount)); std::u16string total_amount = spec()->GetFormattedCurrencyAmount( spec()->GetTotal(state()->selected_app())->amount); - layout->AddView(CreateInlineCurrencyAmountItem(total_currency_code, - total_amount, false, true)); + + view_builder.AddRows(1, views::TableLayout::kFixedSize, 0) + .AddChildren( + views::Builder(CreateBoldLabel(total_label_text)), + views::Builder(CreateInlineCurrencyAmountItem( + total_currency_code, total_amount, false, true))); PaymentSheetRowBuilder builder( this, l10n_util::GetStringUTF16(IDS_PAYMENTS_ORDER_SUMMARY_LABEL)); @@ -591,7 +560,7 @@ PaymentSheetViewController::CreatePaymentSheetSummaryRow() { IDS_PAYMENT_REQUEST_ORDER_SUMMARY_SECTION_TOTAL_FORMAT, total_label_text, total_currency_code, total_amount))); - return builder.CreateWithChevron(std::move(inline_summary), nullptr); + return builder.CreateWithChevron(std::move(view_builder).Build(), nullptr); } std::unique_ptr @@ -690,29 +659,27 @@ PaymentSheetViewController::CreatePaymentMethodRow() { : base::BindRepeating( &PaymentRequestDialogView::ShowPaymentMethodSheet, dialog())); - if (selected_app) { - auto content_view = std::make_unique(); - - views::GridLayout* layout = - content_view->SetLayoutManager(std::make_unique()); - views::ColumnSet* columns = layout->AddColumnSet(0); - columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER, - 1.0, views::GridLayout::ColumnSize::kUsePreferred, 0, 0); - layout->StartRow(views::GridLayout::kFixedSize, 0); - layout->AddView(std::make_unique(selected_app->GetLabel())) - ->SetHorizontalAlignment(gfx::ALIGN_LEFT); - - layout->StartRow(views::GridLayout::kFixedSize, 0); - layout->AddView(std::make_unique(selected_app->GetSublabel())) - ->SetHorizontalAlignment(gfx::ALIGN_LEFT); + if (selected_app) { + auto content_view = + views::Builder() + .SetOrientation(views::BoxLayout::Orientation::kVertical) + .SetCrossAxisAlignment(views::BoxLayout::CrossAxisAlignment::kStart) + .SetMainAxisAlignment(views::BoxLayout::MainAxisAlignment::kCenter) + .AddChildren(views::Builder() + .SetText(selected_app->GetLabel()) + .SetHorizontalAlignment(gfx::ALIGN_LEFT), + views::Builder() + .SetText(selected_app->GetSublabel()) + .SetHorizontalAlignment(gfx::ALIGN_LEFT)); std::unique_ptr icon_view = CreateAppIconView( selected_app->icon_resource_id(), selected_app->icon_bitmap(), selected_app->GetLabel()); return builder.AccessibleContent(selected_app->GetLabel()) - .CreateWithChevron(std::move(content_view), std::move(icon_view)); + .CreateWithChevron(std::move(content_view).Build(), + std::move(icon_view)); } if (state()->available_apps().empty()) { return builder.CreateWithButton(std::u16string(), @@ -858,15 +825,6 @@ PaymentSheetViewController::CreateShippingOptionRow() { } std::unique_ptr PaymentSheetViewController::CreateDataSourceRow() { - std::unique_ptr content_view = std::make_unique(); - auto layout = std::make_unique( - views::BoxLayout::Orientation::kVertical, - gfx::Insets(0, kPaymentRequestRowHorizontalInsets)); - layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart); - layout->set_cross_axis_alignment( - views::BoxLayout::CrossAxisAlignment::kStart); - content_view->SetLayoutManager(std::move(layout)); - std::u16string data_source; // If no transaction has been completed so far, choose which string to display // as a function of the profile's signed in state. Otherwise, always show the @@ -909,13 +867,6 @@ std::unique_ptr PaymentSheetViewController::CreateDataSourceRow() { data_source.erase(link_end, end_tag.size()); data_source.erase(link_begin, begin_tag.size()); - auto data_source_label = std::make_unique(); - data_source_label->SetText(data_source); - - data_source_label->SetBorder(views::CreateEmptyBorder(22, 0, 0, 0)); - data_source_label->SetID(static_cast(DialogViewID::DATA_SOURCE_LABEL)); - data_source_label->SetDefaultTextStyle(views::style::STYLE_DISABLED); - views::StyledLabel::RangeStyleInfo link_style = views::StyledLabel::RangeStyleInfo::CreateForLink(base::BindRepeating( [](base::WeakPtr dialog) { @@ -929,11 +880,21 @@ std::unique_ptr PaymentSheetViewController::CreateDataSourceRow() { // TODO(pbos): Investigate whether this override is necessary. link_style.override_color = gfx::kGoogleBlue700; - data_source_label->AddStyleRange( - gfx::Range(link_begin, link_begin + link_length), link_style); - data_source_label->SizeToFit(0); - content_view->AddChildView(data_source_label.release()); - return content_view; + return views::Builder() + .SetOrientation(views::BoxLayout::Orientation::kVertical) + .SetInsideBorderInsets(gfx::Insets(0, kPaymentRequestRowHorizontalInsets)) + .SetMainAxisAlignment(views::BoxLayout::MainAxisAlignment::kStart) + .SetCrossAxisAlignment(views::BoxLayout::CrossAxisAlignment::kStart) + .AddChild( + views::Builder() + .SetText(data_source) + .SetBorder(views::CreateEmptyBorder(22, 0, 0, 0)) + .SetID(static_cast(DialogViewID::DATA_SOURCE_LABEL)) + .SetDefaultTextStyle(views::style::STYLE_DISABLED) + .AddStyleRange(gfx::Range(link_begin, link_begin + link_length), + link_style) + .SizeToFit(0)) + .Build(); } void PaymentSheetViewController::AddShippingButtonPressed() { diff --git a/chrome/browser/ui/views/profiles/profile_picker_view.cc b/chrome/browser/ui/views/profiles/profile_picker_view.cc index c273cce58c93e5..693b4bf8816a3d 100644 --- a/chrome/browser/ui/views/profiles/profile_picker_view.cc +++ b/chrome/browser/ui/views/profiles/profile_picker_view.cc @@ -11,6 +11,7 @@ #include "base/files/file_path.h" #include "base/memory/raw_ptr.h" #include "base/metrics/histogram_functions.h" +#include "base/trace_event/trace_event.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" #include "chrome/app/chrome_command_ids.h" @@ -440,6 +441,7 @@ void ProfilePickerView::ShowScreenInPickerContents( } void ProfilePickerView::Clear() { + TRACE_EVENT1("browser,startup", "ProfilePickerView::Clear", "state", state_); if (state_ == kClosing) return; @@ -530,6 +532,8 @@ bool ProfilePickerView::ShouldReopen( void ProfilePickerView::Display(ProfilePicker::EntryPoint entry_point) { DCHECK_NE(state_, kClosing); + TRACE_EVENT2("browser,startup", "ProfilePickerView::Display", "entry_point", + entry_point, "state", state_); // Record creation metrics. base::UmaHistogramEnumeration("ProfilePicker.Shown", entry_point); if (entry_point == ProfilePicker::EntryPoint::kOnStartup) { @@ -564,6 +568,10 @@ void ProfilePickerView::Display(ProfilePicker::EntryPoint entry_point) { void ProfilePickerView::OnPickerProfileCreated(Profile* picker_profile, Profile::CreateStatus status) { + TRACE_EVENT2("browser,startup", "ProfilePickerView::OnPickerProfileCreated", + "profile_path", + (picker_profile ? picker_profile->GetPath().AsUTF8Unsafe() : ""), + "status", status); DCHECK_NE(status, Profile::CREATE_STATUS_LOCAL_FAIL); if (status != Profile::CREATE_STATUS_INITIALIZED) return; @@ -573,6 +581,9 @@ void ProfilePickerView::OnPickerProfileCreated(Profile* picker_profile, void ProfilePickerView::Init(Profile* picker_profile) { DCHECK_EQ(state_, kInitializing); + TRACE_EVENT1( + "browser,startup", "ProfilePickerView::Init", "profile_path", + (picker_profile ? picker_profile->GetPath().AsUTF8Unsafe() : "")); contents_ = content::WebContents::Create( content::WebContents::CreateParams(picker_profile)); contents_->SetDelegate(this); diff --git a/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc b/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc index c0a88a0d08c4a5..205472b5dda3ce 100644 --- a/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc +++ b/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc @@ -4,12 +4,14 @@ #include "chrome/browser/ui/views/profiles/profile_picker_view.h" +#include "base/barrier_closure.h" #include "base/callback_helpers.h" #include "base/json/values_util.h" #include "base/memory/raw_ptr.h" #include "base/scoped_observation.h" #include "base/strings/utf_string_conversions.h" #include "base/test/bind.h" +#include "base/test/metrics/histogram_tester.h" #include "base/test/mock_callback.h" #include "base/test/scoped_feature_list.h" #include "base/time/time.h" @@ -18,6 +20,7 @@ #include "chrome/browser/browser_process.h" #include "chrome/browser/feature_engagement/tracker_factory.h" #include "chrome/browser/interstitials/chrome_settings_page_helper.h" +#include "chrome/browser/metrics/first_web_contents_profiler_base.h" #include "chrome/browser/policy/cloud/user_policy_signin_service.h" #include "chrome/browser/policy/cloud/user_policy_signin_service_factory.h" #include "chrome/browser/profiles/profile_attributes_entry.h" @@ -65,6 +68,7 @@ #include "components/signin/public/identity_manager/identity_manager.h" #include "components/signin/public/identity_manager/identity_test_utils.h" #include "components/signin/public/identity_manager/primary_account_mutator.h" +#include "components/startup_metric_utils/browser/startup_metric_utils.h" #include "components/sync/base/sync_prefs.h" #include "components/sync/driver/sync_driver_switches.h" #include "components/sync/driver/sync_service.h" @@ -292,6 +296,67 @@ std::unique_ptr CreateTestTracker(content::BrowserContext*) { return feature_engagement::CreateTestTracker(); } +class PageNonEmptyPaintObserver : public content::WebContentsObserver { + public: + explicit PageNonEmptyPaintObserver(const GURL& url, + content::WebContents* web_contents) + : WebContentsObserver(web_contents), + barrier_closure_(base::BarrierClosure(2, run_loop_.QuitClosure())), + url_(url) {} + + void Wait() { + // Check if the right page has already been painted or loaded. + if (web_contents()->GetLastCommittedURL() == url_) { + if (web_contents()->CompletedFirstVisuallyNonEmptyPaint()) + DidFirstVisuallyNonEmptyPaint(); + if (!web_contents()->IsLoading()) + DidStopLoading(); + } + + run_loop_.Run(); + } + + private: + // WebContentsObserver: + void DidFirstVisuallyNonEmptyPaint() override { + // Making sure that the same event does not trigger the barrier twice. + if (did_paint_) + return; + + did_paint_ = true; + barrier_closure_.Run(); + } + + void DidStopLoading() override { + ASSERT_EQ(web_contents()->GetLastCommittedURL(), url_); + + // Making sure that the same event does not trigger the barrier twice. + if (did_load_) + return; + + // It shouldn't technically be necessary to wait for load stop here, we do + // this to be consistent with the other tests relying on `WaitForLoadStop()` + did_load_ = true; + barrier_closure_.Run(); + } + + base::RunLoop run_loop_; + base::RepeatingClosure barrier_closure_; + GURL url_; + + bool did_paint_ = false; + bool did_load_ = false; +}; + +// Waits for a first non empty paint for `target` and expects that it will load +// the given `url`. +void WaitForFirstNonEmptyPaint(const GURL& url, content::WebContents* target) { + ASSERT_NE(target, nullptr); + + PageNonEmptyPaintObserver observer(url, target); + observer.Wait(); +} + } // namespace class ProfilePickerCreationFlowBrowserTest : public ProfilePickerTestBase { @@ -1007,6 +1072,8 @@ IN_PROC_BROWSER_TEST_F(ProfilePickerCreationFlowBrowserTest, } IN_PROC_BROWSER_TEST_F(ProfilePickerCreationFlowBrowserTest, OpenProfile) { + base::HistogramTester histogram_tester; + AvatarToolbarButton::SetIPHMinDelayAfterCreationForTesting(base::Seconds(0)); FeaturePromoControllerViews::BlockActiveWindowCheckForTesting(); ASSERT_EQ(1u, BrowserList::GetInstance()->size()); @@ -1019,12 +1086,52 @@ IN_PROC_BROWSER_TEST_F(ProfilePickerCreationFlowBrowserTest, OpenProfile) { OpenProfileFromPicker(other_path, /*open_settings=*/false); // Browser for the profile is displayed. Browser* new_browser = BrowserAddedWaiter(2u).Wait(); - WaitForLoadStop(GURL("chrome://newtab/"), - new_browser->tab_strip_model()->GetActiveWebContents()); + WaitForFirstNonEmptyPaint( + GURL("chrome://newtab/"), + new_browser->tab_strip_model()->GetActiveWebContents()); EXPECT_EQ(new_browser->profile()->GetPath(), other_path); WaitForPickerClosed(); // IPH is shown. EXPECT_TRUE(ProfileSwitchPromoHasBeenShown(new_browser)); + + // FirstProfileTime.* histograms aren't recorded because the picker + // is opened from the menu. + EXPECT_THAT(histogram_tester.GetTotalCountsForPrefix( + "ProfilePicker.FirstProfileTime."), + testing::IsEmpty()); +} + +IN_PROC_BROWSER_TEST_F(ProfilePickerCreationFlowBrowserTest, + OpenProfileFromStartup) { + base::HistogramTester histogram_tester; + ASSERT_FALSE(ProfilePicker::IsOpen()); + + // Create a second profile. + base::FilePath other_path = CreateNewProfileWithoutBrowser(); + + // Open the picker. + ProfilePicker::Show(ProfilePicker::EntryPoint::kOnStartup); + EXPECT_TRUE(ProfilePicker::IsOpen()); + WaitForLoadStop(GURL("chrome://profile-picker")); + + // Open the new profile. + OpenProfileFromPicker(other_path, /*open_settings=*/false); + + // Measurement of startup performance started. + + // Browser for the profile is displayed. + Browser* new_browser = BrowserAddedWaiter(2u).Wait(); + WaitForFirstNonEmptyPaint( + GURL("chrome://newtab/"), + new_browser->tab_strip_model()->GetActiveWebContents()); + EXPECT_EQ(new_browser->profile()->GetPath(), other_path); + WaitForPickerClosed(); + + histogram_tester.ExpectTotalCount( + "ProfilePicker.FirstProfileTime.FirstWebContentsNonEmptyPaint", 1); + histogram_tester.ExpectUniqueSample( + "ProfilePicker.FirstProfileTime.FirstWebContentsFinishReason", + metrics::StartupProfilingFinishReason::kDone, 1); } IN_PROC_BROWSER_TEST_F(ProfilePickerCreationFlowBrowserTest, diff --git a/chrome/browser/ui/views/safe_browsing/tailored_security_unconsented_modal.cc b/chrome/browser/ui/views/safe_browsing/tailored_security_unconsented_modal.cc index 904ea33a834623..51d0fc1e9e5119 100644 --- a/chrome/browser/ui/views/safe_browsing/tailored_security_unconsented_modal.cc +++ b/chrome/browser/ui/views/safe_browsing/tailored_security_unconsented_modal.cc @@ -61,6 +61,9 @@ TailoredSecurityUnconsentedModal::TailoredSecurityUnconsentedModal( l10n_util::GetStringUTF16( IDS_TAILORED_SECURITY_UNCONSENTED_CANCEL_BUTTON)); + RecordModalOutcomeAndRunCallback(TailoredSecurityOutcome::kShown, + base::DoNothing()); + SetAcceptCallback(base::BindOnce( RecordModalOutcomeAndRunCallback, TailoredSecurityOutcome::kAccepted, base::BindOnce(&EnableEsbAndShowSettings, web_contents_))); diff --git a/chrome/browser/ui/views/tabs/tab_style_views.cc b/chrome/browser/ui/views/tabs/tab_style_views.cc index 94524e89b12f3a..ee5ab2331b9392 100644 --- a/chrome/browser/ui/views/tabs/tab_style_views.cc +++ b/chrome/browser/ui/views/tabs/tab_style_views.cc @@ -302,6 +302,11 @@ SkPath GM2TabStyle::GetPath(PathType path_type, radius - inset, radius - inset); path.addRRect(rrect); } else { + // Avoid mallocs at every new path verb by preallocating an + // empirically-determined amount of space in the verb and point buffers. + const int kMaxPathPoints = 20; + path.incReserve(kMaxPathPoints); + // We will go clockwise from the lower left. We start in the overlap region, // preventing a gap between toolbar and tabstrip. // TODO(dfried): verify that the we actually want to start the stroke for diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest.cc b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest.cc index 11c00ea6db54be..0b661053defac6 100644 --- a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest.cc +++ b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest.cc @@ -1575,17 +1575,9 @@ IN_PROC_BROWSER_TEST_F( helper_.CheckWindowCreated(); } -// TODO(https://crbug.com/1277870): Flaky on Linux builders. -#if defined(OS_LINUX) -#define MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_InstPlcyTabShctSiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated \ - DISABLED_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_InstPlcyTabShctSiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated -#else -#define MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_InstPlcyTabShctSiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated \ - WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_InstPlcyTabShctSiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated -#endif IN_PROC_BROWSER_TEST_F( WebAppIntegrationBrowserTest, - MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_InstPlcyTabShctSiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated) { + WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_InstPlcyTabShctSiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated) { // Test contents are generated by script. Please do not modify! // See `chrome/test/webapps/README.md` for more info. // Sheriffs: Disabling this test is supported. @@ -1642,17 +1634,9 @@ IN_PROC_BROWSER_TEST_F( helper_.CheckWindowCreated(); } -// TODO(https://crbug.com/1277870): Flaky on Linux builders. -#if defined(OS_LINUX) -#define MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_InstPlcyTabShctSiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated \ - DISABLED_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_InstPlcyTabShctSiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated -#else -#define MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_InstPlcyTabShctSiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated \ - WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_InstPlcyTabShctSiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated -#endif IN_PROC_BROWSER_TEST_F( WebAppIntegrationBrowserTest, - MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_InstPlcyTabShctSiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated) { + WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_InstPlcyTabShctSiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated) { // Test contents are generated by script. Please do not modify! // See `chrome/test/webapps/README.md` for more info. // Sheriffs: Disabling this test is supported. diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc index 63602eeff18af9..916ae159d4a040 100644 --- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc +++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc @@ -185,6 +185,8 @@ #include "ash/webui/shimless_rma/url_constants.h" #include "ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.h" #include "ash/webui/shortcut_customization_ui/url_constants.h" +#include "ash/webui/system_extensions_internals_ui/system_extensions_internals_ui.h" +#include "ash/webui/system_extensions_internals_ui/url_constants.h" #include "base/system/sys_info.h" #include "chrome/browser/app_mode/app_mode_utils.h" #include "chrome/browser/ash/arc/arc_util.h" @@ -961,6 +963,10 @@ WebUIFactoryFunction GetWebUIFactoryFunction(WebUI* web_ui, return &NewComponentUI; } + if (url.host_piece() == ash::kChromeUISystemExtensionsInternalsHost && + base::FeatureList::IsEnabled(ash::features::kSystemExtensions)) { + return &NewWebUI; + } #if !defined(OFFICIAL_BUILD) #if !defined(USE_REAL_DBUS_CLIENTS) diff --git a/chrome/browser/ui/webui/management/management_ui_handler_unittest.cc b/chrome/browser/ui/webui/management/management_ui_handler_unittest.cc index 97f16a13150f5e..397171e1236c55 100644 --- a/chrome/browser/ui/webui/management/management_ui_handler_unittest.cc +++ b/chrome/browser/ui/webui/management/management_ui_handler_unittest.cc @@ -440,12 +440,11 @@ class ManagementUIHandlerTests : public TestingBaseClass { GetTestConfig().override_policy_connector_is_managed = true; GetTestConfig().managed_device = true; SetUpProfileAndHandler(); - const TestDeviceStatusCollector* status_collector = - new TestDeviceStatusCollector( - &local_state_, GetTestConfig().report_activity_times, - GetTestConfig().report_nics, GetTestConfig().report_hardware_data, - GetTestConfig().report_users, GetTestConfig().report_crash_info, - GetTestConfig().report_app_info_and_activity); + const TestDeviceStatusCollector status_collector( + &local_state_, GetTestConfig().report_activity_times, + GetTestConfig().report_nics, GetTestConfig().report_hardware_data, + GetTestConfig().report_users, GetTestConfig().report_crash_info, + GetTestConfig().report_app_info_and_activity); settings_.device_settings()->SetTrustedStatus( ash::CrosSettingsProvider::TRUSTED); settings_.device_settings()->SetBoolean(ash::kSystemLogUploadEnabled, @@ -464,17 +463,17 @@ class ManagementUIHandlerTests : public TestingBaseClass { GetTestConfig().crostini_ansible_playbook_filepath); crostini_features()->set_is_allowed_now(true); - const policy::SystemLogUploader* system_uploader = - new policy::SystemLogUploader(/*syslog_delegate=*/nullptr, - /*task_runner=*/task_runner_); + const policy::SystemLogUploader system_log_uploader( + /*syslog_delegate=*/nullptr, + /*task_runner=*/task_runner_); ON_CALL(testing::Const(handler_), GetDeviceCloudPolicyManager()) .WillByDefault(Return(manager_.get())); EXPECT_CALL(*static_cast( handler_.GetDlpRulesManager()), IsReportingEnabled) .WillRepeatedly(testing::Return(GetTestConfig().report_dlp_events)); - return handler_.GetDeviceReportingInfo(manager_.get(), status_collector, - system_uploader, GetProfile()); + return handler_.GetDeviceReportingInfo(manager_.get(), &status_collector, + &system_log_uploader, GetProfile()); } #endif // BUILDFLAG(IS_CHROMEOS_ASH) diff --git a/chrome/browser/ui/webui/realbox/realbox.mojom b/chrome/browser/ui/webui/realbox/realbox.mojom index c5aa16a3083d43..16ceea42772bc7 100644 --- a/chrome/browser/ui/webui/realbox/realbox.mojom +++ b/chrome/browser/ui/webui/realbox/realbox.mojom @@ -69,6 +69,11 @@ struct AutocompleteMatch { // ID of the group the suggestion belongs to. 0 if it does not belong to any. int32 suggestion_group_id; bool supports_deletion; + // Holds the common part of tail suggestion. Not every match has a tail + // suggestion prefix. For example, the tail suggestion prefix for "hobbit + // holes for sale in" is "hobbit holes for sale" and the match contents + // would be the text following "sale". + mojo_base.mojom.String16? tail_suggest_common_prefix; }; struct SuggestionGroup { diff --git a/chrome/browser/ui/webui/realbox/realbox_handler.cc b/chrome/browser/ui/webui/realbox/realbox_handler.cc index fed518482fffa4..05adedd646472f 100644 --- a/chrome/browser/ui/webui/realbox/realbox_handler.cc +++ b/chrome/browser/ui/webui/realbox/realbox_handler.cc @@ -253,6 +253,8 @@ std::vector CreateAutocompleteMatches( match, RealboxHandler::FocusState::kFocusedButtonRemoveSuggestion)); + mojom_match->tail_suggest_common_prefix = match.tail_suggest_common_prefix; + matches.push_back(std::move(mojom_match)); line++; } @@ -683,6 +685,11 @@ void RealboxHandler::OnResultChanged(AutocompleteController* controller, bool default_match_changed) { DCHECK(controller == autocomplete_controller_.get()); + // Prepend missing tail suggestion prefixes in results, if present. + if (base::FeatureList::IsEnabled(omnibox::kNtpRealboxTailSuggest)) { + autocomplete_controller_->SetTailSuggestCommonPrefixes(); + } + page_->AutocompleteResultChanged(CreateAutocompleteResult( autocomplete_controller_->input().text(), autocomplete_controller_->result(), diff --git a/chrome/browser/ui/webui/signin/profile_picker_handler.cc b/chrome/browser/ui/webui/signin/profile_picker_handler.cc index 3e21ece974e06b..26d60334c4236c 100644 --- a/chrome/browser/ui/webui/signin/profile_picker_handler.cc +++ b/chrome/browser/ui/webui/signin/profile_picker_handler.cc @@ -13,8 +13,10 @@ #include "base/metrics/histogram_functions.h" #include "base/notreached.h" #include "base/strings/utf_string_conversions.h" +#include "base/trace_event/trace_event.h" #include "base/values.h" #include "chrome/browser/browser_process.h" +#include "chrome/browser/metrics/first_web_contents_profiler_base.h" #include "chrome/browser/new_tab_page/chrome_colors/chrome_colors_service.h" #include "chrome/browser/new_tab_page/chrome_colors/generated_colors_info.h" #include "chrome/browser/profiles/profile.h" @@ -242,6 +244,99 @@ SkBitmap GetAvailableAccountBitmap(const gfx::Image& gaia_image, } #endif // BUILDFLAG(IS_CHROMEOS_LACROS) +void RecordProfilingFinishReason( + metrics::StartupProfilingFinishReason finish_reason) { + base::UmaHistogramEnumeration( + "ProfilePicker.FirstProfileTime.FirstWebContentsFinishReason", + finish_reason); +} + +class FirstWebContentsProfilerForProfilePicker + : public metrics::FirstWebContentsProfilerBase { + public: + explicit FirstWebContentsProfilerForProfilePicker( + content::WebContents* web_contents, + base::TimeTicks pick_time); + + FirstWebContentsProfilerForProfilePicker( + const FirstWebContentsProfilerForProfilePicker&) = delete; + FirstWebContentsProfilerForProfilePicker& operator=( + const FirstWebContentsProfilerForProfilePicker&) = delete; + + protected: + // FirstWebContentsProfilerBase: + void RecordFinishReason( + metrics::StartupProfilingFinishReason finish_reason) override; + void RecordNavigationFinished(base::TimeTicks navigation_start) override; + void RecordFirstNonEmptyPaint() override; + bool WasStartupInterrupted() override; + + private: + ~FirstWebContentsProfilerForProfilePicker() override; + + const base::TimeTicks pick_time_; +}; + +FirstWebContentsProfilerForProfilePicker:: + FirstWebContentsProfilerForProfilePicker(content::WebContents* web_contents, + base::TimeTicks pick_time) + : FirstWebContentsProfilerBase(web_contents), pick_time_(pick_time) { + DCHECK(!pick_time_.is_null()); +} + +FirstWebContentsProfilerForProfilePicker:: + ~FirstWebContentsProfilerForProfilePicker() = default; + +void FirstWebContentsProfilerForProfilePicker::RecordFinishReason( + metrics::StartupProfilingFinishReason finish_reason) { + RecordProfilingFinishReason(finish_reason); +} + +void FirstWebContentsProfilerForProfilePicker::RecordNavigationFinished( + base::TimeTicks navigation_start) { + // Nothing to record here for Profile Picker startups. +} + +void FirstWebContentsProfilerForProfilePicker::RecordFirstNonEmptyPaint() { + const char histogram_name[] = + "ProfilePicker.FirstProfileTime.FirstWebContentsNonEmptyPaint"; + base::TimeTicks paint_time = base::TimeTicks::Now(); + base::UmaHistogramLongTimes100(histogram_name, paint_time - pick_time_); + TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0("startup", histogram_name, + this, pick_time_); + TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0("startup", histogram_name, + this, paint_time); +} + +bool FirstWebContentsProfilerForProfilePicker::WasStartupInterrupted() { + // We're assuming that no interruptions block opening an existing profile + // from the profile picker. We would detect this by observing really high + // latency on the tracked metric, and can start tracking interruptions if we + // find that such cases occur. + return false; +} + +void BeginFirstWebContentsProfiling(Browser* browser, + base::TimeTicks pick_time) { + content::WebContents* visible_contents = + metrics::FirstWebContentsProfilerBase::GetVisibleContents(browser); + if (!visible_contents) { + RecordProfilingFinishReason(metrics::StartupProfilingFinishReason:: + kAbandonNoInitiallyVisibleContent); + return; + } + + if (visible_contents->CompletedFirstVisuallyNonEmptyPaint()) { + RecordProfilingFinishReason( + metrics::StartupProfilingFinishReason::kAbandonAlreadyPaintedContent); + return; + } + + // FirstWebContentsProfilerForProfilePicker owns itself and is also bound to + // |visible_contents|'s lifetime by observing WebContentsDestroyed(). + new FirstWebContentsProfilerForProfilePicker(visible_contents, pick_time); +} + } // namespace ProfilePickerHandler::ProfilePickerHandler() = default; @@ -383,6 +478,8 @@ void ProfilePickerHandler::HandleMainViewInitialize( void ProfilePickerHandler::HandleLaunchSelectedProfile( bool open_settings, const base::ListValue* args) { + TRACE_EVENT1("browser", "ProfilePickerHandler::HandleLaunchSelectedProfile", + "args", args->DebugString()); if (args->GetList().empty()) return; const base::Value& profile_path_value = args->GetList()[0]; @@ -441,6 +538,12 @@ void ProfilePickerHandler::HandleLaunchSelectedProfile( } #endif // BUILDFLAG(IS_CHROMEOS_LACROS) + if (!creation_time_on_startup_.is_null() && + // Avoid overriding the picked time if already recorded. This can happen + // for example if multiple profiles are picked: https://crbug.com/1277466. + profile_picked_time_on_startup_.is_null()) { + profile_picked_time_on_startup_ = base::TimeTicks::Now(); + } profiles::SwitchToProfile( *profile_path, /*always_create=*/false, base::BindRepeating(&ProfilePickerHandler::OnSwitchToProfileComplete, @@ -833,10 +936,20 @@ void ProfilePickerHandler::OnSwitchToProfileComplete( bool open_settings, Profile* profile, Profile::CreateStatus profile_create_status) { + TRACE_EVENT2("browser", "ProfilePickerHandler::OnSwitchToProfileComplete", + "profile_path", profile->GetPath().AsUTF8Unsafe(), + "create_status", profile_create_status); Browser* browser = chrome::FindAnyBrowser(profile, false); DCHECK(browser); DCHECK(browser->window()); + // Measure startup time to display first web contents if the profile picker + // was displayed on startup and if the initiating action is instrumented. For + // example we don't record pick time for profile creations. + if (!profile_picked_time_on_startup_.is_null()) { + BeginFirstWebContentsProfiling(browser, profile_picked_time_on_startup_); + } + // Only show the profile switch IPH when the user clicked the card, and there // are multiple profiles. std::vector entries = diff --git a/chrome/browser/ui/webui/signin/profile_picker_handler.h b/chrome/browser/ui/webui/signin/profile_picker_handler.h index 026c3b3b04f99f..ab92d9ba5b628f 100644 --- a/chrome/browser/ui/webui/signin/profile_picker_handler.h +++ b/chrome/browser/ui/webui/signin/profile_picker_handler.h @@ -182,6 +182,11 @@ class ProfilePickerHandler : public content::WebUIMessageHandler, // Creation time of the handler, to measure performance on startup. Only set // when the picker is shown on startup. base::TimeTicks creation_time_on_startup_; + + // Time when the user picked a profile to open, to measure browser startup + // performance. Only set when the picker is shown on startup. + base::TimeTicks profile_picked_time_on_startup_; + bool main_view_initialized_ = false; #if BUILDFLAG(IS_CHROMEOS_LACROS) diff --git a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc index e457ca8be6f445..496005d3299b6f 100644 --- a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc +++ b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc @@ -4,6 +4,7 @@ #include "chrome/browser/ui/webui/tab_search/tab_search_page_handler.h" +#include #include #include #include @@ -12,6 +13,7 @@ #include "base/base64.h" #include "base/metrics/histogram_functions.h" +#include "base/ranges/algorithm.h" #include "base/strings/utf_string_conversions.h" #include "base/time/time.h" #include "base/timer/timer.h" @@ -27,6 +29,7 @@ #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_live_tab_context.h" #include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/tabs/tab_enums.h" #include "chrome/browser/ui/tabs/tab_renderer_data.h" #include "chrome/browser/ui/tabs/tab_strip_model_observer.h" #include "chrome/browser/ui/tabs/tab_utils.h" @@ -432,8 +435,18 @@ tab_search::mojom::TabPtr TabSearchPageHandler::GetTab( tab_data->last_active_elapsed_text = GetLastActiveElapsedText(last_active_time_ticks); - if (base::FeatureList::IsEnabled(features::kTabSearchMediaTabs)) - tab_data->alert_states = chrome::GetTabAlertStatesForContents(contents); + if (base::FeatureList::IsEnabled(features::kTabSearchMediaTabs)) { + std::vector alert_states = + chrome::GetTabAlertStatesForContents(contents); + // Currently, we only report media alert states. + base::ranges::copy_if(alert_states.begin(), alert_states.end(), + std::back_inserter(tab_data->alert_states), + [](TabAlertState alert) { + return alert == TabAlertState::MEDIA_RECORDING || + alert == TabAlertState::AUDIO_PLAYING || + alert == TabAlertState::AUDIO_MUTING; + }); + } return tab_data; } diff --git a/chrome/browser/ui/webui/whats_new/whats_new_util.cc b/chrome/browser/ui/webui/whats_new/whats_new_util.cc index 73cee59b1595ee..322cddcf2441ff 100644 --- a/chrome/browser/ui/webui/whats_new/whats_new_util.cc +++ b/chrome/browser/ui/webui/whats_new/whats_new_util.cc @@ -59,19 +59,19 @@ bool ShouldShowForState(PrefService* local_state) { if (!base::FeatureList::IsEnabled(features::kChromeWhatsNewUI)) return false; - // Show What's New if the page hasn't yet been shown for the current - // milestone. int last_version = local_state->GetInteger(prefs::kLastWhatsNewVersion); - return CHROME_VERSION_MAJOR > last_version; -} - -void SetLastVersion(PrefService* local_state) { - if (!local_state) { - return; - } + // Don't show What's New if it's already been shown for the current major + // milestone. + if (CHROME_VERSION_MAJOR <= last_version) + return false; + // Set the last version here to indicate that What's New should not attempt + // to display again for this milestone. This prevents the page from + // potentially displaying multiple times in a given milestone, e.g. for + // multiple profile relaunches (see https://crbug.com/1274313). local_state->SetInteger(prefs::kLastWhatsNewVersion, CHROME_VERSION_MAJOR); + return true; } GURL GetServerURL(bool may_redirect) { @@ -191,11 +191,6 @@ class WhatsNewFetcher : public BrowserListObserver { DCHECK(browser_); - // Update pref if shown automatically. Do this even if the load failed - we - // only want to try once, so that the network request only occurs once per - // version and not every time the browser opens. - SetLastVersion(g_browser_process->local_state()); - LogLoadEvent(success ? LoadEvent::kLoadSuccess : LoadEvent::kLoadFailAndDoNotShow); if (success) diff --git a/chrome/browser/ui/webui/whats_new/whats_new_util.h b/chrome/browser/ui/webui/whats_new/whats_new_util.h index 4c341af4700afd..05f8e7eff3b5a5 100644 --- a/chrome/browser/ui/webui/whats_new/whats_new_util.h +++ b/chrome/browser/ui/webui/whats_new/whats_new_util.h @@ -35,12 +35,20 @@ void DisableRemoteContentForTests(); // DisableRemoteContentForTests(). bool IsRemoteContentDisabled(); -// Whether the What's New page should be shown, based on |local_state|. +// Returns true if the user has not yet seen the What's New page for the +// current major milestone. When returning true, sets the pref in |local_state| +// to indicate that What's New should not try to display again for the current +// major milestone. +// Note that this does not guarantee that the page will always show (for +// example, onboarding tabs override What's New, or remote content can fail to +// load, which will result in the tab not opening). However, What's New should +// only display automatically on the first relaunch after updating to a new +// major milestone, and it is preferable to only attempt to show the page once +// and possibly miss some users instead of repeatedly triggering a network +// request at startup and/or showing the same What's New page many times for a +// given user. bool ShouldShowForState(PrefService* local_state); -// Sets the last What's New version in |local_state| to the current version. -void SetLastVersion(PrefService* local_state); - // Gets the server side URL for the What's New page for the current version of // Chrome. If |may_redirect| is true, return a server URL that will redirect to // the closest milestone page. Otherwise, return the direct URL of the current diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt index 5607ed5338dade..5fc9bc8ecdef1a 100644 --- a/chrome/build/linux.pgo.txt +++ b/chrome/build/linux.pgo.txt @@ -1 +1 @@ -chrome-linux-main-1639483122-20e6b9acc183f41a276a93022da6f201498b4d81.profdata +chrome-linux-main-1639504726-00818556d27ae6f0ca222d22829579a009d0b826.profdata diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt index c1d4c58849f2dc..892f302a8699a9 100644 --- a/chrome/build/win32.pgo.txt +++ b/chrome/build/win32.pgo.txt @@ -1 +1 @@ -chrome-win32-main-1639483122-6c64e4735696edd79f94e428f8b51a0056079d88.profdata +chrome-win32-main-1639493922-9a45d06329fbed3e310b5994955083e355361eb8.profdata diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt index 4ad085a6601fd2..48ccf98e16857e 100644 --- a/chrome/build/win64.pgo.txt +++ b/chrome/build/win64.pgo.txt @@ -1 +1 @@ -chrome-win64-main-1639483122-b852875a95ce2ee39f0bd733dae39836e0f19b13.profdata +chrome-win64-main-1639504726-a5cec93f623091d76267247a1ff888db818a2b02.profdata diff --git a/chrome/chrome_cleaner/parsers/json_parser/test_json_parser.cc b/chrome/chrome_cleaner/parsers/json_parser/test_json_parser.cc index 9b846fcdda02bd..d289305c6db6f1 100644 --- a/chrome/chrome_cleaner/parsers/json_parser/test_json_parser.cc +++ b/chrome/chrome_cleaner/parsers/json_parser/test_json_parser.cc @@ -14,7 +14,8 @@ void TestJsonParser::Parse(const std::string& json, ParseDoneCallback callback) { base::JSONReader::ValueWithError value_with_error = base::JSONReader::ReadAndReturnValueWithError( - json, base::JSON_ALLOW_TRAILING_COMMAS | + json, base::JSON_PARSE_CHROMIUM_EXTENSIONS | + base::JSON_ALLOW_TRAILING_COMMAS | base::JSON_REPLACE_INVALID_CHARACTERS); if (value_with_error.value) { std::move(callback).Run(std::move(value_with_error.value), absl::nullopt); diff --git a/chrome/chrome_cleaner/parsers/target/parser_impl.cc b/chrome/chrome_cleaner/parsers/target/parser_impl.cc index 7db38ae8eb01a4..69b2baf07b5611 100644 --- a/chrome/chrome_cleaner/parsers/target/parser_impl.cc +++ b/chrome/chrome_cleaner/parsers/target/parser_impl.cc @@ -25,7 +25,8 @@ void ParserImpl::ParseJson(const std::string& json, ParseJsonCallback callback) { base::JSONReader::ValueWithError parsed_json = base::JSONReader::ReadAndReturnValueWithError( - json, base::JSON_ALLOW_TRAILING_COMMAS | + json, base::JSON_PARSE_CHROMIUM_EXTENSIONS | + base::JSON_ALLOW_TRAILING_COMMAS | base::JSON_REPLACE_INVALID_CHARACTERS); if (parsed_json.value) { std::move(callback).Run(std::move(parsed_json.value), absl::nullopt); diff --git a/chrome/chrome_paks.gni b/chrome/chrome_paks.gni index 88286bc85c49c9..b676c7bc87f46f 100644 --- a/chrome/chrome_paks.gni +++ b/chrome/chrome_paks.gni @@ -214,6 +214,7 @@ template("chrome_extra_paks") { "$root_gen_dir/ash/ash_scanning_app_resources.pak", "$root_gen_dir/ash/ash_shimless_rma_resources.pak", "$root_gen_dir/ash/ash_shortcut_customization_app_resources.pak", + "$root_gen_dir/ash/ash_system_extensions_internals_resources.pak", "$root_gen_dir/ash/connectivity_diagnostics_resources.pak", "$root_gen_dir/ash/public/cpp/resources/ash_public_unscaled_resources.pak", "$root_gen_dir/ash/webui/file_manager/resources/file_manager_swa_resources.pak", @@ -268,6 +269,7 @@ template("chrome_extra_paks") { "//ash/webui/resources:scanning_app_resources", "//ash/webui/resources:shimless_rma_resources", "//ash/webui/resources:shortcut_customization_app_resources", + "//ash/webui/resources:system_extensions_internals_resources", "//chrome/browser/resources:bluetooth_pairing_dialog_resources", "//chrome/browser/resources:internet_config_dialog_resources", "//chrome/browser/resources:internet_detail_dialog_resources", diff --git a/chrome/common/extensions/api/extension.json b/chrome/common/extensions/api/extension.json index 86c407fda26270..ac9f029aa306bb 100644 --- a/chrome/common/extensions/api/extension.json +++ b/chrome/common/extensions/api/extension.json @@ -156,38 +156,34 @@ "type": "function", "description": "Retrieves the state of the extension's access to Incognito-mode. This corresponds to the user-controlled per-extension 'Allowed in Incognito' setting accessible via the chrome://extensions page.", "min_version": "12.0.706.0", - "parameters": [ - { - "type": "function", - "name": "callback", - "parameters": [ - { - "name": "isAllowedAccess", - "type": "boolean", - "description": "True if the extension has access to Incognito mode, false otherwise." - } - ] - } - ] + "parameters": [], + "returns_async": { + "name": "callback", + "parameters": [ + { + "name": "isAllowedAccess", + "type": "boolean", + "description": "True if the extension has access to Incognito mode, false otherwise." + } + ] + } }, { "name": "isAllowedFileSchemeAccess", "type": "function", "description": "Retrieves the state of the extension's access to the 'file://' scheme. This corresponds to the user-controlled per-extension 'Allow access to File URLs' setting accessible via the chrome://extensions page.", "min_version": "12.0.706.0", - "parameters": [ - { - "type": "function", - "name": "callback", - "parameters": [ - { - "name": "isAllowedAccess", - "type": "boolean", - "description": "True if the extension can access the 'file://' scheme, false otherwise." - } - ] - } - ] + "parameters": [], + "returns_async": { + "name": "callback", + "parameters": [ + { + "name": "isAllowedAccess", + "type": "boolean", + "description": "True if the extension can access the 'file://' scheme, false otherwise." + } + ] + } }, { "name": "setUpdateUrlData", diff --git a/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc b/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc index 2310f8d2dacf97..60806a74b61839 100644 --- a/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc +++ b/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc @@ -109,7 +109,8 @@ class HttpServiceRequest { result = base::JSONReader::Read( base::StringPiece(response_.data(), response_.size()), - base::JSON_ALLOW_TRAILING_COMMAS); + base::JSON_PARSE_CHROMIUM_EXTENSIONS | + base::JSON_ALLOW_TRAILING_COMMAS); if (!result || !result->is_dict()) { LOGFN(ERROR) << "Failed to read json result from server response"; result.reset(); diff --git a/chrome/installer/mac/BUILD.gn b/chrome/installer/mac/BUILD.gn index a2d2713e1961ec..9bd016f514421c 100644 --- a/chrome/installer/mac/BUILD.gn +++ b/chrome/installer/mac/BUILD.gn @@ -166,7 +166,7 @@ config("fat_binary_config") { # Help the compiler find lipo for creating fat binaries. "-B", mac_bin_path, - + "-fno-lto", "-arch", "x86_64", "-arch", diff --git a/chrome/renderer/extensions/app_hooks_delegate.cc b/chrome/renderer/extensions/app_hooks_delegate.cc index 14dec951973e24..35fb7832c2c44f 100644 --- a/chrome/renderer/extensions/app_hooks_delegate.cc +++ b/chrome/renderer/extensions/app_hooks_delegate.cc @@ -35,7 +35,7 @@ void AppHooksDelegate::IsInstalledGetterCallback( v8::Local property, const v8::PropertyCallbackInfo& info) { v8::HandleScope handle_scope(info.GetIsolate()); - v8::Local context = info.Holder()->CreationContext(); + v8::Local context = info.Holder()->GetCreationContextChecked(); ScriptContext* script_context = ScriptContextSet::GetContextByV8Context(context); diff --git a/chrome/renderer/extensions/extension_hooks_delegate.cc b/chrome/renderer/extensions/extension_hooks_delegate.cc index 520d6435547147..4a1d642b455865 100644 --- a/chrome/renderer/extensions/extension_hooks_delegate.cc +++ b/chrome/renderer/extensions/extension_hooks_delegate.cc @@ -54,7 +54,7 @@ void GetAliasedFeature(v8::Local property_name, const v8::PropertyCallbackInfo& info) { v8::Isolate* isolate = info.GetIsolate(); v8::HandleScope handle_scope(isolate); - v8::Local context = info.Holder()->CreationContext(); + v8::Local context = info.Holder()->GetCreationContextChecked(); v8::TryCatch try_catch(isolate); v8::Local chrome; diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index da7a68bb5daa9b..9f957902bd87cd 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn @@ -1691,7 +1691,6 @@ if (!is_android && !is_fuchsia) { "../browser/optimization_guide/browser_test_util.h", "../browser/optimization_guide/hints_fetcher_browsertest.cc", "../browser/optimization_guide/optimization_guide_keyed_service_browsertest.cc", - "../browser/optimization_guide/page_content_annotations_service_browsertest.cc", "../browser/optimization_guide/page_text_observer_browsertest.cc", "../browser/optimization_guide/prediction/prediction_manager_browsertest.cc", "../browser/page_load_metrics/observers/ad_metrics/ad_density_intervention_browsertest.cc", @@ -2120,6 +2119,13 @@ if (!is_android && !is_fuchsia) { "v8/wasm_trap_handler_browsertest.cc", ] + # crbug.com/1279884 Flaky on CrOS + if (!is_chromeos) { + sources += [ + "../browser/optimization_guide/page_content_annotations_service_browsertest.cc" + ] + } + if (enable_reporting) { sources += [ "../browser/net/reporting_browsertest.cc" ] } @@ -2404,6 +2410,7 @@ if (!is_android && !is_fuchsia) { "//ash/webui/media_app_ui:browser_tests_js", "//ash/webui/personalization_app:browser_tests_js", "//ash/webui/system_apps:browser_tests", + "//ash/webui/system_extensions_internals_ui:browser_tests_js", "//chrome/browser/resources/gaia_auth_host:browser_tests", ] diff --git a/chrome/test/data/extensions/api_test/declarative_net_request/fenced_frames/rules.json b/chrome/test/data/extensions/api_test/declarative_net_request/fenced_frames/rules.json index c8ab892ab7e094..712e74d8f9d7c6 100644 --- a/chrome/test/data/extensions/api_test/declarative_net_request/fenced_frames/rules.json +++ b/chrome/test/data/extensions/api_test/declarative_net_request/fenced_frames/rules.json @@ -4,19 +4,23 @@ "action": { "type" : "block" }, "condition" : { "urlFilter" : "blocked", "resourceTypes" : [ "sub_frame" ] } }, -// Add a rule to block any *main* frames with "allowed". Since the only -// frame matching "allowed" is a fenced frame, it shouldn't match this -// rule. { + "_comment": [ + "Add a rule to block any *main* frames with 'allowed'. Since the only", + "frame matching 'allowed' is a fenced frame, it shouldn't match this", + "rule." + ], "id" : 2, "priority": 1, "action": { "type" : "block" }, "condition" : { "urlFilter" : "allowed", "resourceTypes" : [ "main_frame" ] } }, -// Add a rule to block any thirdParty frames with "allowed". Since the only -// frame matching "allowed" is a fenced frame and is considered firstParty -// it shouldn't match this rule. { + "_comment": [ + "Add a rule to block any thirdParty frames with 'allowed'. Since the only", + "frame matching 'allowed' is a fenced frame and is considered firstParty", + "it shouldn't match this rule." + ], "id" : 3, "priority": 2, "action": { "type" : "block" }, diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json index 2254eadc616cc9..75d6773f94b7b8 100644 --- a/chrome/test/data/policy/policy_test_cases.json +++ b/chrome/test/data/policy/policy_test_cases.json @@ -12095,9 +12095,6 @@ "SystemTimezoneAutomaticDetection": { "reason_for_missing_test": "Maps into CrosSettings" }, - "WebRestrictionsAuthority": { - "reason_for_missing_test": "TODO(crbug.com/1213429) add test case" - }, "TaskManagerEndProcessEnabled": { "os": [ "win", diff --git a/chrome/test/data/webui/new_tab_page/realbox/realbox_test.js b/chrome/test/data/webui/new_tab_page/realbox/realbox_test.js index 58992a937e6171..55437c2b603625 100644 --- a/chrome/test/data/webui/new_tab_page/realbox/realbox_test.js +++ b/chrome/test/data/webui/new_tab_page/realbox/realbox_test.js @@ -214,11 +214,15 @@ function verifyMatch(match, matchEl) { match.answer ? match.answer.secondLine : match.description); const separatorText = matchDescription ? loadTimeData.getString('realboxSeparator') : ''; + const contents = matchEl.$['contents'].textContent.trim(); + const separator = matchEl.$['separator'].textContent.trim(); + const description = matchEl.$['description'].textContent.trim(); + const text = (contents + ' ' + separator + ' ' + description).trim(); assertEquals( match.swapContentsAndDescription ? matchDescription + separatorText + matchContents : matchContents + separatorText + matchDescription, - matchEl.$['text-container'].textContent.trim()); + text); } suite('NewTabPageRealboxTest', () => { diff --git a/chrome/test/data/webui/welcome/BUILD.gn b/chrome/test/data/webui/welcome/BUILD.gn index 39eeb0d10ed063..d1729650c6cec6 100644 --- a/chrome/test/data/webui/welcome/BUILD.gn +++ b/chrome/test/data/webui/welcome/BUILD.gn @@ -22,9 +22,9 @@ ts_library("build_ts") { out_dir = "$target_gen_dir/tsc" tsconfig_base = "tsconfig_base.json" path_mappings = [ - "chrome://welcome/*|" + rebase_path( - "$root_gen_dir/chrome/browser/resources/welcome/preprocessed/*", - target_gen_dir), + "chrome://welcome/*|" + + rebase_path("$root_gen_dir/chrome/browser/resources/welcome/tsc/*", + target_gen_dir), "chrome://webui-test/*|" + rebase_path("$root_gen_dir/chrome/test/data/webui/tsc/*", target_gen_dir), @@ -47,6 +47,6 @@ ts_library("build_ts") { "welcome_app_test.ts", ] definitions = [ "//tools/typescript/definitions/bookmarks.d.ts" ] - deps = [ "//chrome/browser/resources/welcome:build" ] + deps = [ "//chrome/browser/resources/welcome:build_ts" ] extra_deps = [ "..:generate_definitions" ] } diff --git a/chrome/test/media_router/media_router_e2e_ui_browsertest.cc b/chrome/test/media_router/media_router_e2e_ui_browsertest.cc index 12219d0ead6745..eaf0e0ec99e554 100644 --- a/chrome/test/media_router/media_router_e2e_ui_browsertest.cc +++ b/chrome/test/media_router/media_router_e2e_ui_browsertest.cc @@ -105,7 +105,7 @@ IN_PROC_BROWSER_TEST_P(MediaRouterE2EBrowserTest, MANUAL_MirrorHTML5Video) { test_ui_->ShowDialog(); test_ui_->WaitForSink(receiver_); test_ui_->StopCasting(receiver_); - test_ui_->WaitUntilNoRoutes(); + WaitUntilNoRoutes(web_contents); test_ui_->HideDialog(); } diff --git a/chrome/test/media_router/media_router_integration_browsertest.cc b/chrome/test/media_router/media_router_integration_browsertest.cc index 0ef4b5d1afece7..2423140ab09d92 100644 --- a/chrome/test/media_router/media_router_integration_browsertest.cc +++ b/chrome/test/media_router/media_router_integration_browsertest.cc @@ -91,6 +91,23 @@ std::string GetDefaultRequestSessionId(WebContents* web_contents) { return session_id; } +// Routes observer that calls a callback once there are no routes. +class NoRoutesObserver : public MediaRoutesObserver { + public: + NoRoutesObserver(MediaRouter* router, base::OnceClosure callback) + : MediaRoutesObserver(router), callback_(std::move(callback)) {} + + ~NoRoutesObserver() override = default; + + void OnRoutesUpdated(const std::vector& routes) override { + if (callback_ && routes.empty()) + std::move(callback_).Run(); + } + + private: + base::OnceClosure callback_; +}; + } // namespace MediaRouterIntegrationBrowserTest::MediaRouterIntegrationBrowserTest() { @@ -180,6 +197,23 @@ void MediaRouterIntegrationBrowserTest::Wait(base::TimeDelta timeout) { run_loop.Run(); } +void MediaRouterIntegrationBrowserTest::WaitUntilNoRoutes( + WebContents* web_contents) { + if (!test_provider_->HasRoutes()) + return; + + // FIXME: There can't be a good reason to use the observer API to check for + // routes asynchronously, which is fragile. However, some browser tests rely + // on this behavior. Either add a callback parameter to TerminateRoute, or + // add pass callback to the TestProvider to run when all routes are gone. + base::RunLoop run_loop; + NoRoutesObserver no_routes_observer( + MediaRouterFactory::GetApiForBrowserContext( + web_contents->GetBrowserContext()), + run_loop.QuitClosure()); + run_loop.Run(); +} + void MediaRouterIntegrationBrowserTest::ExecuteJavaScriptAPI( WebContents* web_contents, const std::string& script) { @@ -385,7 +419,7 @@ void MediaRouterIntegrationBrowserTest::RunBasicTest() { WebContents* web_contents = StartSessionWithTestPageAndChooseSink(); CheckSessionValidity(web_contents); ExecuteJavaScriptAPI(web_contents, kTerminateSessionScript); - test_ui_->WaitUntilNoRoutes(); + WaitUntilNoRoutes(web_contents); } void MediaRouterIntegrationBrowserTest::RunSendMessageTest( @@ -425,7 +459,7 @@ void MediaRouterIntegrationBrowserTest::RunReconnectSessionTest() { ASSERT_EQ(session_id, reconnected_session_id); ExecuteJavaScriptAPI(web_contents, kTerminateSessionScript); - test_ui_->WaitUntilNoRoutes(); + WaitUntilNoRoutes(web_contents); } void MediaRouterIntegrationBrowserTest::RunFailedReconnectSessionTest() { @@ -442,7 +476,7 @@ void MediaRouterIntegrationBrowserTest::RunFailedReconnectSessionTest() { base::StringPrintf(kCheckReconnectSessionFailsScript, session_id.c_str())); ExecuteJavaScriptAPI(web_contents, kTerminateSessionScript); - test_ui_->WaitUntilNoRoutes(); + WaitUntilNoRoutes(web_contents); } void MediaRouterIntegrationBrowserTest::SetEnableMediaRouter(bool enable) { @@ -693,7 +727,7 @@ IN_PROC_BROWSER_TEST_P(MediaRouterIntegrationIncognitoBrowserTest, Basic) { // If we tear down before route observers are notified of route termination, // MediaRouter will create another TerminateRoute() request which will have a // dangling Mojo callback at shutdown. So we must wait for the update. - test_ui_->WaitUntilNoRoutes(); + WaitUntilNoRoutes(GetActiveWebContents()); } IN_PROC_BROWSER_TEST_P(MediaRouterIntegrationIncognitoBrowserTest, @@ -703,7 +737,7 @@ IN_PROC_BROWSER_TEST_P(MediaRouterIntegrationIncognitoBrowserTest, // If we tear down before route observers are notified of route termination, // MediaRouter will create another TerminateRoute() request which will have a // dangling Mojo callback at shutdown. So we must wait for the update. - test_ui_->WaitUntilNoRoutes(); + WaitUntilNoRoutes(GetActiveWebContents()); } INSTANTIATE_MEDIA_ROUTER_INTEGRATION_BROWER_TEST_SUITE( diff --git a/chrome/test/media_router/media_router_integration_browsertest.h b/chrome/test/media_router/media_router_integration_browsertest.h index bb57ceea2083d2..7d2314cf90afc8 100644 --- a/chrome/test/media_router/media_router_integration_browsertest.h +++ b/chrome/test/media_router/media_router_integration_browsertest.h @@ -186,6 +186,8 @@ class MediaRouterIntegrationBrowserTest // Wait for a specific time. void Wait(base::TimeDelta timeout); + void WaitUntilNoRoutes(content::WebContents* web_contents); + // Test API for manipulating the UI. raw_ptr test_ui_ = nullptr; diff --git a/chrome/test/media_router/media_router_integration_ui_browsertest.cc b/chrome/test/media_router/media_router_integration_ui_browsertest.cc index 45a6aa0732a5b5..d79670e8aca730 100644 --- a/chrome/test/media_router/media_router_integration_ui_browsertest.cc +++ b/chrome/test/media_router/media_router_integration_ui_browsertest.cc @@ -31,7 +31,7 @@ IN_PROC_BROWSER_TEST_P(MediaRouterIntegrationBrowserTest, MANUAL_Dialog_Basic) { ASSERT_EQ("Test Route", test_ui_->GetStatusTextForSink(receiver_)); test_ui_->StopCasting(receiver_); - test_ui_->WaitUntilNoRoutes(); + WaitUntilNoRoutes(GetActiveWebContents()); // TODO(takumif): Remove the HideCastDialog() call once the dialog can close // on its own. test_ui_->HideDialog(); diff --git a/chrome/test/media_router/media_router_ui_for_test_base.cc b/chrome/test/media_router/media_router_ui_for_test_base.cc index 005b8224d3db03..bec76a12d05c13 100644 --- a/chrome/test/media_router/media_router_ui_for_test_base.cc +++ b/chrome/test/media_router/media_router_ui_for_test_base.cc @@ -26,24 +26,6 @@ ui::MouseEvent CreateMouseReleasedEvent() { return CreateMouseEvent(ui::ET_MOUSE_RELEASED); } -// Routes observer that calls a callback once there are no routes. -class NoRoutesObserver : public MediaRoutesObserver { - public: - NoRoutesObserver(MediaRouter* router, base::OnceClosure callback) - : MediaRoutesObserver(router), callback_(std::move(callback)) {} - ~NoRoutesObserver() override = default; - - void OnRoutesUpdated( - const std::vector& routes, - const std::vector& joinable_route_ids) override { - if (callback_ && routes.empty()) - std::move(callback_).Run(); - } - - private: - base::OnceClosure callback_; -}; - } // namespace void MediaRouterUiForTestBase::TearDown() { @@ -59,15 +41,6 @@ void MediaRouterUiForTestBase::StopCasting(const std::string& sink_name) { StopCasting(GetSinkButton(sink_name)); } -void MediaRouterUiForTestBase::WaitUntilNoRoutes() { - base::RunLoop run_loop; - NoRoutesObserver no_routes_observer( - MediaRouterFactory::GetApiForBrowserContext( - web_contents_->GetBrowserContext()), - run_loop.QuitClosure()); - run_loop.Run(); -} - MediaRoute::Id MediaRouterUiForTestBase::GetRouteIdForSink( const std::string& sink_name) const { CastDialogSinkButton* sink_button = GetSinkButton(sink_name); diff --git a/chrome/test/media_router/media_router_ui_for_test_base.h b/chrome/test/media_router/media_router_ui_for_test_base.h index 59f704c566e114..76d7521a6e3a24 100644 --- a/chrome/test/media_router/media_router_ui_for_test_base.h +++ b/chrome/test/media_router/media_router_ui_for_test_base.h @@ -49,7 +49,6 @@ class MediaRouterUiForTestBase { virtual void WaitForAnyRoute() = 0; virtual void WaitForDialogShown() = 0; virtual void WaitForDialogHidden() = 0; - void WaitUntilNoRoutes(); // These methods require that the dialog is shown, and the sink specified by // |sink_name| is in the dialog. diff --git a/chrome/test/ppapi/ppapi_test.h b/chrome/test/ppapi/ppapi_test.h index 8f0bc4f82e1bac..65019b98f1149b 100644 --- a/chrome/test/ppapi/ppapi_test.h +++ b/chrome/test/ppapi/ppapi_test.h @@ -187,21 +187,6 @@ class PPAPIPrivateNaClPNaClTest : public PPAPINaClPNaClTest { void SetUpCommandLine(base::CommandLine* command_line) override; }; -// Test Non-SFI Mode, using PNaCl toolchain to produce nexes. -class PPAPINaClPNaClNonSfiTest : public PPAPINaClTest { - public: - void SetUpCommandLine(base::CommandLine* command_line) override; - - std::string BuildQuery(const std::string& base, - const std::string& test_case) override; -}; - -class PPAPIPrivateNaClPNaClNonSfiTest : public PPAPINaClPNaClNonSfiTest { - protected: - void SetUpCommandLine(base::CommandLine* command_line) override; -}; - - class PPAPINaClTestDisallowedSockets : public PPAPITestBase { public: void SetUpCommandLine(base::CommandLine* command_line) override; diff --git a/chrome/updater/app/app_update.cc b/chrome/updater/app/app_update.cc index 0da2c894ff078c..6dca67cfadcc0c 100644 --- a/chrome/updater/app/app_update.cc +++ b/chrome/updater/app/app_update.cc @@ -30,17 +30,12 @@ class AppUpdate : public App { void FirstTaskRun() override; void SetupDone(int result); - - scoped_refptr config_; }; void AppUpdate::Initialize() { - config_ = base::MakeRefCounted( - CreateGlobalPrefs(updater_scope()), CreateExternalConstants()); } void AppUpdate::Uninitialize() { - PrefsCommitPendingWrites(config_->GetPrefService()); } void AppUpdate::FirstTaskRun() { diff --git a/chrome/updater/test/integration_test_commands.h b/chrome/updater/test/integration_test_commands.h index 8a9464dab05f6d..29090b63ab8620 100644 --- a/chrome/updater/test/integration_test_commands.h +++ b/chrome/updater/test/integration_test_commands.h @@ -60,7 +60,7 @@ class IntegrationTestCommands virtual void UpdateAll() const = 0; virtual void PrintLog() const = 0; virtual base::FilePath GetDifferentUserPath() const = 0; - virtual void WaitForUpdaterExit() const = 0; + virtual void WaitForServerExit() const = 0; #if defined(OS_WIN) virtual void ExpectInterfacesRegistered() const = 0; virtual void ExpectLegacyUpdate3WebSucceeds( diff --git a/chrome/updater/test/integration_test_commands_system.cc b/chrome/updater/test/integration_test_commands_system.cc index b016a44ed60d19..16ebb5fe6b7eef 100644 --- a/chrome/updater/test/integration_test_commands_system.cc +++ b/chrome/updater/test/integration_test_commands_system.cc @@ -146,8 +146,8 @@ class IntegrationTestCommandsSystem : public IntegrationTestCommands { RunCommand("register_app", {Param("app_id", app_id)}); } - void WaitForUpdaterExit() const override { - updater::test::WaitForUpdaterExit(updater_scope_); + void WaitForServerExit() const override { + updater::test::WaitForServerExit(updater_scope_); } #if defined(OS_WIN) diff --git a/chrome/updater/test/integration_test_commands_user.cc b/chrome/updater/test/integration_test_commands_user.cc index c70a01d53c992a..cc1997a6888b6d 100644 --- a/chrome/updater/test/integration_test_commands_user.cc +++ b/chrome/updater/test/integration_test_commands_user.cc @@ -135,8 +135,8 @@ class IntegrationTestCommandsUser : public IntegrationTestCommands { updater::test::RegisterApp(updater_scope_, app_id); } - void WaitForUpdaterExit() const override { - updater::test::WaitForUpdaterExit(updater_scope_); + void WaitForServerExit() const override { + updater::test::WaitForServerExit(updater_scope_); } #if defined(OS_WIN) diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc index be20f5ca4d1570..f9fbc75b9373fb 100644 --- a/chrome/updater/test/integration_tests.cc +++ b/chrome/updater/test/integration_tests.cc @@ -120,7 +120,6 @@ class IntegrationTest : public ::testing::Test { PrintLog(); CopyLog(); test_commands_->Uninstall(); - WaitForUpdaterExit(); } void ExpectCandidateUninstalled() { @@ -212,7 +211,7 @@ class IntegrationTest : public ::testing::Test { return test_commands_->GetDifferentUserPath(); } - void WaitForUpdaterExit() { test_commands_->WaitForUpdaterExit(); } + void WaitForServerExit() { test_commands_->WaitForServerExit(); } void SetUpTestService() { #if defined(OS_WIN) @@ -266,7 +265,7 @@ class IntegrationTest : public ::testing::Test { TEST_F(IntegrationTest, InstallUninstall) { Install(); - WaitForUpdaterExit(); + WaitForServerExit(); ExpectInstalled(); ExpectVersionActive(kUpdaterVersion); ExpectActiveUpdater(); @@ -282,12 +281,16 @@ TEST_F(IntegrationTest, InstallUninstall) { TEST_F(IntegrationTest, SelfUninstallOutdatedUpdater) { Install(); ExpectInstalled(); - WaitForUpdaterExit(); + SleepFor(2); SetupFakeUpdaterHigherVersion(); ExpectVersionNotActive(kUpdaterVersion); RunWake(0); - WaitForUpdaterExit(); + + // The mac server will remain active for 10 seconds after it replies to the + // wake client, then shut down and uninstall itself. Sleep to wait for this + // to happen. + SleepFor(11); ExpectCandidateUninstalled(); // The candidate uninstall should not have altered global prefs. @@ -303,7 +306,7 @@ TEST_F(IntegrationTest, QualifyUpdater) { ExpectRegistrationEvent(&test_server, kUpdaterAppId); Install(); ExpectInstalled(); - WaitForUpdaterExit(); + WaitForServerExit(); SetupFakeUpdaterLowerVersion(); ExpectVersionNotActive(kUpdaterVersion); @@ -312,7 +315,7 @@ TEST_F(IntegrationTest, QualifyUpdater) { base::Version("0.2")); RunWake(0); - WaitForUpdaterExit(); + WaitForServerExit(); // This instance is now qualified and should activate itself and check itself // for updates on the next check. @@ -321,7 +324,7 @@ TEST_F(IntegrationTest, QualifyUpdater) { base::StringPrintf(".*%s.*", kUpdaterAppId))}, ")]}'\n"); RunWake(0); - WaitForUpdaterExit(); + WaitForServerExit(); ExpectVersionActive(kUpdaterVersion); Uninstall(); @@ -338,7 +341,7 @@ TEST_F(IntegrationTest, SelfUpdate) { base::Version(kUpdaterVersion), next_version); RunWake(0); - WaitForUpdaterExit(); + WaitForServerExit(); ExpectAppVersion(kUpdaterAppId, next_version); Uninstall(); @@ -401,7 +404,7 @@ TEST_F(IntegrationTest, UpdateApp) { base::Version v2("2"); ExpectUpdateSequence(&test_server, kAppId, v1, v2); Update(kAppId); - WaitForUpdaterExit(); + WaitForServerExit(); ExpectAppVersion(kAppId, v2); Uninstall(); @@ -499,7 +502,8 @@ TEST_F(IntegrationTest, UninstallCmdLine) { ExpectActiveUpdater(); RunUninstallCmdLine(); - WaitForUpdaterExit(); + WaitForServerExit(); + SleepFor(2); ExpectClean(); } #endif // defined(OS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING) @@ -510,14 +514,14 @@ TEST_F(IntegrationTest, UnregisterUninstalledApp) { RegisterApp("test1"); RegisterApp("test2"); - WaitForUpdaterExit(); + WaitForServerExit(); ExpectVersionActive(kUpdaterVersion); ExpectActiveUpdater(); SetExistenceCheckerPath("test1", base::FilePath(FILE_PATH_LITERAL("NONE"))); RunWake(0); - WaitForUpdaterExit(); + WaitForServerExit(); ExpectInstalled(); ExpectAppUnregisteredExistenceCheckerPath("test1"); @@ -526,11 +530,12 @@ TEST_F(IntegrationTest, UnregisterUninstalledApp) { TEST_F(IntegrationTest, UninstallIfMaxServerWakesBeforeRegistrationExceeded) { Install(); - WaitForUpdaterExit(); + WaitForServerExit(); ExpectInstalled(); SetServerStarts(24); RunWake(0); - WaitForUpdaterExit(); + WaitForServerExit(); + SleepFor(2); ExpectClean(); } @@ -538,16 +543,17 @@ TEST_F(IntegrationTest, UninstallUpdaterWhenAllAppsUninstalled) { Install(); RegisterApp("test1"); ExpectInstalled(); - WaitForUpdaterExit(); + WaitForServerExit(); SetServerStarts(24); RunWake(0); - WaitForUpdaterExit(); + WaitForServerExit(); ExpectInstalled(); ExpectVersionActive(kUpdaterVersion); ExpectActiveUpdater(); SetExistenceCheckerPath("test1", base::FilePath(FILE_PATH_LITERAL("NONE"))); RunWake(0); - WaitForUpdaterExit(); + WaitForServerExit(); + SleepFor(2); ExpectClean(); } @@ -566,7 +572,7 @@ TEST_F(IntegrationTest, UnregisterUnownedApp) { SetExistenceCheckerPath("test1", GetDifferentUserPath()); RunWake(0); - WaitForUpdaterExit(); + WaitForServerExit(); ExpectAppUnregisteredExistenceCheckerPath("test1"); diff --git a/chrome/updater/test/integration_tests_impl.cc b/chrome/updater/test/integration_tests_impl.cc index 06e90b0abee0c1..9ca520761f83a8 100644 --- a/chrome/updater/test/integration_tests_impl.cc +++ b/chrome/updater/test/integration_tests_impl.cc @@ -313,6 +313,18 @@ bool Run(UpdaterScope scope, base::CommandLine command_line, int* exit_code) { return process.WaitForExitWithTimeout(base::Seconds(45), exit_code); } +void SleepFor(int seconds) { + VLOG(2) << "Sleeping " << seconds << " seconds..."; + base::WaitableEvent sleep(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + base::ThreadPool::PostDelayedTask( + FROM_HERE, {base::MayBlock()}, + base::BindOnce(&base::WaitableEvent::Signal, base::Unretained(&sleep)), + base::Seconds(seconds)); + sleep.Wait(); + VLOG(2) << "Sleep complete."; +} + bool WaitFor(base::RepeatingCallback predicate) { base::TimeTicks deadline = base::TimeTicks::Now() + TestTimeouts::action_max_timeout(); diff --git a/chrome/updater/test/integration_tests_impl.h b/chrome/updater/test/integration_tests_impl.h index 7e5937895a53ac..dc8c38b043309a 100644 --- a/chrome/updater/test/integration_tests_impl.h +++ b/chrome/updater/test/integration_tests_impl.h @@ -51,6 +51,11 @@ void EnterTestMode(const GURL& url); // Copies the logs to a location where they can be retrieved by ResultDB. void CopyLog(const base::FilePath& src_dir); +// Sleeps for the given number of seconds. This should be avoided, but in some +// cases surrounding uninstall it is necessary since the processes can exit +// prior to completing the actual uninstallation. +void SleepFor(int seconds); + // Waits for a given predicate to become true, testing it by polling. Returns // true if the predicate becomes true before a timeout, otherwise returns false. bool WaitFor(base::RepeatingCallback predicate); @@ -138,7 +143,7 @@ void ExpectAppVersion(UpdaterScope scope, void RegisterApp(UpdaterScope scope, const std::string& app_id); -void WaitForUpdaterExit(UpdaterScope scope); +void WaitForServerExit(UpdaterScope scope); #if defined(OS_WIN) void ExpectInterfacesRegistered(UpdaterScope scope); diff --git a/chrome/updater/test/integration_tests_linux.cc b/chrome/updater/test/integration_tests_linux.cc index 6850c64921d38f..d4fb5b6795dc86 100644 --- a/chrome/updater/test/integration_tests_linux.cc +++ b/chrome/updater/test/integration_tests_linux.cc @@ -27,7 +27,7 @@ absl::optional GetInstalledExecutablePath(UpdaterScope scope) { return absl::nullopt; } -void WaitForUpdaterExit(UpdaterScope scope) { +void WaitForServerExit(UpdaterScope scope) { NOTREACHED(); } diff --git a/chrome/updater/test/integration_tests_mac.mm b/chrome/updater/test/integration_tests_mac.mm index 4ff231403eab88..89aa572bfc6612 100644 --- a/chrome/updater/test/integration_tests_mac.mm +++ b/chrome/updater/test/integration_tests_mac.mm @@ -300,7 +300,7 @@ void ExpectNotActive(UpdaterScope scope, const std::string& app_id) { EXPECT_FALSE(base::PathIsWritable(*path)); } -void WaitForUpdaterExit(UpdaterScope /*scope*/) { +void WaitForServerExit(UpdaterScope /*scope*/) { ASSERT_TRUE(WaitFor(base::BindRepeating([]() { std::string ps_stdout; EXPECT_TRUE(base::GetAppOutput({"ps", "ax", "-o", "command"}, &ps_stdout)); diff --git a/chrome/updater/test/integration_tests_win.cc b/chrome/updater/test/integration_tests_win.cc index 1a29f59ffefe53..e7bbeacb6319ae 100644 --- a/chrome/updater/test/integration_tests_win.cc +++ b/chrome/updater/test/integration_tests_win.cc @@ -290,8 +290,8 @@ void CheckInstallation(UpdaterScope scope, // Returns true is any updater process is found running in any session in the // system, regardless of its path. bool IsUpdaterRunning() { - return IsProcessRunning(kUpdaterProcessName) || - IsProcessRunning(base::UTF8ToWide(kUninstallScript).c_str()); + ProcessFilterName filter(kUpdaterProcessName); + return base::ProcessIterator(&filter).NextProcessEntry(); } } // namespace @@ -411,6 +411,10 @@ void Uninstall(UpdaterScope scope) { int exit_code = -1; ASSERT_TRUE(Run(scope, command_line, &exit_code)); EXPECT_EQ(0, exit_code); + + // Uninstallation involves a race with the uninstall.cmd script and the + // process exit. Sleep to allow the script to complete its work. + SleepFor(5); } void SetActive(UpdaterScope /*scope*/, const std::string& id) { @@ -446,7 +450,7 @@ void ExpectNotActive(UpdaterScope /*scope*/, const std::string& id) { // Waits for all updater processes to end, including the server process holding // the prefs lock. -void WaitForUpdaterExit(UpdaterScope /*scope*/) { +void WaitForServerExit(UpdaterScope /*scope*/) { WaitFor(base::BindRepeating([]() { return !IsUpdaterRunning(); })); } diff --git a/chromecast/app/BUILD.gn b/chromecast/app/BUILD.gn index aa703d36f9438b..0b84bbd35ae9f8 100644 --- a/chromecast/app/BUILD.gn +++ b/chromecast/app/BUILD.gn @@ -52,6 +52,7 @@ if (enable_cast_media_runtime) { "//chromecast:chromecast_buildflags", "//chromecast/base", "//chromecast/browser", + "//chromecast/browser/migration", "//chromecast/cast_core:cast_runtime_content_client_factories", "//chromecast/common", "//chromecast/common:resource_delegate", diff --git a/chromecast/browser/cast_display_configurator.cc b/chromecast/browser/cast_display_configurator.cc index f0c789dcead5d7..6349cf4aba80b2 100644 --- a/chromecast/browser/cast_display_configurator.cc +++ b/chromecast/browser/cast_display_configurator.cc @@ -246,7 +246,15 @@ void CastDisplayConfigurator::OnDisplayConfigured( bool config_success) { DCHECK(display); DCHECK(mode); - DCHECK_EQ(display, display_); + + // Discard events for previous configurations. It is safe to discard since a + // new configuration round was initiated and we're waiting for another + // OnDisplayConfigured() event with the up-to-date display to arrive. + // + // This typically only happens when there's crashes and the state updates at + // the same time old notifications are received. + if (display != display_) + return; const gfx::Rect bounds(origin, mode->size()); DVLOG(1) << __func__ << " success=" << config_success diff --git a/chromecast/renderer/extensions/extension_hooks_delegate.cc b/chromecast/renderer/extensions/extension_hooks_delegate.cc index 3e7b878e692a54..51726cb49c46ed 100644 --- a/chromecast/renderer/extensions/extension_hooks_delegate.cc +++ b/chromecast/renderer/extensions/extension_hooks_delegate.cc @@ -58,7 +58,7 @@ void GetAliasedFeature(v8::Local property_name, const v8::PropertyCallbackInfo& info) { v8::Isolate* isolate = info.GetIsolate(); v8::HandleScope handle_scope(isolate); - v8::Local context = info.Holder()->CreationContext(); + v8::Local context = info.Holder()->GetCreationContextChecked(); v8::TryCatch try_catch(isolate); v8::Local chrome; diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd index b4241327732b82..76b227d30d56c7 100644 --- a/chromeos/chromeos_strings.grd +++ b/chromeos/chromeos_strings.grd @@ -2191,10 +2191,7 @@ Try tapping the mic to ask me anything. Use the QR code or <a id="rsuCodeDialogLink">this link</a> to retrieve an 8 character unlock code to perform RMA Server Unlock - Enter the unlock code: - - - Enter RSU Code + Enter the 8-character unlock code Go to this link for your RSU unlock code diff --git a/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_RSU_CODE_LABEL.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_RSU_CODE_LABEL.png.sha1 index 740709fbfe81a3..3aaba881206460 100644 --- a/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_RSU_CODE_LABEL.png.sha1 +++ b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_RSU_CODE_LABEL.png.sha1 @@ -1 +1 @@ -c1105993eb549b25da9b7b1b790cc6817f7c1b23 \ No newline at end of file +32e93e3a03f8c7fbb1126237f873a0041b7542aa \ No newline at end of file diff --git a/chromeos/components/onc/onc_test_utils.cc b/chromeos/components/onc/onc_test_utils.cc index 3be625da6091f6..6832634f6efa20 100644 --- a/chromeos/components/onc/onc_test_utils.cc +++ b/chromeos/components/onc/onc_test_utils.cc @@ -65,8 +65,9 @@ std::unique_ptr ReadTestJson(const std::string& filename) { LOG(FATAL) << "Unable to get test file path for: " << filename; return result; } - JSONFileValueDeserializer deserializer(path, - base::JSON_ALLOW_TRAILING_COMMAS); + JSONFileValueDeserializer deserializer( + path, + base::JSON_PARSE_CHROMIUM_EXTENSIONS | base::JSON_ALLOW_TRAILING_COMMAS); std::string error_message; result = deserializer.Deserialize(nullptr, &error_message); CHECK(result != nullptr) << "Couldn't json-deserialize file: " << filename diff --git a/chromeos/components/onc/onc_utils.cc b/chromeos/components/onc/onc_utils.cc index 8a298fc366c59c..4fa47c7847811c 100644 --- a/chromeos/components/onc/onc_utils.cc +++ b/chromeos/components/onc/onc_utils.cc @@ -332,7 +332,8 @@ base::Value ReadDictionaryFromJson(const std::string& json) { } base::JSONReader::ValueWithError parsed_json = base::JSONReader::ReadAndReturnValueWithError( - json, base::JSON_ALLOW_TRAILING_COMMAS); + json, base::JSON_PARSE_CHROMIUM_EXTENSIONS | + base::JSON_ALLOW_TRAILING_COMMAS); if (!parsed_json.value || !parsed_json.value->is_dict()) { NET_LOG(ERROR) << "Invalid JSON Dictionary: " << parsed_json.error_message; return base::Value(); diff --git a/chromeos/network/client_cert_resolver_unittest.cc b/chromeos/network/client_cert_resolver_unittest.cc index 5086535328dec1..aa07f021d25aa2 100644 --- a/chromeos/network/client_cert_resolver_unittest.cc +++ b/chromeos/network/client_cert_resolver_unittest.cc @@ -376,7 +376,8 @@ class ClientCertResolverTest : public testing::Test, base::StringPiece policy_json) { base::JSONReader::ValueWithError parsed_json = base::JSONReader::ReadAndReturnValueWithError( - policy_json, base::JSON_ALLOW_TRAILING_COMMAS); + policy_json, + base::JSON_ALLOW_TRAILING_COMMAS | base::JSON_ALLOW_CONTROL_CHARS); ASSERT_TRUE(parsed_json.value) << parsed_json.error_message; base::ListValue* policy = nullptr; diff --git a/chromeos/network/network_sms_handler.cc b/chromeos/network/network_sms_handler.cc index e65eba4a81415d..a0636db45f2ba7 100644 --- a/chromeos/network/network_sms_handler.cc +++ b/chromeos/network/network_sms_handler.cc @@ -336,8 +336,9 @@ void NetworkSmsHandler::DevicePropertiesCallback( const std::string* object_path_string = properties->FindStringKey(shill::kDBusObjectProperty); - if (!object_path_string) { - NET_LOG(ERROR) << "Device has no DBusObject Property: " << device_path; + if (!object_path_string || object_path_string->empty()) { + NET_LOG(ERROR) << "Device has no or empty DBusObject Property: " + << device_path; return; } dbus::ObjectPath object_path(*object_path_string); diff --git a/chromeos/network/network_sms_handler_unittest.cc b/chromeos/network/network_sms_handler_unittest.cc index 5b99bb25009608..1a03d5e7bd285f 100644 --- a/chromeos/network/network_sms_handler_unittest.cc +++ b/chromeos/network/network_sms_handler_unittest.cc @@ -130,6 +130,19 @@ TEST_F(NetworkSmsHandlerTest, SmsHandlerDbusStub) { EXPECT_NE(messages.find(kMessage1), messages.end()); } +TEST_F(NetworkSmsHandlerTest, SmsHandlerEmptyDbusObjectPath) { + // This test verifies no crash should occur when the device dbus object path + // is an empty value. + device_test_->SetDeviceProperty(kCellularDevicePath, + shill::kDBusObjectProperty, base::Value(""), + /*notify_changed=*/true); + base::RunLoop().RunUntilIdle(); + network_sms_handler_->RequestUpdate(); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(test_observer_->message_count(), 0); +} + TEST_F(NetworkSmsHandlerTest, SmsHandlerDeviceObjectPathChange) { // Fake the SIM being switched to a different SIM. device_test_->SetDeviceProperty( diff --git a/chromeos/profiles/orderfile.newest.txt b/chromeos/profiles/orderfile.newest.txt index bd0dbb3045b3f1..d5652da902d1a6 100644 --- a/chromeos/profiles/orderfile.newest.txt +++ b/chromeos/profiles/orderfile.newest.txt @@ -1 +1 @@ -chromeos-chrome-orderfile-field-98-4729.0-1638787322-benchmark-98.0.4758.3-r1.orderfile.xz +chromeos-chrome-orderfile-field-98-4729.0-1638787322-benchmark-98.0.4758.5-r1.orderfile.xz diff --git a/chromeos/services/bluetooth_config/device_cache_impl.cc b/chromeos/services/bluetooth_config/device_cache_impl.cc index 0e96712874af31..7e689b1a08be41 100644 --- a/chromeos/services/bluetooth_config/device_cache_impl.cc +++ b/chromeos/services/bluetooth_config/device_cache_impl.cc @@ -8,6 +8,7 @@ #include "base/containers/contains.h" #include "chromeos/services/bluetooth_config/device_conversion_util.h" +#include "device/bluetooth/chromeos/bluetooth_utils.h" namespace chromeos { namespace bluetooth_config { @@ -156,8 +157,10 @@ void DeviceCacheImpl::OnDeviceNicknameChanged(const std::string& device_id, } void DeviceCacheImpl::FetchInitialDeviceLists() { - for (const device::BluetoothDevice* device : - bluetooth_adapter_->GetDevices()) { + device::BluetoothAdapter::DeviceList devices = FilterBluetoothDeviceList( + bluetooth_adapter_->GetDevices(), device::BluetoothFilterType::KNOWN, + /*max_devices=*/0); + for (const device::BluetoothDevice* device : devices) { if (device->IsPaired()) { paired_devices_.push_back( GeneratePairedBluetoothDeviceProperties(device)); @@ -204,7 +207,12 @@ bool DeviceCacheImpl::AttemptUpdatePairedDeviceMetadata( return paired_device->device_properties->id; }); - // If device is not found in |paired_devices|, don't update. + // If device is not found in |paired_devices|, don't update. This is done + // because when a paired device is forgotten, it is removed from + // |paired_devices|, but then OnDeviceChanged() is called with + // device->IsPaired() == true. If we don't have this check here, the device + // will be incorrectly added back into |paired_devices|. See + // crrev.com/c/3287422. if (!device_found) return false; @@ -230,6 +238,10 @@ bool DeviceCacheImpl::AttemptSetDeviceInUnpairedDeviceList( if (device->IsPaired()) return false; + // Check if the device should be added to the unpaired device list. + if (device::IsUnsupportedDevice(device)) + return false; + // Remove the old (stale) properties, if they exist. RemoveFromUnpairedDeviceList(device); @@ -254,16 +266,6 @@ bool DeviceCacheImpl::RemoveFromUnpairedDeviceList( bool DeviceCacheImpl::AttemptUpdateUnpairedDeviceMetadata( device::BluetoothDevice* device) { - bool device_found = - base::Contains(unpaired_devices_, device->GetIdentifier(), - [](const auto& unpaired_device) { - return unpaired_device->device_properties->id; - }); - - // If device is not found in |unpaired_devices|, don't update. - if (!device_found) - return false; - // Remove existing metadata about |device|. bool updated = RemoveFromUnpairedDeviceList(device); diff --git a/chromeos/services/bluetooth_config/device_cache_impl.h b/chromeos/services/bluetooth_config/device_cache_impl.h index 0492b934dc7625..13e05ff877259b 100644 --- a/chromeos/services/bluetooth_config/device_cache_impl.h +++ b/chromeos/services/bluetooth_config/device_cache_impl.h @@ -119,8 +119,8 @@ class DeviceCacheImpl : public DeviceCache, bool RemoveFromUnpairedDeviceList(device::BluetoothDevice* device); // Attempts to add updated metadata about |device| to |paired_devices_|. If - // |device| is not found in |unpaired_devices_|, no update is performed. - // Returns true if the device was updated in the list. + // |device| is not found in |unpaired_devices_|, it is added. Returns true if + // the device was updated in the list. bool AttemptUpdateUnpairedDeviceMetadata(device::BluetoothDevice* device); // Sorts |unpaired_devices_| based on signal strength. This function is called diff --git a/chromeos/services/bluetooth_config/device_cache_impl_unittest.cc b/chromeos/services/bluetooth_config/device_cache_impl_unittest.cc index 4f8235607170fe..6ef722f109b6b8 100644 --- a/chromeos/services/bluetooth_config/device_cache_impl_unittest.cc +++ b/chromeos/services/bluetooth_config/device_cache_impl_unittest.cc @@ -94,7 +94,9 @@ class DeviceCacheImplTest : public testing::Test { void AddDevice(bool paired, bool connected, std::string* id_out, - const absl::optional inquiry_rssi = absl::nullopt) { + const absl::optional inquiry_rssi = absl::nullopt, + const device::BluetoothDeviceType device_type = + device::BluetoothDeviceType::AUDIO) { // We use the number of devices created in this test as the address. std::string address = base::NumberToString(num_devices_created_); ++num_devices_created_; @@ -106,8 +108,12 @@ class DeviceCacheImplTest : public testing::Test { std::make_unique>( mock_adapter_.get(), kTestBluetoothClass, kTestBluetoothName, address, paired, connected); + ON_CALL(*mock_device, GetType()) + .WillByDefault(testing::Return(device::BLUETOOTH_TRANSPORT_DUAL)); ON_CALL(*mock_device, GetInquiryRSSI()) .WillByDefault(testing::Return(inquiry_rssi)); + ON_CALL(*mock_device, GetDeviceType()) + .WillByDefault(testing::Return(device_type)); device::BluetoothDevice* device = mock_device.get(); mock_devices_.push_back(std::move(mock_device)); @@ -473,7 +479,7 @@ TEST_F(DeviceCacheImplTest, PairedDeviceBluetoothClassChanges) { PairedDeviceList list = GetPairedDevices(); EXPECT_EQ(1u, list.size()); EXPECT_EQ(paired_device_id, list[0]->device_properties->id); - EXPECT_EQ(mojom::DeviceType::kUnknown, + EXPECT_EQ(mojom::DeviceType::kHeadset, list[0]->device_properties->device_type); // Change its device type. @@ -496,8 +502,6 @@ TEST_F(DeviceCacheImplTest, PairedDeviceForgotten) { PairedDeviceList list = GetPairedDevices(); EXPECT_EQ(1u, list.size()); EXPECT_EQ(paired_device_id, list[0]->device_properties->id); - EXPECT_EQ(mojom::DeviceType::kUnknown, - list[0]->device_properties->device_type); ForgetDevice(paired_device_id); EXPECT_EQ(2u, GetNumPairedDeviceListObserverEvents()); @@ -515,15 +519,15 @@ TEST_F(DeviceCacheImplTest, UnpairedDeviceBluetoothClassChanges) { UnpairedDeviceList list = GetUnpairedDevices(); EXPECT_EQ(1u, list.size()); EXPECT_EQ(unpaired_device_id, list[0]->id); - EXPECT_EQ(mojom::DeviceType::kUnknown, list[0]->device_type); + EXPECT_EQ(mojom::DeviceType::kHeadset, list[0]->device_type); // Change its device type. - ChangeDeviceType(unpaired_device_id, device::BluetoothDeviceType::PHONE); + ChangeDeviceType(unpaired_device_id, device::BluetoothDeviceType::VIDEO); EXPECT_EQ(2u, GetNumUnpairedDeviceListObserverEvents()); list = GetUnpairedDevices(); EXPECT_EQ(1u, list.size()); EXPECT_EQ(unpaired_device_id, list[0]->id); - EXPECT_EQ(mojom::DeviceType::kPhone, list[0]->device_type); + EXPECT_EQ(mojom::DeviceType::kVideoCamera, list[0]->device_type); } TEST_F(DeviceCacheImplTest, UnpairedDeviceSignalStrengthChanges) { @@ -593,5 +597,140 @@ TEST_F(DeviceCacheImplTest, BluetoothTurnsOff) { EXPECT_TRUE(GetUnpairedDevices().empty()); } +TEST_F(DeviceCacheImplTest, UnknownUnpairedDeviceNotReturned) { + Init(); + EXPECT_TRUE(GetUnpairedDevices().empty()); + + // Add an unknown device. This should not be added to the unpaired list and no + // observers notified. + std::string unpaired_device_id; + AddDevice(/*paired=*/false, /*connected=*/false, &unpaired_device_id, + /*inquiry_rssi=*/1, + /*device_type=*/device::BluetoothDeviceType::UNKNOWN); + EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents()); + EXPECT_TRUE(GetUnpairedDevices().empty()); + + // Update a property in the device. This should not cause any observable + // actions. + ChangeInquiryRssi(unpaired_device_id, 3); + EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents()); + EXPECT_TRUE(GetUnpairedDevices().empty()); + + // Remove the device. This should not cause any observable actions. + RemoveDevice(unpaired_device_id); + EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents()); + EXPECT_TRUE(GetUnpairedDevices().empty()); +} + +TEST_F(DeviceCacheImplTest, UnsupportedUnpairedDeviceNotReturned) { + Init(); + EXPECT_TRUE(GetUnpairedDevices().empty()); + + // Add a device of type PHONE. This should not be added to the unpaired list + // and no observers notified because this device type is unsupported. + std::string unpaired_device_id; + AddDevice(/*paired=*/false, /*connected=*/false, &unpaired_device_id, + /*inquiry_rssi=*/1, + /*device_type=*/device::BluetoothDeviceType::PHONE); + EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents()); + EXPECT_TRUE(GetUnpairedDevices().empty()); + + // Update a property in the device. This should not cause any observable + // actions. + ChangeInquiryRssi(unpaired_device_id, 3); + EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents()); + EXPECT_TRUE(GetUnpairedDevices().empty()); + + // Remove the device. This should not cause any observable actions. + RemoveDevice(unpaired_device_id); + EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents()); + EXPECT_TRUE(GetUnpairedDevices().empty()); +} + +TEST_F(DeviceCacheImplTest, UnknownUnpairedDeviceChangesToKnown) { + Init(); + EXPECT_TRUE(GetUnpairedDevices().empty()); + + // Add an unknown device. This should not be added to the unpaired list and no + // observers notified. + std::string unpaired_device_id; + AddDevice(/*paired=*/false, /*connected=*/false, &unpaired_device_id, + /*inquiry_rssi=*/1, + /*device_type=*/device::BluetoothDeviceType::UNKNOWN); + EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents()); + EXPECT_TRUE(GetUnpairedDevices().empty()); + + // Update the device type to a known type. This should add the device to the + // unpaired list. + ChangeDeviceType(unpaired_device_id, device::BluetoothDeviceType::VIDEO); + UnpairedDeviceList unpaired_list = GetUnpairedDevices(); + EXPECT_EQ(1u, GetNumUnpairedDeviceListObserverEvents()); + EXPECT_EQ(1u, unpaired_list.size()); + EXPECT_EQ(unpaired_device_id, unpaired_list[0]->id); + + // Remove the device. This should notify observers. + RemoveDevice(unpaired_device_id); + EXPECT_EQ(2u, GetNumUnpairedDeviceListObserverEvents()); + EXPECT_TRUE(GetUnpairedDevices().empty()); +} + +TEST_F(DeviceCacheImplTest, KnownUnpairedDeviceChangesToUnknown) { + Init(); + EXPECT_TRUE(GetUnpairedDevices().empty()); + + // Add a known device. This should notify observers. + std::string unpaired_device_id; + AddDevice(/*paired=*/false, /*connected=*/false, &unpaired_device_id, + /*inquiry_rssi=*/1, + /*device_type=*/device::BluetoothDeviceType::VIDEO); + UnpairedDeviceList unpaired_list = GetUnpairedDevices(); + EXPECT_EQ(1u, GetNumUnpairedDeviceListObserverEvents()); + EXPECT_EQ(1u, unpaired_list.size()); + EXPECT_EQ(unpaired_device_id, unpaired_list[0]->id); + + // Update the device type to unknown type. This should remove the device from + // the unpaired list and notify observers. + ChangeDeviceType(unpaired_device_id, device::BluetoothDeviceType::UNKNOWN); + EXPECT_EQ(2u, GetNumUnpairedDeviceListObserverEvents()); + EXPECT_TRUE(GetUnpairedDevices().empty()); + + // Remove the device. This should not cause any observable actions. + RemoveDevice(unpaired_device_id); + EXPECT_EQ(2u, GetNumUnpairedDeviceListObserverEvents()); + EXPECT_TRUE(GetUnpairedDevices().empty()); +} + +TEST_F(DeviceCacheImplTest, UnknownPairedDeviceReturned) { + Init(); + EXPECT_TRUE(GetPairedDevices().empty()); + EXPECT_TRUE(GetUnpairedDevices().empty()); + + // Add an unknown paired device. This should notify observers. + std::string paired_device_id; + AddDevice(/*paired=*/true, /*connected=*/false, &paired_device_id, + /*inquiry_rssi=*/1, + /*device_type=*/device::BluetoothDeviceType::UNKNOWN); + EXPECT_EQ(1u, GetNumPairedDeviceListObserverEvents()); + PairedDeviceList paired_list = GetPairedDevices(); + EXPECT_EQ(1u, paired_list.size()); + EXPECT_EQ(paired_device_id, paired_list[0]->device_properties->id); + + // Update a property in the device. This should notify observers. + ChangeDeviceIsBlockedByPolicy(paired_device_id, + /*is_blocked_by_policy=*/true); + EXPECT_EQ(2u, GetNumPairedDeviceListObserverEvents()); + paired_list = GetPairedDevices(); + EXPECT_EQ(1u, paired_list.size()); + EXPECT_TRUE(paired_list[0]->device_properties->is_blocked_by_policy); + + // Change the device to unpaired. This should update the paired device list + // but not the unpaired device list. + ChangePairingState(paired_device_id, /*is_now_paired=*/false); + EXPECT_EQ(3u, GetNumPairedDeviceListObserverEvents()); + EXPECT_TRUE(GetPairedDevices().empty()); + EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents()); + EXPECT_TRUE(GetUnpairedDevices().empty()); +} + } // namespace bluetooth_config } // namespace chromeos diff --git a/components/autofill/ios/form_util/unique_id_data_tab_helper_unittest.mm b/components/autofill/ios/form_util/unique_id_data_tab_helper_unittest.mm index d8f2ef397c07f9..b7d59003c1d804 100644 --- a/components/autofill/ios/form_util/unique_id_data_tab_helper_unittest.mm +++ b/components/autofill/ios/form_util/unique_id_data_tab_helper_unittest.mm @@ -13,15 +13,15 @@ #error "This file requires ARC support." #endif -// Test fixture for TabIdTabHelper class. +// Test fixture for UniqueIDDataTabHelper class. class UniqueIDDataTabHelperTest : public PlatformTest { protected: web::FakeWebState first_web_state_; web::FakeWebState second_web_state_; }; -// Tests that a tab ID is returned for a WebState, and tab ID's are different -// for different WebStates if they were once set differently. +// Tests that a renderer ID is returned for a WebState, and rendered ID's are +// different for different WebStates if they were once set differently. TEST_F(UniqueIDDataTabHelperTest, UniqueIdentifiers) { UniqueIDDataTabHelper::CreateForWebState(&first_web_state_); UniqueIDDataTabHelper::CreateForWebState(&second_web_state_); @@ -51,7 +51,7 @@ EXPECT_NE(first_available_unique_id, second_available_unique_id); } -// Tests that a tab ID is stable across successive calls. +// Tests that a renderer ID is stable across successive calls. TEST_F(UniqueIDDataTabHelperTest, StableAcrossCalls) { UniqueIDDataTabHelper::CreateForWebState(&first_web_state_); UniqueIDDataTabHelper* tab_helper = diff --git a/components/autofill_assistant/browser/BUILD.gn b/components/autofill_assistant/browser/BUILD.gn index e9ddc6e326ee82..f6311158258235 100644 --- a/components/autofill_assistant/browser/BUILD.gn +++ b/components/autofill_assistant/browser/BUILD.gn @@ -198,6 +198,10 @@ static_library("browser") { "service/access_token_fetcher.h", "service/api_key_fetcher.cc", "service/api_key_fetcher.h", + "service/cup.cc", + "service/cup.h", + "service/cup_factory.cc", + "service/cup_factory.h", "service/cup_impl.cc", "service/cup_impl.h", "service/rpc_type.h", @@ -434,9 +438,13 @@ source_set("unit_tests") { "script_tracker_unittest.cc", "selector_unittest.cc", "service/api_key_fetcher_unittest.cc", + "service/cup_factory_unittest.cc", "service/cup_impl_unittest.cc", + "service/cup_unittest.cc", "service/mock_access_token_fetcher.cc", "service/mock_access_token_fetcher.h", + "service/mock_cup.cc", + "service/mock_cup.h", "service/mock_service_request_sender.cc", "service/mock_service_request_sender.h", "service/mock_simple_url_loader_factory.cc", diff --git a/components/autofill_assistant/browser/service/cup.cc b/components/autofill_assistant/browser/service/cup.cc new file mode 100644 index 00000000000000..53508a2f6769ff --- /dev/null +++ b/components/autofill_assistant/browser/service/cup.cc @@ -0,0 +1,40 @@ +// 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 "cup.h" + +#include "base/feature_list.h" +#include "components/autofill_assistant/browser/features.h" + +namespace { + +bool ShouldSignGetActionsRequests() { + return base::FeatureList::IsEnabled( + autofill_assistant::features::kAutofillAssistantSignGetActionsRequests); +} + +bool ShouldVerifyGetActionsResponses() { + return ShouldSignGetActionsRequests() && + base::FeatureList::IsEnabled( + autofill_assistant::features:: + kAutofillAssistantVerifyGetActionsResponses); +} + +} // namespace + +namespace autofill_assistant { + +namespace cup { + +bool ShouldSignRequests(RpcType rpc_type) { + return ShouldSignGetActionsRequests() && rpc_type == RpcType::GET_ACTIONS; +} + +bool ShouldVerifyResponses(RpcType rpc_type) { + return ShouldVerifyGetActionsResponses() && rpc_type == RpcType::GET_ACTIONS; +} + +} // namespace cup + +} // namespace autofill_assistant diff --git a/components/autofill_assistant/browser/service/cup.h b/components/autofill_assistant/browser/service/cup.h new file mode 100644 index 00000000000000..eb4d4a82312950 --- /dev/null +++ b/components/autofill_assistant/browser/service/cup.h @@ -0,0 +1,47 @@ +// 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 COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_CUP_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_CUP_H_ + +#include "components/autofill_assistant/browser/service/rpc_type.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace autofill_assistant { + +namespace cup { + +// Whether |PackAndSignRequest| should be called before the request is +// submitted. Can be |false| because signing is disabled via feature flag, +// or given message type doesn't support CUP signing. +bool ShouldSignRequests(RpcType rpc_type); + +// Whether |UnpackResponse| should be called on the response from the service +// call. Can be false because verification is disabled via feature flag or +// |ShouldSignRequest| returns |false|. +bool ShouldVerifyResponses(RpcType rpc_type); + +class CUP { + public: + virtual ~CUP() = default; + + // Generates a new |request| where |original_request| is packed and signed in + // its |cup_data| field. + virtual std::string PackAndSignRequest( + const std::string& original_request) = 0; + + // Generates a new |response| where |original_response| is unpacked from + // the |cup_data| field. + virtual absl::optional UnpackResponse( + const std::string& original_response) = 0; + + protected: + CUP() = default; +}; + +} // namespace cup + +} // namespace autofill_assistant + +#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_CUP_H_ diff --git a/components/autofill_assistant/browser/service/cup_factory.cc b/components/autofill_assistant/browser/service/cup_factory.cc new file mode 100644 index 00000000000000..ee916792752df4 --- /dev/null +++ b/components/autofill_assistant/browser/service/cup_factory.cc @@ -0,0 +1,18 @@ +// 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 "components/autofill_assistant/browser/service/cup_factory.h" +#include "components/autofill_assistant/browser/service/cup_impl.h" + +namespace autofill_assistant { + +namespace cup { + +std::unique_ptr CUPImplFactory::CreateInstance(RpcType rpc_type) const { + return std::make_unique(CUPImpl::CreateQuerySigner(), rpc_type); +} + +} // namespace cup + +} // namespace autofill_assistant diff --git a/components/autofill_assistant/browser/service/cup_factory.h b/components/autofill_assistant/browser/service/cup_factory.h new file mode 100644 index 00000000000000..aab3b010786ded --- /dev/null +++ b/components/autofill_assistant/browser/service/cup_factory.h @@ -0,0 +1,41 @@ +// 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 COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_CUP_FACTORY_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_CUP_FACTORY_H_ + +#include "components/autofill_assistant/browser/service/cup.h" + +namespace autofill_assistant { + +namespace cup { + +// Base interface for creators of CUP (Client Update Protocol) instances. +class CUPFactory { + public: + virtual ~CUPFactory() = default; + + // Creates an instance of CUP for a call of given |rpc_type|. + virtual std::unique_ptr CreateInstance(RpcType rpc_type) const = 0; + + protected: + CUPFactory() = default; +}; + +// Implementation of |CUPFactory| for |CUPImpl| instances. +class CUPImplFactory : public CUPFactory { + public: + CUPImplFactory() = default; + ~CUPImplFactory() override = default; + CUPImplFactory(const CUPImplFactory&) = delete; + CUPImplFactory& operator=(const CUPImplFactory&) = delete; + + std::unique_ptr CreateInstance(RpcType rpc_type) const override; +}; + +} // namespace cup + +} // namespace autofill_assistant + +#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_CUP_FACTORY_H_ diff --git a/components/autofill_assistant/browser/service/cup_factory_unittest.cc b/components/autofill_assistant/browser/service/cup_factory_unittest.cc new file mode 100644 index 00000000000000..f7b99556e7fe83 --- /dev/null +++ b/components/autofill_assistant/browser/service/cup_factory_unittest.cc @@ -0,0 +1,31 @@ +// 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 "components/autofill_assistant/browser/service/cup_factory.h" +#include "components/autofill_assistant/browser/service/cup_impl.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace { + +class CUPFactoryTest : public testing::Test { + public: + CUPFactoryTest() + : cup_factory_{ + std::make_unique()} {} + ~CUPFactoryTest() override = default; + + protected: + std::unique_ptr cup_factory_; +}; + +TEST_F(CUPFactoryTest, ShouldCreateCupImplInstance) { + std::unique_ptr cup = + cup_factory_->CreateInstance(autofill_assistant::RpcType::GET_ACTIONS); + EXPECT_NE(cup, nullptr); + + std::string packed_request = cup->PackAndSignRequest("request"); + EXPECT_FALSE(packed_request.empty()); +} + +} // namespace diff --git a/components/autofill_assistant/browser/service/cup_impl.cc b/components/autofill_assistant/browser/service/cup_impl.cc index e7c2f1cd96ec36..569f521e65700b 100644 --- a/components/autofill_assistant/browser/service/cup_impl.cc +++ b/components/autofill_assistant/browser/service/cup_impl.cc @@ -5,10 +5,9 @@ #include "cup_impl.h" #include "base/base64.h" -#include "base/feature_list.h" +#include "base/logging.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" -#include "components/autofill_assistant/browser/features.h" #include "components/autofill_assistant/browser/service.pb.h" #include "components/client_update_protocol/ecdsa.h" @@ -27,38 +26,24 @@ std::string GetKey(const char* key_bytes_base64) { : std::string(); } -bool ShouldSignGetActionsRequests() { - return base::FeatureList::IsEnabled( - autofill_assistant::features::kAutofillAssistantSignGetActionsRequests); -} - -bool ShouldVerifyGetActionsResponses() { - return ShouldSignGetActionsRequests() && - base::FeatureList::IsEnabled( - autofill_assistant::features:: - kAutofillAssistantVerifyGetActionsResponses); -} - } // namespace namespace autofill_assistant { +namespace cup { + std::unique_ptr CUPImpl::CreateQuerySigner() { return client_update_protocol::Ecdsa::Create(kKeyVersion, GetKey(kKeyPubBytesBase64)); } -bool CUPImpl::ShouldSignRequests(RpcType rpc_type) { - return ShouldSignGetActionsRequests() && rpc_type == RpcType::GET_ACTIONS; -} - -bool CUPImpl::ShouldVerifyResponses(RpcType rpc_type) { - return ShouldVerifyGetActionsResponses() && rpc_type == RpcType::GET_ACTIONS; -} - -CUPImpl::CUPImpl(std::unique_ptr query_signer) +CUPImpl::CUPImpl(std::unique_ptr query_signer, + RpcType rpc_type) : query_signer_{std::move(query_signer)} { DCHECK(query_signer_); + + // Only GET_ACTIONS calls have support for CUP at this moment. + DCHECK(rpc_type == RpcType::GET_ACTIONS); } CUPImpl::~CUPImpl() = default; @@ -110,4 +95,6 @@ client_update_protocol::Ecdsa& CUPImpl::GetQuerySigner() { return *query_signer_.get(); } +} // namespace cup + } // namespace autofill_assistant diff --git a/components/autofill_assistant/browser/service/cup_impl.h b/components/autofill_assistant/browser/service/cup_impl.h index 106ec7361ffd11..e1ecca1ff1931e 100644 --- a/components/autofill_assistant/browser/service/cup_impl.h +++ b/components/autofill_assistant/browser/service/cup_impl.h @@ -5,12 +5,13 @@ #ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_CUP_IMPL_H_ #define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_CUP_IMPL_H_ -#include "components/autofill_assistant/browser/service/rpc_type.h" +#include "components/autofill_assistant/browser/service/cup.h" #include "components/client_update_protocol/ecdsa.h" -#include "third_party/abseil-cpp/absl/types/optional.h" namespace autofill_assistant { +namespace cup { + // Implementation of the Client Update Protocol (CUP) for the service calls in // |autofill_assistant|. // https://source.chromium.org/chromium/chromium/src/+/main:docs/updater/cup.md @@ -19,37 +20,28 @@ namespace autofill_assistant { // HTTP headers, and is sent as part of the request and response body instead. // // This class can only be used once per service call. -class CUPImpl { +class CUPImpl : public CUP { public: static std::unique_ptr CreateQuerySigner(); - // Whether |PackAndSignRequest| should be called before the request is - // submitted. Can be |false| because signing is disabled via feature flag, - // or given message type doesn't support CUP signing. - static bool ShouldSignRequests(RpcType rpc_type); - - // Whether |UnpackResponse| should be called on the response from the service - // call. Can be false because verification is disabled via feature flag or - // |ShouldSignRequest| returns |false|. - static bool ShouldVerifyResponses(RpcType rpc_type); - - CUPImpl(std::unique_ptr query_signer); + CUPImpl(std::unique_ptr query_signer, + RpcType rpc_type); CUPImpl(const CUPImpl&) = delete; CUPImpl& operator=(const CUPImpl&) = delete; - ~CUPImpl(); + ~CUPImpl() override; // Generates a new |request| where |original_request| is packed and signed in // its |cup_data| field. // // Should only be called if |ShouldSignRequest| returns true. - std::string PackAndSignRequest(const std::string& original_request); + std::string PackAndSignRequest(const std::string& original_request) override; // Generates a new |response| where |original_response| is unpacked from // the |cup_data| field. // // Should only be called if |ShouldVerifyResponse| returns true. absl::optional UnpackResponse( - const std::string& original_response); + const std::string& original_response) override; // Gets the query signer object being used by this CUP instance. Needed for // testing. @@ -64,6 +56,8 @@ class CUPImpl { std::unique_ptr query_signer_; }; +} // namespace cup + } // namespace autofill_assistant #endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_CUP_IMPL_H_ diff --git a/components/autofill_assistant/browser/service/cup_impl_unittest.cc b/components/autofill_assistant/browser/service/cup_impl_unittest.cc index e5e6e32095a8fd..1272e9af41db90 100644 --- a/components/autofill_assistant/browser/service/cup_impl_unittest.cc +++ b/components/autofill_assistant/browser/service/cup_impl_unittest.cc @@ -13,92 +13,16 @@ namespace { class CUPImplTest : public testing::Test { public: - CUPImplTest() : cup_{autofill_assistant::CUPImpl::CreateQuerySigner()} {} + CUPImplTest() + : cup_{autofill_assistant::cup::CUPImpl::CreateQuerySigner(), + autofill_assistant::RpcType::GET_ACTIONS} {} ~CUPImplTest() override = default; protected: - autofill_assistant::CUPImpl cup_; - base::test::ScopedFeatureList scoped_feature_list_; - - void InitCupFeatures(bool enableSigning, bool enableVerifying) { - std::vector enabled_features; - std::vector disabled_features; - - if (enableSigning) { - enabled_features.push_back(autofill_assistant::features:: - kAutofillAssistantSignGetActionsRequests); - } else { - disabled_features.push_back(autofill_assistant::features:: - kAutofillAssistantSignGetActionsRequests); - } - - if (enableVerifying) { - enabled_features.push_back( - autofill_assistant::features:: - kAutofillAssistantVerifyGetActionsResponses); - } else { - disabled_features.push_back( - autofill_assistant::features:: - kAutofillAssistantVerifyGetActionsResponses); - } - - scoped_feature_list_.Reset(); - scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features); - } + autofill_assistant::cup::CUPImpl cup_; }; -TEST_F(CUPImplTest, ShouldSignGetActionsRequestWhenFeatureActivated) { - InitCupFeatures(true, false); - - EXPECT_TRUE(autofill_assistant::CUPImpl::ShouldSignRequests( - autofill_assistant::RpcType::GET_ACTIONS)); -} - -TEST_F(CUPImplTest, ShouldNotSignGetActionsRequestWhenFeatureNotActivated) { - InitCupFeatures(false, false); - - EXPECT_FALSE(autofill_assistant::CUPImpl::ShouldSignRequests( - autofill_assistant::RpcType::GET_ACTIONS)); -} - -TEST_F(CUPImplTest, ShouldNotSignNotGetActionsRequest) { - InitCupFeatures(true, false); - - EXPECT_FALSE(autofill_assistant::CUPImpl::ShouldSignRequests( - autofill_assistant::RpcType::GET_TRIGGER_SCRIPTS)); -} - -TEST_F(CUPImplTest, ShouldVerifyGetActionsResponseWhenFeatureActivated) { - InitCupFeatures(true, true); - - EXPECT_TRUE(autofill_assistant::CUPImpl::ShouldVerifyResponses( - autofill_assistant::RpcType::GET_ACTIONS)); -} - -TEST_F(CUPImplTest, ShouldNotVerifyGetActionsResponseWhenFeatureNotActivated) { - InitCupFeatures(true, false); - - EXPECT_FALSE(autofill_assistant::CUPImpl::ShouldVerifyResponses( - autofill_assistant::RpcType::GET_ACTIONS)); -} - -TEST_F(CUPImplTest, ShouldNotVerifyGetActionsResponseWhenSigningNotActivated) { - InitCupFeatures(false, true); - - EXPECT_FALSE(autofill_assistant::CUPImpl::ShouldVerifyResponses( - autofill_assistant::RpcType::GET_ACTIONS)); -} - -TEST_F(CUPImplTest, ShouldNotVerifyNotGetActionsResponse) { - InitCupFeatures(true, true); - - EXPECT_FALSE(autofill_assistant::CUPImpl::ShouldVerifyResponses( - autofill_assistant::RpcType::GET_TRIGGER_SCRIPTS)); -} - TEST_F(CUPImplTest, PacksAndSignsGetActionsRequest) { - InitCupFeatures(true, false); - autofill_assistant::ScriptActionRequestProto user_request; user_request.mutable_client_context()->set_experiment_ids("test"); std::string user_request_str; @@ -125,8 +49,6 @@ TEST_F(CUPImplTest, UnpacksTrustedGetActionsResponse) { } TEST_F(CUPImplTest, FailsToUnpackNonTrustedGetActionsResponse) { - InitCupFeatures(true, true); - autofill_assistant::ScriptActionRequestProto user_request; user_request.mutable_client_context()->set_experiment_ids("123"); std::string user_request_str; diff --git a/components/autofill_assistant/browser/service/cup_unittest.cc b/components/autofill_assistant/browser/service/cup_unittest.cc new file mode 100644 index 00000000000000..45aaf8d30eee83 --- /dev/null +++ b/components/autofill_assistant/browser/service/cup_unittest.cc @@ -0,0 +1,98 @@ +// 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 "cup.h" + +#include "base/test/scoped_feature_list.h" +#include "components/autofill_assistant/browser/features.h" +#include "components/autofill_assistant/browser/service.pb.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace { + +class CUPTest : public testing::Test { + public: + CUPTest() = default; + ~CUPTest() override = default; + + protected: + base::test::ScopedFeatureList scoped_feature_list_; + + void InitCupFeatures(bool enableSigning, bool enableVerifying) { + std::vector enabled_features; + std::vector disabled_features; + + if (enableSigning) { + enabled_features.push_back(autofill_assistant::features:: + kAutofillAssistantSignGetActionsRequests); + } else { + disabled_features.push_back(autofill_assistant::features:: + kAutofillAssistantSignGetActionsRequests); + } + + if (enableVerifying) { + enabled_features.push_back( + autofill_assistant::features:: + kAutofillAssistantVerifyGetActionsResponses); + } else { + disabled_features.push_back( + autofill_assistant::features:: + kAutofillAssistantVerifyGetActionsResponses); + } + + scoped_feature_list_.Reset(); + scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features); + } +}; + +TEST_F(CUPTest, ShouldSignGetActionsRequestWhenFeatureActivated) { + InitCupFeatures(true, false); + + EXPECT_TRUE(autofill_assistant::cup::ShouldSignRequests( + autofill_assistant::RpcType::GET_ACTIONS)); +} + +TEST_F(CUPTest, ShouldNotSignGetActionsRequestWhenFeatureNotActivated) { + InitCupFeatures(false, false); + + EXPECT_FALSE(autofill_assistant::cup::ShouldSignRequests( + autofill_assistant::RpcType::GET_ACTIONS)); +} + +TEST_F(CUPTest, ShouldNotSignNotGetActionsRequest) { + InitCupFeatures(true, false); + + EXPECT_FALSE(autofill_assistant::cup::ShouldSignRequests( + autofill_assistant::RpcType::GET_TRIGGER_SCRIPTS)); +} + +TEST_F(CUPTest, ShouldVerifyGetActionsResponseWhenFeatureActivated) { + InitCupFeatures(true, true); + + EXPECT_TRUE(autofill_assistant::cup::ShouldVerifyResponses( + autofill_assistant::RpcType::GET_ACTIONS)); +} + +TEST_F(CUPTest, ShouldNotVerifyGetActionsResponseWhenFeatureNotActivated) { + InitCupFeatures(true, false); + + EXPECT_FALSE(autofill_assistant::cup::ShouldVerifyResponses( + autofill_assistant::RpcType::GET_ACTIONS)); +} + +TEST_F(CUPTest, ShouldNotVerifyGetActionsResponseWhenSigningNotActivated) { + InitCupFeatures(false, true); + + EXPECT_FALSE(autofill_assistant::cup::ShouldVerifyResponses( + autofill_assistant::RpcType::GET_ACTIONS)); +} + +TEST_F(CUPTest, ShouldNotVerifyNotGetActionsResponse) { + InitCupFeatures(true, true); + + EXPECT_FALSE(autofill_assistant::cup::ShouldVerifyResponses( + autofill_assistant::RpcType::GET_TRIGGER_SCRIPTS)); +} + +} // namespace diff --git a/components/autofill_assistant/browser/service/java_service_request_sender.cc b/components/autofill_assistant/browser/service/java_service_request_sender.cc index 7fa568dea8b607..2d601550c6b648 100644 --- a/components/autofill_assistant/browser/service/java_service_request_sender.cc +++ b/components/autofill_assistant/browser/service/java_service_request_sender.cc @@ -24,7 +24,8 @@ JavaServiceRequestSender::~JavaServiceRequestSender() = default; void JavaServiceRequestSender::SendRequest(const GURL& url, const std::string& request_body, - ResponseCallback callback) { + ResponseCallback callback, + RpcType rpc_type) { DCHECK(!callback_) << __func__ << " invoked while still waiting for response to previous request"; diff --git a/components/autofill_assistant/browser/service/java_service_request_sender.h b/components/autofill_assistant/browser/service/java_service_request_sender.h index 200d17a007baab..f1e32626867d63 100644 --- a/components/autofill_assistant/browser/service/java_service_request_sender.h +++ b/components/autofill_assistant/browser/service/java_service_request_sender.h @@ -30,7 +30,8 @@ class JavaServiceRequestSender : public ServiceRequestSender { void SendRequest(const GURL& url, const std::string& request_body, - ResponseCallback callback) override; + ResponseCallback callback, + RpcType rpc_type) override; void OnResponse(JNIEnv* env, const base::android::JavaParamRef& jcaller, diff --git a/components/autofill_assistant/browser/service/mock_cup.cc b/components/autofill_assistant/browser/service/mock_cup.cc new file mode 100644 index 00000000000000..713a7b0ff32899 --- /dev/null +++ b/components/autofill_assistant/browser/service/mock_cup.cc @@ -0,0 +1,21 @@ +// 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 + +#include "components/autofill_assistant/browser/service/mock_cup.h" + +namespace autofill_assistant { + +namespace cup { + +MockCUP::MockCUP() = default; +MockCUP::~MockCUP() = default; + +MockCUPFactory::MockCUPFactory() = default; +MockCUPFactory::~MockCUPFactory() = default; + +} // namespace cup + +} // namespace autofill_assistant diff --git a/components/autofill_assistant/browser/service/mock_cup.h b/components/autofill_assistant/browser/service/mock_cup.h new file mode 100644 index 00000000000000..3621736164734f --- /dev/null +++ b/components/autofill_assistant/browser/service/mock_cup.h @@ -0,0 +1,41 @@ +// 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 COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_MOCK_CUP_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_MOCK_CUP_H_ + +#include "components/autofill_assistant/browser/service/cup.h" +#include "components/autofill_assistant/browser/service/cup_factory.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace autofill_assistant { + +namespace cup { + +class MockCUP : public CUP { + public: + MockCUP(); + ~MockCUP() override; + + MOCK_METHOD1(PackAndSignRequest, + std::string(const std::string& original_request)); + + MOCK_METHOD1( + UnpackResponse, + absl::optional(const std::string& original_response)); +}; + +class MockCUPFactory : public CUPFactory { + public: + MockCUPFactory(); + ~MockCUPFactory() override; + + MOCK_CONST_METHOD1(CreateInstance, std::unique_ptr(RpcType rpc_type)); +}; + +} // namespace cup + +} // namespace autofill_assistant + +#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_MOCK_CUP_H_ diff --git a/components/autofill_assistant/browser/service/mock_service_request_sender.h b/components/autofill_assistant/browser/service/mock_service_request_sender.h index 2911f6f92b686d..0f7d44d1a498b8 100644 --- a/components/autofill_assistant/browser/service/mock_service_request_sender.h +++ b/components/autofill_assistant/browser/service/mock_service_request_sender.h @@ -20,14 +20,16 @@ class MockServiceRequestSender : public ServiceRequestSender { void SendRequest(const GURL& url, const std::string& request_body, - ResponseCallback callback) override { - OnSendRequest(url, request_body, callback); + ResponseCallback callback, + RpcType rpc_type) override { + OnSendRequest(url, request_body, callback, rpc_type); } - MOCK_METHOD3(OnSendRequest, + MOCK_METHOD4(OnSendRequest, void(const GURL& url, const std::string& request_body, - ResponseCallback& callback)); + ResponseCallback& callback, + RpcType rpc_type)); }; } // namespace autofill_assistant diff --git a/components/autofill_assistant/browser/service/service_impl.cc b/components/autofill_assistant/browser/service/service_impl.cc index a9b7c5313415da..633a953c630ffa 100644 --- a/components/autofill_assistant/browser/service/service_impl.cc +++ b/components/autofill_assistant/browser/service/service_impl.cc @@ -16,6 +16,7 @@ #include "components/autofill_assistant/browser/features.h" #include "components/autofill_assistant/browser/protocol_utils.h" #include "components/autofill_assistant/browser/service/api_key_fetcher.h" +#include "components/autofill_assistant/browser/service/cup_factory.h" #include "components/autofill_assistant/browser/service/service_request_sender_impl.h" #include "components/autofill_assistant/browser/switches.h" #include "components/autofill_assistant/browser/trigger_context.h" @@ -44,6 +45,7 @@ std::unique_ptr ServiceImpl::Create( const ServerUrlFetcher& url_fetcher) { auto request_sender = std::make_unique( context, client->GetAccessTokenFetcher(), + std::make_unique(), std::make_unique(), ApiKeyFetcher().GetAPIKey(client->GetChannel()), /* auth_enabled = */ "false" != @@ -88,7 +90,7 @@ void ServiceImpl::GetScriptsForUrl(const GURL& url, ProtocolUtils::CreateGetScriptsRequest( url, client_context_->AsProto(), trigger_context.GetScriptParameters()), - std::move(callback)); + std::move(callback), RpcType::SUPPORTS_SCRIPT); } void ServiceImpl::GetActions(const std::string& script_path, @@ -139,7 +141,7 @@ void ServiceImpl::SendGetActions(const std::string& script_path, script_path, url, global_payload, script_payload, client_context_->AsProto(), trigger_context.GetScriptParameters(), script_store_config_), - std::move(callback)); + std::move(callback), RpcType::GET_ACTIONS); } void ServiceImpl::GetNextActions( @@ -155,7 +157,7 @@ void ServiceImpl::GetNextActions( ProtocolUtils::CreateNextScriptActionsRequest( previous_global_payload, previous_script_payload, processed_actions, timing_stats, client_context_->AsProto()), - std::move(callback)); + std::move(callback), RpcType::GET_ACTIONS); } } // namespace autofill_assistant diff --git a/components/autofill_assistant/browser/service/service_impl_unittest.cc b/components/autofill_assistant/browser/service/service_impl_unittest.cc index 267f85d9e0677e..314d6ce4289209 100644 --- a/components/autofill_assistant/browser/service/service_impl_unittest.cc +++ b/components/autofill_assistant/browser/service/service_impl_unittest.cc @@ -58,8 +58,8 @@ class ServiceImplTest : public testing::Test { TEST_F(ServiceImplTest, GetScriptsForUrl) { EXPECT_CALL(*mock_client_context_, Update); - EXPECT_CALL(*mock_request_sender_, - OnSendRequest(GURL(kScriptServerUrl), _, _)) + EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kScriptServerUrl), _, _, + RpcType::SUPPORTS_SCRIPT)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, std::string("response"))); EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, std::string("response"))); @@ -74,7 +74,7 @@ TEST_F(ServiceImplTest, GetActions) { .WillOnce(RunOnceCallback<0>("token")); EXPECT_CALL(*mock_client_context_, SetPaymentsClientToken("token")); EXPECT_CALL(*mock_request_sender_, - OnSendRequest(GURL(kActionServerUrl), _, _)) + OnSendRequest(GURL(kActionServerUrl), _, _, RpcType::GET_ACTIONS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, std::string("response"))); EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, std::string("response"))); @@ -99,7 +99,7 @@ TEST_F(ServiceImplTest, GetActionsForwardsScriptStoreConfig) { std::string get_actions_request; EXPECT_CALL(*mock_request_sender_, - OnSendRequest(GURL(kActionServerUrl), _, _)) + OnSendRequest(GURL(kActionServerUrl), _, _, RpcType::GET_ACTIONS)) .WillOnce(SaveArg<1>(&get_actions_request)); ScriptStoreConfig set_config; @@ -131,7 +131,7 @@ TEST_F(ServiceImplTest, GetActionsWithoutClientToken) { EXPECT_CALL(mock_client_, FetchPaymentsClientToken).Times(0); EXPECT_CALL(*mock_client_context_, SetPaymentsClientToken).Times(0); EXPECT_CALL(*mock_request_sender_, - OnSendRequest(GURL(kActionServerUrl), _, _)) + OnSendRequest(GURL(kActionServerUrl), _, _, RpcType::GET_ACTIONS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, std::string("response"))); EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, std::string("response"))); @@ -152,7 +152,7 @@ TEST_F(ServiceImplTest, GetActionsDoesNotReloadClientToken) { EXPECT_CALL(mock_client_, FetchPaymentsClientToken).Times(0); EXPECT_CALL(*mock_client_context_, SetPaymentsClientToken).Times(0); EXPECT_CALL(*mock_request_sender_, - OnSendRequest(GURL(kActionServerUrl), _, _)) + OnSendRequest(GURL(kActionServerUrl), _, _, RpcType::GET_ACTIONS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, std::string("response"))); EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, std::string("response"))); @@ -166,7 +166,7 @@ TEST_F(ServiceImplTest, GetActionsDoesNotReloadClientToken) { TEST_F(ServiceImplTest, GetNextActions) { EXPECT_CALL(*mock_client_context_, Update); EXPECT_CALL(*mock_request_sender_, - OnSendRequest(GURL(kActionServerUrl), _, _)) + OnSendRequest(GURL(kActionServerUrl), _, _, RpcType::GET_ACTIONS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, std::string("response"))); EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, std::string("response"))); diff --git a/components/autofill_assistant/browser/service/service_request_sender.h b/components/autofill_assistant/browser/service/service_request_sender.h index 865e9ddd2fdc4e..f50790d2fe6b32 100644 --- a/components/autofill_assistant/browser/service/service_request_sender.h +++ b/components/autofill_assistant/browser/service/service_request_sender.h @@ -8,6 +8,7 @@ #include #include "base/callback.h" +#include "components/autofill_assistant/browser/service/rpc_type.h" #include "url/gurl.h" namespace autofill_assistant { @@ -24,7 +25,8 @@ class ServiceRequestSender { // response itself. virtual void SendRequest(const GURL& url, const std::string& request_body, - ResponseCallback callback) = 0; + ResponseCallback response_callback, + RpcType rpc_type) = 0; }; } // namespace autofill_assistant diff --git a/components/autofill_assistant/browser/service/service_request_sender_impl.cc b/components/autofill_assistant/browser/service/service_request_sender_impl.cc index 7bde65e77004ca..b3d19e2c1100d0 100644 --- a/components/autofill_assistant/browser/service/service_request_sender_impl.cc +++ b/components/autofill_assistant/browser/service/service_request_sender_impl.cc @@ -4,7 +4,12 @@ #include "components/autofill_assistant/browser/service/service_request_sender_impl.h" +#include "base/feature_list.h" #include "base/strings/strcat.h" +#include "components/autofill_assistant/browser/features.h" +#include "components/autofill_assistant/browser/service.pb.h" +#include "components/autofill_assistant/browser/service/cup.h" +#include "components/autofill_assistant/browser/service/cup_impl.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/storage_partition.h" #include "net/base/load_flags.h" @@ -115,6 +120,20 @@ void SendRequestNoAuth( loader_factory, std::move(callback)); } +void VerifyCupResponse( + std::unique_ptr cup, + autofill_assistant::ServiceRequestSender::ResponseCallback callback, + int http_status, + const std::string& response) { + absl::optional unpacked_response = cup->UnpackResponse(response); + if (!unpacked_response) { + LOG(ERROR) << "Failed to unpack or verify a response."; + return std::move(callback).Run(net::HTTP_UNAUTHORIZED, std::string()); + } + + return std::move(callback).Run(http_status, *unpacked_response); +} + } // namespace namespace autofill_assistant { @@ -122,12 +141,14 @@ namespace autofill_assistant { ServiceRequestSenderImpl::ServiceRequestSenderImpl( content::BrowserContext* context, AccessTokenFetcher* access_token_fetcher, + std::unique_ptr cup_factory, std::unique_ptr loader_factory, const std::string& api_key, bool auth_enabled, bool disable_auth_if_no_access_token) : context_(context), access_token_fetcher_(access_token_fetcher), + cup_factory_(std::move(cup_factory)), loader_factory_(std::move(loader_factory)), api_key_(api_key), auth_enabled_(auth_enabled), @@ -139,7 +160,30 @@ ServiceRequestSenderImpl::~ServiceRequestSenderImpl() = default; void ServiceRequestSenderImpl::SendRequest(const GURL& url, const std::string& request_body, - ResponseCallback callback) { + ResponseCallback callback, + RpcType rpc_type) { + if (!cup::ShouldSignRequests(rpc_type)) { + InternalSendRequest(url, request_body, std::move(callback)); + return; + } + + std::unique_ptr cup = + cup_factory_->CreateInstance(RpcType::GET_ACTIONS); + std::string signed_request = cup->PackAndSignRequest(request_body); + + auto maybe_wrapped_callback = std::move(callback); + if (cup::ShouldVerifyResponses(rpc_type)) { + maybe_wrapped_callback = base::BindOnce(&VerifyCupResponse, std::move(cup), + std::move(maybe_wrapped_callback)); + } + + InternalSendRequest(url, signed_request, std::move(maybe_wrapped_callback)); +} + +void ServiceRequestSenderImpl::InternalSendRequest( + const GURL& url, + const std::string& request_body, + ResponseCallback callback) { if (auth_enabled_ && access_token_fetcher_ == nullptr) { LOG(ERROR) << "auth requested, but no access token fetcher provided"; std::move(callback).Run(net::HTTP_UNAUTHORIZED, std::string()); @@ -220,7 +264,7 @@ void ServiceRequestSenderImpl::RetryIfUnauthorized( DCHECK(!retried_with_fresh_access_token_); retried_with_fresh_access_token_ = true; access_token_fetcher_->InvalidateAccessToken(access_token); - SendRequest(url, request_body, std::move(callback)); + InternalSendRequest(url, request_body, std::move(callback)); return; } std::move(callback).Run(http_status, response); diff --git a/components/autofill_assistant/browser/service/service_request_sender_impl.h b/components/autofill_assistant/browser/service/service_request_sender_impl.h index 4d2b305fe5f57c..3fd743d9c355d4 100644 --- a/components/autofill_assistant/browser/service/service_request_sender_impl.h +++ b/components/autofill_assistant/browser/service/service_request_sender_impl.h @@ -12,6 +12,7 @@ #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "components/autofill_assistant/browser/service/access_token_fetcher.h" +#include "components/autofill_assistant/browser/service/cup_factory.h" #include "components/autofill_assistant/browser/service/service_request_sender.h" #include "components/autofill_assistant/browser/service/simple_url_loader_factory.h" #include "content/public/browser/browser_context.h" @@ -33,6 +34,7 @@ class ServiceRequestSenderImpl : public ServiceRequestSender { ServiceRequestSenderImpl( content::BrowserContext* context, AccessTokenFetcher* access_token_fetcher, + std::unique_ptr cup_factory, std::unique_ptr loader_factory, const std::string& api_key, bool auth_enabled, @@ -42,18 +44,27 @@ class ServiceRequestSenderImpl : public ServiceRequestSender { ServiceRequestSenderImpl& operator=(const ServiceRequestSenderImpl&) = delete; // Sends |request_body| to |url|. Depending on configuration, the request - // will be authenticated either with an Oauth access token or the api key. - // Returns the http status code and the response itself. If the returned http - // headers could not be parsed, the http code will be 0. + // will be authenticated either with an Oauth access token or the api key. The + // |rpc_type| will be used to decide whether to use CUP verification. Returns + // the http status code and the response itself. If the returned http headers + // could not be parsed, the http code will be 0. // // When an auth-request first fails with a 401, the access token is // invalidated and fetched again. If the request fails again, the request // is considered failed and the callback is invoked. void SendRequest(const GURL& url, const std::string& request_body, - ResponseCallback callback) override; + ResponseCallback callback, + RpcType rpc_type) override; private: + // Unlike |ServiceRequestSenderImpl::SendRequest|, assumes that any necessary + // CUP signing and validation is already done or accounted for in the + // |callback|. + void InternalSendRequest(const GURL& url, + const std::string& request_body, + ResponseCallback callback); + void SendRequestAuth(const GURL& url, const std::string& request_body, const std::string& access_token, @@ -74,6 +85,7 @@ class ServiceRequestSenderImpl : public ServiceRequestSender { raw_ptr context_ = nullptr; raw_ptr access_token_fetcher_ = nullptr; + std::unique_ptr cup_factory_; std::unique_ptr loader_factory_; // API key to add to the URL of unauthenticated requests. diff --git a/components/autofill_assistant/browser/service/service_request_sender_impl_unittest.cc b/components/autofill_assistant/browser/service/service_request_sender_impl_unittest.cc index f2586ecf8b8ad5..896e175abb96ff 100644 --- a/components/autofill_assistant/browser/service/service_request_sender_impl_unittest.cc +++ b/components/autofill_assistant/browser/service/service_request_sender_impl_unittest.cc @@ -11,8 +11,12 @@ #include "base/strings/string_number_conversions.h" #include "base/test/gmock_callback_support.h" #include "base/test/mock_callback.h" +#include "base/test/scoped_feature_list.h" +#include "components/autofill_assistant/browser/features.h" +#include "components/autofill_assistant/browser/service.pb.h" #include "components/autofill_assistant/browser/service/access_token_fetcher.h" #include "components/autofill_assistant/browser/service/mock_access_token_fetcher.h" +#include "components/autofill_assistant/browser/service/mock_cup.h" #include "components/autofill_assistant/browser/service/mock_simple_url_loader_factory.h" #include "components/autofill_assistant/browser/service/mock_url_loader.h" #include "content/public/test/browser_task_environment.h" @@ -51,6 +55,7 @@ class ServiceRequestSenderImplTest : public testing::Test { ~ServiceRequestSenderImplTest() override = default; protected: + base::test::ScopedFeatureList scoped_feature_list_; base::MockCallback> mock_response_callback_; // Note: |task_environment_| must be created before |context_|, else creation @@ -58,9 +63,37 @@ class ServiceRequestSenderImplTest : public testing::Test { content::BrowserTaskEnvironment task_environment_; content::TestBrowserContext context_; NiceMock mock_access_token_fetcher_; + + void InitCupFeatures(bool enableSigning, bool enableVerifying) { + std::vector enabled_features; + std::vector disabled_features; + + if (enableSigning) { + enabled_features.push_back(autofill_assistant::features:: + kAutofillAssistantSignGetActionsRequests); + } else { + disabled_features.push_back(autofill_assistant::features:: + kAutofillAssistantSignGetActionsRequests); + } + + if (enableVerifying) { + enabled_features.push_back( + autofill_assistant::features:: + kAutofillAssistantVerifyGetActionsResponses); + } else { + disabled_features.push_back( + autofill_assistant::features:: + kAutofillAssistantVerifyGetActionsResponses); + } + + scoped_feature_list_.Reset(); + scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features); + } }; TEST_F(ServiceRequestSenderImplTest, SendUnauthenticatedRequest) { + auto cup_factory = + std::make_unique>(); auto loader_factory = std::make_unique>(); auto loader = std::make_unique>(); @@ -88,16 +121,19 @@ TEST_F(ServiceRequestSenderImplTest, SendUnauthenticatedRequest) { ServiceRequestSenderImpl request_sender{ &context_, /* access_token_fetcher = */ nullptr, + std::move(cup_factory), std::move(loader_factory), std::string("fake_api_key"), /* auth_enabled = */ false, /* disable_auth_if_no_access_token = */ true}; - request_sender.SendRequest(GURL("https://www.example.com"), - std::string("request"), - mock_response_callback_.Get()); + request_sender.SendRequest( + GURL("https://www.example.com"), std::string("request"), + mock_response_callback_.Get(), RpcType::GET_TRIGGER_SCRIPTS); } TEST_F(ServiceRequestSenderImplTest, SendAuthenticatedRequest) { + auto cup_factory = + std::make_unique>(); auto loader_factory = std::make_unique>(); auto loader = std::make_unique>(); @@ -131,13 +167,15 @@ TEST_F(ServiceRequestSenderImplTest, SendAuthenticatedRequest) { ServiceRequestSenderImpl request_sender{ &context_, /* access_token_fetcher = */ &mock_access_token_fetcher_, + std::move(cup_factory), std::move(loader_factory), /* api_key = */ std::string(""), /* auth_enabled = */ true, /* disable_auth_if_no_access_token = */ true}; request_sender.SendRequest(GURL("https://www.example.com"), std::string("request"), - mock_response_callback_.Get()); + mock_response_callback_.Get(), + autofill_assistant::RpcType::GET_TRIGGER_SCRIPTS); } TEST_F(ServiceRequestSenderImplTest, @@ -146,6 +184,8 @@ TEST_F(ServiceRequestSenderImplTest, .Times(1) .WillOnce(RunOnceCallback<0>(true, /*access_token = */ "")); + auto cup_factory = + std::make_unique>(); auto loader_factory = std::make_unique>(); auto loader = std::make_unique>(); @@ -173,13 +213,15 @@ TEST_F(ServiceRequestSenderImplTest, ServiceRequestSenderImpl request_sender{ &context_, /* access_token_fetcher = */ &mock_access_token_fetcher_, + std::move(cup_factory), std::move(loader_factory), /* api_key = */ std::string("fake_api_key"), /* auth_enabled = */ true, /* disable_auth_if_no_access_token = */ true}; request_sender.SendRequest(GURL("https://www.example.com"), std::string("request"), - mock_response_callback_.Get()); + mock_response_callback_.Get(), + autofill_assistant::RpcType::GET_TRIGGER_SCRIPTS); } TEST_F(ServiceRequestSenderImplTest, @@ -189,6 +231,8 @@ TEST_F(ServiceRequestSenderImplTest, .WillOnce( RunOnceCallback<0>(/*success = */ false, /*access_token = */ "")); + auto cup_factory = + std::make_unique>(); auto loader_factory = std::make_unique>(); auto loader = std::make_unique>(); @@ -216,13 +260,159 @@ TEST_F(ServiceRequestSenderImplTest, ServiceRequestSenderImpl request_sender{ &context_, /* access_token_fetcher = */ &mock_access_token_fetcher_, + std::move(cup_factory), std::move(loader_factory), /* api_key = */ std::string("fake_api_key"), /* auth_enabled = */ true, /* disable_auth_if_no_access_token = */ true}; request_sender.SendRequest(GURL("https://www.example.com"), std::string("request"), - mock_response_callback_.Get()); + mock_response_callback_.Get(), + autofill_assistant::RpcType::GET_TRIGGER_SCRIPTS); +} + +TEST_F(ServiceRequestSenderImplTest, + DoesNotCreateInstanceWhenFeatureNotEnabled) { + InitCupFeatures(false, false); + auto cup_factory = + std::make_unique>(); + auto loader_factory = + std::make_unique>(); + auto loader = std::make_unique>(); + auto response_info = CreateResponseInfo(net::HTTP_OK, "OK"); + EXPECT_CALL(*loader_factory, OnCreateLoader(_, _)) + .WillOnce([&](::network::ResourceRequest* resource_request, + const ::net::NetworkTrafficAnnotationTag& annotation_tag) { + EXPECT_FALSE(resource_request->headers.HasHeader("Authorization")); + EXPECT_EQ(resource_request->url, + GURL("https://www.example.com/?key=fake_api_key")); + return std::move(loader); + }); + EXPECT_CALL(*loader, + AttachStringForUpload(std::string("request"), + std::string("application/x-protobuffer"))) + .Times(1); + EXPECT_CALL(*loader, DownloadToStringOfUnboundedSizeUntilCrashAndDie(_, _)) + .WillOnce(WithArgs<1>([&](auto&& callback) { + std::move(callback).Run(std::make_unique("response")); + })); + EXPECT_CALL(*loader, ResponseInfo) + .WillRepeatedly(Return(response_info.get())); + EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, "response")); + + EXPECT_CALL(*cup_factory, CreateInstance(_)).Times(0); + ServiceRequestSenderImpl request_sender{ + &context_, + /* access_token_fetcher = */ nullptr, + std::move(cup_factory), + std::move(loader_factory), + std::string("fake_api_key"), + /* auth_enabled = */ false, + /* disable_auth_if_no_access_token = */ true}; + request_sender.SendRequest( + GURL("https://www.example.com"), std::string("request"), + mock_response_callback_.Get(), RpcType::GET_ACTIONS); +} + +TEST_F(ServiceRequestSenderImplTest, SignsGetActionsRequestWhenFeatureEnabled) { + InitCupFeatures(true, false); + auto cup_factory = + std::make_unique>(); + auto cup = std::make_unique>(); + auto loader_factory = + std::make_unique>(); + auto loader = std::make_unique>(); + auto response_info = CreateResponseInfo(net::HTTP_OK, "OK"); + EXPECT_CALL(*loader_factory, OnCreateLoader(_, _)) + .WillOnce([&](::network::ResourceRequest* resource_request, + const ::net::NetworkTrafficAnnotationTag& annotation_tag) { + EXPECT_FALSE(resource_request->headers.HasHeader("Authorization")); + EXPECT_EQ(resource_request->url, + GURL("https://www.example.com/?key=fake_api_key")); + return std::move(loader); + }); + EXPECT_CALL(*loader, + AttachStringForUpload(std::string("signed_request"), + std::string("application/x-protobuffer"))) + .Times(1); + EXPECT_CALL(*loader, DownloadToStringOfUnboundedSizeUntilCrashAndDie(_, _)) + .WillOnce(WithArgs<1>([&](auto&& callback) { + std::move(callback).Run(std::make_unique("response")); + })); + EXPECT_CALL(*loader, ResponseInfo) + .WillRepeatedly(Return(response_info.get())); + EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, "response")); + + EXPECT_CALL(*cup_factory, + CreateInstance(autofill_assistant::RpcType::GET_ACTIONS)) + .WillOnce([&]() { return std::move(cup); }); + EXPECT_CALL(*cup, PackAndSignRequest("request")).WillOnce([&]() { + return "signed_request"; + }); + EXPECT_CALL(*cup, UnpackResponse(_)).Times(0); + ServiceRequestSenderImpl request_sender{ + &context_, + /* access_token_fetcher = */ nullptr, + std::move(cup_factory), + std::move(loader_factory), + std::string("fake_api_key"), + /* auth_enabled = */ false, + /* disable_auth_if_no_access_token = */ true}; + request_sender.SendRequest( + GURL("https://www.example.com"), std::string("request"), + mock_response_callback_.Get(), RpcType::GET_ACTIONS); +} + +TEST_F(ServiceRequestSenderImplTest, ValidatesGetActionsResponsesWhenEnabled) { + InitCupFeatures(true, true); + auto cup_factory = + std::make_unique>(); + auto cup = std::make_unique>(); + auto loader_factory = + std::make_unique>(); + auto loader = std::make_unique>(); + auto response_info = CreateResponseInfo(net::HTTP_OK, "OK"); + EXPECT_CALL(*loader_factory, OnCreateLoader(_, _)) + .WillOnce([&](::network::ResourceRequest* resource_request, + const ::net::NetworkTrafficAnnotationTag& annotation_tag) { + EXPECT_FALSE(resource_request->headers.HasHeader("Authorization")); + EXPECT_EQ(resource_request->url, + GURL("https://www.example.com/?key=fake_api_key")); + return std::move(loader); + }); + EXPECT_CALL(*loader, + AttachStringForUpload(std::string("signed_request"), + std::string("application/x-protobuffer"))) + .Times(1); + EXPECT_CALL(*loader, DownloadToStringOfUnboundedSizeUntilCrashAndDie(_, _)) + .WillOnce(WithArgs<1>([&](auto&& callback) { + std::move(callback).Run( + std::make_unique("packed_response")); + })); + EXPECT_CALL(*loader, ResponseInfo) + .WillRepeatedly(Return(response_info.get())); + EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, "response")); + + EXPECT_CALL(*cup_factory, + CreateInstance(autofill_assistant::RpcType::GET_ACTIONS)) + .WillOnce([&]() { return std::move(cup); }); + EXPECT_CALL(*cup, PackAndSignRequest("request")).WillOnce([&]() { + return "signed_request"; + }); + EXPECT_CALL(*cup, UnpackResponse("packed_response")).WillOnce([&]() { + return "response"; + }); + ServiceRequestSenderImpl request_sender{ + &context_, + /* access_token_fetcher = */ nullptr, + std::move(cup_factory), + std::move(loader_factory), + std::string("fake_api_key"), + /* auth_enabled = */ false, + /* disable_auth_if_no_access_token = */ true}; + request_sender.SendRequest( + GURL("https://www.example.com"), std::string("request"), + mock_response_callback_.Get(), autofill_assistant::RpcType::GET_ACTIONS); } // TODO(b/170934170): Add tests for full unit test coverage of diff --git a/components/autofill_assistant/browser/service/service_request_sender_local_impl.cc b/components/autofill_assistant/browser/service/service_request_sender_local_impl.cc index 8c4b7a18b84553..707ea7b82d44b5 100644 --- a/components/autofill_assistant/browser/service/service_request_sender_local_impl.cc +++ b/components/autofill_assistant/browser/service/service_request_sender_local_impl.cc @@ -15,7 +15,8 @@ ServiceRequestSenderLocalImpl::~ServiceRequestSenderLocalImpl() = default; void ServiceRequestSenderLocalImpl::SendRequest(const GURL& url, const std::string& request_body, - ResponseCallback callback) { + ResponseCallback callback, + RpcType rpc_type) { std::move(callback).Run(net::HTTP_OK, response_); } diff --git a/components/autofill_assistant/browser/service/service_request_sender_local_impl.h b/components/autofill_assistant/browser/service/service_request_sender_local_impl.h index a7cd8fd47d1232..ff360395462040 100644 --- a/components/autofill_assistant/browser/service/service_request_sender_local_impl.h +++ b/components/autofill_assistant/browser/service/service_request_sender_local_impl.h @@ -22,7 +22,8 @@ class ServiceRequestSenderLocalImpl : public ServiceRequestSender { // TODO(arbesser): Make this more flexible. void SendRequest(const GURL& url, const std::string& request_body, - ResponseCallback callback) override; + ResponseCallback callback, + RpcType rpc_type) override; private: std::string response_; diff --git a/components/autofill_assistant/browser/service/service_request_sender_local_impl_unittest.cc b/components/autofill_assistant/browser/service/service_request_sender_local_impl_unittest.cc index 3f36931dee3aec..ba85aafb79fdfb 100644 --- a/components/autofill_assistant/browser/service/service_request_sender_local_impl_unittest.cc +++ b/components/autofill_assistant/browser/service/service_request_sender_local_impl_unittest.cc @@ -28,10 +28,10 @@ class ServiceRequestSenderLocalImplTest : public testing::Test { TEST_F(ServiceRequestSenderLocalImplTest, SendRequestAlwaysReturnsResponse) { ServiceRequestSenderLocalImpl service_request_sender = {"response"}; EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, "response")).Times(2); - service_request_sender.SendRequest(GURL(), "request_1", - mock_response_callback_.Get()); - service_request_sender.SendRequest(GURL(), "request_2", - mock_response_callback_.Get()); + service_request_sender.SendRequest( + GURL(), "request_1", mock_response_callback_.Get(), RpcType::UNKNOWN); + service_request_sender.SendRequest( + GURL(), "request_2", mock_response_callback_.Get(), RpcType::UNKNOWN); } } // namespace diff --git a/components/autofill_assistant/browser/starter.cc b/components/autofill_assistant/browser/starter.cc index d573cc686cea81..2bb1c9aab8c061 100644 --- a/components/autofill_assistant/browser/starter.cc +++ b/components/autofill_assistant/browser/starter.cc @@ -19,6 +19,7 @@ #include "components/autofill_assistant/browser/features.h" #include "components/autofill_assistant/browser/intent_strings.h" #include "components/autofill_assistant/browser/service/api_key_fetcher.h" +#include "components/autofill_assistant/browser/service/cup_impl.h" #include "components/autofill_assistant/browser/service/server_url_fetcher.h" #include "components/autofill_assistant/browser/service/service_request_sender.h" #include "components/autofill_assistant/browser/service/service_request_sender_impl.h" @@ -78,6 +79,7 @@ std::unique_ptr CreateRpcTriggerScriptRequestSender( return std::make_unique( browser_context, /* access_token_fetcher = */ nullptr, + std::make_unique(), std::make_unique(), ApiKeyFetcher().GetAPIKey(delegate->GetChannel()), /* auth_enabled = */ false, diff --git a/components/autofill_assistant/browser/starter_unittest.cc b/components/autofill_assistant/browser/starter_unittest.cc index 95051d5b794d7c..b1c2dc8075157f 100644 --- a/components/autofill_assistant/browser/starter_unittest.cc +++ b/components/autofill_assistant/browser/starter_unittest.cc @@ -657,9 +657,10 @@ TEST_F(StarterTest, RpcTriggerScriptSucceeds) { trigger_script_coordinator_->PerformTriggerScriptAction( TriggerScriptProto::ACCEPT); }); - EXPECT_CALL(*mock_trigger_script_service_request_sender_, - OnSendRequest( - GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _)) + EXPECT_CALL( + *mock_trigger_script_service_request_sender_, + OnSendRequest(GURL("https://automate-pa.googleapis.com/v1/triggers"), _, + _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce( WithArgs<1, 2>([&](const std::string& request_body, ServiceRequestSender::ResponseCallback& callback) { @@ -1076,9 +1077,10 @@ TEST_F(StarterTest, ImplicitStartupOnSupportedDomain) { features::kAutofillAssistantInCCTTriggering); starter_->CheckSettings(); - EXPECT_CALL(*mock_trigger_script_service_request_sender_, - OnSendRequest( - GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _)) + EXPECT_CALL( + *mock_trigger_script_service_request_sender_, + OnSendRequest(GURL("https://automate-pa.googleapis.com/v1/triggers"), _, + _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce( WithArgs<1, 2>([&](const std::string& request_body, ServiceRequestSender::ResponseCallback& callback) { @@ -1183,9 +1185,10 @@ TEST_F(StarterTest, ImplicitStartupOnCurrentUrlAfterSettingEnabled) { .Times(0); SimulateNavigateToUrl(GURL("https://www.some-website.com/cart")); - EXPECT_CALL(*mock_trigger_script_service_request_sender_, - OnSendRequest( - GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _)) + EXPECT_CALL( + *mock_trigger_script_service_request_sender_, + OnSendRequest(GURL("https://automate-pa.googleapis.com/v1/triggers"), _, + _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, CreateTriggerScriptResponseForTest())); EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript).Times(1); @@ -1422,9 +1425,10 @@ TEST_F(StarterTest, FailedTriggerScriptFetchesForImplicitStartupAreCached) { features::kAutofillAssistantInCCTTriggering); starter_->CheckSettings(); - EXPECT_CALL(*mock_trigger_script_service_request_sender_, - OnSendRequest( - GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _)) + EXPECT_CALL( + *mock_trigger_script_service_request_sender_, + OnSendRequest(GURL("https://automate-pa.googleapis.com/v1/triggers"), _, + _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_FORBIDDEN, std::string())); EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript).Times(0); EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0); @@ -1479,9 +1483,10 @@ TEST_F(StarterTest, features::kAutofillAssistantInCCTTriggering); starter_->CheckSettings(); - EXPECT_CALL(*mock_trigger_script_service_request_sender_, - OnSendRequest( - GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _)) + EXPECT_CALL( + *mock_trigger_script_service_request_sender_, + OnSendRequest(GURL("https://automate-pa.googleapis.com/v1/triggers"), _, + _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, CreateTriggerScriptResponseForTest())); EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript) @@ -1540,9 +1545,10 @@ TEST_F(StarterTest, EmptyTriggerScriptFetchesForImplicitStartupAreCached) { features::kAutofillAssistantInCCTTriggering); starter_->CheckSettings(); - EXPECT_CALL(*mock_trigger_script_service_request_sender_, - OnSendRequest( - GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _)) + EXPECT_CALL( + *mock_trigger_script_service_request_sender_, + OnSendRequest(GURL("https://automate-pa.googleapis.com/v1/triggers"), _, + _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce( WithArg<2>([&](ServiceRequestSender::ResponseCallback& callback) { // Empty response == no trigger scripts available. @@ -2017,9 +2023,10 @@ TEST_F(StarterTest, CommandLineScriptParametersAreAddedToImplicitTriggers) { mock_runtime_manager_.GetWeakPtr(), task_environment()->GetMockTickClock()); - EXPECT_CALL(*mock_trigger_script_service_request_sender_, - OnSendRequest( - GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _)) + EXPECT_CALL( + *mock_trigger_script_service_request_sender_, + OnSendRequest(GURL("https://automate-pa.googleapis.com/v1/triggers"), _, + _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(WithArg<1>([&](const std::string& request_body) { GetTriggerScriptsRequestProto request; ASSERT_TRUE(request.ParseFromString(request_body)); @@ -2086,9 +2093,10 @@ TEST(MultipleIntentStarterTest, ImplicitTriggeringSendsAllMatchingIntents) { fake_platform_delegate.trigger_script_request_sender_for_test_ = std::move(service_request_sender); - EXPECT_CALL(*service_request_sender_ptr, - OnSendRequest( - GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _)) + EXPECT_CALL( + *service_request_sender_ptr, + OnSendRequest(GURL("https://automate-pa.googleapis.com/v1/triggers"), _, + _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(WithArg<1>([&](const std::string& request_body) { GetTriggerScriptsRequestProto request; ASSERT_TRUE(request.ParseFromString(request_body)); diff --git a/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.cc b/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.cc index 72444229ba88c8..d32c05eac0026b 100644 --- a/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.cc +++ b/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.cc @@ -78,7 +78,8 @@ void TriggerScriptCoordinator::Start( deeplink_url_, client_context, trigger_context_->GetScriptParameters()), base::BindOnce(&TriggerScriptCoordinator::OnGetTriggerScripts, - weak_ptr_factory_.GetWeakPtr())); + weak_ptr_factory_.GetWeakPtr()), + autofill_assistant::RpcType::GET_TRIGGER_SCRIPTS); } void TriggerScriptCoordinator::OnGetTriggerScripts( diff --git a/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator_unittest.cc b/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator_unittest.cc index fd89a25dedf9f8..25a9dcde34efe6 100644 --- a/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator_unittest.cc +++ b/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator_unittest.cc @@ -175,9 +175,12 @@ TEST_F(TriggerScriptCoordinatorTest, StartSendsOnlyApprovedFields) { {"FALLBACK_BUNDLE_ID", "fallback_id"}, {"FALLBACK_BUNDLE_VERSION", "fallback_version"}}; - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce([&](const GURL& url, const std::string& request_body, - ServiceRequestSender::ResponseCallback& callback) { + ServiceRequestSender::ResponseCallback& callback, + RpcType rpc_type) { GetTriggerScriptsRequestProto request; ASSERT_TRUE(request.ParseFromString(request_body)); EXPECT_THAT(request.url(), Eq(kFakeDeepLink)); @@ -210,7 +213,9 @@ TEST_F(TriggerScriptCoordinatorTest, StartSendsOnlyApprovedFields) { } TEST_F(TriggerScriptCoordinatorTest, StopOnBackendRequestFailed) { - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_FORBIDDEN, "")); EXPECT_CALL( mock_callback_, @@ -225,7 +230,9 @@ TEST_F(TriggerScriptCoordinatorTest, StopOnBackendRequestFailed) { } TEST_F(TriggerScriptCoordinatorTest, StopOnParsingError) { - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, "invalid")); EXPECT_CALL( mock_callback_, @@ -241,7 +248,9 @@ TEST_F(TriggerScriptCoordinatorTest, StopOnParsingError) { } TEST_F(TriggerScriptCoordinatorTest, StopOnNoTriggerScriptsAvailable) { - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, "")); EXPECT_CALL( mock_callback_, @@ -268,7 +277,9 @@ TEST_F(TriggerScriptCoordinatorTest, StartChecksStaticAndDynamicConditions) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); EXPECT_CALL(*mock_dynamic_trigger_conditions_, ClearConditions).Times(1); EXPECT_CALL(*mock_dynamic_trigger_conditions_, @@ -296,7 +307,9 @@ TEST_F(TriggerScriptCoordinatorTest, ShowAndHideTriggerScript) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); ON_CALL(*mock_dynamic_trigger_conditions_, @@ -332,7 +345,9 @@ TEST_F(TriggerScriptCoordinatorTest, PauseAndResumeOnTabVisibilityChange) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); EXPECT_CALL(*mock_dynamic_trigger_conditions_, @@ -354,7 +369,9 @@ TEST_F(TriggerScriptCoordinatorTest, PauseAndResumeOnTabVisibilityChange) { // When a hidden tab becomes visible again, the trigger scripts must be // fetched again. - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); EXPECT_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -373,7 +390,9 @@ TEST_F(TriggerScriptCoordinatorTest, PerformTriggerScriptActionNotNow) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); ON_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -413,7 +432,9 @@ TEST_F(TriggerScriptCoordinatorTest, PerformTriggerScriptActionCancelSession) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); ON_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -447,7 +468,9 @@ TEST_F(TriggerScriptCoordinatorTest, PerformTriggerScriptActionCancelForever) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); ON_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -482,7 +505,9 @@ TEST_F(TriggerScriptCoordinatorTest, PerformTriggerScriptActionAccept) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); ON_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -506,7 +531,9 @@ TEST_F(TriggerScriptCoordinatorTest, CancelOnNavigateAway) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); ON_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -553,7 +580,9 @@ TEST_F(TriggerScriptCoordinatorTest, IgnoreNavigationEventsWhileNotStarted) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); EXPECT_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -575,7 +604,9 @@ TEST_F(TriggerScriptCoordinatorTest, IgnoreNavigationEventsWhileNotStarted) { SimulateNavigateToUrl(GURL("https://example.different.com")); SimulateNavigateToUrl(GURL("https://also-not-supported.com")); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, /* response = */ "")); // However, when the tab becomes visible again, the trigger script is // restarted and thus fails if the tab is still on an unsupported domain. @@ -600,7 +631,9 @@ TEST_F(TriggerScriptCoordinatorTest, BottomSheetClosedWithSwipe) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); ON_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -639,7 +672,9 @@ TEST_F(TriggerScriptCoordinatorTest, TimeoutAfterInvisibleForTooLong) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); // Note: expect 4 calls: 1 initial plus 3 until timeout. @@ -679,7 +714,9 @@ TEST_F(TriggerScriptCoordinatorTest, TimeoutResetsAfterTriggerScriptShown) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); EXPECT_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -725,7 +762,9 @@ TEST_F(TriggerScriptCoordinatorTest, NoTimeoutByDefault) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); EXPECT_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -751,7 +790,9 @@ TEST_F(TriggerScriptCoordinatorTest, KeyboardEventTriggersOutOfScheduleCheck) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); EXPECT_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -801,7 +842,9 @@ TEST_F(TriggerScriptCoordinatorTest, UrlChangeOutOfScheduleCheckPathMatch) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); EXPECT_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -830,7 +873,9 @@ TEST_F(TriggerScriptCoordinatorTest, UrlChangeOutOfScheduleCheckDomainMatch) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); EXPECT_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -859,7 +904,9 @@ TEST_F(TriggerScriptCoordinatorTest, std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); EXPECT_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -887,7 +934,9 @@ TEST_F(TriggerScriptCoordinatorTest, OnTriggerScriptFailedToShow) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); EXPECT_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -913,7 +962,9 @@ TEST_F(TriggerScriptCoordinatorTest, OnProactiveHelpSettingDisabled) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); EXPECT_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -946,7 +997,9 @@ TEST_F(TriggerScriptCoordinatorTest, PauseAndResumeOnTabSwitch) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); EXPECT_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -968,7 +1021,9 @@ TEST_F(TriggerScriptCoordinatorTest, PauseAndResumeOnTabSwitch) { // When a non-interactable tab becomes interactable again, the trigger scripts // must be fetched again. - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); EXPECT_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -987,7 +1042,9 @@ TEST_F(TriggerScriptCoordinatorTest, OnboardingShownAndAccepted) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); EXPECT_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -1028,7 +1085,9 @@ TEST_F(TriggerScriptCoordinatorTest, std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); EXPECT_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -1094,7 +1153,9 @@ TEST_F(TriggerScriptCoordinatorTest, std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); EXPECT_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -1135,7 +1196,9 @@ TEST_F(TriggerScriptCoordinatorTest, OnboardingNotShown) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); EXPECT_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -1173,7 +1236,9 @@ TEST_F(TriggerScriptCoordinatorTest, RecordUkmsForCurrentUrlIfPossible) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); ON_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_.get(), _)) @@ -1213,7 +1278,9 @@ TEST_F(TriggerScriptCoordinatorTest, BackendCanOverrideScriptParameters) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); coordinator_->Start( @@ -1238,7 +1305,9 @@ TEST_F(TriggerScriptCoordinatorTest, UiTimeoutWhileShown) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); ON_CALL(*mock_dynamic_trigger_conditions_, @@ -1292,7 +1361,9 @@ TEST_F(TriggerScriptCoordinatorTest, UiTimeoutInterruptedByCancelPopup) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); ON_CALL(*mock_dynamic_trigger_conditions_, @@ -1331,7 +1402,9 @@ TEST_F(TriggerScriptCoordinatorTest, UiTimeoutInterruptedByOnboarding) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); ON_CALL(*mock_dynamic_trigger_conditions_, @@ -1357,7 +1430,9 @@ TEST_F(TriggerScriptCoordinatorTest, UiTimeoutInterruptedBySkipSession) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); ON_CALL(*mock_dynamic_trigger_conditions_, @@ -1389,7 +1464,9 @@ TEST_F(TriggerScriptCoordinatorTest, UiTimeoutInterruptedByNotNow) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); ON_CALL(*mock_dynamic_trigger_conditions_, @@ -1418,7 +1495,9 @@ TEST_F(TriggerScriptCoordinatorTest, UiTimeoutInterruptedByNotNow) { } TEST_F(TriggerScriptCoordinatorTest, StoppingTwiceDoesNotCrash) { - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_FORBIDDEN, "")); EXPECT_CALL(*mock_ui_delegate_, Detach).Times(2); EXPECT_CALL(*mock_ui_delegate_, HideTriggerScript).Times(0); @@ -1444,7 +1523,9 @@ TEST_F(TriggerScriptCoordinatorTest, RecordTriggerConditionEvaluationTime) { std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); EXPECT_CALL(*mock_dynamic_trigger_conditions_, OnUpdate) @@ -1479,7 +1560,9 @@ TEST_F(TriggerScriptCoordinatorTest, RecordIfPrimaryPageFailed) { response.add_trigger_scripts(); std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); coordinator_->Start(GURL(kFakeDeepLink), std::make_unique(), @@ -1524,7 +1607,9 @@ TEST_F(TriggerScriptCoordinatorPrerenderTest, DoNotRecordIfPrerenderingFailed) { response.add_trigger_scripts(); std::string serialized_response; response.SerializeToString(&serialized_response); - EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _)) + EXPECT_CALL( + *mock_request_sender_, + OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response)); EXPECT_CALL(mock_callback_, Run).Times(0); diff --git a/components/browser_ui/styles/android/java/res/values/themes.xml b/components/browser_ui/styles/android/java/res/values/themes.xml index 53b0292c2e5bf8..f3c3995c5019f7 100644 --- a/components/browser_ui/styles/android/java/res/values/themes.xml +++ b/components/browser_ui/styles/android/java/res/values/themes.xml @@ -4,6 +4,7 @@ found in the LICENSE file. --> + - +