diff --git a/.gn b/.gn index 1810cebe..5356717 100644 --- a/.gn +++ b/.gn
@@ -70,9 +70,6 @@ # their includes checked for proper dependencies when you run either # "gn check" or "gn gen --check". no_check_targets = [ - "//headless:headless_browsertests", # 47 errors - "//headless:headless_browsertests__exec", - "//headless:headless_example", # 3 errors "//headless:headless_non_renderer", # 9 errors "//headless:headless_renderer", # 13 errors "//headless:headless_shared_sources", # 4 errors
diff --git a/DEPS b/DEPS index ab2ed84..ee1ebb40 100644 --- a/DEPS +++ b/DEPS
@@ -253,19 +253,19 @@ # 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': 'c25fd021edd04dabe4acad54a6288d6645dc2af8', + 'skia_revision': '1e420a3652562bcc59a634adcda5ebc4d49054c9', # 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': 'a2bf6c77c34ff27bf346c8eeaff106a1ea59c2e9', + 'v8_revision': '4a29e433a8ca9aae93e896b17c5b802a941573a5', # 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': '2944e265dedd8e9b9dbbf7c763b62f4716c5b0bd', + 'angle_revision': '3154b00bab776e366538e712558a7f8c2967d4ca', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling SwiftShader # and whatever else without interference from each other. - 'swiftshader_revision': 'cb6f827982e7e378ce26070c58dabd0bd1af207d', + 'swiftshader_revision': '7529468c7902921e025ec185a59cb50efa8f1258', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling PDFium # and whatever else without interference from each other. @@ -320,7 +320,7 @@ # 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': '5242864bdf5c33c046ad795adfa9e60096e0cf11', + 'catapult_revision': '4ac0eac51b57114d5a50793dee6ffbda9859850f', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libFuzzer # and whatever else without interference from each other. @@ -368,7 +368,7 @@ # 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': '9fa7022ce3986985022850b875565e9faccd19b7', + 'dawn_revision': 'bf44da52b98e7b3d8bd3a8b8aae39c38ec480ad9', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. @@ -392,7 +392,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libavif # and whatever else without interference from each other. - 'libavif_revision': '380d91a64a2a8a3a408766070227b5c6369ce234', + 'libavif_revision': '7a6d13be831da40859c6b61fb513b7a7a654a58b', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling nearby # and whatever else without interference from each other. @@ -1131,7 +1131,7 @@ }, 'src/third_party/depot_tools': - Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '42cf2acbaee8f149fdc08661eece63aaf271b4bc', + Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '64f5f26f1a0c8b8333514cdb861847f02d405f36', 'src/third_party/devtools-frontend/src': Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'), @@ -1762,7 +1762,7 @@ 'packages': [ { 'package': 'skia/tools/goldctl/linux-amd64', - 'version': 't-kuo00LREzFSvvcKqhCDfF9a3vrU6x8bvpcqoOolxQC', + 'version': 'td7IhN6Q3eTDLXn6p5jlbeSIDYl7rI75dlX0qj8fEEsC', }, ], 'dep_type': 'cipd', @@ -1772,7 +1772,7 @@ 'packages': [ { 'package': 'skia/tools/goldctl/windows-amd64', - 'version': '7MVl6nWD2T_LTrD1aXv5dLQaPIBY04wfSmCsu5NdVdwC', + 'version': '5k9ZnDE42Xoqs07enkcOdWOf9jT-bhW-OXOp2fY-IR4C', }, ], 'dep_type': 'cipd', @@ -1783,7 +1783,7 @@ 'packages': [ { 'package': 'skia/tools/goldctl/mac-amd64', - 'version': 'q0e0eoFci9WHabP-U6xwl2vazg4Fh0iDyXPwa7GGoQMC', + 'version': 'im5u9GiTMHxNcLH_Nc2X3RqzjfDs2oDmC0VhkLgUCeYC', }, ], 'dep_type': 'cipd', @@ -1794,7 +1794,7 @@ 'packages': [ { 'package': 'skia/tools/goldctl/mac-arm64', - 'version': 'qRTt0rC2ItXtcqu1dxTrhYh8FVz21OFuYU2YzKfboRMC', + 'version': 'edDMT5wDXf_HjD5qeNPgIEmYXDGUB1BswQ0G84CQBdUC', }, ], 'dep_type': 'cipd', @@ -1805,7 +1805,7 @@ Var('chromium_git') + '/v8/v8.git' + '@' + Var('v8_revision'), 'src-internal': { - 'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@349121b6ec93bd08d1e1129f22bfaceeaf877d16', + 'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@a0657139bdfb2bb07c4046abab66c0a2c6c3ff27', 'condition': 'checkout_src_internal', },
diff --git a/ash/BUILD.gn b/ash/BUILD.gn index 3622365f..d8340c9 100644 --- a/ash/BUILD.gn +++ b/ash/BUILD.gn
@@ -1066,6 +1066,8 @@ "system/cast/unified_cast_detailed_view_controller.h", "system/dark_mode/dark_mode_feature_pod_controller.cc", "system/dark_mode/dark_mode_feature_pod_controller.h", + "system/eche/eche_tray.cc", + "system/eche/eche_tray.h", "system/enterprise/enterprise_domain_observer.h", "system/firmware_update/firmware_update_notification_controller.cc", "system/firmware_update/firmware_update_notification_controller.h", @@ -2556,6 +2558,7 @@ "system/bluetooth/tray_bluetooth_helper_legacy_unittest.cc", "system/bluetooth/unified_bluetooth_detailed_view_controller_unittest.cc", "system/caps_lock_notification_controller_unittest.cc", + "system/eche/eche_tray_unittest.cc", "system/firmware_update/firmware_update_notification_controller_unittest.cc", "system/geolocation/geolocation_controller_unittest.cc", "system/gesture_education/gesture_education_notification_controller_unittest.cc",
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc index a9c704cd..4c29d82 100644 --- a/ash/constants/ash_features.cc +++ b/ash/constants/ash_features.cc
@@ -461,6 +461,10 @@ // Enables the System Web App (SWA) version of Eche. const base::Feature kEcheSWA{"EcheSWA", base::FEATURE_DISABLED_BY_DEFAULT}; +// Moves Eche into a custom widget. +const base::Feature kEcheCustomWidget{"EcheCustomWidget", + base::FEATURE_DISABLED_BY_DEFAULT}; + // Enables the naive resize for the Eche window. const base::Feature kEcheSWAResizing{"EcheSWAResizing", base::FEATURE_DISABLED_BY_DEFAULT}; @@ -997,6 +1001,12 @@ const base::Feature kOobeConsolidatedConsent{"OobeConsolidatedConsent", base::FEATURE_DISABLED_BY_DEFAULT}; +// Enables or disables the Chrome OS OOBE HID Detection Revamp, which updates +// the OOBE HID detection screen UI and related infrastructure. See +// https://crbug.com/1299099. +const base::Feature kOobeHidDetectionRevamp{"OobeHidDetectionRevamp", + base::FEATURE_DISABLED_BY_DEFAULT}; + // Enables or disables the Oobe quick start flow. const base::Feature kOobeQuickStart{"OobeQuickStart", base::FEATURE_DISABLED_BY_DEFAULT}; @@ -1574,6 +1584,10 @@ return base::FeatureList::IsEnabled(kEcheSWA); } +bool IsEcheCustomWidgetEnabled() { + return base::FeatureList::IsEnabled(kEcheCustomWidget); +} + bool IsEcheSWAResizingEnabled() { return base::FeatureList::IsEnabled(kEcheSWAResizing); } @@ -1810,6 +1824,11 @@ return base::FeatureList::IsEnabled(kEnableOobeChromeVoxHint); } +bool IsOobeHidDetectionRevampEnabled() { + return base::FeatureList::IsEnabled(kOobeHidDetectionRevamp) && + base::FeatureList::IsEnabled(kBluetoothRevamp); +} + bool IsOobePolymer3Enabled() { return base::FeatureList::IsEnabled(kEnableOobePolymer3); }
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h index 7cf92dd..bffa4d4c 100644 --- a/ash/constants/ash_features.h +++ b/ash/constants/ash_features.h
@@ -188,6 +188,7 @@ COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kEchePhoneHubPermissionsOnboarding; COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kEcheSWA; +COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kEcheCustomWidget; COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kEcheSWAResizing; COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kEcheSWADebugMode; COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kEcheSWAInBackground; @@ -390,6 +391,8 @@ COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kOobeConsolidatedConsent; COMPONENT_EXPORT(ASH_CONSTANTS) +extern const base::Feature kOobeHidDetectionRevamp; +COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kOobeQuickStart; COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kOobeNewRecommendApps; @@ -579,6 +582,7 @@ COMPONENT_EXPORT(ASH_CONSTANTS) bool IsDragWindowToNewDeskEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsEchePhoneHubPermissionsOnboarding(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsEcheSWAEnabled(); +COMPONENT_EXPORT(ASH_CONSTANTS) bool IsEcheCustomWidgetEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsEcheSWAResizingEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsEcheSWADebugModeEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsEcheSWAInBackgroundEnabled(); @@ -642,6 +646,7 @@ COMPONENT_EXPORT(ASH_CONSTANTS) bool IsNotificationsRefreshEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOAuthIppEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeChromeVoxHintEnabled(); +COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeHidDetectionRevampEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobePolymer3Enabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeNetworkScreenSkipEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeConsolidatedConsentEnabled();
diff --git a/ash/debug.cc b/ash/debug.cc index 02c4c4b7..b5a8385 100644 --- a/ash/debug.cc +++ b/ash/debug.cc
@@ -51,6 +51,7 @@ void PrintWindowHierarchy(const aura::Window* active_window, const aura::Window* focused_window, + const aura::Window* capture_window, aura::Window* window, int indent, bool scrub_data, @@ -72,6 +73,7 @@ *out << " " << WindowState::Get(window)->GetStateType(); *out << ((window == active_window) ? " [active]" : "") << ((window == focused_window) ? " [focused]" : "") + << ((window == capture_window) ? " [capture]" : "") << (window->GetTransparent() ? " [transparent]" : "") << (window->IsVisible() ? " [visible]" : "") << " " << (window->GetOcclusionState() != aura::Window::OcclusionState::UNKNOWN @@ -102,8 +104,8 @@ *out << '\n'; for (aura::Window* child : window->children()) { - PrintWindowHierarchy(active_window, focused_window, child, indent + 3, - scrub_data, out_window_titles, out); + PrintWindowHierarchy(active_window, focused_window, capture_window, child, + indent + 3, scrub_data, out_window_titles, out); } } @@ -111,12 +113,13 @@ bool scrub_data) { aura::Window* active_window = window_util::GetActiveWindow(); aura::Window* focused_window = window_util::GetFocusedWindow(); + aura::Window* capture_window = window_util::GetCaptureWindow(); aura::Window::Windows roots = Shell::Get()->GetAllRootWindows(); std::vector<std::string> window_titles; for (size_t i = 0; i < roots.size(); ++i) { *out << "RootWindow " << i << ":\n"; - PrintWindowHierarchy(active_window, focused_window, roots[i], 0, scrub_data, - &window_titles, out); + PrintWindowHierarchy(active_window, focused_window, capture_window, + roots[i], 0, scrub_data, &window_titles, out); } return window_titles; }
diff --git a/ash/system/eche/eche_tray.cc b/ash/system/eche/eche_tray.cc new file mode 100644 index 0000000..48d9477 --- /dev/null +++ b/ash/system/eche/eche_tray.cc
@@ -0,0 +1,286 @@ +// Copyright 2022 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/system/eche/eche_tray.h" + +#include "ash/accessibility/accessibility_controller_impl.h" +#include "ash/public/cpp/ash_web_view.h" +#include "ash/public/cpp/ash_web_view_factory.h" +#include "ash/resources/vector_icons/vector_icons.h" +#include "ash/session/session_controller_impl.h" +#include "ash/shelf/shelf.h" +#include "ash/shell.h" +#include "ash/strings/grit/ash_strings.h" +#include "ash/style/icon_button.h" +#include "ash/system/phonehub/ui_constants.h" +#include "ash/system/tray/tray_bubble_wrapper.h" +#include "ash/system/tray/tray_container.h" +#include "ash/system/tray/tray_popup_utils.h" +#include "ash/system/tray/tray_utils.h" +#include "base/bind.h" +#include "components/account_id/account_id.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/metadata/metadata_impl_macros.h" +#include "ui/compositor/layer.h" +#include "ui/events/event.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/paint_vector_icon.h" +#include "ui/views/border.h" +#include "ui/views/controls/image_view.h" +#include "ui/views/layout/flex_layout.h" +#include "ui/views/view.h" +#include "url/gurl.h" + +namespace ash { + +namespace { + +// Padding for tray icon (dp; the button that shows the eche menu). +constexpr int kTrayIconMainAxisInset = 6; +constexpr int kTrayIconCrossAxisInset = 0; + +constexpr int kIconColumnWidth = 16; + +constexpr gfx::Insets kBubblePadding(4, 4, kBubbleBottomPaddingDip, 4); + +} // namespace + +EcheTray::EcheTray(Shelf* shelf) + : TrayBackgroundView(shelf), + icon_(tray_container()->AddChildView( + std::make_unique<views::ImageView>())) { + icon_->SetTooltipText(GetAccessibleNameForTray()); + tray_container()->SetMargin(kTrayIconMainAxisInset, kTrayIconCrossAxisInset); +} + +EcheTray::~EcheTray() { + if (bubble_) + bubble_->bubble_view()->ResetDelegate(); +} + +void EcheTray::ClickedOutsideBubble() { + // Do nothing +} + +std::u16string EcheTray::GetAccessibleNameForTray() { + // TODO(nayebi): Change this based on the final model of interaction + // between phone hub and Eche. + return l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_TRAY_ACCESSIBLE_NAME); +} + +void EcheTray::HandleLocaleChange() { + icon_->SetTooltipText(GetAccessibleNameForTray()); +} + +void EcheTray::HideBubbleWithView(const TrayBubbleView* bubble_view) { + if (bubble_->bubble_view() == bubble_view) + HideBubble(); +} + +void EcheTray::AnchorUpdated() { + if (bubble_) + bubble_->bubble_view()->UpdateBubble(); +} + +void EcheTray::Initialize() { + TrayBackgroundView::Initialize(); + UpdateVisibility(); +} + +void EcheTray::CloseBubble() { + if (bubble_) + HideBubble(); +} + +void EcheTray::ShowBubble() { + if (bubble_) { + bubble_->GetBubbleWidget()->Show(); + bubble_->GetBubbleWidget()->Activate(); + bubble_->bubble_view()->SetVisible(true); + SetIsActive(true); + return; + } + + TrayBubbleView::InitParams init_params; + init_params.delegate = this; + init_params.parent_window = GetBubbleWindowContainer(); + init_params.anchor_mode = TrayBubbleView::AnchorMode::kRect; + init_params.anchor_rect = GetBubbleAnchor()->GetAnchorBoundsInScreen(); + init_params.insets = GetTrayBubbleInsets(); + init_params.shelf_alignment = shelf()->alignment(); + // TODO(nayebi): get the width relative to the screen size + init_params.preferred_width = 400; + init_params.close_on_deactivate = false; + init_params.has_shadow = false; + init_params.translucent = true; + init_params.reroute_event_handler = false; + init_params.corner_radius = kTrayItemCornerRadius; + + auto bubble_view = std::make_unique<TrayBubbleView>(init_params); + bubble_view->SetCanActivate(true); + bubble_view->SetBorder(views::CreateEmptyBorder(kBubblePadding)); + + auto* header_view = bubble_view->AddChildView(CreateBubbleHeaderView()); + // The layer is needed to draw the header non-opaquely that is needed to + // match the phone hub behavior. + header_view->SetPaintToLayer(); + header_view->layer()->SetFillsBoundsOpaquely(false); + + AshWebView::InitParams params; + auto web_view = AshWebViewFactory::Get()->Create(params); + // TODO(nayebi): Use GetDefaultBoundsForEche() + web_view->SetPreferredSize(gfx::Size(400, 600)); + if (!url_.is_empty()) + web_view->Navigate(url_); + bubble_view->AddChildView(std::move(web_view)); + + bubble_ = std::make_unique<TrayBubbleWrapper>(this, bubble_view.release(), + /*event_handling=*/false); + + SetIsActive(true); + bubble_->GetBubbleView()->UpdateBubble(); + + // TODO(nayebi): Add metric updates. +} + +bool EcheTray::PerformAction(const ui::Event& event) { + // Simply toggle between visible/invisibvle + if (bubble_ && bubble_->bubble_view()->GetVisible()) { + HideBubble(); + } else { + ShowBubble(); + } + return true; +} + +TrayBubbleView* EcheTray::GetBubbleView() { + return bubble_ ? bubble_->bubble_view() : nullptr; +} + +views::Widget* EcheTray::GetBubbleWidget() const { + return bubble_ ? bubble_->GetBubbleWidget() : nullptr; +} + +void EcheTray::OnThemeChanged() { + // TODO(nayebi): Should we redraw the bubble? + TrayBackgroundView::OnThemeChanged(); + // TODO(nayebi): Change this based on the final interaction model between the + // phone hub and Eche components. + icon_->SetImage(CreateVectorIcon( + kPhoneHubPhoneIcon, + TrayIconColor(Shell::Get()->session_controller()->GetSessionState()))); +} + +std::u16string EcheTray::GetAccessibleNameForBubble() { + return GetAccessibleNameForTray(); +} + +bool EcheTray::ShouldEnableExtraKeyboardAccessibility() { + return Shell::Get()->accessibility_controller()->spoken_feedback().enabled(); +} + +void EcheTray::HideBubble(const TrayBubbleView* bubble_view) { + HideBubbleWithView(bubble_view); +} + +void EcheTray::OnSessionStateChanged(session_manager::SessionState state) { + icon_->SetImage(CreateVectorIcon(kPhoneHubPhoneIcon, TrayIconColor(state))); + // TODO(nayebi): Investigate if animations need to be stopped temporaily +} + +void EcheTray::OnActiveUserSessionChanged(const AccountId& account_id) { + // TODO(nayebi): Investigate if animations need to be stopped temporaily +} + +void EcheTray::SetUrl(const GURL& url) { + if (url_ != url) + PurgeAndClose(); + url_ = url; +} + +void EcheTray::PurgeAndClose() { + if (!bubble_) + return; + + auto* bubble_view = bubble_->GetBubbleView(); + if (bubble_view) + bubble_view->ResetDelegate(); + + bubble_.reset(); + SetIsActive(false); +} + +void EcheTray::HideBubble() { + SetIsActive(false); + bubble_->bubble_view()->SetVisible(false); + bubble_->GetBubbleWidget()->Deactivate(); + bubble_->GetBubbleWidget()->Hide(); +} + +void EcheTray::UpdateVisibility() { + SetVisiblePreferred(true); +} + +void EcheTray::OnArrowBackActivated() { + // TODO(nayebi): implement this +} + +std::unique_ptr<views::View> EcheTray::CreateBubbleHeaderView() { + auto header = std::make_unique<views::View>(); + header->SetLayoutManager(std::make_unique<views::FlexLayout>()) + ->SetInteriorMargin(gfx::Insets(0, kIconColumnWidth)); + // TODO(nayebi) Set the right IDS for this button + auto arrow_back_buttom = std::make_unique<IconButton>( + base::BindRepeating(&EcheTray::OnArrowBackActivated, + weak_factory_.GetWeakPtr()), + IconButton::Type::kSmall, &kSystemMenuArrowBackIcon, + IDS_ASH_PHONE_HUB_CONNECTED_DEVICE_SETTINGS_LABEL); + // TODO(nayebi): Make it visible when we are ready to handle this. + // The theme of the button is also different from minimize and close + // see screen/BZApR32ACmrtHZi + arrow_back_buttom->SetVisible(false); + header->AddChildView(arrow_back_buttom.release()); + + views::Label* title = header->AddChildView(std::make_unique<views::Label>( + std::u16string(), views::style::CONTEXT_DIALOG_TITLE, + views::style::STYLE_PRIMARY, + gfx::DirectionalityMode::DIRECTIONALITY_AS_URL)); + title->SetMultiLine(true); + title->SetAllowCharacterBreak(true); + title->SetProperty( + views::kFlexBehaviorKey, + views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero, + views::MaximumFlexSizeRule::kUnbounded, + /*adjust_height_for_width =*/true) + .WithWeight(1)); + title->SetHorizontalAlignment(gfx::ALIGN_LEFT); + + auto minimize_button = views::BubbleFrameView::CreateMinimizeButton( + base::BindRepeating(&EcheTray::CloseBubble, weak_factory_.GetWeakPtr())); + + minimize_button->SetProperty(views::kCrossAxisAlignmentKey, + views::LayoutAlignment::kStart); + minimize_button->SetProperty(views::kInternalPaddingKey, + minimize_button->GetInsets()); + header->AddChildView(minimize_button.release()); + + auto close_button = + views::BubbleFrameView::CreateCloseButton(base::BindRepeating( + &EcheTray::PurgeAndClose, weak_factory_.GetWeakPtr())); + + close_button->SetProperty(views::kCrossAxisAlignmentKey, + views::LayoutAlignment::kStart); + // Set views::kInternalPaddingKey for flex layout to account for internal + // button padding when calculating margins. + close_button->SetProperty(views::kInternalPaddingKey, + close_button->GetInsets()); + header->AddChildView(close_button.release()); + + return header; +} + +BEGIN_METADATA(EcheTray, TrayBackgroundView) +END_METADATA + +} // namespace ash
diff --git a/ash/system/eche/eche_tray.h b/ash/system/eche/eche_tray.h new file mode 100644 index 0000000..394e710f --- /dev/null +++ b/ash/system/eche/eche_tray.h
@@ -0,0 +1,110 @@ +// Copyright 2022 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_SYSTEM_ECHE_ECHE_TRAY_H_ +#define ASH_SYSTEM_ECHE_ECHE_TRAY_H_ + +#include <string> + +#include "ash/ash_export.h" +#include "ash/public/cpp/session/session_observer.h" +#include "ash/system/tray/tray_background_view.h" +#include "components/session_manager/session_manager_types.h" +#include "url/gurl.h" + +class AccountId; + +namespace views { +class ImageView; +class View; +class Widget; +} // namespace views + +namespace ui { +class Event; +} // namespace ui + +namespace ash { + +class Shelf; +class TrayBubbleView; +class TrayBubbleWrapper; + +// This class represents the Eche tray button in the status area and +// controls the bubble that is shown when the tray button is clicked. +class ASH_EXPORT EcheTray : public TrayBackgroundView, public SessionObserver { + public: + METADATA_HEADER(EcheTray); + + explicit EcheTray(Shelf* shelf); + EcheTray(const EcheTray&) = delete; + EcheTray& operator=(const EcheTray&) = delete; + ~EcheTray() override; + + // TrayBackgroundView: + void ClickedOutsideBubble() override; + std::u16string GetAccessibleNameForTray() override; + void HandleLocaleChange() override; + void HideBubbleWithView(const TrayBubbleView* bubble_view) override; + void AnchorUpdated() override; + void Initialize() override; + void CloseBubble() override; + void ShowBubble() override; + bool PerformAction(const ui::Event& event) override; + TrayBubbleView* GetBubbleView() override; + views::Widget* GetBubbleWidget() const override; + void OnThemeChanged() override; + + // TrayBubbleView::Delegate: + std::u16string GetAccessibleNameForBubble() override; + bool ShouldEnableExtraKeyboardAccessibility() override; + void HideBubble(const TrayBubbleView* bubble_view) override; + + // SessionObserver: + void OnSessionStateChanged(session_manager::SessionState state) override; + void OnActiveUserSessionChanged(const AccountId& account_id) override; + + // Sets the url that will be passed to the webview. + // Setting a new value will cause the current bubble be destroyed. + void SetUrl(const GURL& url); + + // Destroys the view inclusing the web view. + // Note: `CloseBubble` only hides the view. + void PurgeAndClose(); + + void HideBubble(); + + // Test helpers + TrayBubbleWrapper* get_bubble_wrapper_for_test() { return bubble_.get(); } + + private: + // Updates the visibility of the tray in the shelf based on the enable state + // of the `EcheCustomWidget` feature. + void UpdateVisibility(); + + // Handles the click on the "back" arrow in the header. + void OnArrowBackActivated(); + + // Creates the header of the bubble that includes a back arrow, + // close, and minimize buttons. + std::unique_ptr<views::View> CreateBubbleHeaderView(); + + // The url that is transferred to the web view. + // In the current implementation, this is supposed to be + // Eche window URL. However, the bubble does not interpret, + // validate, or expect a special url format or page behabvior. + GURL url_; + + // Icon of the tray. Unowned. + views::ImageView* const icon_; + + // The bubble that appears after clicking the tray button. + std::unique_ptr<TrayBubbleWrapper> bubble_; + + base::WeakPtrFactory<EcheTray> weak_factory_{this}; +}; + +} // namespace ash + +#endif // ASH_SYSTEM_ECHE_ECHE_TRAY_H_
diff --git a/ash/system/eche/eche_tray_unittest.cc b/ash/system/eche/eche_tray_unittest.cc new file mode 100644 index 0000000..5d4b700e --- /dev/null +++ b/ash/system/eche/eche_tray_unittest.cc
@@ -0,0 +1,98 @@ +// Copyright 2022 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/system/eche/eche_tray.h" + +#include <memory> +#include <string> + +#include "ash/shell.h" +#include "ash/system/status_area_widget_test_helper.h" +#include "ash/system/tray/tray_bubble_wrapper.h" +#include "ash/test/ash_test_base.h" +#include "ash/test/test_ash_web_view_factory.h" +#include "base/test/scoped_feature_list.h" +#include "ui/display/test/display_manager_test_api.h" + +namespace ash { + +class EcheTrayTest : public AshTestBase { + public: + EcheTrayTest() = default; + + EcheTrayTest(const EcheTrayTest&) = delete; + EcheTrayTest& operator=(const EcheTrayTest&) = delete; + + ~EcheTrayTest() override = default; + + // AshTestBase: + void SetUp() override { + feature_list_.InitWithFeatures( + {chromeos::features::kEcheSWA, chromeos::features::kEcheCustomWidget}, + {}); + + DCHECK(test_web_view_factory_.get()); + + AshTestBase::SetUp(); + + eche_tray_ = StatusAreaWidgetTestHelper::GetStatusAreaWidget()->eche_tray(); + + display::test::DisplayManagerTestApi(display_manager()) + .SetFirstDisplayAsInternalDisplay(); + } + + // Performs a tap on the eche tray button. + void PerformTap() { + GetEventGenerator()->GestureTapAt( + eche_tray_->GetBoundsInScreen().CenterPoint()); + } + + EcheTray* eche_tray() { return eche_tray_; } + + private: + EcheTray* eche_tray_ = nullptr; // Not owned + base::test::ScopedFeatureList feature_list_; + + // Calling the factory constructor is enough to set it up. + std::unique_ptr<TestAshWebViewFactory> test_web_view_factory_ = + std::make_unique<TestAshWebViewFactory>(); +}; + +// Verify taps on the eche tray button results in expected behaviour. +// It also sets the url and calls `ShowBubble`. +TEST_F(EcheTrayTest, EcheTrayShowBubbleAndTapTwice) { + // Verify the eche tray button is not active, and the eche tray bubble + // is not shown initially. + EXPECT_FALSE(eche_tray()->is_active()); + EXPECT_FALSE(eche_tray()->get_bubble_wrapper_for_test()); + + eche_tray()->SetUrl(GURL("http://google.com")); + eche_tray()->ShowBubble(); + + EXPECT_TRUE(eche_tray()->is_active()); + EXPECT_TRUE(eche_tray()->get_bubble_wrapper_for_test()); + EXPECT_TRUE( + eche_tray()->get_bubble_wrapper_for_test()->bubble_view()->GetVisible()); + + // Verify that by tapping the eche tray button, the button will become + // inactive and the eche tray bubble will be closed. + PerformTap(); + // Wait for the tray bubble widget to close. + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(eche_tray()->is_active()); + EXPECT_TRUE(eche_tray()->get_bubble_wrapper_for_test()); + EXPECT_FALSE( + eche_tray()->get_bubble_wrapper_for_test()->bubble_view()->GetVisible()); + + // Verify that tapping again will show the bubble. + PerformTap(); + // Wait for the tray bubble widget to open. + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(eche_tray()->is_active()); + EXPECT_TRUE(eche_tray()->get_bubble_wrapper_for_test()); + EXPECT_TRUE( + eche_tray()->get_bubble_wrapper_for_test()->bubble_view()->GetVisible()); +} + +} // namespace ash
diff --git a/ash/system/status_area_widget.cc b/ash/system/status_area_widget.cc index 6fd133fd..d6709f5 100644 --- a/ash/system/status_area_widget.cc +++ b/ash/system/status_area_widget.cc
@@ -17,6 +17,7 @@ #include "ash/shell.h" #include "ash/system/accessibility/dictation_button_tray.h" #include "ash/system/accessibility/select_to_speak/select_to_speak_tray.h" +#include "ash/system/eche/eche_tray.h" #include "ash/system/holding_space/holding_space_tray.h" #include "ash/system/ime_menu/ime_menu_tray.h" #include "ash/system/media/media_tray.h" @@ -156,6 +157,12 @@ AddTrayButton(std::move(phone_hub_tray)); } + if (chromeos::features::IsEcheCustomWidgetEnabled()) { + auto eche_tray = std::make_unique<EcheTray>(shelf_); + eche_tray_ = eche_tray.get(); + AddTrayButton(std::move(eche_tray)); + } + auto unified_system_tray = std::make_unique<UnifiedSystemTray>(shelf_); unified_system_tray_ = unified_system_tray.get(); AddTrayButton(std::move(unified_system_tray));
diff --git a/ash/system/status_area_widget.h b/ash/system/status_area_widget.h index 7d2576a..2f8e491 100644 --- a/ash/system/status_area_widget.h +++ b/ash/system/status_area_widget.h
@@ -35,6 +35,7 @@ class TrayBackgroundView; class UnifiedSystemTray; class VirtualKeyboardTray; +class EcheTray; // Widget showing the system tray, notification tray, and other tray views in // the bottom-right of the screen. Exists separately from ShelfView/ShelfWidget @@ -133,6 +134,7 @@ ImeMenuTray* ime_menu_tray() { return ime_menu_tray_; } HoldingSpaceTray* holding_space_tray() { return holding_space_tray_; } PhoneHubTray* phone_hub_tray() { return phone_hub_tray_; } + EcheTray* eche_tray() { return eche_tray_; } SelectToSpeakTray* select_to_speak_tray() { return select_to_speak_tray_; } @@ -234,6 +236,7 @@ LogoutButtonTray* logout_button_tray_ = nullptr; PaletteTray* palette_tray_ = nullptr; PhoneHubTray* phone_hub_tray_ = nullptr; + EcheTray* eche_tray_ = nullptr; StopRecordingButtonTray* stop_recording_button_tray_ = nullptr; ProjectorAnnotationTray* projector_annotation_tray_ = nullptr; VirtualKeyboardTray* virtual_keyboard_tray_ = nullptr;
diff --git a/ash/system/tray/tray_bubble_wrapper.cc b/ash/system/tray/tray_bubble_wrapper.cc index 9ccd61f..f7b7997 100644 --- a/ash/system/tray/tray_bubble_wrapper.cc +++ b/ash/system/tray/tray_bubble_wrapper.cc
@@ -18,8 +18,9 @@ namespace ash { TrayBubbleWrapper::TrayBubbleWrapper(TrayBackgroundView* tray, - TrayBubbleView* bubble_view) - : tray_(tray), bubble_view_(bubble_view) { + TrayBubbleView* bubble_view, + bool event_handling) + : tray_(tray), bubble_view_(bubble_view), event_handling_(event_handling) { bubble_widget_ = views::BubbleDialogDelegateView::CreateBubble(bubble_view_); bubble_widget_->AddObserver(this); @@ -29,15 +30,17 @@ if (!Shell::Get()->tablet_mode_controller()->InTabletMode()) Shell::Get()->app_list_controller()->DismissAppList(); - tray->tray_event_filter()->AddBubble(this); - - Shell::Get()->activation_client()->AddObserver(this); + if (event_handling_) { + tray->tray_event_filter()->AddBubble(this); + Shell::Get()->activation_client()->AddObserver(this); + } } TrayBubbleWrapper::~TrayBubbleWrapper() { - Shell::Get()->activation_client()->RemoveObserver(this); - - tray_->tray_event_filter()->RemoveBubble(this); + if (event_handling_) { + Shell::Get()->activation_client()->RemoveObserver(this); + tray_->tray_event_filter()->RemoveBubble(this); + } if (bubble_widget_) { auto* transient_manager = ::wm::TransientWindowManager::GetOrCreate( bubble_widget_->GetNativeWindow());
diff --git a/ash/system/tray/tray_bubble_wrapper.h b/ash/system/tray/tray_bubble_wrapper.h index b7f5bda4..0e1518b 100644 --- a/ash/system/tray/tray_bubble_wrapper.h +++ b/ash/system/tray/tray_bubble_wrapper.h
@@ -21,7 +21,14 @@ public views::WidgetObserver, public ::wm::ActivationChangeObserver { public: - TrayBubbleWrapper(TrayBackgroundView* tray, TrayBubbleView* bubble_view); + // `event_handling` When set to false disables the tray's event filtering + // and also ignores the activation events. Eche window is an example of a use + // case in which we do not want the keyboard events (both inside and outside + // of the bubble) be filtered and also we do not want activaion of other + // windows closes the bubble. + TrayBubbleWrapper(TrayBackgroundView* tray, + TrayBubbleView* bubble_view, + bool event_handling = true); TrayBubbleWrapper(const TrayBubbleWrapper&) = delete; TrayBubbleWrapper& operator=(const TrayBubbleWrapper&) = delete; @@ -51,6 +58,13 @@ TrayBackgroundView* tray_; TrayBubbleView* bubble_view_; // unowned views::Widget* bubble_widget_; + + // When set to false disables the tray's event filtering + // and also ignores the activation events. Eche window is an example of a use + // case in which we do not want the keyboard events (both inside and outside + // of the bubble) be filtered and also we do not want activaion of other + // windows closes the bubble. + const bool event_handling_; }; } // namespace ash
diff --git a/ash/webui/os_feedback_ui/backend/help_content_provider.cc b/ash/webui/os_feedback_ui/backend/help_content_provider.cc index 2a22095..b1e37a4 100644 --- a/ash/webui/os_feedback_ui/backend/help_content_provider.cc +++ b/ash/webui/os_feedback_ui/backend/help_content_provider.cc
@@ -9,10 +9,13 @@ #include "ash/webui/os_feedback_ui/mojom/os_feedback_ui.mojom.h" #include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/receiver.h" +#include "url/gurl.h" namespace ash { namespace feedback { +using os_feedback_ui::mojom::HelpContent; +using os_feedback_ui::mojom::HelpContentType; using os_feedback_ui::mojom::SearchResponse; using os_feedback_ui::mojom::SearchResponsePtr; @@ -24,6 +27,10 @@ os_feedback_ui::mojom::SearchResponsePtr& response) { // TODO(xiagndongkong): implement the search and populate response. response->total_results = 0; + // TODO(xiangdongkong): Remove the following dummy item. + response->results.emplace_back(HelpContent::New(u"how to fix wifi issue", + GURL("https://fakehelp.com"), + HelpContentType::kArticle)); } HelpContentProvider::HelpContentProvider()
diff --git a/ash/webui/os_feedback_ui/os_feedback_ui.cc b/ash/webui/os_feedback_ui/os_feedback_ui.cc index b2a5cbf..19a7f28 100644 --- a/ash/webui/os_feedback_ui/os_feedback_ui.cc +++ b/ash/webui/os_feedback_ui/os_feedback_ui.cc
@@ -85,4 +85,5 @@ helpContentProvider_->BindInterface(std::move(receiver)); } +WEB_UI_CONTROLLER_TYPE_IMPL(OSFeedbackUI) } // namespace ash
diff --git a/ash/webui/os_feedback_ui/os_feedback_ui.h b/ash/webui/os_feedback_ui/os_feedback_ui.h index 5af76637..0b99da3 100644 --- a/ash/webui/os_feedback_ui/os_feedback_ui.h +++ b/ash/webui/os_feedback_ui/os_feedback_ui.h
@@ -24,11 +24,14 @@ OSFeedbackUI& operator=(const OSFeedbackUI&) = delete; ~OSFeedbackUI() override; - private: void BindInterface( mojo::PendingReceiver<os_feedback_ui::mojom::HelpContentProvider> receiver); + + private: std::unique_ptr<feedback::HelpContentProvider> helpContentProvider_; + + WEB_UI_CONTROLLER_TYPE_DECL(); }; } // namespace ash
diff --git a/ash/webui/os_feedback_ui/resources/feedback_types.js b/ash/webui/os_feedback_ui/resources/feedback_types.js index fe92c6d7..0e8943d 100644 --- a/ash/webui/os_feedback_ui/resources/feedback_types.js +++ b/ash/webui/os_feedback_ui/resources/feedback_types.js
@@ -73,3 +73,9 @@ */ export const HelpContentProviderInterface = ash.osFeedbackUi.mojom.HelpContentProviderInterface; + +/** + * Type alias for the HelpContentProvider. + * @typedef {ash.osFeedbackUi.mojom.HelpContentProvider} + */ +export const HelpContentProvider = ash.osFeedbackUi.mojom.HelpContentProvider;
diff --git a/ash/webui/os_feedback_ui/resources/mojo_interface_provider.js b/ash/webui/os_feedback_ui/resources/mojo_interface_provider.js index 11eaf9c..d3b22867 100644 --- a/ash/webui/os_feedback_ui/resources/mojo_interface_provider.js +++ b/ash/webui/os_feedback_ui/resources/mojo_interface_provider.js
@@ -4,9 +4,7 @@ import {assert} from 'chrome://resources/js/assert.m.js'; -import {fakeHelpContentList, fakeSearchResponse} from './fake_data.js'; -import {FakeHelpContentProvider} from './fake_help_content_provider.js'; -import {HelpContentProviderInterface} from './feedback_types.js'; +import {HelpContentProvider, HelpContentProviderInterface} from './feedback_types.js'; /** * @fileoverview @@ -27,27 +25,11 @@ } /** - * Create a FakeHelpContentProvider with reasonable fake data. - * TODO(xiangdongkong): Remove once mojo bindings are implemented. - */ -function setupFakeHelpContentProvider() { - // Create provider. - const provider = new FakeHelpContentProvider(); - - // Setup search response. - provider.setFakeSearchResponse(fakeSearchResponse); - - // Set the fake provider. - setHelpContentProviderForTesting(provider); -} - -/** * @return {!HelpContentProviderInterface} */ export function getHelpContentProvider() { if (!helpContentProvider) { - // TODO(xiangdongkong): Instantiate a real mojo interface here. - setupFakeHelpContentProvider(); + helpContentProvider = HelpContentProvider.getRemote(); } assert(!!helpContentProvider);
diff --git a/ash/webui/personalization_app/mojom/BUILD.gn b/ash/webui/personalization_app/mojom/BUILD.gn index 64289f0..2e69726 100644 --- a/ash/webui/personalization_app/mojom/BUILD.gn +++ b/ash/webui/personalization_app/mojom/BUILD.gn
@@ -53,6 +53,10 @@ mojom = "ash.personalization_app.mojom.TopicSource" cpp = "ash::AmbientModeTopicSource" }, + { + mojom = "ash.personalization_app.mojom.TemperatureUnit" + cpp = "ash::AmbientModeTemperatureUnit" + }, ] traits_headers = [ "personalization_app_mojom_traits.h" ] traits_sources = [ "personalization_app_mojom_traits.cc" ]
diff --git a/ash/webui/personalization_app/mojom/personalization_app.mojom b/ash/webui/personalization_app/mojom/personalization_app.mojom index 3e4aa8b5..81781ee 100644 --- a/ash/webui/personalization_app/mojom/personalization_app.mojom +++ b/ash/webui/personalization_app/mojom/personalization_app.mojom
@@ -350,8 +350,8 @@ // Temperature unit for screensaver weather section. enum TemperatureUnit { - kFahrenheit = 1, - kCelsius = 2, + kFahrenheit = 0, + kCelsius = 1, }; // Album metadaba to display. Contains necessary data for both topic sources. @@ -385,6 +385,9 @@ // Notifies the JS side about the current state of TopicSource. OnTopicSourceChanged(TopicSource topic_source); + // Notifies the JS side about the current state of TemperatureUnit. + OnTemperatureUnitChanged(TemperatureUnit temperature_unit); + // Notifies the JS side about the current albums. OnAlbumsChanged(array<AmbientModeAlbum> albums); };
diff --git a/ash/webui/personalization_app/mojom/personalization_app_mojom_traits.cc b/ash/webui/personalization_app/mojom/personalization_app_mojom_traits.cc index 1f40965..28a1339 100644 --- a/ash/webui/personalization_app/mojom/personalization_app_mojom_traits.cc +++ b/ash/webui/personalization_app/mojom/personalization_app_mojom_traits.cc
@@ -30,6 +30,7 @@ using MojomWallpaperType = ash::personalization_app::mojom::WallpaperType; using MojomOnlineImageType = ash::personalization_app::mojom::OnlineImageType; using MojomTopicSource = ash::personalization_app::mojom::TopicSource; +using MojomTemperatureUnit = ash::personalization_app::mojom::TemperatureUnit; MojomWallpaperLayout EnumTraits<MojomWallpaperLayout, ash::WallpaperLayout>::ToMojom( @@ -298,6 +299,8 @@ return data.ReadTitle(&out->title) && data.ReadUrl(&out->url); } +// TODO (b/220933864): remove ash::AmbientModeTopicSource and +// ash::AmbientModeTemperatureUnit enums. MojomTopicSource EnumTraits<MojomTopicSource, ash::AmbientModeTopicSource>::ToMojom( ash::AmbientModeTopicSource input) { @@ -324,4 +327,30 @@ return false; } +MojomTemperatureUnit +EnumTraits<MojomTemperatureUnit, ash::AmbientModeTemperatureUnit>::ToMojom( + ash::AmbientModeTemperatureUnit input) { + switch (input) { + case ash::AmbientModeTemperatureUnit::kFahrenheit: + return MojomTemperatureUnit::kFahrenheit; + case ash::AmbientModeTemperatureUnit::kCelsius: + return MojomTemperatureUnit::kCelsius; + } +} + +bool EnumTraits<MojomTemperatureUnit, ash::AmbientModeTemperatureUnit>:: + FromMojom(MojomTemperatureUnit input, + ash::AmbientModeTemperatureUnit* output) { + switch (input) { + case MojomTemperatureUnit::kFahrenheit: + *output = ash::AmbientModeTemperatureUnit::kFahrenheit; + return true; + case MojomTemperatureUnit::kCelsius: + *output = ash::AmbientModeTemperatureUnit::kCelsius; + return true; + } + NOTREACHED(); + return false; +} + } // namespace mojo
diff --git a/ash/webui/personalization_app/mojom/personalization_app_mojom_traits.h b/ash/webui/personalization_app/mojom/personalization_app_mojom_traits.h index 5bc3758e..8efaa29 100644 --- a/ash/webui/personalization_app/mojom/personalization_app_mojom_traits.h +++ b/ash/webui/personalization_app/mojom/personalization_app_mojom_traits.h
@@ -112,6 +112,16 @@ ash::AmbientModeTopicSource* output); }; +template <> +struct EnumTraits<ash::personalization_app::mojom::TemperatureUnit, + ash::AmbientModeTemperatureUnit> { + using MojomTemperatureUnit = + ::ash::personalization_app::mojom::TemperatureUnit; + static MojomTemperatureUnit ToMojom(ash::AmbientModeTemperatureUnit input); + static bool FromMojom(MojomTemperatureUnit input, + ash::AmbientModeTemperatureUnit* output); +}; + } // namespace mojo #endif // ASH_WEBUI_PERSONALIZATION_APP_MOJOM_PERSONALIZATION_APP_MOJOM_TRAITS_H_
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_actions.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_actions.ts index e7afb56..21fc1d5 100644 --- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_actions.ts +++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_actions.ts
@@ -4,7 +4,7 @@ import {Action} from 'chrome://resources/js/cr/ui/store.js'; -import {TopicSource} from '../personalization_app.mojom-webui.js'; +import {TemperatureUnit, TopicSource} from '../personalization_app.mojom-webui.js'; /** * @fileoverview Defines the actions to change ambient state. @@ -13,9 +13,11 @@ export enum AmbientActionName { SET_AMBIENT_MODE_ENABLED = 'set_ambient_mode_enabled', SET_TOPIC_SOURCE = 'set_topic_source', + SET_TEMPERATURE_UNIT = 'set_temperature_unit', } -export type AmbientActions = SetAmbientModeEnabledAction|SetTopicSourceAction; +export type AmbientActions = + SetAmbientModeEnabledAction|SetTopicSourceAction|SetTemperatureUnitAction; export type SetAmbientModeEnabledAction = Action&{ name: AmbientActionName.SET_AMBIENT_MODE_ENABLED; @@ -27,6 +29,11 @@ topicSource: TopicSource; }; +export type SetTemperatureUnitAction = Action&{ + name: AmbientActionName.SET_TEMPERATURE_UNIT; + temperatureUnit: TemperatureUnit; +}; + /** * Sets the current value of the ambient mode pref. */ @@ -42,3 +49,11 @@ SetTopicSourceAction { return {name: AmbientActionName.SET_TOPIC_SOURCE, topicSource}; } + +/** + * Sets the current value of the temperature unit. + */ +export function setTemperatureUnitAction(temperatureUnit: TemperatureUnit): + SetTemperatureUnitAction { + return {name: AmbientActionName.SET_TEMPERATURE_UNIT, temperatureUnit}; +}
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_controller.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_controller.ts index 94512b2..f8c555c 100644 --- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_controller.ts +++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_controller.ts
@@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import {AmbientProviderInterface, TopicSource} from '../personalization_app.mojom-webui.js'; +import {AmbientProviderInterface, TemperatureUnit, TopicSource} from '../personalization_app.mojom-webui.js'; import {PersonalizationStore} from '../personalization_store.js'; -import {setAmbientModeEnabledAction, setTopicSourceAction} from './ambient_actions.js'; + +import {setAmbientModeEnabledAction, setTemperatureUnitAction, setTopicSourceAction} from './ambient_actions.js'; /** * @fileoverview contains all of the functions to interact with ambient mode @@ -32,3 +33,13 @@ // Dispatch action to select topic source. store.dispatch(setTopicSourceAction(topicSource)); } + +// Set ambient mode temperature unit. +export function setTemperatureUnit( + temperatureUnit: TemperatureUnit, _: AmbientProviderInterface, + store: PersonalizationStore): void { + // Dispatch action to select temperature unit. + store.dispatch(setTemperatureUnitAction(temperatureUnit)); + + // TODO(b/217281778): implement SetTemperatureUnit API. +}
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_observer.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_observer.ts index 2dc211c..91484cd 100644 --- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_observer.ts +++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_observer.ts
@@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import {AmbientModeAlbum, AmbientObserverInterface, AmbientObserverReceiver, AmbientProviderInterface, TopicSource} from '../personalization_app.mojom-webui.js'; +import {AmbientModeAlbum, AmbientObserverInterface, AmbientObserverReceiver, AmbientProviderInterface, TemperatureUnit, TopicSource} from '../personalization_app.mojom-webui.js'; import {PersonalizationStore} from '../personalization_store.js'; -import {setAmbientModeEnabledAction, setTopicSourceAction} from './ambient_actions.js'; + +import {setAmbientModeEnabledAction, setTemperatureUnitAction, setTopicSourceAction} from './ambient_actions.js'; import {getAmbientProvider} from './ambient_interface_provider.js'; /** @fileoverview listens for updates on color mode changes. */ @@ -47,6 +48,11 @@ store.dispatch(setTopicSourceAction(topicSource)); } + onTemperatureUnitChanged(temperatureUnit: TemperatureUnit) { + const store = PersonalizationStore.getInstance(); + store.dispatch(setTemperatureUnitAction(temperatureUnit)); + } + onAlbumsChanged(albums: AmbientModeAlbum[]) { // TODO(219247189): Handle albums changes. console.log('albums received', albums[0]);
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_reducers.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_reducers.ts index d1a4bb4..5cc032b 100644 --- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_reducers.ts +++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_reducers.ts
@@ -31,8 +31,20 @@ } } +export function temperatureUnitReducer( + state: AmbientState['temperatureUnit'], action: Actions, + _: PersonalizationState): AmbientState['temperatureUnit'] { + switch (action.name) { + case AmbientActionName.SET_TEMPERATURE_UNIT: + return action.temperatureUnit; + default: + return state; + } +} + export const ambientReducers: {[K in keyof AmbientState]: ReducerFunction<AmbientState[K]>} = { ambientModeEnabled: ambientModeEnabledReducer, topicSource: topicSourceReducer, + temperatureUnit: temperatureUnitReducer, };
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_state.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_state.ts index 7b31c76..7931578 100644 --- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_state.ts +++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_state.ts
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import {TopicSource} from '../personalization_app.mojom-webui.js'; +import {TemperatureUnit, TopicSource} from '../personalization_app.mojom-webui.js'; /** * Stores ambient related states. @@ -10,11 +10,13 @@ export interface AmbientState { ambientModeEnabled: boolean; topicSource: TopicSource|null; + temperatureUnit: TemperatureUnit|null; } export function emptyState(): AmbientState { return { ambientModeEnabled: false, topicSource: null, + temperatureUnit: null, }; }
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.html b/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.html index e5e4f15..dadd7e8 100644 --- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.html +++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.html
@@ -31,5 +31,7 @@ <topic-source-list selected-topic-source="[[topicSource_]]" has-google-photos-albums="[[hasGooglePhotosAlbums_]]"> </topic-source-list> - <ambient-weather-unit></ambient-weather-unit> + <ambient-weather-unit + selected-temperature-unit="[[temperatureUnitToString_(temperatureUnit_)]]"> + </ambient-weather-unit> </div>
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.ts index 47eacd3..c3053c5 100644 --- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.ts +++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.ts
@@ -9,11 +9,12 @@ import 'chrome://personalization/trusted/ambient/toggle_row_element.js'; import 'chrome://personalization/trusted/ambient/topic_source_list_element.js'; +import 'chrome://personalization/trusted/ambient/ambient_weather_element.js'; import {CrToggleElement} from 'chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js'; import {html} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; -import {TopicSource} from '../personalization_app.mojom-webui.js'; +import {TemperatureUnit, TopicSource} from '../personalization_app.mojom-webui.js'; import {WithPersonalizationStore} from '../personalization_store.js'; import {setAmbientModeEnabled} from './ambient_controller.js'; @@ -36,12 +37,14 @@ ambientModeEnabled_: Boolean, hasGooglePhotosAlbums_: {type: Boolean, value: true}, topicSource_: Number, + temperatureUnit_: Number, }; } private ambientModeEnabled_: boolean; private hasGooglePhotosAlbums_: boolean; private topicSource_: TopicSource|null = null; + private temperatureUnit_: TemperatureUnit|null = null; connectedCallback() { super.connectedCallback(); @@ -50,6 +53,8 @@ 'ambientModeEnabled_', state => state.ambient.ambientModeEnabled); this.watch<AmbientSubpage['topicSource_']>( 'topicSource_', state => state.ambient.topicSource); + this.watch<AmbientSubpage['temperatureUnit_']>( + 'temperatureUnit_', state => state.ambient.temperatureUnit); this.updateFromStore(); } @@ -68,6 +73,10 @@ setAmbientModeEnabled( ambientModeEnabled, getAmbientProvider(), this.getStore()); } + + private temperatureUnitToString_(temperatureUnit: TemperatureUnit): string { + return temperatureUnit != null ? temperatureUnit.toString() : ''; + } } customElements.define(AmbientSubpage.is, AmbientSubpage);
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_weather_element.html b/ash/webui/personalization_app/resources/trusted/ambient/ambient_weather_element.html index 04f99f5..1b04f13 100644 --- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_weather_element.html +++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_weather_element.html
@@ -1,5 +1,7 @@ <style> #weatherTitle { + color: var(--cros-text-color-primary); + font: var(--personalization-app-label-font); margin-bottom: 16px; margin-top: 34px; } @@ -20,7 +22,7 @@ <div class="weather-unit-list"> <cr-radio-group id="ambientTemperatureUnit" - selected="{{selectedTemperatureUnit_}}" + selected="{{selectedTemperatureUnit}}" aria-labelledby="weatherTitle"> <cr-radio-button name="[[temperatureUnit_.kFahrenheit]]"
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_weather_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_weather_element.ts index 265e9b7..b10d40e 100644 --- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_weather_element.ts +++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_weather_element.ts
@@ -16,6 +16,13 @@ import {TemperatureUnit} from '../personalization_app.mojom-webui.js'; import {WithPersonalizationStore} from '../personalization_store.js'; +import {setTemperatureUnit} from './ambient_controller.js'; +import {getAmbientProvider} from './ambient_interface_provider.js'; + +export function inBetween(num: number, minValue: number, maxValue: number) { + return minValue <= num && num <= maxValue; +} + export class AmbientWeatherUnit extends WithPersonalizationStore { static get is() { return 'ambient-weather-unit'; @@ -35,21 +42,23 @@ value: TemperatureUnit, }, - selectedTemperatureUnit_: { - type: TemperatureUnit, - value: null, + selectedTemperatureUnit: { + type: String, observer: 'onSelectedTemperatureUnitChanged_', } }; } private temperatureUnit_: TemperatureUnit; - private selectedTemperatureUnit_: TemperatureUnit|null; + private selectedTemperatureUnit: TemperatureUnit; - private onSelectedTemperatureUnitChanged_( - newValue: TemperatureUnit, oldValue: TemperatureUnit) { - console.log(oldValue, newValue); - // TODO: implementation will be updated in followup CLs. + private onSelectedTemperatureUnitChanged_(newValue: string, _: string) { + const num = parseInt(newValue, 10); + if (!isNaN(num) && + inBetween(num, TemperatureUnit.MIN_VALUE, TemperatureUnit.MAX_VALUE)) { + setTemperatureUnit( + num as TemperatureUnit, getAmbientProvider(), this.getStore()); + } } }
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/toggle_row_element.html b/ash/webui/personalization_app/resources/trusted/ambient/toggle_row_element.html index c187b21..906b091a 100644 --- a/ash/webui/personalization_app/resources/trusted/ambient/toggle_row_element.html +++ b/ash/webui/personalization_app/resources/trusted/ambient/toggle_row_element.html
@@ -19,6 +19,7 @@ border-top: 1px solid var(--cros-separator-color); } .row > p { + font: var(--cros-body-1-font); height: 20px; margin: 0; }
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/topic_source_item_element.html b/ash/webui/personalization_app/resources/trusted/ambient/topic_source_item_element.html index fd3047d..207e3d2 100644 --- a/ash/webui/personalization_app/resources/trusted/ambient/topic_source_item_element.html +++ b/ash/webui/personalization_app/resources/trusted/ambient/topic_source_item_element.html
@@ -9,6 +9,10 @@ padding-inline-end: var(--cr-icon-ripple-padding); padding-inline-start: var(--cr-section-padding); } + .primary-text { + color: var(--cros-text-color-primary); + font: var(--cros-body-2-font); + } </style> <div id="container"> @@ -20,7 +24,7 @@ </div> <div id="labelWrapper" aria-hidden="true"> - <div>[[getItemName_(topicSource, hasGooglePhotosAlbums)]]</div> + <div class="primary-text">[[getItemName_(topicSource, hasGooglePhotosAlbums)]]</div> <div class="cr-secondary-text"> [[getItemDescription_(topicSource, hasGooglePhotosAlbums)]] </div>
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/topic_source_list_element.html b/ash/webui/personalization_app/resources/trusted/ambient/topic_source_list_element.html index d03ea9a..d8dbca0 100644 --- a/ash/webui/personalization_app/resources/trusted/ambient/topic_source_list_element.html +++ b/ash/webui/personalization_app/resources/trusted/ambient/topic_source_list_element.html
@@ -1,5 +1,7 @@ <style> #topicSourceTitle { + color: var(--cros-text-color-primary); + font: var(--personalization-app-label-font); margin-bottom: 16px; margin-top: 34px; }
diff --git a/base/values.cc b/base/values.cc index 25feea9c..fcb00bf 100644 --- a/base/values.cc +++ b/base/values.cc
@@ -608,6 +608,9 @@ if (!splitter.HasNext()) { return current_value; } + if (!current_value) { + return nullptr; + } current_dict = current_value->GetIfDict(); if (!current_dict) { return nullptr;
diff --git a/base/values.h b/base/values.h index 5a375d5..053bf08 100644 --- a/base/values.h +++ b/base/values.h
@@ -1144,8 +1144,6 @@ explicit Value(absl::monostate); explicit Value(DoubleStorage storage); - friend class ValuesTest_SizeOfValue_Test; - absl::variant<absl::monostate, bool, int,
diff --git a/base/values_unittest.cc b/base/values_unittest.cc index cbead377..0cc5620 100644 --- a/base/values_unittest.cc +++ b/base/values_unittest.cc
@@ -34,32 +34,36 @@ namespace base { -// Ensure that base::Value is as small as possible, i.e. that there is -// no wasted space after the inner value due to alignment constraints. -// Distinguish between the 'header' that includes |type_| and and the inner -// value that follows it, which can be a bool, int, double, string, blob, list -// or dict. +#ifdef NDEBUG +// `Value` should have a (relatively) small size to avoid creating excess +// overhead, e.g. for lists of values that are all ints. // -// This test is only enabled when NDEBUG is defined. This way the test will not -// fail in debug builds that sometimes contain larger versions of the standard -// containers used inside base::Value. +// This test is limited to NDEBUG builds, since some containers may require +// extra storage for supporting debug checks for things like iterators. TEST(ValuesTest, SizeOfValue) { - static_assert( - std::max({alignof(size_t), alignof(bool), alignof(int), - alignof(Value::DoubleStorage), alignof(std::string), - alignof(Value::BlobStorage), alignof(Value::ListStorage), - alignof(Value::DictStorage)}) == alignof(Value), - "Value does not have smallest possible alignof"); +#if BUILDFLAG(IS_WIN) + // On Windows, clang-cl does not support `[[no_unique_address]]` (see + // https://github.com/llvm/llvm-project/issues/49358). `base::Value::Dict` has + // a `base::flat_tree` which relies on this attribute to avoid wasting space + // when the comparator is stateless. Unfortunately, this means + // `base::Value::Dict` ends up taking 4 machine words instead of 3. An + // additional word is used by absl::variant for the type index. + constexpr size_t kExpectedSize = 5 * sizeof(void*); +#else // !BUILDFLAG(IS_WIN) + // libc++'s std::string and std::vector both take 3 machine words. An + // additional word is used by absl::variant for the type index. + constexpr size_t kExpectedSize = 4 * sizeof(void*); +#endif // BUILDFLAG(IS_WIN) - static_assert( - sizeof(size_t) + - std::max({sizeof(bool), sizeof(int), sizeof(Value::DoubleStorage), - sizeof(std::string), sizeof(Value::BlobStorage), - sizeof(Value::ListStorage), - sizeof(Value::DictStorage)}) == - sizeof(Value), - "Value does not have smallest possible sizeof"); + // Use std::integral_constant so the compiler error message includes the + // evaluated size. In future versions of clang, it should be possible to + // simplify this to an equality comparison (i.e. newer clangs print out + // "comparison reduces to '(1 == 2)'"). + static_assert(std::is_same_v<std::integral_constant<size_t, sizeof(Value)>, + std::integral_constant<size_t, kExpectedSize>>, + "base::Value has an unexpected size!"); } +#endif TEST(ValuesTest, TestNothrow) { static_assert(std::is_nothrow_move_constructible<Value>::value, @@ -575,6 +579,23 @@ // TODO(dcheng): Add more tests directly exercising the updated dictionary and // list APIs. For now, most of the updated APIs are tested indirectly via the // legacy APIs that are largely backed by the updated APIs. +TEST(ValuesTest, DictFindByDottedPath) { + Value::Dict dict; + + EXPECT_EQ(nullptr, dict.FindByDottedPath("a.b.c")); + + Value::Dict& a_dict = dict.Set("a", Value::Dict())->GetDict(); + EXPECT_EQ(nullptr, dict.FindByDottedPath("a.b.c")); + + Value::Dict& b_dict = a_dict.Set("b", Value::Dict())->GetDict(); + EXPECT_EQ(nullptr, dict.FindByDottedPath("a.b.c")); + + b_dict.Set("c", true); + const Value* value = dict.FindByDottedPath("a.b.c"); + ASSERT_NE(nullptr, value); + EXPECT_TRUE(value->GetBool()); +} + TEST(ValuesTest, ListFront) { Value::List list; const Value::List& const_list = list;
diff --git a/build/android/gyp/util/resource_utils.py b/build/android/gyp/util/resource_utils.py index c78b1a21..db724bfb 100644 --- a/build/android/gyp/util/resource_utils.py +++ b/build/android/gyp/util/resource_utils.py
@@ -704,6 +704,10 @@ extends_string = 'extends {{ parent_path }}.R.{{ resource_type }} ' dep_path = GetCustomPackagePath(grandparent_custom_package_name) + # Don't actually mark fields as "final" or else R8 complain when aapt2 uses + # --proguard-conditional-keep-rules. E.g.: + # Rule precondition matches static final fields javac has inlined. + # Such rules are unsound as the shrinker cannot infer the inlining precisely. template = Template("""/* AUTO-GENERATED FILE. DO NOT MODIFY. */ package {{ package }}; @@ -712,7 +716,7 @@ {% for resource_type in resource_types %} public static class {{ resource_type }} """ + extends_string + """ { {% for e in final_resources[resource_type] %} - public static final {{ e.java_type }} {{ e.name }} = {{ e.value }}; + public static {{ e.java_type }} {{ e.name }} = {{ e.value }}; {% endfor %} {% for e in non_final_resources[resource_type] %} {% if e.value != '0' %}
diff --git a/build/fuchsia/linux_internal.sdk.sha1 b/build/fuchsia/linux_internal.sdk.sha1 index 0303687e..7079ef7 100644 --- a/build/fuchsia/linux_internal.sdk.sha1 +++ b/build/fuchsia/linux_internal.sdk.sha1
@@ -1 +1 @@ -7.20220217.4.1 +7.20220222.4.1
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn index 2c3cea9..6c92312 100644 --- a/chrome/android/BUILD.gn +++ b/chrome/android/BUILD.gn
@@ -3798,6 +3798,7 @@ "java/src/org/chromium/chrome/browser/autofill/SaveUpdateAddressProfilePromptController.java", "java/src/org/chromium/chrome/browser/autofill/settings/AutofillPaymentMethodsDelegate.java", "java/src/org/chromium/chrome/browser/autofill/settings/AutofillProfileBridge.java", + "java/src/org/chromium/chrome/browser/autofill/settings/VirtualCardEnrollmentFields.java", "java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTask.java", "java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTaskScheduler.java", "java/src/org/chromium/chrome/browser/background_sync/GooglePlayServicesChecker.java",
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni index ec9730f1..4a1dd5b1 100644 --- a/chrome/android/chrome_java_resources.gni +++ b/chrome/android/chrome_java_resources.gni
@@ -30,7 +30,6 @@ "java/res/drawable-hdpi/btn_tab_close_normal.png", "java/res/drawable-hdpi/btn_tabstrip_new_incognito_tab_normal.png", "java/res/drawable-hdpi/btn_tabstrip_new_tab_normal.png", - "java/res/drawable-hdpi/btn_tabstrip_new_tab_pressed.png", "java/res/drawable-hdpi/btn_tabstrip_switch_normal.png", "java/res/drawable-hdpi/contextual_search_promo_ripple.9.png", "java/res/drawable-hdpi/cvc_icon.png", @@ -91,17 +90,12 @@ "java/res/drawable-hdpi/toolbar_shadow_normal.png", "java/res/drawable-hdpi/verify_checkmark.png", "java/res/drawable-ldrtl-hdpi-v17/btn_tabstrip_new_incognito_tab_normal.png", - "java/res/drawable-ldrtl-hdpi-v17/btn_tabstrip_new_tab_pressed.png", "java/res/drawable-ldrtl-mdpi-v17/btn_tabstrip_new_incognito_tab_normal.png", - "java/res/drawable-ldrtl-mdpi-v17/btn_tabstrip_new_tab_pressed.png", "java/res/drawable-ldrtl-v17/btn_back.xml", "java/res/drawable-ldrtl-v17/btn_forward.xml", "java/res/drawable-ldrtl-xhdpi-v17/btn_tabstrip_new_incognito_tab_normal.png", - "java/res/drawable-ldrtl-xhdpi-v17/btn_tabstrip_new_tab_pressed.png", "java/res/drawable-ldrtl-xxhdpi-v17/btn_tabstrip_new_incognito_tab_normal.png", - "java/res/drawable-ldrtl-xxhdpi-v17/btn_tabstrip_new_tab_pressed.png", "java/res/drawable-ldrtl-xxxhdpi-v17/btn_tabstrip_new_incognito_tab_normal.png", - "java/res/drawable-ldrtl-xxxhdpi-v17/btn_tabstrip_new_tab_pressed.png", "java/res/drawable-ldrtl/google_pay_with_divider.xml", "java/res/drawable-mdpi/bg_tabstrip_background_tab_outline.9.png", "java/res/drawable-mdpi/bg_tabstrip_tab.9.png", @@ -117,7 +111,6 @@ "java/res/drawable-mdpi/btn_tab_close_normal.png", "java/res/drawable-mdpi/btn_tabstrip_new_incognito_tab_normal.png", "java/res/drawable-mdpi/btn_tabstrip_new_tab_normal.png", - "java/res/drawable-mdpi/btn_tabstrip_new_tab_pressed.png", "java/res/drawable-mdpi/btn_tabstrip_switch_normal.png", "java/res/drawable-mdpi/contextual_search_promo_ripple.9.png", "java/res/drawable-mdpi/cvc_icon.png", @@ -202,7 +195,6 @@ "java/res/drawable-xhdpi/btn_tab_close_normal.png", "java/res/drawable-xhdpi/btn_tabstrip_new_incognito_tab_normal.png", "java/res/drawable-xhdpi/btn_tabstrip_new_tab_normal.png", - "java/res/drawable-xhdpi/btn_tabstrip_new_tab_pressed.png", "java/res/drawable-xhdpi/btn_tabstrip_switch_normal.png", "java/res/drawable-xhdpi/contextual_search_promo_ripple.9.png", "java/res/drawable-xhdpi/cvc_icon.png", @@ -273,7 +265,6 @@ "java/res/drawable-xxhdpi/btn_tab_close_normal.png", "java/res/drawable-xxhdpi/btn_tabstrip_new_incognito_tab_normal.png", "java/res/drawable-xxhdpi/btn_tabstrip_new_tab_normal.png", - "java/res/drawable-xxhdpi/btn_tabstrip_new_tab_pressed.png", "java/res/drawable-xxhdpi/btn_tabstrip_switch_normal.png", "java/res/drawable-xxhdpi/contextual_search_promo_ripple.9.png", "java/res/drawable-xxhdpi/cvc_icon.png", @@ -344,7 +335,6 @@ "java/res/drawable-xxxhdpi/btn_tab_close_normal.png", "java/res/drawable-xxxhdpi/btn_tabstrip_new_incognito_tab_normal.png", "java/res/drawable-xxxhdpi/btn_tabstrip_new_tab_normal.png", - "java/res/drawable-xxxhdpi/btn_tabstrip_new_tab_pressed.png", "java/res/drawable-xxxhdpi/btn_tabstrip_switch_normal.png", "java/res/drawable-xxxhdpi/contextual_search_promo_ripple.9.png", "java/res/drawable-xxxhdpi/cvc_icon.png",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni index 17c3613..d8115dc1 100644 --- a/chrome/android/chrome_java_sources.gni +++ b/chrome/android/chrome_java_sources.gni
@@ -158,6 +158,7 @@ "java/src/org/chromium/chrome/browser/autofill/settings/AutofillVirtualCardUnenrollmentDialog.java", "java/src/org/chromium/chrome/browser/autofill/settings/CardEditor.java", "java/src/org/chromium/chrome/browser/autofill/settings/CreditCardNumberFormattingTextWatcher.java", + "java/src/org/chromium/chrome/browser/autofill/settings/VirtualCardEnrollmentFields.java", "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPreferenceFragment.java", "java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTask.java", "java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTaskScheduler.java",
diff --git a/chrome/android/chrome_junit_test_java_sources.gni b/chrome/android/chrome_junit_test_java_sources.gni index b7ea8b3..f1e2615 100644 --- a/chrome/android/chrome_junit_test_java_sources.gni +++ b/chrome/android/chrome_junit_test_java_sources.gni
@@ -20,6 +20,7 @@ "junit/src/org/chromium/chrome/browser/app/video_tutorials/NewTabPageVideoIPHManagerTest.java", "junit/src/org/chromium/chrome/browser/autofill/AutofillSuggestionTest.java", "junit/src/org/chromium/chrome/browser/autofill/AutofillUiUtilsTest.java", + "junit/src/org/chromium/chrome/browser/autofill/settings/AutofillVirtualCardEnrollmentDialogTest.java", "junit/src/org/chromium/chrome/browser/autofill/settings/AutofillVirtualCardUnenrollmentDialogTest.java", "junit/src/org/chromium/chrome/browser/autofill/settings/FakeModalDialogManager.java", "junit/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTaskSchedulerTest.java", @@ -127,6 +128,7 @@ "junit/src/org/chromium/chrome/browser/incognito/IncognitoTabSnapshotControllerTest.java", "junit/src/org/chromium/chrome/browser/infobar/IPHInfoBarSupportTest.java", "junit/src/org/chromium/chrome/browser/init/AsyncInitTaskRunnerTest.java", + "junit/src/org/chromium/chrome/browser/instantapps/InstantAppsMessageDelegateTest.java", "junit/src/org/chromium/chrome/browser/invalidation/ResumableDelayedTaskRunnerTest.java", "junit/src/org/chromium/chrome/browser/invalidation/SessionsInvalidationManagerTest.java", "junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationActionsUpdatedTest.java",
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceLayout.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceLayout.java index b067f83e7..c36b107 100644 --- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceLayout.java +++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceLayout.java
@@ -18,7 +18,6 @@ import androidx.annotation.VisibleForTesting; import org.chromium.base.Log; -import org.chromium.base.MathUtils; import org.chromium.base.TraceEvent; import org.chromium.base.jank_tracker.JankScenario; import org.chromium.base.jank_tracker.JankTracker; @@ -38,6 +37,7 @@ import org.chromium.chrome.browser.layouts.animation.CompositorAnimator; import org.chromium.chrome.browser.layouts.scene_layer.SceneLayer; import org.chromium.chrome.browser.tab.Tab; +import org.chromium.chrome.browser.tab.TabUtils; import org.chromium.chrome.browser.tabmodel.TabModelSelector; import org.chromium.chrome.browser.tasks.ReturnToChromeExperimentsUtil; import org.chromium.chrome.browser.tasks.TasksSurface; @@ -173,10 +173,7 @@ }; mController.addOverviewModeObserver(mStartSurfaceObserver); - if (TabUiFeatureUtilities.isTabThumbnailAspectRatioNotOne()) { - mThumbnailAspectRatio = (float) TabUiFeatureUtilities.THUMBNAIL_ASPECT_RATIO.getValue(); - mThumbnailAspectRatio = MathUtils.clamp(mThumbnailAspectRatio, 0.5f, 2.0f); - } + mThumbnailAspectRatio = TabUtils.getTabThumbnailAspectRatio(getContext()); } @Override @@ -479,10 +476,8 @@ // down, making the "create group" visible for a while. animationList.add(CompositorAnimator.ofWritableFloatPropertyKey(handler, sourceLayoutTab, LayoutTab.MAX_CONTENT_HEIGHT, sourceLayoutTab.getUnclampedOriginalContentHeight(), - TabUiFeatureUtilities.isTabThumbnailAspectRatioNotOne() - ? Math.min(getWidth() / mThumbnailAspectRatio, - sourceLayoutTab.getUnclampedOriginalContentHeight()) - : getWidth(), + Math.min(getWidth() / mThumbnailAspectRatio, + sourceLayoutTab.getUnclampedOriginalContentHeight()), ZOOMING_DURATION, Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR)); CompositorAnimator backgroundAlpha = @@ -535,10 +530,8 @@ // down, making the "create group" visible for a while. animationList.add(CompositorAnimator.ofWritableFloatPropertyKey(handler, sourceLayoutTab, LayoutTab.MAX_CONTENT_HEIGHT, - TabUiFeatureUtilities.isTabThumbnailAspectRatioNotOne() - ? Math.min(getWidth() / mThumbnailAspectRatio, - sourceLayoutTab.getUnclampedOriginalContentHeight()) - : getWidth(), + Math.min(getWidth() / mThumbnailAspectRatio, + sourceLayoutTab.getUnclampedOriginalContentHeight()), sourceLayoutTab.getUnclampedOriginalContentHeight(), ZOOMING_DURATION, Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR));
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTabSwitcherTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTabSwitcherTest.java index cd37f8c..9ab590a 100644 --- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTabSwitcherTest.java +++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTabSwitcherTest.java
@@ -45,7 +45,6 @@ import org.junit.Test; import org.junit.runner.RunWith; -import org.chromium.base.MathUtils; import org.chromium.base.metrics.RecordHistogram; import org.chromium.base.test.params.ParameterAnnotations; import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameter; @@ -73,7 +72,6 @@ import org.chromium.chrome.browser.tasks.ReturnToChromeExperimentsUtil; import org.chromium.chrome.browser.tasks.pseudotab.PseudoTab; import org.chromium.chrome.browser.tasks.pseudotab.TabAttributeCache; -import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities; import org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper; import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate; import org.chromium.chrome.test.ChromeTabbedActivityTestRule; @@ -432,10 +430,8 @@ onViewWaiting(allOf(withId(org.chromium.chrome.test.R.id.tab_thumbnail), isDisplayed())); View tabThumbnail = cta.findViewById(org.chromium.chrome.test.R.id.tab_thumbnail); - float defaultRatio = (float) TabUiFeatureUtilities.THUMBNAIL_ASPECT_RATIO.getValue(); - defaultRatio = MathUtils.clamp(defaultRatio, 0.5f, 2.0f); assertEquals(tabThumbnail.getMeasuredHeight(), - (int) (tabThumbnail.getMeasuredWidth() * 1.0 / defaultRatio), 2); + (int) (tabThumbnail.getMeasuredWidth() * 1.0 / 0.85f), 2); } @Test
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java index 66ff007..5a04e15 100644 --- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java +++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
@@ -142,7 +142,7 @@ @Restriction({Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE, UiRestriction.RESTRICTION_TYPE_PHONE, UiRestriction.RESTRICTION_TYPE_TABLET}) @CommandLineFlags.Add({ChromeSwitches.DISABLE_NATIVE_INITIALIZATION, - "force-fieldtrial-params=Study.Group:allow_to_refetch/true/thumbnail_aspect_ratio/2.0"}) + "force-fieldtrial-params=Study.Group:allow_to_refetch/true"}) public void fetchThumbnailsPreNativeTest() { // clang-format on StartSurfaceTestUtils.startMainActivityFromLauncher(mActivityTestRule);
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java index c78cb70..a77a7a6b 100644 --- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java +++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java
@@ -865,8 +865,7 @@ @EnableFeatures({ChromeFeatureList.CLOSE_TAB_SUGGESTIONS + "<Study", ChromeFeatureList.TAB_TO_GTS_ANIMATION + "<Study"}) @CommandLineFlags.Add({BASE_PARAMS + "/baseline_tab_suggestions/true" + - "/baseline_close_tab_suggestions/true/min_time_between_prefetches/0/" - + "thumbnail_aspect_ratio/1.0"}) + "/baseline_close_tab_suggestions/true/min_time_between_prefetches/0/"}) public void testTabSuggestionMessageCard_dismiss() throws InterruptedException { // clang-format on prepareTabs(3, 0, null); @@ -897,8 +896,7 @@ @EnableFeatures({ChromeFeatureList.CLOSE_TAB_SUGGESTIONS + "<Study", ChromeFeatureList.TAB_TO_GTS_ANIMATION + "<Study"}) @CommandLineFlags.Add({BASE_PARAMS + "/baseline_tab_suggestions/true" + - "/baseline_close_tab_suggestions/true/min_time_between_prefetches/0" - + "/thumbnail_aspect_ratio/1.0"}) + "/baseline_close_tab_suggestions/true/min_time_between_prefetches/0"}) public void testTabSuggestionMessageCard_review() throws InterruptedException { // clang-format on prepareTabs(3, 0, null); @@ -930,8 +928,7 @@ @EnableFeatures({ChromeFeatureList.CLOSE_TAB_SUGGESTIONS + "<Study", ChromeFeatureList.TAB_TO_GTS_ANIMATION + "<Study"}) @CommandLineFlags.Add({BASE_PARAMS + "/baseline_tab_suggestions/true" + - "/baseline_close_tab_suggestions/true/min_time_between_prefetches/0/" - + "thumbnail_aspect_ratio/1.0"}) + "/baseline_close_tab_suggestions/true/min_time_between_prefetches/0/"}) public void testShowOnlyOneTabSuggestionMessageCard_withSoftCleanup() throws InterruptedException { // clang-format on @@ -945,8 +942,7 @@ @EnableFeatures({ChromeFeatureList.CLOSE_TAB_SUGGESTIONS + "<Study", ChromeFeatureList.TAB_TO_GTS_ANIMATION + "<Study"}) @CommandLineFlags.Add({BASE_PARAMS + "/baseline_tab_suggestions/true" + - "/baseline_close_tab_suggestions/true/min_time_between_prefetches/0/" - + "thumbnail_aspect_ratio/1.0"}) + "/baseline_close_tab_suggestions/true/min_time_between_prefetches/0/"}) @FlakyTest(message = "https://crbug.com/1198484") public void testShowOnlyOneTabSuggestionMessageCard_withHardCleanup() throws InterruptedException { @@ -961,8 +957,7 @@ @EnableFeatures({ChromeFeatureList.CLOSE_TAB_SUGGESTIONS + "<Study", ChromeFeatureList.TAB_TO_GTS_ANIMATION + "<Study"}) @CommandLineFlags.Add({BASE_PARAMS + "/baseline_tab_suggestions/true" + - "/baseline_close_tab_suggestions/true/min_time_between_prefetches/0/" - + "thumbnail_aspect_ratio/1.0"}) + "/baseline_close_tab_suggestions/true/min_time_between_prefetches/0/"}) public void testTabSuggestionMessageCardDismissAfterTabClosing() throws InterruptedException { // clang-format on prepareTabs(3, 0, mUrl); @@ -1051,8 +1046,7 @@ @EnableFeatures({ChromeFeatureList.CLOSE_TAB_SUGGESTIONS + "<Study", ChromeFeatureList.TAB_TO_GTS_ANIMATION + "<Study"}) @CommandLineFlags.Add({BASE_PARAMS + "/baseline_tab_suggestions/true" + - "/baseline_close_tab_suggestions/true/min_time_between_prefetches/0/" - + "thumbnail_aspect_ratio/1.0"}) + "/baseline_close_tab_suggestions/true/min_time_between_prefetches/0/"}) public void testTabSuggestionMessageCard_orientation() throws InterruptedException { // clang-format on ChromeTabbedActivity cta = mActivityTestRule.getActivity(); @@ -1154,33 +1148,33 @@ @Test @MediumTest @EnableFeatures({ChromeFeatureList.TAB_TO_GTS_ANIMATION + "<Study"}) - @CommandLineFlags.Add({BASE_PARAMS + "/thumbnail_aspect_ratio/0.75"}) + @CommandLineFlags.Add({BASE_PARAMS}) @DisabledTest(message = "https://crbug.com/1122657") - public void testThumbnailAspectRatio_point75() { + public void testThumbnailAspectRatio_point75() throws InterruptedException { prepareTabs(2, 0, mUrl); - enterTabSwitcher(mActivityTestRule.getActivity()); + enterGTSWithThumbnailChecking(); onView(tabSwitcherViewMatcher()) - .check(ThumbnailAspectRatioAssertion.havingAspectRatio(0.75)); + .check(ThumbnailAspectRatioAssertion.havingAspectRatio(0.85)); leaveGTSAndVerifyThumbnailsAreReleased(); Tab tab = mActivityTestRule.getActivity().getTabModelSelector().getCurrentTab(); mActivityTestRule.loadUrlInTab( NTP_URL, PageTransition.TYPED | PageTransition.FROM_ADDRESS_BAR, tab); - enterTabSwitcher(mActivityTestRule.getActivity()); + enterGTSWithThumbnailChecking(); onView(tabSwitcherViewMatcher()) - .check(ThumbnailAspectRatioAssertion.havingAspectRatio(0.75)); + .check(ThumbnailAspectRatioAssertion.havingAspectRatio(0.85)); } @Test @MediumTest @EnableFeatures({ChromeFeatureList.TAB_TO_GTS_ANIMATION + "<Study"}) - @CommandLineFlags.Add({BASE_PARAMS + "/thumbnail_aspect_ratio/2.0/allow_to_refetch/true"}) + @CommandLineFlags.Add({BASE_PARAMS + "/allow_to_refetch/true"}) @DisabledTest(message = "Flaky - https://crbug.com/1124041") - public void testThumbnailAspectRatio_fromTwoToPoint75() throws Exception { + public void testThumbnailAspectRatio_fromPoint85ToPoint75() throws Exception { prepareTabs(2, 0, mUrl); enterTabSwitcher(mActivityTestRule.getActivity()); onView(tabSwitcherViewMatcher()) - .check(ThumbnailAspectRatioAssertion.havingAspectRatio(2.0)); + .check(ThumbnailAspectRatioAssertion.havingAspectRatio(0.85)); TabModel currentTabModel = mActivityTestRule.getActivity().getTabModelSelector().getCurrentModel(); for (int i = 0; i < currentTabModel.getCount(); i++) { @@ -1277,7 +1271,7 @@ @Test @MediumTest @EnableFeatures({ChromeFeatureList.TAB_TO_GTS_ANIMATION + "<Study"}) - @CommandLineFlags.Add({BASE_PARAMS + "/thumbnail_aspect_ratio/2.0/allow_to_refetch/true"}) + @CommandLineFlags.Add({BASE_PARAMS + "/allow_to_refetch/true"}) @DisabledTest(message = "http://crbug/1119527 - Flaky on bots.") public void testThumbnailFetchingResult_changingAspectRatio() throws Exception { prepareTabs(2, 0, mUrl); @@ -1320,7 +1314,7 @@ oldDifferentAspectRatioJpegCount = currentDifferentAspectRatioJpegCount; onView(tabSwitcherViewMatcher()) - .check(ThumbnailAspectRatioAssertion.havingAspectRatio(2.0)); + .check(ThumbnailAspectRatioAssertion.havingAspectRatio(0.85)); TabModel currentTabModel = mActivityTestRule.getActivity().getTabModelSelector().getCurrentModel(); @@ -1354,7 +1348,7 @@ TabContentManager.ThumbnailFetchingResult.GOT_NOTHING) - oldNothingCount); onView(tabSwitcherViewMatcher()) - .check(ThumbnailAspectRatioAssertion.havingAspectRatio(2.0)); + .check(ThumbnailAspectRatioAssertion.havingAspectRatio(0.85)); } @Test @@ -1372,8 +1366,8 @@ @MediumTest // clang-format off @EnableFeatures({ChromeFeatureList.TAB_TO_GTS_ANIMATION + "<Study"}) - @CommandLineFlags.Add({BASE_PARAMS + "/thumbnail_aspect_ratio/0.75"}) - public void testRecycling_aspectRatioPoint75() { + @CommandLineFlags.Add({BASE_PARAMS}) + public void testRecycling() { // clang-format on prepareTabs(10, 0, mUrl); ChromeTabUtils.switchTabInCurrentTabModel(mActivityTestRule.getActivity(), 0); @@ -1385,8 +1379,8 @@ @MediumTest // clang-format off @EnableFeatures({ChromeFeatureList.TAB_TO_GTS_ANIMATION + "<Study"}) - @CommandLineFlags.Add({BASE_PARAMS + "/thumbnail_aspect_ratio/0.75"}) - public void testExpandTab_withAspectRatioPoint75() { + @CommandLineFlags.Add({BASE_PARAMS}) + public void testExpandTab() { // clang-format on prepareTabs(1, 0, mUrl); enterTabSwitcher(mActivityTestRule.getActivity()); @@ -1399,9 +1393,9 @@ // clang-format off @EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID, ChromeFeatureList.TAB_TO_GTS_ANIMATION + "<Study"}) - @CommandLineFlags.Add({BASE_PARAMS + "/thumbnail_aspect_ratio/1.0"}) + @CommandLineFlags.Add({BASE_PARAMS}) @DisabledTest(message = "https://crbug.com/1205952") - public void testRenderGrid_withAspectRatioOfOne() throws IOException { + public void testRenderGrid() throws IOException { // clang-format on ChromeTabbedActivity cta = mActivityTestRule.getActivity(); prepareTabs(3, 0, "about:blank"); @@ -1411,7 +1405,7 @@ createTabGroup(cta, false, tabGroup); // Make sure all tabs have thumbnail. enterGTSWithThumbnailRetry(); - mRenderTestRule.render(cta.findViewById(R.id.tab_list_view), "aspect_ratio_of_one"); + mRenderTestRule.render(cta.findViewById(R.id.tab_list_view), "test_render_grid_id"); } @Test @@ -1897,8 +1891,7 @@ ChromeFeatureList.CLOSE_TAB_SUGGESTIONS + "<Study"}) @DisableFeatures(ChromeFeatureList.TAB_TO_GTS_ANIMATION) @CommandLineFlags.Add({BASE_PARAMS + "/baseline_tab_suggestions/true" + - "/baseline_close_tab_suggestions/true/min_time_between_prefetches/0" + - "/thumbnail_aspect_ratio/1.0"}) + "/baseline_close_tab_suggestions/true/min_time_between_prefetches/0"}) public void testTabGroupManualSelection_AfterReviewTabSuggestion() throws InterruptedException { // clang-format on ChromeTabbedActivity cta = mActivityTestRule.getActivity();
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java index 99b3136..f247ca42 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java
@@ -18,10 +18,10 @@ import org.chromium.base.ApiCompatibilityUtils; import org.chromium.base.Callback; -import org.chromium.base.MathUtils; import org.chromium.base.task.PostTask; import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager; import org.chromium.chrome.browser.profiles.Profile; +import org.chromium.chrome.browser.tab.TabUtils; import org.chromium.chrome.browser.tabmodel.TabModel; import org.chromium.chrome.browser.tabmodel.TabModelSelector; import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver; @@ -216,9 +216,7 @@ TabModelSelector tabModelSelector) { mContext = context; Resources resource = context.getResources(); - float expectedThumbnailAspectRatio = - (float) TabUiFeatureUtilities.THUMBNAIL_ASPECT_RATIO.getValue(); - expectedThumbnailAspectRatio = MathUtils.clamp(expectedThumbnailAspectRatio, 0.5f, 2.0f); + float expectedThumbnailAspectRatio = TabUtils.getTabThumbnailAspectRatio(context); mThumbnailWidth = (int) resource.getDimension(R.dimen.tab_grid_thumbnail_card_default_size); mThumbnailHeight = (int) (mThumbnailWidth / expectedThumbnailAspectRatio);
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/NewTabTileMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/NewTabTileMediator.java index 0faa39b..2ee27f9a 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/NewTabTileMediator.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/NewTabTileMediator.java
@@ -6,9 +6,8 @@ import static org.chromium.chrome.browser.tasks.tab_management.NewTabTileViewProperties.IS_INCOGNITO; -import org.chromium.base.MathUtils; import org.chromium.base.metrics.RecordUserAction; -import org.chromium.chrome.browser.flags.ChromeFeatureList; +import org.chromium.chrome.browser.tab.TabUtils; import org.chromium.chrome.browser.tabmodel.TabCreatorManager; import org.chromium.chrome.browser.tabmodel.TabModel; import org.chromium.chrome.browser.tabmodel.TabModelSelector; @@ -28,10 +27,7 @@ mTabModelSelector = tabModelSelector; // Deliberately use un-cached value to match with native. - float aspectRatio = (float) ChromeFeatureList.getFieldTrialParamByFeatureAsDouble( - ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID, - TabUiFeatureUtilities.THUMBNAIL_ASPECT_RATIO_PARAM, 1.0); - aspectRatio = MathUtils.clamp(aspectRatio, 0.5f, 2.0f); + float aspectRatio = TabUtils.getTabThumbnailAspectRatio(null); model.set(NewTabTileViewProperties.THUMBNAIL_ASPECT_RATIO, aspectRatio); model.set(NewTabTileViewProperties.CARD_HEIGHT_INTERCEPT, 0); model.set(NewTabTileViewProperties.ON_CLICK_LISTENER, view -> {
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridThumbnailView.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridThumbnailView.java index dfda4ef..8effabf5 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridThumbnailView.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridThumbnailView.java
@@ -8,7 +8,7 @@ import android.graphics.drawable.ColorDrawable; import android.util.AttributeSet; -import org.chromium.base.MathUtils; +import org.chromium.chrome.browser.tab.TabUtils; import org.chromium.chrome.browser.tasks.ReturnToChromeExperimentsUtil; import org.chromium.components.browser_ui.widget.RoundedCornerImageView; @@ -23,8 +23,7 @@ public TabGridThumbnailView(Context context, AttributeSet attrs) { super(context, attrs); - mAspectRatio = MathUtils.clamp( - (float) TabUiFeatureUtilities.THUMBNAIL_ASPECT_RATIO.getValue(), 0.5f, 2.0f); + mAspectRatio = TabUtils.getTabThumbnailAspectRatio(context); } @Override @@ -74,15 +73,8 @@ return; } - if (TabUiFeatureUtilities.isTabThumbnailAspectRatioNotOne()) { - float expectedThumbnailAspectRatio = - (float) TabUiFeatureUtilities.THUMBNAIL_ASPECT_RATIO.getValue(); - expectedThumbnailAspectRatio = - MathUtils.clamp(expectedThumbnailAspectRatio, 0.5f, 2.0f); - int height = (int) (getWidth() * 1.0 / expectedThumbnailAspectRatio); - setMinimumHeight(Math.min(getHeight(), height)); - } else { - setMinimumHeight(getWidth()); - } + float expectedThumbnailAspectRatio = TabUtils.getTabThumbnailAspectRatio(getContext()); + int height = (int) (getWidth() * 1.0 / expectedThumbnailAspectRatio); + setMinimumHeight(Math.min(getHeight(), height)); } } \ No newline at end of file
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java index 99d59e9..b93c313 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
@@ -23,10 +23,10 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import org.chromium.base.MathUtils; import org.chromium.chrome.browser.lifecycle.DestroyObserver; import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.tab.Tab; +import org.chromium.chrome.browser.tab.TabUtils; import org.chromium.chrome.browser.tabmodel.TabModelSelector; import org.chromium.chrome.browser.tasks.ReturnToChromeExperimentsUtil; import org.chromium.chrome.browser.tasks.pseudotab.PseudoTab; @@ -165,18 +165,10 @@ return; } - if (TabUiFeatureUtilities.isTabThumbnailAspectRatioNotOne()) { - float expectedThumbnailAspectRatio = - (float) TabUiFeatureUtilities.THUMBNAIL_ASPECT_RATIO.getValue(); - expectedThumbnailAspectRatio = - MathUtils.clamp(expectedThumbnailAspectRatio, 0.5f, 2.0f); - int height = (int) (thumbnail.getWidth() * 1.0 / expectedThumbnailAspectRatio); - thumbnail.setMinimumHeight(Math.min(thumbnail.getHeight(), height)); - thumbnail.setImageDrawable(null); - } else { - thumbnail.setImageDrawable(null); - thumbnail.setMinimumHeight(thumbnail.getWidth()); - } + float expectedThumbnailAspectRatio = TabUtils.getTabThumbnailAspectRatio(context); + int height = (int) (thumbnail.getWidth() * 1.0 / expectedThumbnailAspectRatio); + thumbnail.setMinimumHeight(Math.min(thumbnail.getHeight(), height)); + thumbnail.setImageDrawable(null); }; } else if (mMode == TabListMode.STRIP) { mAdapter.registerType(UiType.STRIP, parent -> {
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java index c0ab9c1..45b742d7 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java
@@ -16,7 +16,6 @@ import org.chromium.chrome.browser.flags.BooleanCachedFieldTrialParameter; import org.chromium.chrome.browser.flags.CachedFeatureFlags; import org.chromium.chrome.browser.flags.ChromeFeatureList; -import org.chromium.chrome.browser.flags.DoubleCachedFieldTrialParameter; import org.chromium.chrome.browser.flags.IntCachedFieldTrialParameter; import org.chromium.chrome.browser.flags.StringCachedFieldTrialParameter; import org.chromium.chrome.browser.tasks.ConditionalTabStripUtils; @@ -41,11 +40,6 @@ new StringCachedFieldTrialParameter(ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID, TAB_GRID_LAYOUT_ANDROID_NEW_TAB_TILE_PARAM, ""); - public static final String THUMBNAIL_ASPECT_RATIO_PARAM = "thumbnail_aspect_ratio"; - public static final DoubleCachedFieldTrialParameter THUMBNAIL_ASPECT_RATIO = - new DoubleCachedFieldTrialParameter( - ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID, THUMBNAIL_ASPECT_RATIO_PARAM, 0.85); - private static final String SEARCH_CHIP_PARAM = "enable_search_term_chip"; public static final BooleanCachedFieldTrialParameter ENABLE_SEARCH_CHIP = new BooleanCachedFieldTrialParameter( @@ -175,13 +169,6 @@ && !ConditionalTabStripUtils.getOptOutIndicator(); } - /** - * @return Whether the thumbnail_aspect_ratio field trail is set. - */ - public static boolean isTabThumbnailAspectRatioNotOne() { - return Double.compare(1.0, THUMBNAIL_ASPECT_RATIO.getValue()) != 0; - } - public static boolean isTabGridLayoutAndroidNewTabTileEnabled() { return TextUtils.equals(TAB_GRID_LAYOUT_ANDROID_NEW_TAB_TILE.getValue(), "NewTabTile"); }
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSuggestionMessageCardTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSuggestionMessageCardTest.java index 1a3a136..589a4eab 100644 --- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSuggestionMessageCardTest.java +++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSuggestionMessageCardTest.java
@@ -72,7 +72,7 @@ // clang-format on private static final String BASE_PARAMS = "force-fieldtrial-params=" + "Study.Group:baseline_tab_suggestions/true/enable_launch_polish/true" - + "/min_time_between_prefetches/0/thumbnail_aspect_ratio/1.0"; + + "/min_time_between_prefetches/0"; private static final String ENABLE_CLOSE_SUGGESTION_PARAM = "/baseline_close_tab_suggestions/true"; private static final String ENABLE_GROUP_SUGGESTION_PARAM =
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherThumbnailTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherThumbnailTest.java index 0d3be5ff..0cb8cca3 100644 --- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherThumbnailTest.java +++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherThumbnailTest.java
@@ -79,22 +79,6 @@ @Test @MediumTest - @CommandLineFlags.Add({BASE_PARAMS + "/thumbnail_aspect_ratio/1.0"}) - @FlakyTest(message = "https://crbug.com/1208059") - public void testThumbnailAspectRatio_one() { - int tabCounts = 11; - TabUiTestHelper.prepareTabsWithThumbnail(mActivityTestRule, tabCounts, 0, "about:blank"); - TabUiTestHelper.enterTabSwitcher(mActivityTestRule.getActivity()); - verifyAllThumbnailHeightWithAspectRatio(tabCounts, 1.f); - - // With hard cleanup. - TabUiTestHelper.leaveTabSwitcher(mActivityTestRule.getActivity()); - TabUiTestHelper.enterTabSwitcher(mActivityTestRule.getActivity()); - verifyAllThumbnailHeightWithAspectRatio(tabCounts, 1.f); - } - - @Test - @MediumTest @CommandLineFlags.Add({BASE_PARAMS}) @FlakyTest(message = "https://crbug.com/1208059") public void testThumbnailAspectRatio_point85() {
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tab/TabUtilsUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tab/TabUtilsUnitTest.java new file mode 100644 index 0000000..cd46e264 --- /dev/null +++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tab/TabUtilsUnitTest.java
@@ -0,0 +1,46 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +package org.chromium.chrome.browser.tab; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +import org.chromium.base.ContextUtils; +import org.chromium.base.test.BaseRobolectricTestRunner; + +/** + * Tests for {@link TabUtils}. + */ +@RunWith(BaseRobolectricTestRunner.class) +public final class TabUtilsUnitTest { + @Test + public void testGetTabThumbnailAspectRatio_withNullContext() { + assertThat(TabUtils.getTabThumbnailAspectRatio(null)) + .isEqualTo(TabUtils.TAB_THUMBNAIL_ASPECT_RATIO); + } + + @Test + @Config(qualifiers = "sw320dp") + public void testGetTabThumbnailAspectRatio_withNonTabletContext() { + assertThat(TabUtils.getTabThumbnailAspectRatio(ContextUtils.getApplicationContext())) + .isEqualTo(TabUtils.TAB_THUMBNAIL_ASPECT_RATIO); + } + + @Test + @Config(qualifiers = "sw800dp-port") + public void testGetTabThumbnailAspectRatio_withTabletPortraitContext() { + assertThat(TabUtils.getTabThumbnailAspectRatio(ContextUtils.getApplicationContext())) + .isEqualTo(TabUtils.TAB_THUMBNAIL_ASPECT_RATIO); + } + + @Test + @Config(qualifiers = "sw800dp-land") + public void testGetTabThumbnailAspectRatio_withTabletLandscapeContext() { + assertThat(TabUtils.getTabThumbnailAspectRatio(ContextUtils.getApplicationContext())) + .isEqualTo(TabUtils.TABLET_LANDSCAPE_TAB_THUMBNAIL_ASPECT_RATIO); + } +}
diff --git a/chrome/android/features/tab_ui/tab_management_java_sources.gni b/chrome/android/features/tab_ui/tab_management_java_sources.gni index b8742179..ddd44d8 100644 --- a/chrome/android/features/tab_ui/tab_management_java_sources.gni +++ b/chrome/android/features/tab_ui/tab_management_java_sources.gni
@@ -62,6 +62,7 @@ ] tab_management_junit_java_sources = [ + "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tab/TabUtilsUnitTest.java", "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/SingleTabSwitcherMediatorUnitTest.java", "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/TasksSurfaceMediatorUnitTest.java", "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/pseudotab/PseudoTabUnitTest.java",
diff --git a/chrome/android/java/res/drawable-hdpi/btn_tabstrip_new_tab_pressed.png b/chrome/android/java/res/drawable-hdpi/btn_tabstrip_new_tab_pressed.png deleted file mode 100644 index be137e1..0000000 --- a/chrome/android/java/res/drawable-hdpi/btn_tabstrip_new_tab_pressed.png +++ /dev/null Binary files differ
diff --git a/chrome/android/java/res/drawable-ldrtl-hdpi-v17/btn_tabstrip_new_tab_pressed.png b/chrome/android/java/res/drawable-ldrtl-hdpi-v17/btn_tabstrip_new_tab_pressed.png deleted file mode 100644 index 5ce5a7a..0000000 --- a/chrome/android/java/res/drawable-ldrtl-hdpi-v17/btn_tabstrip_new_tab_pressed.png +++ /dev/null Binary files differ
diff --git a/chrome/android/java/res/drawable-ldrtl-mdpi-v17/btn_tabstrip_new_tab_pressed.png b/chrome/android/java/res/drawable-ldrtl-mdpi-v17/btn_tabstrip_new_tab_pressed.png deleted file mode 100644 index 9aff980..0000000 --- a/chrome/android/java/res/drawable-ldrtl-mdpi-v17/btn_tabstrip_new_tab_pressed.png +++ /dev/null Binary files differ
diff --git a/chrome/android/java/res/drawable-ldrtl-xhdpi-v17/btn_tabstrip_new_tab_pressed.png b/chrome/android/java/res/drawable-ldrtl-xhdpi-v17/btn_tabstrip_new_tab_pressed.png deleted file mode 100644 index 50e0c58..0000000 --- a/chrome/android/java/res/drawable-ldrtl-xhdpi-v17/btn_tabstrip_new_tab_pressed.png +++ /dev/null Binary files differ
diff --git a/chrome/android/java/res/drawable-ldrtl-xxhdpi-v17/btn_tabstrip_new_tab_pressed.png b/chrome/android/java/res/drawable-ldrtl-xxhdpi-v17/btn_tabstrip_new_tab_pressed.png deleted file mode 100644 index 8c8ba5e..0000000 --- a/chrome/android/java/res/drawable-ldrtl-xxhdpi-v17/btn_tabstrip_new_tab_pressed.png +++ /dev/null Binary files differ
diff --git a/chrome/android/java/res/drawable-ldrtl-xxxhdpi-v17/btn_tabstrip_new_tab_pressed.png b/chrome/android/java/res/drawable-ldrtl-xxxhdpi-v17/btn_tabstrip_new_tab_pressed.png deleted file mode 100644 index f576256..0000000 --- a/chrome/android/java/res/drawable-ldrtl-xxxhdpi-v17/btn_tabstrip_new_tab_pressed.png +++ /dev/null Binary files differ
diff --git a/chrome/android/java/res/drawable-mdpi/btn_tabstrip_new_tab_pressed.png b/chrome/android/java/res/drawable-mdpi/btn_tabstrip_new_tab_pressed.png deleted file mode 100644 index e0f9512..0000000 --- a/chrome/android/java/res/drawable-mdpi/btn_tabstrip_new_tab_pressed.png +++ /dev/null Binary files differ
diff --git a/chrome/android/java/res/drawable-xhdpi/btn_tabstrip_new_tab_pressed.png b/chrome/android/java/res/drawable-xhdpi/btn_tabstrip_new_tab_pressed.png deleted file mode 100644 index 29dfbefc..0000000 --- a/chrome/android/java/res/drawable-xhdpi/btn_tabstrip_new_tab_pressed.png +++ /dev/null Binary files differ
diff --git a/chrome/android/java/res/drawable-xxhdpi/btn_tabstrip_new_tab_pressed.png b/chrome/android/java/res/drawable-xxhdpi/btn_tabstrip_new_tab_pressed.png deleted file mode 100644 index adf7d28..0000000 --- a/chrome/android/java/res/drawable-xxhdpi/btn_tabstrip_new_tab_pressed.png +++ /dev/null Binary files differ
diff --git a/chrome/android/java/res/drawable-xxxhdpi/btn_tabstrip_new_tab_pressed.png b/chrome/android/java/res/drawable-xxxhdpi/btn_tabstrip_new_tab_pressed.png deleted file mode 100644 index c2e2705..0000000 --- a/chrome/android/java/res/drawable-xxxhdpi/btn_tabstrip_new_tab_pressed.png +++ /dev/null Binary files differ
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java b/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java index 4d47c02b..3da11cf 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
@@ -174,7 +174,6 @@ add(TabUiFeatureUtilities.ZOOMING_MIN_SDK); add(TabUiFeatureUtilities.SKIP_SLOW_ZOOMING); add(TabUiFeatureUtilities.TAB_GRID_LAYOUT_ANDROID_NEW_TAB_TILE); - add(TabUiFeatureUtilities.THUMBNAIL_ASPECT_RATIO); add(TabUiFeatureUtilities.GRID_TAB_SWITCHER_FOR_TABLETS_POLISH); add(ThemeUtils.ENABLE_FULL_DYNAMIC_COLORS); }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillPaymentMethodsDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillPaymentMethodsDelegate.java index f98038e9..d9075896 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillPaymentMethodsDelegate.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillPaymentMethodsDelegate.java
@@ -4,6 +4,7 @@ package org.chromium.chrome.browser.autofill.settings; +import org.chromium.base.Callback; import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.NativeMethods; import org.chromium.chrome.browser.profiles.Profile; @@ -32,16 +33,35 @@ * process. * @param instrumentId The instrument ID of the {@link * org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard} to enroll. + * @param virtualCardEnrollmentFieldsLoadedCallback The callback to be triggered when the {@link + * VirtualCardEnrollmentFields} are fetched from the server. * @throws IllegalStateException when called after the native delegate has been cleaned up, or * if an error occurred during initialization. */ - public void offerVirtualCardEnrollment(long instrumentId) { + public void offerVirtualCardEnrollment(long instrumentId, + Callback<VirtualCardEnrollmentFields> virtualCardEnrollmentFieldsLoadedCallback) { if (mNativeAutofillPaymentMethodsDelegate == 0) { throw new IllegalStateException( "The native delegate was cleaned up or not initialized."); } AutofillPaymentMethodsDelegateJni.get().offerVirtualCardEnrollment( - mNativeAutofillPaymentMethodsDelegate, instrumentId); + mNativeAutofillPaymentMethodsDelegate, instrumentId, + virtualCardEnrollmentFieldsLoadedCallback); + } + + /** + * Enroll the card into virtual cards feature. + * Note: This should only be called after the OfferVirtualCardEnrollment is triggered. This is + * because the VirtualCardEnrollmentManager stores some state when offerVirtualCardEnrollment is + * called, which is then reused for enrolling into the virtual cards feature. + */ + public void enrollOfferedVirtualCard() { + if (mNativeAutofillPaymentMethodsDelegate == 0) { + throw new IllegalStateException( + "The native delegate was cleaned up or not initialized."); + } + AutofillPaymentMethodsDelegateJni.get().enrollOfferedVirtualCard( + mNativeAutofillPaymentMethodsDelegate); } /** @@ -76,8 +96,10 @@ interface Natives { long init(Profile profile); void cleanup(long nativeAutofillPaymentMethodsDelegate); - void offerVirtualCardEnrollment( - long nativeAutofillPaymentMethodsDelegate, long instrumentId); + void offerVirtualCardEnrollment(long nativeAutofillPaymentMethodsDelegate, + long instrumentId, + Callback<VirtualCardEnrollmentFields> virtualCardEnrollmentFieldsCallback); + void enrollOfferedVirtualCard(long nativeAutofillPaymentMethodsDelegate); void unenrollVirtualCard(long nativeAutofillPaymentMethodsDelegate, long instrumentId); } }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillServerCardEditor.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillServerCardEditor.java index 4002ace2..721ea46 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillServerCardEditor.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillServerCardEditor.java
@@ -79,15 +79,14 @@ assert mDelegate != null : "mDelegate must be initialized before making (un)enrolment calls."; + final ModalDialogManager modalDialogManager = new ModalDialogManager( + new AppModalPresenter(getActivity()), ModalDialogType.APP); if (!mVirtualCardEnrollmentButtonShowsUnenroll) { - // TODO (crbug/1281695): Implement enroll dialog. - mDelegate.offerVirtualCardEnrollment(mCard.getInstrumentId()); - - // Change button label and behavior to Unenroll. - setVirtualCardEnrollmentButtonLabel(true); + mDelegate.offerVirtualCardEnrollment(mCard.getInstrumentId(), + result -> showVirtualCardEnrollmentDialog(result, modalDialogManager)); + // Disable the button until we receive a response from the server. + mVirtualCardEnrollmentButton.setEnabled(false); } else { - final ModalDialogManager modalDialogManager = new ModalDialogManager( - new AppModalPresenter(getActivity()), ModalDialogType.APP); AutofillVirtualCardUnenrollmentDialog dialog = new AutofillVirtualCardUnenrollmentDialog( getActivity(), modalDialogManager, unenrollRequested -> { @@ -134,6 +133,26 @@ } } + private void showVirtualCardEnrollmentDialog( + VirtualCardEnrollmentFields virtualCardEnrollmentFields, + ModalDialogManager modalDialogManager) { + AutofillVirtualCardEnrollmentDialog dialog = + new AutofillVirtualCardEnrollmentDialog(getActivity(), modalDialogManager, + virtualCardEnrollmentFields, (positiveButtonClicked) -> { + if (positiveButtonClicked) { + // Silently enroll the virtual card. + mDelegate.enrollOfferedVirtualCard(); + // Update the button label to allow un-enroll. + setVirtualCardEnrollmentButtonLabel(true); + } else { + // Since the user canceled the enrollment dialog, enable the button + // again to allow for enrollment. + mVirtualCardEnrollmentButton.setEnabled(true); + } + }); + dialog.show(); + } + private void removeLocalCopyViews() { ViewGroup parent = (ViewGroup) mClearLocalCopy.getParent(); if (parent == null) return; @@ -155,6 +174,7 @@ */ private void setVirtualCardEnrollmentButtonLabel(boolean isEnrolled) { mVirtualCardEnrollmentButtonShowsUnenroll = isEnrolled; + mVirtualCardEnrollmentButton.setEnabled(true); mVirtualCardEnrollmentButton.setText( mVirtualCardEnrollmentButtonShowsUnenroll ? R.string.remove : R.string.add); }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillVirtualCardEnrollmentDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillVirtualCardEnrollmentDialog.java index 058a4e04..90bca74d 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillVirtualCardEnrollmentDialog.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillVirtualCardEnrollmentDialog.java
@@ -5,7 +5,6 @@ package org.chromium.chrome.browser.autofill.settings; import android.content.Context; -import android.graphics.Bitmap; import android.text.method.LinkMovementMethod; import android.view.LayoutInflater; import android.view.View; @@ -16,37 +15,26 @@ import org.chromium.chrome.R; import org.chromium.chrome.browser.ChromeStringConstants; import org.chromium.chrome.browser.autofill.AutofillUiUtils; -import org.chromium.chrome.browser.autofill.LegalMessageLine; import org.chromium.ui.modaldialog.DialogDismissalCause; import org.chromium.ui.modaldialog.ModalDialogManager; import org.chromium.ui.modaldialog.ModalDialogProperties; import org.chromium.ui.modaldialog.SimpleModalDialogController; import org.chromium.ui.modelutil.PropertyModel; -import java.util.LinkedList; - /** Dialog shown to the user to enroll a credit card into the virtual card feature. */ public class AutofillVirtualCardEnrollmentDialog { private final Context mContext; private final ModalDialogManager mModalDialogManager; - private final Bitmap mIssuerIcon; - private final LinkedList<LegalMessageLine> mIssuerLegalMessageLines; - private final LinkedList<LegalMessageLine> mGoogleLegalMessageLines; - private final String mCardLabel; - private final String mTitle; + private final VirtualCardEnrollmentFields mVirtualCardEnrollmentFields; private final Callback<Boolean> mResultHandler; public AutofillVirtualCardEnrollmentDialog(Context context, - ModalDialogManager modalDialogManager, String title, Bitmap issuerIcon, - String cardLabel, LinkedList<LegalMessageLine> issuerLegalMessageLines, - LinkedList<LegalMessageLine> googleLegalMessageLines, Callback<Boolean> resultHandler) { + ModalDialogManager modalDialogManager, + VirtualCardEnrollmentFields virtualCardEnrollmentFields, + Callback<Boolean> resultHandler) { mContext = context; mModalDialogManager = modalDialogManager; - mIssuerIcon = issuerIcon; - mCardLabel = cardLabel; - mTitle = title; - mIssuerLegalMessageLines = issuerLegalMessageLines; - mGoogleLegalMessageLines = googleLegalMessageLines; + mVirtualCardEnrollmentFields = virtualCardEnrollmentFields; mResultHandler = resultHandler; } @@ -74,8 +62,9 @@ R.layout.virtual_card_enrollment_dialog, null); TextView titleTextView = (TextView) customView.findViewById(R.id.dialog_title); - AutofillUiUtils.inlineTitleStringWithLogo( - mContext, titleTextView, mTitle, R.drawable.google_pay_with_divider); + AutofillUiUtils.inlineTitleStringWithLogo(mContext, titleTextView, + mContext.getString(R.string.autofill_virtual_card_enrollment_dialog_title_label), + R.drawable.google_pay_with_divider); TextView virtualCardEducationTextView = (TextView) customView.findViewById(R.id.virtual_card_education); @@ -88,18 +77,19 @@ TextView googleLegalMessageTextView = (TextView) customView.findViewById(R.id.google_legal_message); googleLegalMessageTextView.setText(AutofillUiUtils.getSpannableStringForLegalMessageLines( - mContext, mGoogleLegalMessageLines)); + mContext, mVirtualCardEnrollmentFields.getGoogleLegalMessages())); googleLegalMessageTextView.setMovementMethod(LinkMovementMethod.getInstance()); TextView issuerLegalMessageTextView = (TextView) customView.findViewById(R.id.issuer_legal_message); issuerLegalMessageTextView.setText(AutofillUiUtils.getSpannableStringForLegalMessageLines( - mContext, mIssuerLegalMessageLines)); + mContext, mVirtualCardEnrollmentFields.getIssuerLegalMessages())); issuerLegalMessageTextView.setMovementMethod(LinkMovementMethod.getInstance()); - ((TextView) customView.findViewById(R.id.credit_card_identifier)).setText(mCardLabel); + ((TextView) customView.findViewById(R.id.credit_card_identifier)) + .setText(mVirtualCardEnrollmentFields.getCardIdentifierString()); ((ImageView) customView.findViewById(R.id.credit_card_issuer_icon)) - .setImageBitmap(mIssuerIcon); + .setImageBitmap(mVirtualCardEnrollmentFields.getIssuerCardArt()); return customView; } }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/VirtualCardEnrollmentFields.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/VirtualCardEnrollmentFields.java new file mode 100644 index 0000000..e2e4e65 --- /dev/null +++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/VirtualCardEnrollmentFields.java
@@ -0,0 +1,106 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.autofill.settings; + +import android.graphics.Bitmap; + +import androidx.annotation.VisibleForTesting; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.chrome.browser.autofill.LegalMessageLine; + +import java.util.LinkedList; + +/** + * Class to represent the fields required to show the {@link AutofillVirtualCardEnrollmentDialog} + */ +@JNINamespace("autofill") +public class VirtualCardEnrollmentFields { + @VisibleForTesting + final LinkedList<LegalMessageLine> mGoogleLegalMessages = new LinkedList<>(); + @VisibleForTesting + final LinkedList<LegalMessageLine> mIssuerLegalMessages = new LinkedList<>(); + private final Bitmap mIssuerCardArt; + private final String mCardIdentifierString; + + public VirtualCardEnrollmentFields(String cardIdentifierString, Bitmap issuerCardArt) { + mCardIdentifierString = cardIdentifierString; + mIssuerCardArt = issuerCardArt; + } + + public Bitmap getIssuerCardArt() { + return mIssuerCardArt; + } + + public String getCardIdentifierString() { + return mCardIdentifierString; + } + + public LinkedList<LegalMessageLine> getGoogleLegalMessages() { + return mGoogleLegalMessages; + } + + public LinkedList<LegalMessageLine> getIssuerLegalMessages() { + return mIssuerLegalMessages; + } + + /** + * Returns an instance of {@link VirtualCardEnrollmentFields}. + * + * @param cardIdentifierString The text to be displayed in the enrollment dialog to help with + * identifying the card. + * @param issuerCardArt The image associated with the card being enrolled. + */ + @CalledByNative + @VisibleForTesting + static VirtualCardEnrollmentFields create(String cardIdentifierString, Bitmap issuerCardArt) { + return new VirtualCardEnrollmentFields(cardIdentifierString, issuerCardArt); + } + + /** + * Adds a line of Google legal message plain text. + * + * @param text The legal message plain text. + */ + @CalledByNative + private void addGoogleLegalMessageLine(String text) { + mGoogleLegalMessages.add(new LegalMessageLine(text)); + } + + /** + * Marks up the last added line of the Google legal message text with a link. + * + * @param start The inclusive offset of the start of the link in the text. + * @param end The exclusive offset of the end of the link in the text. + * @param url The URL to open when the link is clicked. + */ + @CalledByNative + private void addLinkToLastGoogleLegalMessageLine(int start, int end, String url) { + mGoogleLegalMessages.getLast().links.add(new LegalMessageLine.Link(start, end, url)); + } + + /** + * Adds a line of issuer legal message plain text. + * + * @param text The legal message plain text. + */ + @CalledByNative + private void addIssuerLegalMessageLine(String text) { + mIssuerLegalMessages.add(new LegalMessageLine(text)); + } + + /** + * Marks up the last added line of the issuer legal message text with a link. + * + * @param start The inclusive offset of the start of the link in the text. + * @param end The exclusive offset of the end of the link in the text. + * @param url The URL to open when the link is clicked. + */ + @CalledByNative + private void addLinkToLastIssuerLegalMessageLine(int start, int end, String url) { + mIssuerLegalMessages.getLast().links.add(new LegalMessageLine.Link(start, end, url)); + } +}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerImpl.java index 0ecfc9a8..8073795 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerImpl.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerImpl.java
@@ -1003,7 +1003,7 @@ getActiveLayout().show(time(), animate); mHost.setContentOverlayVisibility(getActiveLayout().shouldDisplayContentOverlay(), getActiveLayout().canHostBeFocusable()); - mHost.requestRender(); + requestUpdate(); // TODO(crbug.com/1108496): Remove after migrates to LayoutStateObserver#onStartedShowing. // Notify observers about the new scene.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManager.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManager.java index 9d3dd97d..99dc955 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManager.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManager.java
@@ -21,7 +21,6 @@ import org.chromium.base.Callback; import org.chromium.base.CommandLine; -import org.chromium.base.MathUtils; import org.chromium.base.PathUtils; import org.chromium.base.ThreadUtils; import org.chromium.base.TraceEvent; @@ -35,6 +34,7 @@ import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.browser.tab.Tab; +import org.chromium.chrome.browser.tab.TabUtils; import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities; import org.chromium.chrome.browser.ui.native_page.FrozenNativePage; import org.chromium.chrome.browser.ui.native_page.NativePage; @@ -184,12 +184,8 @@ mPriorityTabIds = new int[mFullResThumbnailsMaxSize]; - if (TabUiFeatureUtilities.isTabThumbnailAspectRatioNotOne() - || ALLOW_TO_REFETCH_TAB_THUMBNAIL_VARIATION.getValue()) { - mExpectedThumbnailAspectRatio = - (float) TabUiFeatureUtilities.THUMBNAIL_ASPECT_RATIO.getValue(); - mExpectedThumbnailAspectRatio = - MathUtils.clamp(mExpectedThumbnailAspectRatio, 0.5f, 2.0f); + if (ALLOW_TO_REFETCH_TAB_THUMBNAIL_VARIATION.getValue()) { + mExpectedThumbnailAspectRatio = TabUtils.getTabThumbnailAspectRatio(context); } } @@ -214,7 +210,8 @@ mNativeTabContentManager = TabContentManagerJni.get().init(TabContentManager.this, mFullResThumbnailsMaxSize, approximationCacheSize, compressionQueueMaxSize, - writeQueueMaxSize, useApproximationThumbnails, saveJpegThumbnails); + writeQueueMaxSize, useApproximationThumbnails, saveJpegThumbnails, + mExpectedThumbnailAspectRatio); } /** @@ -475,7 +472,8 @@ mRefectchedTabIds.add(tabId); TabContentManagerJni.get().getEtc1TabThumbnail(mNativeTabContentManager, - TabContentManager.this, tabId, callback); + TabContentManager.this, tabId, mExpectedThumbnailAspectRatio, + callback); return; } } @@ -485,8 +483,8 @@ return; } if (mNativeTabContentManager == 0 || !mSnapshotsEnabled) return; - TabContentManagerJni.get().getEtc1TabThumbnail( - mNativeTabContentManager, TabContentManager.this, tabId, (etc1) -> { + TabContentManagerJni.get().getEtc1TabThumbnail(mNativeTabContentManager, + TabContentManager.this, tabId, mExpectedThumbnailAspectRatio, (etc1) -> { if (etc1 != null) { recordThumbnailFetchingResult(ThumbnailFetchingResult.GOT_ETC1); } else { @@ -519,7 +517,8 @@ Bitmap nativeBitmap = readbackNativeBitmap(tab, mThumbnailScale); if (nativeBitmap == null) return null; TabContentManagerJni.get().cacheTabWithBitmap(mNativeTabContentManager, - TabContentManager.this, tab, nativeBitmap, mThumbnailScale); + TabContentManager.this, tab, nativeBitmap, mThumbnailScale, + mExpectedThumbnailAspectRatio); return nativeBitmap; } @@ -555,10 +554,8 @@ Matrix matrix = new Matrix(); matrix.setScale(downsamplingScale, downsamplingScale); Bitmap resized = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), - TabUiFeatureUtilities.isTabThumbnailAspectRatioNotOne() - ? Math.min(bitmap.getHeight(), - (int) (bitmap.getWidth() * 1.0 / mExpectedThumbnailAspectRatio)) - : min(bitmap.getWidth(), bitmap.getHeight()), + Math.min(bitmap.getHeight(), + (int) (bitmap.getWidth() * 1.0 / mExpectedThumbnailAspectRatio)), matrix, true); callback.onResult(resized); } else { @@ -568,8 +565,8 @@ // This faster path is essential to Tab-to-Grid animation to be smooth. final float downsamplingScale = writeToCache ? 1 : 0.5f; TabContentManagerJni.get().captureThumbnail(mNativeTabContentManager, - TabContentManager.this, tab, mThumbnailScale * downsamplingScale, writeToCache, - callback); + TabContentManager.this, tab, mThumbnailScale * downsamplingScale, + mExpectedThumbnailAspectRatio, writeToCache, callback); } } @@ -653,23 +650,25 @@ // Class Object Methods long init(TabContentManager caller, int defaultCacheSize, int approximationCacheSize, int compressionQueueMaxSize, int writeQueueMaxSize, - boolean useApproximationThumbnail, boolean saveJpegThumbnails); + boolean useApproximationThumbnail, boolean saveJpegThumbnails, + float jpegAspectRatio); void attachTab(long nativeTabContentManager, TabContentManager caller, Tab tab, int tabId); void detachTab(long nativeTabContentManager, TabContentManager caller, Tab tab, int tabId); boolean hasFullCachedThumbnail( long nativeTabContentManager, TabContentManager caller, int tabId); void captureThumbnail(long nativeTabContentManager, TabContentManager caller, Object tab, - float thumbnailScale, boolean writeToCache, Callback<Bitmap> callback); + float thumbnailScale, float aspectRatio, boolean writeToCache, + Callback<Bitmap> callback); void cacheTabWithBitmap(long nativeTabContentManager, TabContentManager caller, Object tab, - Object bitmap, float thumbnailScale); + Object bitmap, float thumbnailScale, float aspectRatio); void invalidateIfChanged( long nativeTabContentManager, TabContentManager caller, int tabId, GURL url); void updateVisibleIds(long nativeTabContentManager, TabContentManager caller, int[] priority, int primaryTabId); void removeTabThumbnail(long nativeTabContentManager, TabContentManager caller, int tabId); void getEtc1TabThumbnail(long nativeTabContentManager, TabContentManager caller, int tabId, - Callback<Bitmap> callback); + float aspectRatio, Callback<Bitmap> callback); void setCaptureMinRequestTimeForTesting( long nativeTabContentManager, TabContentManager caller, int timeMs); int getPendingReadbacksForTesting(long nativeTabContentManager, TabContentManager caller);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java index c077ef8a..0814829a 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
@@ -33,6 +33,7 @@ import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost; import org.chromium.chrome.browser.compositor.layouts.components.CompositorButton; import org.chromium.chrome.browser.compositor.layouts.components.CompositorButton.CompositorOnClickHandler; +import org.chromium.chrome.browser.compositor.layouts.components.TintedCompositorButton; import org.chromium.chrome.browser.compositor.layouts.phone.stack.StackScroller; import org.chromium.chrome.browser.compositor.overlays.strip.TabLoadTracker.TabLoadTrackerCallback; import org.chromium.chrome.browser.layouts.animation.CompositorAnimator; @@ -113,7 +114,7 @@ private final StripTabEventHandler mStripTabEventHandler = new StripTabEventHandler(); private final TabLoadTrackerCallback mTabLoadTrackerHost = new TabLoadTrackerCallbackImpl(); private Animator mRunningAnimator; - private final CompositorButton mNewTabButton; + private final TintedCompositorButton mNewTabButton; // Layout Constants private final float mTabOverlapWidth; @@ -182,11 +183,13 @@ handleNewTabClick(); } }; - mNewTabButton = new CompositorButton( - context, NEW_TAB_BUTTON_WIDTH_DP, NEW_TAB_BUTTON_HEIGHT_DP, newTabClickHandler); - mNewTabButton.setResources(R.drawable.btn_tabstrip_new_tab_normal, - R.drawable.btn_tabstrip_new_tab_pressed, R.drawable.btn_tabstrip_new_tab_normal, - R.drawable.btn_tabstrip_new_tab_pressed); + mNewTabButton = new TintedCompositorButton(context, NEW_TAB_BUTTON_WIDTH_DP, + NEW_TAB_BUTTON_HEIGHT_DP, newTabClickHandler, + R.drawable.btn_tabstrip_new_tab_normal); + + mNewTabButton.setTintResources(R.color.new_tab_button_tint, + R.color.new_tab_button_pressed_tint, R.color.modern_white, + R.color.default_icon_color_blue_light); mNewTabButton.setIncognito(incognito); mNewTabButton.setY(NEW_TAB_BUTTON_Y_OFFSET_DP); mNewTabButton.setClickSlop(NEW_TAB_BUTTON_CLICK_SLOP_DP); @@ -256,9 +259,10 @@ } /** - * @return A {@link CompositorButton} that represents the positioning of the new tab button. + * @return A {@link TintedCompositorButton} that represents the positioning of the new tab + * button. */ - public CompositorButton getNewTabButton() { + public TintedCompositorButton getNewTabButton() { return mNewTabButton; }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java index c1cd620..2f0d072 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java
@@ -22,6 +22,7 @@ import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost; import org.chromium.chrome.browser.compositor.layouts.components.CompositorButton; import org.chromium.chrome.browser.compositor.layouts.components.CompositorButton.CompositorOnClickHandler; +import org.chromium.chrome.browser.compositor.layouts.components.TintedCompositorButton; import org.chromium.chrome.browser.compositor.layouts.eventfilter.AreaGestureEventFilter; import org.chromium.chrome.browser.compositor.layouts.eventfilter.GestureHandler; import org.chromium.chrome.browser.compositor.scene_layer.TabStripSceneLayer; @@ -374,7 +375,7 @@ + MODEL_SELECTOR_BUTTON_START_PADDING_DP; } - public CompositorButton getNewTabButton() { + public TintedCompositorButton getNewTabButton() { return getActiveStripLayoutHelper().getNewTabButton(); }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/resources/StaticResourcePreloads.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/resources/StaticResourcePreloads.java index f29fec0..1d0b375 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/resources/StaticResourcePreloads.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/resources/StaticResourcePreloads.java
@@ -20,7 +20,6 @@ R.drawable.btn_tab_close_normal, R.drawable.btn_tabstrip_new_tab_normal, R.drawable.btn_tabstrip_new_incognito_tab_normal, - R.drawable.btn_tabstrip_new_tab_pressed, R.drawable.spinner, R.drawable.spinner_white, };
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabStripSceneLayer.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabStripSceneLayer.java index f1833ed..dcd6071e 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabStripSceneLayer.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabStripSceneLayer.java
@@ -12,6 +12,7 @@ import org.chromium.chrome.R; import org.chromium.chrome.browser.compositor.LayerTitleCache; import org.chromium.chrome.browser.compositor.layouts.components.CompositorButton; +import org.chromium.chrome.browser.compositor.layouts.components.TintedCompositorButton; import org.chromium.chrome.browser.compositor.overlays.strip.StripLayoutHelperManager; import org.chromium.chrome.browser.compositor.overlays.strip.StripLayoutTab; import org.chromium.chrome.browser.compositor.overlays.strip.StripScrim; @@ -115,7 +116,7 @@ updateStripScrim(layoutHelper.getStripScrim()); - CompositorButton newTabButton = layoutHelper.getNewTabButton(); + TintedCompositorButton newTabButton = layoutHelper.getNewTabButton(); CompositorButton modelSelectorButton = layoutHelper.getModelSelectorButton(); boolean newTabButtonVisible = newTabButton.isVisible(); boolean modelSelectorButtonVisible = modelSelectorButton.isVisible(); @@ -123,7 +124,8 @@ TabStripSceneLayerJni.get().updateNewTabButton(mNativePtr, TabStripSceneLayer.this, newTabButton.getResourceId(), newTabButton.getX() * mDpToPx, newTabButton.getY() * mDpToPx, newTabButton.getWidth() * mDpToPx, - newTabButton.getHeight() * mDpToPx, newTabButtonVisible, resourceManager); + newTabButton.getHeight() * mDpToPx, newTabButtonVisible, newTabButton.getTint(), + newTabButton.getOpacity(), resourceManager); TabStripSceneLayerJni.get().updateModelSelectorButton(mNativePtr, TabStripSceneLayer.this, modelSelectorButton.getResourceId(), modelSelectorButton.getX() * mDpToPx, @@ -182,7 +184,7 @@ float y, float width, float height, int color, float alpha); void updateNewTabButton(long nativeTabStripSceneLayer, TabStripSceneLayer caller, int resourceId, float x, float y, float width, float height, boolean visible, - ResourceManager resourceManager); + int tint, float buttonAlpha, ResourceManager resourceManager); void updateModelSelectorButton(long nativeTabStripSceneLayer, TabStripSceneLayer caller, int resourceId, float x, float y, float width, float height, boolean incognito, boolean visible, ResourceManager resourceManager);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/instantapps/InstantAppsMessageDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/instantapps/InstantAppsMessageDelegate.java index 6cd22bf..ecdf340e 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/instantapps/InstantAppsMessageDelegate.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/instantapps/InstantAppsMessageDelegate.java
@@ -7,6 +7,7 @@ import android.content.Context; import android.graphics.drawable.BitmapDrawable; +import androidx.annotation.VisibleForTesting; import androidx.appcompat.content.res.AppCompatResources; import org.chromium.base.annotations.CalledByNative; @@ -15,7 +16,6 @@ import org.chromium.components.messages.DismissReason; import org.chromium.components.messages.MessageBannerProperties; import org.chromium.components.messages.MessageDispatcher; -import org.chromium.components.messages.MessageDispatcherProvider; import org.chromium.components.messages.MessageIdentifier; import org.chromium.components.messages.MessageScopeType; import org.chromium.components.webapps.WebappsIconUtils; @@ -29,19 +29,20 @@ private final Context mContext; private final WebContents mWebContents; private final InstantAppsBannerData mData; - private MessageDispatcher mMessageDispatcher; + private final MessageDispatcher mMessageDispatcher; private PropertyModel mMessage; - public static InstantAppsMessageDelegate create( - Context context, WebContents webContents, InstantAppsBannerData data) { - return new InstantAppsMessageDelegate(context, webContents, data); + public static InstantAppsMessageDelegate create(Context context, WebContents webContents, + MessageDispatcher messageDispatcher, InstantAppsBannerData data) { + return new InstantAppsMessageDelegate(context, webContents, messageDispatcher, data); } - private InstantAppsMessageDelegate( - Context context, WebContents webContents, InstantAppsBannerData data) { + private InstantAppsMessageDelegate(Context context, WebContents webContents, + MessageDispatcher messageDispatcher, InstantAppsBannerData data) { mContext = context; mData = data; mWebContents = webContents; + mMessageDispatcher = messageDispatcher; InstantAppsMessageDelegateJni.get().initializeNativeDelegate( this, mWebContents, data.getUrl()); } @@ -64,7 +65,8 @@ R.string.accessibility_instant_apps_message_title_content_description), mData.getAppName())) .with(MessageBannerProperties.DESCRIPTION_ICON, - AppCompatResources.getDrawable(mContext, R.drawable.google_play)) + AppCompatResources.getDrawable( + mContext, R.drawable.google_play_dark)) .with(MessageBannerProperties.RESIZE_DESCRIPTION_ICON, true) .with(MessageBannerProperties.ICON, new BitmapDrawable(mData.getIcon())) .with(MessageBannerProperties.ICON_TINT_COLOR, @@ -78,7 +80,6 @@ .with(MessageBannerProperties.ON_DISMISSED, this::handleMessageDismissed) .build(); - mMessageDispatcher = MessageDispatcherProvider.from(mWebContents.getTopLevelNativeWindow()); if (mMessageDispatcher != null) { mMessageDispatcher.enqueueMessage( mMessage, mWebContents, MessageScopeType.WEB_CONTENTS, false); @@ -104,6 +105,11 @@ mWebContents, mData.getUrl(), mData.isInstantAppDefault()); } + @VisibleForTesting + PropertyModel getMessageForTesting() { + return mMessage; + } + @CalledByNative private void dismissMessage() { if (mMessageDispatcher != null) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabUtils.java index 5b5aaaec..e004255 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabUtils.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabUtils.java
@@ -6,12 +6,14 @@ import android.app.Activity; import android.content.Context; +import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.view.Display; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import org.chromium.base.ApplicationStatus; import org.chromium.chrome.R; @@ -34,6 +36,11 @@ public class TabUtils { private static final String REQUEST_DESKTOP_SCREEN_WIDTH_PARAM = "screen_width_dp"; + @VisibleForTesting + static final float TABLET_LANDSCAPE_TAB_THUMBNAIL_ASPECT_RATIO = 1.33f; + @VisibleForTesting + static final float TAB_THUMBNAIL_ASPECT_RATIO = 0.85f; + // Do not instantiate this class. private TabUtils() {} @@ -157,4 +164,19 @@ profile, ContentSettingsType.REQUEST_DESKTOP_SITE, url, url) == ContentSettingValues.ALLOW; } + + /** + * Return tab thumbnail aspect ratio for grid view. + * @param context - to retrieve info on device and layout. + * @return aspect ratio. + */ + public static float getTabThumbnailAspectRatio(Context context) { + if (context != null && DeviceFormFactor.isNonMultiDisplayContextOnTablet(context) + && context.getResources().getConfiguration().orientation + == Configuration.ORIENTATION_LANDSCAPE) { + return TABLET_LANDSCAPE_TAB_THUMBNAIL_ASPECT_RATIO; + } + + return TAB_THUMBNAIL_ASPECT_RATIO; + } }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/settings/AutofillServerCardEditorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/settings/AutofillServerCardEditorTest.java index d2c4958..431e044 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/settings/AutofillServerCardEditorTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/settings/AutofillServerCardEditorTest.java
@@ -8,37 +8,45 @@ import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.isEnabled; import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; import static org.mockito.Mockito.any; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Activity; +import android.graphics.Bitmap; import android.os.Bundle; import android.support.test.runner.lifecycle.Stage; import androidx.test.espresso.matcher.ViewMatchers.Visibility; import androidx.test.filters.MediumTest; +import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import org.chromium.base.Callback; import org.chromium.base.test.util.ApplicationTestUtils; import org.chromium.base.test.util.Batch; import org.chromium.base.test.util.JniMocker; import org.chromium.chrome.R; import org.chromium.chrome.browser.autofill.AutofillTestHelper; +import org.chromium.chrome.browser.autofill.LegalMessageLine; import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard; import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.profiles.Profile; @@ -47,6 +55,7 @@ import org.chromium.chrome.test.ChromeJUnit4ClassRunner; import org.chromium.chrome.test.util.browser.Features; import org.chromium.components.autofill.VirtualCardEnrollmentState; +import org.chromium.content_public.browser.test.util.TestThreadUtils; import java.util.concurrent.TimeoutException; @@ -150,7 +159,7 @@ onView(withId(R.id.virtual_card_ui)).check(matches(isDisplayed())); onView(withId(R.id.virtual_card_enrollment_button)).check(matches(withText(R.string.add))); - // Test that the native delegate is cleaned up when the editor is closed. + // Ensure that the native delegate is cleaned up when the test has finished. finishAndWaitForActivity(activity); } @@ -166,7 +175,7 @@ onView(withId(R.id.virtual_card_ui)) .check(matches(withEffectiveVisibility(Visibility.GONE))); - // Test that the native delegate is cleaned up when the editor is closed. + // Ensure that the native delegate is cleaned up when the test has finished. finishAndWaitForActivity(activity); } @@ -181,14 +190,15 @@ onView(withId(R.id.virtual_card_ui)) .check(matches(withEffectiveVisibility(Visibility.GONE))); - // Test that the native delegate is cleaned up when the editor is closed. + // Ensure that the native delegate is cleaned up when the test has finished. finishAndWaitForActivity(activity); } @Test @MediumTest @Features.EnableFeatures({ChromeFeatureList.AUTOFILL_ENABLE_UPDATE_VIRTUAL_CARD_ENROLLMENT}) - public void virtualCardUnenrolledAndEligible_virtualCardAddButtonClicked() throws Exception { + public void virtualCardUnenrolledAndEligible_virtualCardAddButtonClicked_enrollAccepted() + throws Exception { mAutofillTestHelper.addServerCreditCard(SAMPLE_VIRTUAL_CARD_UNENROLLED_AND_ELIGIBLE_CARD); SettingsActivity activity = mSettingsActivityTestRule.startSettingsActivity( @@ -202,14 +212,112 @@ // Press the Add button. onView(withId(R.id.virtual_card_enrollment_button)).perform(click()); - // Verify that the Virtual Card enrollment button now shows "Remove". - onView(withId(R.id.virtual_card_enrollment_button)) - .check(matches(withText(R.string.remove))); - // Verify that the native enroll method was called with the correct parameters. - verify(mNativeMock) - .offerVirtualCardEnrollment(NATIVE_AUTOFILL_PAYMENTS_METHODS_DELEGATE, 234); - // Test that the native delegate is cleaned up when the editor is closed. + // Verify that the Virtual Card enrollment button still shows "Add" and that the button is + // disabled. + onView(withId(R.id.virtual_card_enrollment_button)) + .check(matches(withText(R.string.add))) + .check(matches(Matchers.not(isEnabled()))); + + // Verify that the native enroll method was called with the correct parameters. + ArgumentCaptor<Callback<VirtualCardEnrollmentFields>> callbackArgumentCaptor = + ArgumentCaptor.forClass(Callback.class); + verify(mNativeMock) + .offerVirtualCardEnrollment( + ArgumentMatchers.eq(NATIVE_AUTOFILL_PAYMENTS_METHODS_DELEGATE), + ArgumentMatchers.eq(234L), callbackArgumentCaptor.capture()); + + // Return VirtualCardEnrollmentFields via the callback to show the dialog. + Callback<VirtualCardEnrollmentFields> virtualCardEnrollmentFieldsCallback = + callbackArgumentCaptor.getValue(); + VirtualCardEnrollmentFields fakeVirtualCardEnrollmentFields = + VirtualCardEnrollmentFields.create( + "Visa 1234", Bitmap.createBitmap(10, 20, Bitmap.Config.ALPHA_8)); + fakeVirtualCardEnrollmentFields.mGoogleLegalMessages.add(new LegalMessageLine("google")); + fakeVirtualCardEnrollmentFields.mIssuerLegalMessages.add(new LegalMessageLine("issuer")); + TestThreadUtils.runOnUiThreadBlocking( + () + -> virtualCardEnrollmentFieldsCallback.onResult( + fakeVirtualCardEnrollmentFields)); + + // Verify that the dialog was displayed. + onView(withId(R.id.dialog_title)).check(matches(isDisplayed())); + + // Click positive button on enrollment dialog. + onView(withId(R.id.positive_button)).perform(click()); + + // Verify that the Virtual Card enrollment button shows "Remove" and that the button is + // enabled. + onView(withId(R.id.virtual_card_enrollment_button)) + .check(matches(withText(R.string.remove))) + .check(matches(isEnabled())); + // Verify that enrollment is called when the user clicks the positive button on the dialog. + verify(mNativeMock).enrollOfferedVirtualCard(NATIVE_AUTOFILL_PAYMENTS_METHODS_DELEGATE); + // Ensure that the native delegate is cleaned up when the test has finished. + finishAndWaitForActivity(activity); + } + + @Test + @MediumTest + @Features.EnableFeatures({ChromeFeatureList.AUTOFILL_ENABLE_UPDATE_VIRTUAL_CARD_ENROLLMENT}) + public void virtualCardUnenrolledAndEligible_virtualCardAddButtonClicked_enrollRejected() + throws Exception { + mAutofillTestHelper.addServerCreditCard(SAMPLE_VIRTUAL_CARD_UNENROLLED_AND_ELIGIBLE_CARD); + + SettingsActivity activity = mSettingsActivityTestRule.startSettingsActivity( + fragmentArgs(SAMPLE_VIRTUAL_CARD_UNENROLLED_AND_ELIGIBLE_CARD.getGUID())); + // Verify that the native delegate was initialized properly. + verify(mNativeMock).init(any(Profile.class)); + + // Verify that the Virtual Card enrollment button is shown and shows "Add". + onView(withId(R.id.virtual_card_ui)).check(matches(isDisplayed())); + onView(withId(R.id.virtual_card_enrollment_button)).check(matches(withText(R.string.add))); + + // Press the Add button. + onView(withId(R.id.virtual_card_enrollment_button)).perform(click()); + + // Verify that the Virtual Card enrollment button still shows "Add" and that the button is + // disabled. + onView(withId(R.id.virtual_card_enrollment_button)) + .check(matches(withText(R.string.add))) + .check(matches(Matchers.not(isEnabled()))); + + // Verify that the native enroll method was called with the correct parameters. + ArgumentCaptor<Callback<VirtualCardEnrollmentFields>> callbackArgumentCaptor = + ArgumentCaptor.forClass(Callback.class); + verify(mNativeMock) + .offerVirtualCardEnrollment( + ArgumentMatchers.eq(NATIVE_AUTOFILL_PAYMENTS_METHODS_DELEGATE), + ArgumentMatchers.eq(234L), callbackArgumentCaptor.capture()); + + // Return VirtualCardEnrollmentFields via the callback to show the dialog. + Callback<VirtualCardEnrollmentFields> virtualCardEnrollmentFieldsCallback = + callbackArgumentCaptor.getValue(); + VirtualCardEnrollmentFields fakeVirtualCardEnrollmentFields = + VirtualCardEnrollmentFields.create( + "Visa 1234", Bitmap.createBitmap(10, 20, Bitmap.Config.ALPHA_8)); + fakeVirtualCardEnrollmentFields.mGoogleLegalMessages.add(new LegalMessageLine("google")); + fakeVirtualCardEnrollmentFields.mIssuerLegalMessages.add(new LegalMessageLine("issuer")); + TestThreadUtils.runOnUiThreadBlocking( + () + -> virtualCardEnrollmentFieldsCallback.onResult( + fakeVirtualCardEnrollmentFields)); + + // Verify that the dialog was displayed. + onView(withId(R.id.dialog_title)).check(matches(isDisplayed())); + + // Click negative button on enrollment dialog. + onView(withId(R.id.negative_button)).perform(click()); + + // Verify that the Virtual Card enrollment button still shows "Add" and that the button is + // now enabled. + onView(withId(R.id.virtual_card_enrollment_button)) + .check(matches(withText(R.string.add))) + .check(matches(isEnabled())); + // Verify that enrollment is called when the user clicks the positive button on the dialog. + verify(mNativeMock, times(0)) + .enrollOfferedVirtualCard(NATIVE_AUTOFILL_PAYMENTS_METHODS_DELEGATE); + // Ensure that the native delegate is cleaned up when the test has finished. finishAndWaitForActivity(activity); } @@ -232,7 +340,7 @@ // Verify that the unenroll dialog is shown. onView(withText(R.string.autofill_credit_card_editor_virtual_card_unenroll_dialog_title)) .check(matches(isDisplayed())); - // Test that the native delegate is cleaned up when the editor is closed. + // Ensure that the native delegate is cleaned up when the test has finished. finishAndWaitForActivity(activity); } @@ -262,7 +370,7 @@ // Verify that the button label has not changed from "Remove". onView(withId(R.id.virtual_card_enrollment_button)) .check(matches(withText(R.string.remove))); - // Test that the native delegate is cleaned up when the editor is closed. + // Ensure that the native delegate is cleaned up when the test has finished. finishAndWaitForActivity(activity); } @@ -298,8 +406,25 @@ // Verify that the native unenroll method was called with the correct parameters. verify(mNativeMock).unenrollVirtualCard(NATIVE_AUTOFILL_PAYMENTS_METHODS_DELEGATE, 123); - // Test that the native delegate is cleaned up when the editor is closed. + // Ensure that the native delegate is cleaned up when the test has finished. finishAndWaitForActivity(activity); + } + + @Test + @MediumTest + @Features.EnableFeatures({ChromeFeatureList.AUTOFILL_ENABLE_UPDATE_VIRTUAL_CARD_ENROLLMENT}) + public void testAutofillPaymentMethodsDelegateLifecycleEvents() throws Exception { + mAutofillTestHelper.addServerCreditCard(SAMPLE_VIRTUAL_CARD_ENROLLED_CARD); + + SettingsActivity activity = mSettingsActivityTestRule.startSettingsActivity( + fragmentArgs(SAMPLE_VIRTUAL_CARD_ENROLLED_CARD.getGUID())); + + // Verify that the native delegate was initialized properly. + verify(mNativeMock).init(any(Profile.class)); + + finishAndWaitForActivity(activity); + + // Ensure that the native delegate is cleaned up when the test has finished. verify(mNativeMock).cleanup(NATIVE_AUTOFILL_PAYMENTS_METHODS_DELEGATE); }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/autofill/settings/AutofillVirtualCardEnrollmentDialogTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/autofill/settings/AutofillVirtualCardEnrollmentDialogTest.java new file mode 100644 index 0000000..1d9ea9a --- /dev/null +++ b/chrome/android/junit/src/org/chromium/chrome/browser/autofill/settings/AutofillVirtualCardEnrollmentDialogTest.java
@@ -0,0 +1,220 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.autofill.settings; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Intent; +import android.graphics.Bitmap; +import android.text.SpannableString; +import android.view.View; +import android.widget.TextView; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.Robolectric; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowActivity; + +import org.chromium.base.Callback; +import org.chromium.base.test.BaseRobolectricTestRunner; +import org.chromium.chrome.R; +import org.chromium.chrome.browser.autofill.LegalMessageLine; +import org.chromium.chrome.browser.customtabs.CustomTabActivity; +import org.chromium.ui.modaldialog.ModalDialogProperties; +import org.chromium.ui.text.NoUnderlineClickableSpan; + +import java.util.ArrayList; +import java.util.List; + +/** Unit tests for {@link AutofillVirtualCardEnrollmentDialog}. */ +@RunWith(BaseRobolectricTestRunner.class) +public class AutofillVirtualCardEnrollmentDialogTest { + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Mock + private Callback<Boolean> mCallbackMock; + private FakeModalDialogManager mModalDialogManager; + private AutofillVirtualCardEnrollmentDialog mDialog; + private VirtualCardEnrollmentFields mVirtualCardEnrollmentFields; + + @Before + public void setUp() { + mModalDialogManager = new FakeModalDialogManager(); + mVirtualCardEnrollmentFields = VirtualCardEnrollmentFields.create( + "card label", Bitmap.createBitmap(100, 100, Bitmap.Config.ALPHA_8)); + mVirtualCardEnrollmentFields.mGoogleLegalMessages.add(createLegalMessageLine("google")); + mVirtualCardEnrollmentFields.mIssuerLegalMessages.add(createLegalMessageLine("issuer")); + mDialog = + new AutofillVirtualCardEnrollmentDialog(ApplicationProvider.getApplicationContext(), + mModalDialogManager, mVirtualCardEnrollmentFields, mCallbackMock); + mDialog.show(); + } + + @Test + @SmallTest + public void dialogShown() { + assertThat(mModalDialogManager.getShownDialogModel()).isNotNull(); + // The callback should not have been called yet. + verify(mCallbackMock, never()).onResult(any()); + } + + @Test + @SmallTest + public void positiveButtonPressed() { + assertThat(mModalDialogManager.getShownDialogModel()).isNotNull(); + mModalDialogManager.clickPositiveButton(); + assertThat(mModalDialogManager.getShownDialogModel()).isNull(); + // Check that callback was called with true. + verify(mCallbackMock).onResult(true); + } + + @Test + @SmallTest + public void negativeButtonPressed() { + assertThat(mModalDialogManager.getShownDialogModel()).isNotNull(); + mModalDialogManager.clickNegativeButton(); + assertThat(mModalDialogManager.getShownDialogModel()).isNull(); + // Check that callback was called with false; + verify(mCallbackMock).onResult(false); + } + + @Test + @SmallTest + public void learnMoreTextClicked() { + // Create activity and its shadow to see if CustomTabActivity is launched. + Activity activity = Robolectric.buildActivity(Activity.class).setup().get(); + ShadowActivity shadowActivity = Shadows.shadowOf(activity); + // Create a new AutofillVirtualCardEnrollmentDialog with Activity as the context instead. + mDialog = new AutofillVirtualCardEnrollmentDialog( + activity, mModalDialogManager, mVirtualCardEnrollmentFields, mCallbackMock); + mDialog.show(); + // Make sure that the dialog was shown properly. + assertThat(mModalDialogManager.getShownDialogModel()).isNotNull(); + // Get the clickable span. + SpannableString virtualCardEducationText = + getSpannableStringForViewFromCurrentDialog(R.id.virtual_card_education); + // Assert that the message is not empty. + assertThat(virtualCardEducationText.length()).isGreaterThan(0); + + // Assert that the text of this span is correct. + NoUnderlineClickableSpan learnMoreSpan = + getOnlyClickableSpanFromString(virtualCardEducationText); + assertThat(getHighlightedTextFromSpannableString(virtualCardEducationText, learnMoreSpan)) + .isEqualTo("Learn more about virtual cards"); + // Click on the link. The callback doesn't use the view so it can be null. + learnMoreSpan.onClick(null); + // Check that the CustomTabActivity was started as expected. + Intent startedIntent = shadowActivity.getNextStartedActivity(); + assertThat(startedIntent.getComponent()) + .isEqualTo(new ComponentName(activity, CustomTabActivity.class)); + } + + @Test + @SmallTest + public void googleLegalMessageClicked() { + // Create activity and its shadow to see if CustomTabActivity is launched. + Activity activity = Robolectric.buildActivity(Activity.class).setup().get(); + ShadowActivity shadowActivity = Shadows.shadowOf(activity); + // Create a new AutofillVirtualCardEnrollmentDialog with Activity as the context instead. + mDialog = new AutofillVirtualCardEnrollmentDialog( + activity, mModalDialogManager, mVirtualCardEnrollmentFields, mCallbackMock); + mDialog.show(); + // Make sure that the dialog was shown properly. + assertThat(mModalDialogManager.getShownDialogModel()).isNotNull(); + // Get the clickable span. + SpannableString googleLegalMessageText = + getSpannableStringForViewFromCurrentDialog(R.id.google_legal_message); + // Assert that the message is not empty. + assertThat(googleLegalMessageText.length()).isGreaterThan(0); + + // Assert that the text of this span is correct. + NoUnderlineClickableSpan googleSpan = + getOnlyClickableSpanFromString(googleLegalMessageText); + assertThat(getHighlightedTextFromSpannableString(googleLegalMessageText, googleSpan)) + .isEqualTo("oo"); + // Click on the link. The callback doesn't use the view so it can be null. + googleSpan.onClick(null); + // Check that the CustomTabActivity was started as expected. + Intent startedIntent = shadowActivity.getNextStartedActivity(); + assertThat(startedIntent.getComponent()) + .isEqualTo(new ComponentName(activity, CustomTabActivity.class)); + } + + @Test + @SmallTest + public void issuerLegalMessageClicked() { + // Create activity and its shadow to see if CustomTabActivity is launched. + Activity activity = Robolectric.buildActivity(Activity.class).setup().get(); + ShadowActivity shadowActivity = Shadows.shadowOf(activity); + // Create a new AutofillVirtualCardEnrollmentDialog with Activity as the context instead. + mDialog = new AutofillVirtualCardEnrollmentDialog( + activity, mModalDialogManager, mVirtualCardEnrollmentFields, mCallbackMock); + mDialog.show(); + // Make sure that the dialog was shown properly. + assertThat(mModalDialogManager.getShownDialogModel()).isNotNull(); + // Get the clickable span. + SpannableString issuerLegalMessageText = + getSpannableStringForViewFromCurrentDialog(R.id.issuer_legal_message); + // Assert that the message is not empty. + assertThat(issuerLegalMessageText.length()).isGreaterThan(0); + + // Assert that the text of this span is correct. + NoUnderlineClickableSpan issuerSpan = + getOnlyClickableSpanFromString(issuerLegalMessageText); + assertThat(getHighlightedTextFromSpannableString(issuerLegalMessageText, issuerSpan)) + .isEqualTo("ss"); + // Click on the link. The callback doesn't use the view so it can be null. + issuerSpan.onClick(null); + // Check that the CustomTabActivity was started as expected. + Intent startedIntent = shadowActivity.getNextStartedActivity(); + assertThat(startedIntent.getComponent()) + .isEqualTo(new ComponentName(activity, CustomTabActivity.class)); + } + + private SpannableString getSpannableStringForViewFromCurrentDialog(int textViewId) { + View customView = + mModalDialogManager.getShownDialogModel().get(ModalDialogProperties.CUSTOM_VIEW); + return (SpannableString) ((TextView) customView.findViewById(textViewId)).getText(); + } + + private NoUnderlineClickableSpan getOnlyClickableSpanFromString(SpannableString string) { + NoUnderlineClickableSpan[] spans = + string.getSpans(0, string.length(), NoUnderlineClickableSpan.class); + // Assert that there is only one NoUnderlineClickableSpan. + assertThat(spans.length).isEqualTo(1); + return spans[0]; + } + + private String getHighlightedTextFromSpannableString( + SpannableString spannableString, NoUnderlineClickableSpan clickableSpan) { + int start = spannableString.getSpanStart(clickableSpan); + int end = spannableString.getSpanEnd(clickableSpan); + return spannableString.subSequence(start, end).toString(); + } + + private static LegalMessageLine createLegalMessageLine(String text) { + List<LegalMessageLine.Link> links = new ArrayList<>(); + links.add(new LegalMessageLine.Link(1, 3, "http://www.google.com")); + LegalMessageLine legalMessageLine = new LegalMessageLine(text); + legalMessageLine.links.addAll(links); + return legalMessageLine; + } +}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/instantapps/InstantAppsMessageDelegateTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/instantapps/InstantAppsMessageDelegateTest.java new file mode 100644 index 0000000..98cc7dc24 --- /dev/null +++ b/chrome/android/junit/src/org/chromium/chrome/browser/instantapps/InstantAppsMessageDelegateTest.java
@@ -0,0 +1,157 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.instantapps; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; + +import androidx.appcompat.content.res.AppCompatResources; +import androidx.test.core.app.ApplicationProvider; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import org.chromium.base.test.BaseRobolectricTestRunner; +import org.chromium.base.test.util.JniMocker; +import org.chromium.chrome.R; +import org.chromium.components.messages.DismissReason; +import org.chromium.components.messages.MessageBannerProperties; +import org.chromium.components.messages.MessageDispatcher; +import org.chromium.components.messages.MessageScopeType; +import org.chromium.components.webapps.WebappsIconUtils; +import org.chromium.content_public.browser.WebContents; +import org.chromium.ui.modelutil.PropertyModel; + +/** Tests for {@link InstantAppsMessageDelegate}. */ +@RunWith(BaseRobolectricTestRunner.class) +public class InstantAppsMessageDelegateTest { + private static final String TEST_APP_NAME = "Test App"; + private static final String PRIMARY_ACTION_LABEL = "Open app"; + + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Mock + private WebContents mWebContents; + + @Mock + private MessageDispatcher mMessageDispatcher; + + @Mock + private Bitmap mAppIcon; + + @Rule + public JniMocker jniMocker = new JniMocker(); + + @Mock + private InstantAppsMessageDelegate.Natives mNativeMock; + + private InstantAppsMessageDelegate mDelegate; + private Context mContext; + private InstantAppsBannerData mData; + + @Before + public void setup() { + jniMocker.mock(InstantAppsMessageDelegateJni.TEST_HOOKS, mNativeMock); + mContext = ApplicationProvider.getApplicationContext(); + } + + /** + * Tests the Instant Apps message properties. + */ + @Test + public void testShowMessage() { + initializeDelegate(); + mDelegate.showMessage(); + PropertyModel message = mDelegate.getMessageForTesting(); + + Resources resources = ApplicationProvider.getApplicationContext().getResources(); + + Assert.assertEquals("Message title should match.", + String.format(resources.getString(R.string.instant_apps_message_title), + mData.getAppName()), + message.get(MessageBannerProperties.TITLE)); + Assert.assertEquals("Message title content description should match.", + String.format( + resources.getString( + R.string.accessibility_instant_apps_message_title_content_description), + mData.getAppName()), + message.get(MessageBannerProperties.TITLE_CONTENT_DESCRIPTION)); + Assert.assertTrue("Message description icon should match.", + ((BitmapDrawable) AppCompatResources.getDrawable(mContext, R.drawable.google_play)) + .getBitmap() + .sameAs(((BitmapDrawable) message.get( + MessageBannerProperties.DESCRIPTION_ICON)) + .getBitmap())); + Assert.assertTrue("Message description icon should be resized.", + message.get(MessageBannerProperties.RESIZE_DESCRIPTION_ICON)); + Assert.assertEquals("Message icon should match.", mAppIcon, + ((BitmapDrawable) message.get(MessageBannerProperties.ICON)).getBitmap()); + Assert.assertTrue( + "Message icon should be large.", message.get(MessageBannerProperties.LARGE_ICON)); + Assert.assertEquals("Message icon should have a rounded corner radius.", + WebappsIconUtils.getIdealIconCornerRadiusPxForPromptUI(), + message.get(MessageBannerProperties.ICON_ROUNDED_CORNER_RADIUS_PX)); + Assert.assertEquals("Message primary button text should match.", + mData.getPrimaryActionLabel(), + message.get(MessageBannerProperties.PRIMARY_BUTTON_TEXT)); + + Mockito.verify(mMessageDispatcher) + .enqueueMessage(message, mWebContents, MessageScopeType.WEB_CONTENTS, false); + Mockito.verify(mNativeMock) + .onMessageShown(mWebContents, mData.getUrl(), mData.isInstantAppDefault()); + } + + /** + * Tests that the Instant Apps message primary action callback invokes the native method to + * account for the primary action. + */ + @Test + public void testMessagePrimaryActionCallback() { + initializeDelegate(); + mDelegate.showMessage(); + PropertyModel message = mDelegate.getMessageForTesting(); + + message.get(MessageBannerProperties.ON_PRIMARY_ACTION).run(); + Mockito.verify(mNativeMock).onPrimaryAction(mData.isInstantAppDefault()); + } + + /** + * Tests that the Instant Apps message dismissal callback invokes the native method to account + * for message dismissal. + */ + @Test + public void testMessageDismissalCallback() { + initializeDelegate(); + mDelegate.showMessage(); + PropertyModel message = mDelegate.getMessageForTesting(); + + message.get(MessageBannerProperties.ON_DISMISSED) + .onResult(DismissReason.DISMISSED_BY_FEATURE); + Mockito.verify(mNativeMock) + .onMessageDismissed(mWebContents, mData.getUrl(), mData.isInstantAppDefault()); + } + + /** + * Helper function that creates the InstantAppsMessageDelegate. + */ + private void initializeDelegate() { + mData = new InstantAppsBannerData(TEST_APP_NAME, mAppIcon, null, null, null, + PRIMARY_ACTION_LABEL, mWebContents, true); + mDelegate = InstantAppsMessageDelegate.create( + mContext, mWebContents, mMessageDispatcher, mData); + Mockito.verify(mNativeMock) + .initializeNativeDelegate(mDelegate, mWebContents, mData.getUrl()); + } +}
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp index a2933ee..2ef16b1 100644 --- a/chrome/app/chromeos_strings.grdp +++ b/chrome/app/chromeos_strings.grdp
@@ -5118,10 +5118,10 @@ <message name="IDS_ECHE_APP_SCREEN_LOCK_NOTIFICATION_MESSAGE" desc="Message for the notification shown when screen lock is not enabled while using app stream of EcheApp."> Set a pin or password on this Chromebook to protect access to your phone’s apps. </message> - <message name="IDS_ECHE_APP_DISABLED_BY_PHONE_NOTIFICATION_TITLE" translateable="false" desc="Title for notification shown when the apps streaming settigs is disabled on phone while using app stream of EcheApp."> + <message name="IDS_ECHE_APP_DISABLED_BY_PHONE_NOTIFICATION_TITLE" desc="Title for notification shown when the apps streaming settigs is disabled on phone while using app stream of EcheApp."> Could not launch <ph name="APP_NAME">$1<ex>the app</ex></ph> </message> - <message name="IDS_ECHE_APP_DISABLED_BY_PHONE_NOTIFICATION_MESSAGE" translateable="false" desc="Message for the notification shown when the apps streaming settings is disabled on phony while using app stream of EcheApp."> + <message name="IDS_ECHE_APP_DISABLED_BY_PHONE_NOTIFICATION_MESSAGE" desc="Message for the notification shown when the apps streaming settings is disabled on phone while using app stream of EcheApp."> Enable apps streaming settings from your phone. </message> <message name="IDS_ECHE_APP_NOTIFICATION_OPEN_AGAIN_BUTTON" desc="Label for the inactivity notification button of EcheApp to open open.">
diff --git a/chrome/app/chromeos_strings_grdp/IDS_ECHE_APP_DISABLED_BY_PHONE_NOTIFICATION_MESSAGE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_ECHE_APP_DISABLED_BY_PHONE_NOTIFICATION_MESSAGE.png.sha1 new file mode 100644 index 0000000..f2efea83 --- /dev/null +++ b/chrome/app/chromeos_strings_grdp/IDS_ECHE_APP_DISABLED_BY_PHONE_NOTIFICATION_MESSAGE.png.sha1
@@ -0,0 +1 @@ +cc4abea71e394e191294e35545f7ec5e6a7ba95c \ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_ECHE_APP_DISABLED_BY_PHONE_NOTIFICATION_TITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_ECHE_APP_DISABLED_BY_PHONE_NOTIFICATION_TITLE.png.sha1 new file mode 100644 index 0000000..f2efea83 --- /dev/null +++ b/chrome/app/chromeos_strings_grdp/IDS_ECHE_APP_DISABLED_BY_PHONE_NOTIFICATION_TITLE.png.sha1
@@ -0,0 +1 @@ +cc4abea71e394e191294e35545f7ec5e6a7ba95c \ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn index 4c8e3fd..82112c1 100644 --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn
@@ -2427,6 +2427,8 @@ "//ash/webui/media_app_ui", "//ash/webui/media_app_ui:mojo_bindings", "//ash/webui/multidevice_debug", + "//ash/webui/os_feedback_ui", + "//ash/webui/os_feedback_ui/mojom", "//ash/webui/personalization_app", "//ash/webui/personalization_app/mojom", "//ash/webui/print_management",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index 619a5706..d954545 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc
@@ -1833,7 +1833,6 @@ {"tab_grid_layout_android_new_tab_tile", "NewTabTile"}}; const FeatureEntry::FeatureParam kTabGridLayoutAndroid_TallNTV[] = { - {"thumbnail_aspect_ratio", "0.85"}, {"allow_to_refetch", "true"}, {"tab_grid_layout_android_new_tab", "NewTabVariation"}, {"enable_launch_polish", "true"}, @@ -3233,6 +3232,10 @@ flag_descriptions::kDisableOfficeEditingComponentAppName, flag_descriptions::kDisableOfficeEditingComponentAppDescription, kOsCrOS, FEATURE_VALUE_TYPE(chromeos::features::kDisableOfficeEditingComponentApp)}, + {"oobe-hid-detection-revamp", + flag_descriptions::kOobeHidDetectionRevampName, + flag_descriptions::kOobeHidDetectionRevampDescription, kOsCrOS, + FEATURE_VALUE_TYPE(chromeos::features::kOobeHidDetectionRevamp)}, {"quick-settings-network-revamp", flag_descriptions::kQuickSettingsNetworkRevampName, flag_descriptions::kQuickSettingsNetworkRevampDescription, kOsCrOS, @@ -4651,6 +4654,9 @@ {"spectre-v2-mitigation", flag_descriptions::kSpectreVariant2MitigationName, flag_descriptions::kSpectreVariant2MitigationDescription, kOsCrOS, FEATURE_VALUE_TYPE(sandbox::policy::features::kSpectreVariant2Mitigation)}, + {"eche-custom-widget", flag_descriptions::kEcheCustomWidgetName, + flag_descriptions::kEcheSWADescription, kOsCrOS, + FEATURE_VALUE_TYPE(chromeos::features::kEcheCustomWidget)}, {"eche-phone-hub-permissions-onboarding", flag_descriptions::kEchePhoneHubPermissionsOnboardingName, flag_descriptions::kEchePhoneHubPermissionsOnboardingDescription, kOsCrOS,
diff --git a/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.cc b/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.cc index a2c7084..5eec7fa 100644 --- a/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.cc +++ b/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.cc
@@ -170,11 +170,13 @@ jfloat width, jfloat height, jboolean visible, + jint tint, + jfloat button_alpha, const JavaParamRef<jobject>& jresource_manager) { ui::ResourceManager* resource_manager = ui::ResourceManagerImpl::FromJavaObject(jresource_manager); - ui::Resource* button_resource = resource_manager->GetResource( - ui::ANDROID_RESOURCE_TYPE_STATIC, resource_id); + ui::Resource* button_resource = + resource_manager->GetStaticResourceWithTint(resource_id, tint); new_tab_button_->SetUIResourceId(button_resource->ui_resource()->id()); float left_offset = (width - button_resource->size().width()) / 2; @@ -182,6 +184,7 @@ new_tab_button_->SetPosition(gfx::PointF(x + left_offset, y + top_offset)); new_tab_button_->SetBounds(button_resource->size()); new_tab_button_->SetHideLayerAndSubtree(!visible); + new_tab_button_->SetOpacity(button_alpha); } void TabStripSceneLayer::UpdateModelSelectorButton(
diff --git a/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.h b/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.h index a0963be..50bfdbb8 100644 --- a/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.h +++ b/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.h
@@ -75,6 +75,8 @@ jfloat width, jfloat height, jboolean visible, + jint tint, + jfloat button_alpha, const base::android::JavaParamRef<jobject>& jresource_manager); void UpdateModelSelectorButton(
diff --git a/chrome/browser/android/compositor/tab_content_manager.cc b/chrome/browser/android/compositor/tab_content_manager.cc index 6f60ff72..8e5a568e 100644 --- a/chrome/browser/android/compositor/tab_content_manager.cc +++ b/chrome/browser/android/compositor/tab_content_manager.cc
@@ -43,9 +43,6 @@ using base::android::JavaRef; namespace { - -const double kDefaultThumbnailAspectRatio = 0.85; - using TabReadbackCallback = base::OnceCallback<void(float, const SkBitmap&)>; } // namespace @@ -56,6 +53,7 @@ public: TabReadbackRequest(content::RenderWidgetHostView* rwhv, float thumbnail_scale, + float aspect_ratio, bool crop_to_match_aspect_ratio, TabReadbackCallback end_callback) : thumbnail_scale_(thumbnail_scale), @@ -73,10 +71,6 @@ return; } if (crop_to_match_aspect_ratio) { - double aspect_ratio = base::GetFieldTrialParamByFeatureAsDouble( - chrome::android::kTabGridLayoutAndroid, "thumbnail_aspect_ratio", - kDefaultThumbnailAspectRatio); - aspect_ratio = base::clamp(aspect_ratio, 0.5, 2.0); int height = std::min(view_size_in_pixels.height(), (int)(view_size_in_pixels.width() / aspect_ratio)); view_size_in_pixels.set_height(height); @@ -131,11 +125,9 @@ jint compression_queue_max_size, jint write_queue_max_size, jboolean use_approximation_thumbnail, - jboolean save_jpeg_thumbnails) + jboolean save_jpeg_thumbnails, + jfloat jpeg_aspect_ratio) : weak_java_tab_content_manager_(env, obj) { - double jpeg_aspect_ratio = base::GetFieldTrialParamByFeatureAsDouble( - chrome::android::kTabGridLayoutAndroid, "thumbnail_aspect_ratio", - kDefaultThumbnailAspectRatio); thumbnail_cache_ = std::make_unique<ThumbnailCache>( static_cast<size_t>(default_cache_size), static_cast<size_t>(approximation_cache_size), @@ -262,6 +254,7 @@ const JavaParamRef<jobject>& obj, const JavaParamRef<jobject>& tab, jfloat thumbnail_scale, + jfloat aspect_ratio, jboolean write_to_cache, const base::android::JavaParamRef<jobject>& j_callback) { TabAndroid* tab_android = TabAndroid::GetNativeTab(env, tab); @@ -280,9 +273,11 @@ } TabReadbackCallback readback_done_callback = base::BindOnce( &TabContentManager::OnTabReadback, weak_factory_.GetWeakPtr(), tab_id, - base::android::ScopedJavaGlobalRef<jobject>(j_callback), write_to_cache); + base::android::ScopedJavaGlobalRef<jobject>(j_callback), write_to_cache, + aspect_ratio); + pending_tab_readbacks_[tab_id] = std::make_unique<TabReadbackRequest>( - rwhv, thumbnail_scale, !write_to_cache, + rwhv, thumbnail_scale, aspect_ratio, !write_to_cache, std::move(readback_done_callback)); } @@ -290,7 +285,8 @@ const JavaParamRef<jobject>& obj, const JavaParamRef<jobject>& tab, const JavaParamRef<jobject>& bitmap, - jfloat thumbnail_scale) { + jfloat thumbnail_scale, + jfloat aspect_ratio) { TabAndroid* tab_android = TabAndroid::GetNativeTab(env, tab); DCHECK(tab_android); int tab_id = tab_android->GetAndroidId(); @@ -301,7 +297,8 @@ skbitmap.setImmutable(); if (thumbnail_cache_->CheckAndUpdateThumbnailMetaData(tab_id, url)) - OnTabReadback(tab_id, nullptr, true, thumbnail_scale, skbitmap); + OnTabReadback(tab_id, nullptr, true, aspect_ratio, thumbnail_scale, + skbitmap); } void TabContentManager::InvalidateIfChanged(JNIEnv* env, @@ -345,13 +342,14 @@ JNIEnv* env, const base::android::JavaParamRef<jobject>& obj, jint tab_id, + jfloat aspect_ratio, const base::android::JavaParamRef<jobject>& j_callback) { thumbnail_cache_->DecompressThumbnailFromFile( tab_id, base::BindOnce(&TabContentManager::SendThumbnailToJava, weak_factory_.GetWeakPtr(), base::android::ScopedJavaGlobalRef<jobject>(j_callback), - /* need_downsampling */ true)); + /* need_downsampling */ true, aspect_ratio)); } void TabContentManager::OnUIResourcesWereEvicted() { @@ -368,6 +366,7 @@ int tab_id, base::android::ScopedJavaGlobalRef<jobject> j_callback, bool write_to_cache, + float aspect_ratio, float thumbnail_scale, const SkBitmap& bitmap) { TabReadbackRequestMap::iterator readback_iter = @@ -377,7 +376,7 @@ pending_tab_readbacks_.erase(tab_id); if (j_callback) { - SendThumbnailToJava(j_callback, write_to_cache, true, bitmap); + SendThumbnailToJava(j_callback, write_to_cache, aspect_ratio, true, bitmap); } if (write_to_cache && thumbnail_scale > 0 && !bitmap.empty()) @@ -387,6 +386,7 @@ void TabContentManager::SendThumbnailToJava( base::android::ScopedJavaGlobalRef<jobject> j_callback, bool need_downsampling, + float aspect_ratio, bool result, const SkBitmap& bitmap) { ScopedJavaLocalRef<jobject> j_bitmap; @@ -397,15 +397,11 @@ // portrait mode, or it would be shown in the wrong aspect ratio in // landscape mode. int scale = need_downsampling ? 2 : 1; - double aspect_ratio = base::GetFieldTrialParamByFeatureAsDouble( - chrome::android::kTabGridLayoutAndroid, "thumbnail_aspect_ratio", - kDefaultThumbnailAspectRatio); - aspect_ratio = base::clamp(aspect_ratio, 0.5, 2.0); - int width = std::min(bitmap.width() / scale, (int)(bitmap.height() * aspect_ratio / scale)); int height = std::min(bitmap.height() / scale, (int)(bitmap.width() / aspect_ratio / scale)); + // When cropping the thumbnails, we want to keep the top center portion. int begin_x = (bitmap.width() / scale - width) / 2; int end_x = begin_x + width; @@ -442,11 +438,12 @@ jint compression_queue_max_size, jint write_queue_max_size, jboolean use_approximation_thumbnail, - jboolean save_jpeg_thumbnails) { + jboolean save_jpeg_thumbnails, + jfloat jpeg_aspect_ratio) { TabContentManager* manager = new TabContentManager( env, obj, default_cache_size, approximation_cache_size, compression_queue_max_size, write_queue_max_size, - use_approximation_thumbnail, save_jpeg_thumbnails); + use_approximation_thumbnail, save_jpeg_thumbnails, jpeg_aspect_ratio); return reinterpret_cast<intptr_t>(manager); }
diff --git a/chrome/browser/android/compositor/tab_content_manager.h b/chrome/browser/android/compositor/tab_content_manager.h index be66ab6..c6fe4514d 100644 --- a/chrome/browser/android/compositor/tab_content_manager.h +++ b/chrome/browser/android/compositor/tab_content_manager.h
@@ -45,7 +45,8 @@ jint compression_queue_max_size, jint write_queue_max_size, jboolean use_approximation_thumbnail, - jboolean save_jpeg_thumbnails); + jboolean save_jpeg_thumbnails, + jfloat jpeg_aspect_ratio); TabContentManager(const TabContentManager&) = delete; TabContentManager& operator=(const TabContentManager&) = delete; @@ -88,13 +89,15 @@ const base::android::JavaParamRef<jobject>& obj, const base::android::JavaParamRef<jobject>& tab, jfloat thumbnail_scale, + jfloat aspect_ratio, jboolean write_to_cache, const base::android::JavaParamRef<jobject>& j_callback); void CacheTabWithBitmap(JNIEnv* env, const base::android::JavaParamRef<jobject>& obj, const base::android::JavaParamRef<jobject>& tab, const base::android::JavaParamRef<jobject>& bitmap, - jfloat thumbnail_scale); + jfloat thumbnail_scale, + jfloat aspect_ratio); void InvalidateIfChanged(JNIEnv* env, const base::android::JavaParamRef<jobject>& obj, jint tab_id, @@ -112,6 +115,7 @@ JNIEnv* env, const base::android::JavaParamRef<jobject>& obj, jint tab_id, + float aspect_ratio, const base::android::JavaParamRef<jobject>& j_callback); void SetCaptureMinRequestTimeForTesting( JNIEnv* env, @@ -141,11 +145,13 @@ base::android::ScopedJavaGlobalRef<jobject> j_callback, bool write_to_cache, float thumbnail_scale, + float aspect_ratio, const SkBitmap& bitmap); void SendThumbnailToJava( base::android::ScopedJavaGlobalRef<jobject> j_callback, bool need_downsampling, + float aspect_ratio, bool result, const SkBitmap& bitmap);
diff --git a/chrome/browser/android/preferences/autofill/autofill_payment_methods_delegate.cc b/chrome/browser/android/preferences/autofill/autofill_payment_methods_delegate.cc index d8e06e4..4bb176c 100644 --- a/chrome/browser/android/preferences/autofill/autofill_payment_methods_delegate.cc +++ b/chrome/browser/android/preferences/autofill/autofill_payment_methods_delegate.cc
@@ -6,26 +6,75 @@ #include <memory> +#include "base/android/jni_string.h" #include "chrome/android/chrome_jni_headers/AutofillPaymentMethodsDelegate_jni.h" +#include "chrome/android/chrome_jni_headers/VirtualCardEnrollmentFields_jni.h" #include "chrome/browser/autofill/personal_data_manager_factory.h" #include "chrome/browser/autofill/risk_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_android.h" #include "chrome/browser/signin/identity_manager_factory.h" - #include "components/autofill/content/browser/content_autofill_driver.h" #include "components/autofill/core/browser/browser_autofill_manager.h" #include "components/autofill/core/browser/payments/payments_client.h" #include "components/autofill/core/browser/payments/virtual_card_enrollment_flow.h" #include "components/autofill/core/browser/payments/virtual_card_enrollment_manager.h" #include "components/autofill/core/browser/personal_data_manager.h" - #include "services/network/public/cpp/shared_url_loader_factory.h" +#include "ui/gfx/android/java_bitmap.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_skia.h" +#include "url/android/gurl_android.h" +#include "url/gurl.h" -using base::android::JavaParamRef; +using base::android::AttachCurrentThread; +using base::android::ConvertUTF16ToJavaString; +using base::android::ConvertUTF8ToJavaString; +using base::android::ScopedJavaLocalRef; namespace autofill { +// static +ScopedJavaLocalRef<jobject> GetVirtualCardEnrollmentFieldsJavaObject( + autofill::VirtualCardEnrollmentFields* virtual_card_enrollment_fields) { + JNIEnv* env = AttachCurrentThread(); + // Create VirtualCardEnrollmentFields java object. + ScopedJavaLocalRef<jobject> java_object = + Java_VirtualCardEnrollmentFields_create( + env, + ConvertUTF16ToJavaString( + env, virtual_card_enrollment_fields->credit_card + .CardIdentifierStringForAutofillDisplay()), + gfx::ConvertToJavaBitmap( + *virtual_card_enrollment_fields->card_art_image->AsImageSkia() + .bitmap())); + // Add Google legal messages. + for (const auto& legal_message_line : + virtual_card_enrollment_fields->google_legal_message) { + Java_VirtualCardEnrollmentFields_addGoogleLegalMessageLine( + env, java_object, + ConvertUTF16ToJavaString(env, legal_message_line.text())); + for (const auto& link : legal_message_line.links()) { + Java_VirtualCardEnrollmentFields_addLinkToLastGoogleLegalMessageLine( + env, java_object, link.range.start(), link.range.end(), + ConvertUTF8ToJavaString(env, link.url.spec())); + } + } + // Add issuer legal messages. + for (const auto& legal_message_line : + virtual_card_enrollment_fields->issuer_legal_message) { + Java_VirtualCardEnrollmentFields_addIssuerLegalMessageLine( + env, java_object, + ConvertUTF16ToJavaString(env, legal_message_line.text())); + for (const auto& link : legal_message_line.links()) { + Java_VirtualCardEnrollmentFields_addLinkToLastIssuerLegalMessageLine( + env, java_object, link.range.start(), link.range.end(), + ConvertUTF8ToJavaString(env, link.url.spec())); + } + } + return java_object; +} + AutofillPaymentMethodsDelegate::AutofillPaymentMethodsDelegate(Profile* profile) : profile_(profile) { personal_data_manager_ = PersonalDataManagerFactory::GetForProfile(profile); @@ -55,16 +104,22 @@ void AutofillPaymentMethodsDelegate::OfferVirtualCardEnrollment( JNIEnv* env, - int64_t instrumentId) { + int64_t instrument_id, + const JavaParamRef<jobject>& jcallback) { CreditCard* credit_card = - personal_data_manager_->GetCreditCardByInstrumentId(instrumentId); + personal_data_manager_->GetCreditCardByInstrumentId(instrument_id); virtual_card_enrollment_manager_->OfferVirtualCardEnroll( *credit_card, VirtualCardEnrollmentSource::kSettingsPage, profile_->GetPrefs(), base::BindOnce(&risk_util::LoadRiskDataHelper)); } -void AutofillPaymentMethodsDelegate::UnenrollVirtualCard(JNIEnv* env, - int64_t instrumentId) { - virtual_card_enrollment_manager_->Unenroll(instrumentId); +void AutofillPaymentMethodsDelegate::EnrollOfferedVirtualCard(JNIEnv* env) { + // TODO (crbug/1281695) Implement call to enroll Virtual Cards. +} + +void AutofillPaymentMethodsDelegate::UnenrollVirtualCard( + JNIEnv* env, + int64_t instrument_id) { + virtual_card_enrollment_manager_->Unenroll(instrument_id); } } // namespace autofill
diff --git a/chrome/browser/android/preferences/autofill/autofill_payment_methods_delegate.h b/chrome/browser/android/preferences/autofill/autofill_payment_methods_delegate.h index d8ad6c09..2a46fa9 100644 --- a/chrome/browser/android/preferences/autofill/autofill_payment_methods_delegate.h +++ b/chrome/browser/android/preferences/autofill/autofill_payment_methods_delegate.h
@@ -10,8 +10,12 @@ #include "build/build_config.h" +#include "base/android/jni_android.h" #include "base/android/scoped_java_ref.h" #include "base/memory/raw_ptr.h" +#include "components/autofill/core/browser/payments/virtual_card_enrollment_manager.h" + +using base::android::JavaParamRef; class Profile; @@ -40,8 +44,11 @@ void Cleanup(JNIEnv* env); // Trigger enrollment/unenrollment action. - void OfferVirtualCardEnrollment(JNIEnv* env, int64_t instrumentId); - void UnenrollVirtualCard(JNIEnv* env, int64_t instrumentId); + void OfferVirtualCardEnrollment(JNIEnv* env, + int64_t instrument_id, + const JavaParamRef<jobject>& jcallback); + void EnrollOfferedVirtualCard(JNIEnv* env); + void UnenrollVirtualCard(JNIEnv* env, int64_t instrument_id); private: raw_ptr<Profile> profile_; // weak reference
diff --git a/chrome/browser/apps/app_service/app_service_proxy_ash.cc b/chrome/browser/apps/app_service/app_service_proxy_ash.cc index 430563a..ef240fc 100644 --- a/chrome/browser/apps/app_service/app_service_proxy_ash.cc +++ b/chrome/browser/apps/app_service/app_service_proxy_ash.cc
@@ -243,9 +243,10 @@ dialog_created_callback_ = std::move(callback); } -void AppServiceProxyAsh::UninstallForTesting(const std::string& app_id, - gfx::NativeWindow parent_window, - base::OnceClosure callback) { +void AppServiceProxyAsh::UninstallForTesting( + const std::string& app_id, + gfx::NativeWindow parent_window, + OnUninstallForTestingCallback callback) { UninstallImpl(app_id, apps::mojom::UninstallSource::kUnknown, parent_window, std::move(callback)); } @@ -270,8 +271,18 @@ const std::string& app_id, apps::mojom::UninstallSource uninstall_source, gfx::NativeWindow parent_window, - base::OnceClosure callback) { + OnUninstallForTestingCallback callback) { if (!app_service_.is_connected()) { + if (!callback.is_null()) { + std::move(callback).Run(false); + } + return; + } + + if (uninstall_dialogs_.find(app_id) != uninstall_dialogs_.end()) { + if (!callback.is_null()) { + std::move(callback).Run(false); + } return; } @@ -288,7 +299,7 @@ UninstallDialog* uninstall_dialog = uninstall_dialog_ptr.get(); uninstall_dialog_ptr->SetDialogCreatedCallbackForTesting( std::move(callback)); - uninstall_dialogs_.emplace(std::move(uninstall_dialog_ptr)); + uninstall_dialogs_.emplace(update.AppId(), std::move(uninstall_dialog_ptr)); uninstall_dialog->PrepareToShow(std::move(icon_key), this); }); } @@ -311,7 +322,7 @@ } DCHECK(uninstall_dialog); - auto it = uninstall_dialogs_.find(uninstall_dialog); + auto it = uninstall_dialogs_.find(app_id); DCHECK(it != uninstall_dialogs_.end()); uninstall_dialogs_.erase(it); }
diff --git a/chrome/browser/apps/app_service/app_service_proxy_ash.h b/chrome/browser/apps/app_service/app_service_proxy_ash.h index f3497c8..8f3932a 100644 --- a/chrome/browser/apps/app_service/app_service_proxy_ash.h +++ b/chrome/browser/apps/app_service/app_service_proxy_ash.h
@@ -7,10 +7,10 @@ #include <map> #include <memory> -#include <set> #include <string> #include "base/callback.h" +#include "base/containers/flat_map.h" #include "base/containers/unique_ptr_adapters.h" #include "base/memory/weak_ptr.h" #include "base/scoped_observation.h" @@ -61,6 +61,7 @@ public apps::InstanceRegistry::Observer { public: using OnPauseDialogClosedCallback = base::OnceCallback<void()>; + using OnUninstallForTestingCallback = base::OnceCallback<void(bool)>; explicit AppServiceProxyAsh(Profile* profile); AppServiceProxyAsh(const AppServiceProxyAsh&) = delete; @@ -104,7 +105,7 @@ void SetDialogCreatedCallbackForTesting(base::OnceClosure callback); void UninstallForTesting(const std::string& app_id, gfx::NativeWindow parent_window, - base::OnceClosure callback); + OnUninstallForTestingCallback callback); void SetAppPlatformMetricsServiceForTesting( std::unique_ptr<apps::AppPlatformMetricsService> app_platform_metrics_service); @@ -114,8 +115,8 @@ friend class AppServiceProxyFactory; FRIEND_TEST_ALL_PREFIXES(AppServiceProxyTest, LaunchCallback); - using UninstallDialogs = std::set<std::unique_ptr<apps::UninstallDialog>, - base::UniquePtrComparator>; + using UninstallDialogs = + base::flat_map<std::string, std::unique_ptr<apps::UninstallDialog>>; void Initialize() override; @@ -135,7 +136,7 @@ void UninstallImpl(const std::string& app_id, apps::mojom::UninstallSource uninstall_source, gfx::NativeWindow parent_window, - base::OnceClosure callback); + OnUninstallForTestingCallback callback); // Invoked when the uninstall dialog is closed. The app for the given // |app_type| and |app_id| will be uninstalled directly if |uninstall| is
diff --git a/chrome/browser/apps/app_service/uninstall_dialog.cc b/chrome/browser/apps/app_service/uninstall_dialog.cc index c6e01c41..37bfee43 100644 --- a/chrome/browser/apps/app_service/uninstall_dialog.cc +++ b/chrome/browser/apps/app_service/uninstall_dialog.cc
@@ -93,8 +93,8 @@ } void UninstallDialog::SetDialogCreatedCallbackForTesting( - base::OnceClosure callback) { - dialog_created_callback_ = std::move(callback); + OnUninstallForTestingCallback callback) { + uninstall_dialog_created_callback_ = std::move(callback); } void UninstallDialog::OnLoadIcon(IconValuePtr icon_value) { @@ -114,8 +114,8 @@ // For browser tests, if the callback is set, run the callback to stop the run // loop. - if (!dialog_created_callback_.is_null()) { - std::move(dialog_created_callback_).Run(); + if (!uninstall_dialog_created_callback_.is_null()) { + std::move(uninstall_dialog_created_callback_).Run(true); } }
diff --git a/chrome/browser/apps/app_service/uninstall_dialog.h b/chrome/browser/apps/app_service/uninstall_dialog.h index 0b2309ca..2e50ff6 100644 --- a/chrome/browser/apps/app_service/uninstall_dialog.h +++ b/chrome/browser/apps/app_service/uninstall_dialog.h
@@ -81,6 +81,8 @@ bool report_rebuse, UninstallDialog* uninstall_dialog)>; + using OnUninstallForTestingCallback = base::OnceCallback<void(bool)>; + UninstallDialog(Profile* profile, apps::mojom::AppType app_type, const std::string& app_id, @@ -100,7 +102,8 @@ // the uninstall. void OnDialogClosed(bool uninstall, bool clear_site_data, bool report_abuse); - void SetDialogCreatedCallbackForTesting(base::OnceClosure callback); + void SetDialogCreatedCallbackForTesting( + OnUninstallForTestingCallback callback); private: // Callback invoked when the icon is loaded. @@ -113,7 +116,7 @@ gfx::NativeWindow parent_window_; UninstallCallback uninstall_callback_; - base::OnceClosure dialog_created_callback_; + OnUninstallForTestingCallback uninstall_dialog_created_callback_; // Tracks whether |parent_window_| got destroyed. std::unique_ptr<NativeWindowTracker> parent_window_tracker_;
diff --git a/chrome/browser/ash/accessibility/dictation_browsertest.cc b/chrome/browser/ash/accessibility/dictation_browsertest.cc index e7fd1aa..09ff5c2 100644 --- a/chrome/browser/ash/accessibility/dictation_browsertest.cc +++ b/chrome/browser/ash/accessibility/dictation_browsertest.cc
@@ -11,8 +11,10 @@ #include "ash/shell.h" #include "base/bind.h" #include "base/hash/hash.h" +#include "base/memory/weak_ptr.h" #include "base/metrics/metrics_hashes.h" #include "base/metrics/statistics_recorder.h" +#include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/test/bind.h" #include "base/test/metrics/histogram_tester.h" @@ -35,6 +37,8 @@ #include "components/metrics/content/subprocess_metrics_provider.h" #include "components/prefs/pref_service.h" #include "components/soda/soda_installer.h" +#include "content/public/browser/browser_task_traits.h" +#include "content/public/browser/browser_thread.h" #include "content/public/test/accessibility_notification_waiter.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" @@ -57,6 +61,8 @@ namespace ash { namespace { +constexpr int kPrintErrorMessageDelayMs = 3500; + const char kFirstSpeechResult[] = "help"; const char16_t kFirstSpeechResult16[] = u"help"; const char kSecondSpeechResult[] = "help oh"; @@ -114,6 +120,19 @@ GetManager()->EnableSpokenFeedback(true); } +std::string ToString(DictationBubbleIconType icon) { + switch (icon) { + case DictationBubbleIconType::kHidden: + return "hidden"; + case DictationBubbleIconType::kStandby: + return "standby"; + case DictationBubbleIconType::kMacroSuccess: + return "macro success"; + case DictationBubbleIconType::kMacroFail: + return "macro fail"; + } +} + // Listens for changes to the histogram provided at construction. This class // only allows `Wait()` to be called once. If you need to call `Wait()` multiple // times, create multiple instances of this class. @@ -150,8 +169,9 @@ // until it evaluates to true. class SuccessWaiter { public: - explicit SuccessWaiter(const base::RepeatingCallback<bool()>& is_success) - : is_success_(is_success) {} + SuccessWaiter(const base::RepeatingCallback<bool()>& is_success, + const std::string& error_message) + : is_success_(is_success), error_message_(error_message) {} ~SuccessWaiter() = default; SuccessWaiter(const SuccessWaiter&) = delete; SuccessWaiter& operator=(const SuccessWaiter&) = delete; @@ -159,6 +179,11 @@ void Wait() { timer_.Start(FROM_HERE, base::Milliseconds(200), this, &SuccessWaiter::OnTimer); + content::GetUIThreadTaskRunner({})->PostDelayedTask( + FROM_HERE, + base::BindOnce(&SuccessWaiter::MaybePrintErrorMessage, + weak_factory_.GetWeakPtr()), + base::Milliseconds(kPrintErrorMessageDelayMs)); run_loop_.Run(); ASSERT_TRUE(is_success_.Run()); } @@ -170,10 +195,20 @@ } } + void MaybePrintErrorMessage() { + if (!timer_.IsRunning() || run_loop_.AnyQuitCalled() || is_success_.Run()) + return; + + LOG(ERROR) << "Still waiting for SuccessWaiter\n" + << "SuccessWaiter error message: " << error_message_ << "\n"; + } + private: - base::RepeatingTimer timer_; base::RepeatingCallback<bool()> is_success_; + std::string error_message_; + base::RepeatingTimer timer_; base::RunLoop run_loop_; + base::WeakPtrFactory<SuccessWaiter> weak_factory_{this}; }; class CaretBoundsChangedWaiter : public ui::InputMethodObserver { @@ -773,9 +808,11 @@ } void WaitForTextAreaValue(const std::string& value) { - SuccessWaiter(base::BindLambdaForTesting([&]() { - return value == GetTextAreaValue(); - })).Wait(); + std::string error_message = "Still waiting for text area value: " + value; + SuccessWaiter(base::BindLambdaForTesting( + [&]() { return value == GetTextAreaValue(); }), + error_message) + .Wait(); } void ToggleDictationWithKeystroke() { @@ -801,18 +838,27 @@ } void WaitForCompositionText(const std::u16string& value) { + std::string error_message = + base::UTF16ToUTF8(u"Still waiting for composition text: " + value); EXPECT_TRUE(input_context_handler_); SuccessWaiter(base::BindLambdaForTesting([&]() { - return value == input_context_handler_->last_update_composition_arg() - .composition_text.text; - })).Wait(); + return value == + input_context_handler_->last_update_composition_arg() + .composition_text.text; + }), + error_message) + .Wait(); } void WaitForCommitText(const std::u16string& value) { + std::string error_message = + base::UTF16ToUTF8(u"Still waiting for commit text: " + value); EXPECT_TRUE(input_context_handler_); SuccessWaiter(base::BindLambdaForTesting([&]() { - return value == input_context_handler_->last_commit_text(); - })).Wait(); + return value == input_context_handler_->last_commit_text(); + }), + error_message) + .Wait(); } private: @@ -1082,12 +1128,16 @@ } void WaitForHelpUrlVisible() { + std::string error_message = "Still waiting for help URL to be visible"; SuccessWaiter(base::BindLambdaForTesting([&]() { - content::WebContents* web_contents = - browser()->tab_strip_model()->GetActiveWebContents(); - return web_contents->GetVisibleURL().spec().rfind( - "https://support.google.com/chromebook", /*pos=*/0) != 0; - })).Wait(); + content::WebContents* web_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + return web_contents->GetVisibleURL().spec().rfind( + "https://support.google.com/chromebook", + /*pos=*/0) != 0; + }), + error_message) + .Wait(); } private: @@ -1260,27 +1310,43 @@ private: void WaitForVisibility(bool visible) { + std::string error_message = "Still waiting for UI visibility: "; + error_message += visible ? "true" : "false"; SuccessWaiter(base::BindLambdaForTesting([&]() { - return dictation_bubble_test_helper_.IsVisible() == visible; - })).Wait(); + return dictation_bubble_test_helper_.IsVisible() == visible; + }), + error_message) + .Wait(); } void WaitForVisibleIcon(DictationBubbleIconType icon) { + std::string error_message = "Still waiting for UI icon: " + ToString(icon); SuccessWaiter(base::BindLambdaForTesting([&]() { - return dictation_bubble_test_helper_.GetVisibleIcon() == icon; - })).Wait(); + return dictation_bubble_test_helper_.GetVisibleIcon() == + icon; + }), + error_message) + .Wait(); } void WaitForVisibleText(const std::u16string& text) { + std::string error_message = + "Still waiting for UI text: " + base::UTF16ToUTF8(text); SuccessWaiter(base::BindLambdaForTesting([&]() { - return dictation_bubble_test_helper_.GetText() == text; - })).Wait(); + return dictation_bubble_test_helper_.GetText() == text; + }), + error_message) + .Wait(); } void WaitForVisibleHints(const std::vector<std::u16string>& hints) { + std::string error_message = base::UTF16ToUTF8( + u"Still waiting for UI hints: " + base::JoinString(hints, u",")); SuccessWaiter(base::BindLambdaForTesting([&]() { - return dictation_bubble_test_helper_.HasVisibleHints(hints); - })).Wait(); + return dictation_bubble_test_helper_.HasVisibleHints(hints); + }), + error_message) + .Wait(); } DictationBubbleTestHelper dictation_bubble_test_helper_;
diff --git a/chrome/browser/ash/app_restore/app_launch_handler.cc b/chrome/browser/ash/app_restore/app_launch_handler.cc index cedd4c9..2fcc94a 100644 --- a/chrome/browser/ash/app_restore/app_launch_handler.cc +++ b/chrome/browser/ash/app_restore/app_launch_handler.cc
@@ -146,7 +146,8 @@ } else { base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, - base::BindOnce(&AppLaunchHandler::LaunchApp, base::Unretained(this), + base::BindOnce(&AppLaunchHandler::LaunchApp, + GetWeakPtrAppLaunchHandler(), cache->GetAppType(app_id), app_id), current_delay); current_delay += delay_;
diff --git a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc index 5bfa3f6..62f5881 100644 --- a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc +++ b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc
@@ -135,10 +135,13 @@ DCHECK(overlay_widget); auto input_mapping_view = std::make_unique<InputMappingView>(this); input_mapping_view->SetPosition(gfx::Point()); - input_mapping_view_ = overlay_widget->GetContentsView()->AddChildView( std::move(input_mapping_view)); - input_mapping_view_->SetPosition(gfx::Point()); + + // Set input mapping view visibility according to the saved status. + DCHECK(touch_injector_); + if (touch_injector_) + SetInputMappingVisible(touch_injector_->input_mapping_visible()); } void DisplayOverlayController::AddMenuEntryView(views::Widget* overlay_widget) { @@ -293,6 +296,10 @@ if (!input_mapping_view_) return; input_mapping_view_->SetVisible(visible); + DCHECK(touch_injector_); + if (!touch_injector_) + return; + touch_injector_->store_input_mapping_visible(visible); } bool DisplayOverlayController::GetInputMappingViewVisible() const { @@ -301,5 +308,19 @@ return input_mapping_view_->GetVisible(); } +void DisplayOverlayController::SetTouchInjectorEnable(bool enable) { + DCHECK(touch_injector_); + if (!touch_injector_) + return; + touch_injector_->store_touch_injector_enable(enable); +} + +bool DisplayOverlayController::GetTouchInjectorEnable() { + DCHECK(touch_injector_); + if (!touch_injector_) + return false; + return touch_injector_->touch_injector_enable(); +} + } // namespace input_overlay } // namespace arc
diff --git a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h index 69c6843f..d91cc15f 100644 --- a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h +++ b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h
@@ -63,6 +63,9 @@ void SetInputMappingVisible(bool visible); bool GetInputMappingViewVisible() const; + void SetTouchInjectorEnable(bool enable); + bool GetTouchInjectorEnable(); + TouchInjector* touch_injector_; // References to UI elements owned by the overlay widget.
diff --git a/chrome/browser/ash/arc/input_overlay/touch_injector.cc b/chrome/browser/ash/arc/input_overlay/touch_injector.cc index 855e30f..01d07ea5e 100644 --- a/chrome/browser/ash/arc/input_overlay/touch_injector.cc +++ b/chrome/browser/ash/arc/input_overlay/touch_injector.cc
@@ -278,9 +278,6 @@ if (text_input_active_) return SendEvent(continuation, &event); - if (switch_mode_ && switch_mode_->Process(event)) - return DiscardEvent(continuation); - if (display_mode_ != DisplayMode::kView) return SendEvent(continuation, &event); @@ -298,6 +295,9 @@ return SendEvent(continuation, &event); } + if (!touch_injector_enable_) + return SendEvent(continuation, &event); + if (mouse_lock_ && mouse_lock_->Process(event)) return DiscardEvent(continuation);
diff --git a/chrome/browser/ash/arc/input_overlay/touch_injector.h b/chrome/browser/ash/arc/input_overlay/touch_injector.h index ea4d0b27..8b5bbf3e 100644 --- a/chrome/browser/ash/arc/input_overlay/touch_injector.h +++ b/chrome/browser/ash/arc/input_overlay/touch_injector.h
@@ -46,10 +46,18 @@ return actions_; } bool is_mouse_locked() const { return is_mouse_locked_; } + bool touch_injector_enable() const { return touch_injector_enable_; } + bool input_mapping_visible() const { return input_mapping_visible_; } void set_display_mode(DisplayMode mode) { display_mode_ = mode; } void set_display_overlay_controller(DisplayOverlayController* controller) { display_overlay_controller_ = controller; } + void store_touch_injector_enable(bool enable) { + touch_injector_enable_ = enable; + } + void store_input_mapping_visible(bool enable) { + input_mapping_visible_ = enable; + } // Parse Json to actions. // Json value format: @@ -108,14 +116,18 @@ &ui::EventSource::RemoveEventRewriter> observation_{this}; std::unique_ptr<KeyCommand> mouse_lock_; - // It is used temporarily for switching view and edit mode. - // TODO(cuicuiruan): Remove this after the entry point is ready. - std::unique_ptr<KeyCommand> switch_mode_; bool text_input_active_ = false; // The mouse is unlocked by default. bool is_mouse_locked_ = false; DisplayMode display_mode_ = DisplayMode::kView; DisplayOverlayController* display_overlay_controller_ = nullptr; + // Linked to game controller toggle in the menu. Set it enabled by default. + // This is to save status if display overlay is destroyed during window + // operations. + bool touch_injector_enable_ = true; + // Linked to input mapping toggle in the menu. Set it enabled by default. This + // is to save status if display overlay is destroyed during window operations. + bool input_mapping_visible_ = true; base::WeakPtrFactory<TouchInjector> weak_ptr_factory_{this}; };
diff --git a/chrome/browser/ash/arc/input_overlay/ui/input_menu_view.cc b/chrome/browser/ash/arc/input_overlay/ui/input_menu_view.cc index e3000826..bda76741 100644 --- a/chrome/browser/ash/arc/input_overlay/ui/input_menu_view.cc +++ b/chrome/browser/ash/arc/input_overlay/ui/input_menu_view.cc
@@ -125,7 +125,8 @@ &InputMenuView::OnToggleGameControlPressed, base::Unretained(this)))); game_control_toggle_->SetAccessibleName( l10n_util::GetStringUTF16(IDS_INPUT_OVERLAY_MENU_GAME_CONTROL)); - game_control_toggle_->SetIsOn(true); + game_control_toggle_->SetIsOn( + display_overlay_controller_->GetTouchInjectorEnable()); auto close_icon = gfx::CreateVectorIcon(vector_icons::kCloseIcon, color); auto close_button = std::make_unique<views::ImageButton>( @@ -247,8 +248,15 @@ } void InputMenuView::OnToggleGameControlPressed() { - // TODO(djacobo|cuicuiruan): General knob to turn feature ON/OFF, determinate - // functionality of this main knob. + DCHECK(display_overlay_controller_); + if (!display_overlay_controller_) + return; + display_overlay_controller_->SetTouchInjectorEnable( + game_control_toggle_->GetIsOn()); + // Also show/hide the input mapping when enable/disable the game controller. + show_hint_toggle_->SetIsOn(game_control_toggle_->GetIsOn()); + display_overlay_controller_->SetInputMappingVisible( + show_hint_toggle_->GetIsOn()); } void InputMenuView::OnToggleShowHintPressed() {
diff --git a/chrome/browser/ash/crosapi/arc_ash.cc b/chrome/browser/ash/crosapi/arc_ash.cc index b21b45f..d8f627b 100644 --- a/chrome/browser/ash/crosapi/arc_ash.cc +++ b/chrome/browser/ash/crosapi/arc_ash.cc
@@ -69,6 +69,13 @@ intent->extras); } +void OnIsInstallable(mojom::Arc::IsInstallableCallback callback, + bool installable) { + std::move(callback).Run( + installable ? crosapi::mojom::IsInstallableResult::kInstallable + : crosapi::mojom::IsInstallableResult::kNotInstallable); +} + } // namespace ArcAsh::ArcAsh() = default; @@ -357,6 +364,24 @@ instance->AddPreferredPackage(package_name); } +void ArcAsh::IsInstallable(const std::string& package_name, + IsInstallableCallback callback) { + auto* arc_service_manager = arc::ArcServiceManager::Get(); + if (!arc_service_manager) { + std::move(callback).Run( + crosapi::mojom::IsInstallableResult::kArcIsNotRunning); + return; + } + auto* instance = ARC_GET_INSTANCE_FOR_METHOD( + arc_service_manager->arc_bridge_service()->app(), IsInstallable); + if (!instance) { + std::move(callback).Run(crosapi::mojom::IsInstallableResult::kArcIsTooOld); + return; + } + instance->IsInstallable( + package_name, base::BindOnce(&OnIsInstallable, std::move(callback))); +} + void ArcAsh::OnIconInvalidated(const std::string& package_name) { for (auto& observer : observers_) observer->OnIconInvalidated(package_name);
diff --git a/chrome/browser/ash/crosapi/arc_ash.h b/chrome/browser/ash/crosapi/arc_ash.h index b6d2383..c61a5fc7 100644 --- a/chrome/browser/ash/crosapi/arc_ash.h +++ b/chrome/browser/ash/crosapi/arc_ash.h
@@ -49,6 +49,8 @@ void HandleIntent(mojom::IntentInfoPtr intent, mojom::ActivityNamePtr activity) override; void AddPreferredPackage(const std::string& package_name) override; + void IsInstallable(const std::string& package_name, + IsInstallableCallback callback) override; // arc::ArcIntentHelperObserver: void OnIconInvalidated(const std::string& package_name) override;
diff --git a/chrome/browser/ash/eche_app/eche_app_manager_factory.cc b/chrome/browser/ash/eche_app/eche_app_manager_factory.cc index 8d8493f..4e0e706 100644 --- a/chrome/browser/ash/eche_app/eche_app_manager_factory.cc +++ b/chrome/browser/ash/eche_app/eche_app_manager_factory.cc
@@ -8,9 +8,13 @@ #include "ash/components/phonehub/phone_hub_manager.h" #include "ash/constants/ash_features.h" +#include "ash/root_window_controller.h" #include "ash/services/secure_channel/presence_monitor_impl.h" #include "ash/services/secure_channel/public/cpp/client/presence_monitor_client_impl.h" #include "ash/services/secure_channel/public/cpp/shared/presence_monitor.h" +#include "ash/shell.h" +#include "ash/system/eche/eche_tray.h" +#include "ash/system/status_area_widget.h" #include "ash/webui/eche_app_ui/apps_access_manager_impl.h" #include "ash/webui/eche_app_ui/eche_app_manager.h" #include "ash/webui/eche_app_ui/eche_uid_provider.h" @@ -27,6 +31,7 @@ #include "chrome/browser/ash/profiles/profile_helper.h" #include "chrome/browser/ash/secure_channel/nearby_connector_factory.h" #include "chrome/browser/ash/secure_channel/secure_channel_client_provider.h" +#include "chrome/browser/ash/web_applications/eche_app_info.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_window.h" @@ -40,6 +45,8 @@ #include "third_party/abseil-cpp/absl/types/optional.h" #include "ui/base/l10n/l10n_util.h" #include "ui/chromeos/devicetype_utils.h" +#include "ui/views/view.h" +#include "ui/views/widget/widget.h" #include "url/gurl.h" namespace ash { @@ -47,7 +54,20 @@ namespace { -void CloseEcheApp(Profile* profile) { +EcheTray* GetEcheTray() { + return Shell::GetPrimaryRootWindowController() + ->GetStatusAreaWidget() + ->eche_tray(); +} + +void CloseEche(Profile* profile) { + if (features::IsEcheCustomWidgetEnabled()) { + auto* eche_tray = GetEcheTray(); + if (eche_tray) { + eche_tray->PurgeAndClose(); + } + return; + } for (auto* browser : *(BrowserList::GetInstance())) { if (browser->profile() != profile) continue; @@ -71,18 +91,26 @@ kMaxValue = kOpenAppStreaming, }; -void LaunchSystemWebApp(Profile* profile, - const std::string& package_name, - const absl::optional<int64_t>& notification_id, - const std::u16string& visible_name, - const absl::optional<int64_t>& user_id) { +void LaunchBubble(const GURL& url) { + auto* eche_tray = GetEcheTray(); + // TODO(nayebi): if it is null log an error? Dcheck? + if (eche_tray) { + eche_tray->SetUrl(url); + eche_tray->ShowBubble(); + } +} + +void LaunchWebApp(const std::string& package_name, + const absl::optional<int64_t>& notification_id, + const std::u16string& visible_name, + const absl::optional<int64_t>& user_id, + Profile* profile) { EcheAppManagerFactory::GetInstance()->SetLastLaunchedAppInfo( LaunchedAppInfo::Builder() .SetPackageName(package_name) .SetVisibleName(visible_name) .SetUserId(user_id) .Build()); - std::u16string url; // Use hash mark(#) to send params to webui so we don't need to reload the // whole eche window. @@ -107,9 +135,13 @@ url.append(u"&user_id="); url.append(base::NumberToString16(user_id.value())); } + const auto gurl = GURL(url); + if (features::IsEcheCustomWidgetEnabled()) { + return LaunchBubble(gurl); + } web_app::SystemAppLaunchParams params; - params.url = GURL(url); + params.url = gurl; web_app::LaunchSystemWebAppAsync(profile, web_app::SystemAppType::ECHE, params); } @@ -119,8 +151,7 @@ const std::string& package_name, const std::u16string& visible_name, const absl::optional<int64_t>& user_id) { - LaunchSystemWebApp(profile, package_name, notification_id, visible_name, - user_id); + LaunchWebApp(package_name, notification_id, visible_name, user_id, profile); base::UmaHistogramEnumeration("Eche.NotificationClicked", NotificationInteraction::kOpenAppStreaming); EcheAppManagerFactory::GetInstance() @@ -237,7 +268,7 @@ device_sync_client, multidevice_setup_client, secure_channel_client, std::move(presence_monitor_client), base::BindRepeating(&LaunchEcheApp, profile), - base::BindRepeating(&CloseEcheApp, profile), + base::BindRepeating(&CloseEche, profile), base::BindRepeating(&EcheAppManagerFactory::ShowNotification, weak_ptr_factory_.GetWeakPtr(), profile)); }
diff --git a/chrome/browser/ash/login/demo_mode/demo_session.cc b/chrome/browser/ash/login/demo_mode/demo_session.cc index 1c30a98..4fe8f59 100644 --- a/chrome/browser/ash/login/demo_mode/demo_session.cc +++ b/chrome/browser/ash/login/demo_mode/demo_session.cc
@@ -607,7 +607,7 @@ void DemoSession::OnAppUpdate(const apps::AppUpdate& update) { if (update.AppId() != GetHighlightsAppId() || - !(update.GetPriorReadiness() == apps::Readiness::kUnknown && + !(update.PriorReadiness() == apps::Readiness::kUnknown && update.GetReadiness() == apps::Readiness::kReady)) { return; }
diff --git a/chrome/browser/ash/policy/status_collector/managed_session_service.cc b/chrome/browser/ash/policy/status_collector/managed_session_service.cc index 8a33231d1..48543d6 100644 --- a/chrome/browser/ash/policy/status_collector/managed_session_service.cc +++ b/chrome/browser/ash/policy/status_collector/managed_session_service.cc
@@ -48,7 +48,7 @@ void ManagedSessionService::AddObserver( ManagedSessionService::Observer* observer) { observers_.AddObserver(observer); - if (is_logged_in_) { + if (is_logged_in_observed_) { if (user_manager::UserManager::Get()->IsLoggedInAsGuest()) { observer->OnGuestLogin(); } else { @@ -87,7 +87,7 @@ ash::ProfileHelper::Get()->GetProfileByAccountId(account_id); bool is_primary_profile = ash::ProfileHelper::Get()->IsPrimaryProfile(profile); - if (is_logged_in_ && is_primary_profile) { + if (is_logged_in_observed_ && is_primary_profile) { return; } else if (!is_primary_profile) { profile_observations_.AddObservation(profile); @@ -158,24 +158,30 @@ } void ManagedSessionService::SetLoginStatus() { - if (is_logged_in_) { + if (is_logged_in_observed_ || !user_manager::UserManager::Get() || + !user_manager::UserManager::Get()->IsUserLoggedIn()) { return; } - if (user_manager::UserManager::Get() && - user_manager::UserManager::Get()->IsUserLoggedIn() && - user_manager::UserManager::Get()->GetPrimaryUser()) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + auto* const primary_user = user_manager::UserManager::Get()->GetPrimaryUser(); + if (!primary_user || !primary_user->is_profile_created()) { + return; + } - is_logged_in_ = true; - if (!user_manager::UserManager::Get()->IsLoggedInAsGuest()) { - auto* const profile = ash::ProfileHelper::Get()->GetProfileByUser( - user_manager::UserManager::Get()->GetPrimaryUser()); - profile_observations_.AddObservation(profile); - } - if (ash::SessionTerminationManager::Get()) { - ash::SessionTerminationManager::Get()->AddObserver(this); - } + auto* const profile = + ash::ProfileHelper::Get()->GetProfileByUser(primary_user); + if (!profile) { + // Profile is not fully initialized yet. + return; + } + + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + is_logged_in_observed_ = true; + if (!user_manager::UserManager::Get()->IsLoggedInAsGuest()) { + profile_observations_.AddObservation(profile); + } + if (ash::SessionTerminationManager::Get()) { + ash::SessionTerminationManager::Get()->AddObserver(this); } } } // namespace policy
diff --git a/chrome/browser/ash/policy/status_collector/managed_session_service.h b/chrome/browser/ash/policy/status_collector/managed_session_service.h index 7bcc5c2..bdb3319 100644 --- a/chrome/browser/ash/policy/status_collector/managed_session_service.h +++ b/chrome/browser/ash/policy/status_collector/managed_session_service.h
@@ -109,7 +109,7 @@ bool is_session_locked_; - bool is_logged_in_ = false; + bool is_logged_in_observed_ = false; base::Clock* clock_;
diff --git a/chrome/browser/ash/policy/status_collector/managed_session_service_unittest.cc b/chrome/browser/ash/policy/status_collector/managed_session_service_unittest.cc index 575262a6..9af3f5a 100644 --- a/chrome/browser/ash/policy/status_collector/managed_session_service_unittest.cc +++ b/chrome/browser/ash/policy/status_collector/managed_session_service_unittest.cc
@@ -63,6 +63,12 @@ void GuestLogin() { user_manager::User* const user = user_manager_->AddGuestUser(); + TestingProfile::Builder profile_builder; + profile_builder.SetProfileName(user->GetAccountId().GetUserEmail()); + auto profile = profile_builder.Build(); + ash::ProfileHelper::Get()->SetProfileToUserMappingForTesting(user); + ash::ProfileHelper::Get()->SetUserToProfileMappingForTesting(user, + profile.get()); user_manager_->LoginUser(user->GetAccountId(), true); } @@ -74,6 +80,8 @@ return &session_manager_; } + ash::FakeChromeUserManager* user_manager() { return user_manager_; } + chromeos::FakePowerManagerClient* power_manager_client() { return chromeos::FakePowerManagerClient::Get(); } @@ -327,4 +335,28 @@ EXPECT_EQ(ObservedSessionTerminationCount(), 1); } + +TEST_F(ManagedSessionServiceTest, LoggedInProfileNotCreated) { + const AccountId account_id = AccountId::FromUserEmail("user0@managed.com"); + auto* const user = user_manager()->AddUser(account_id); + // User logged in but profile is not created. + user_manager()->LoginUser(account_id, /*set_profile_created_flag=*/false); + + ManagedSessionService managed_session_service; + managed_session_service.AddObserver(this); + + EXPECT_EQ(ObservedLoginCount(), 0); + + // Simulate user profile loaded. + TestingProfile::Builder profile_builder; + profile_builder.SetProfileName(account_id.GetUserEmail()); + auto profile = profile_builder.Build(); + ash::ProfileHelper::Get()->SetUserToProfileMappingForTesting(user, + profile.get()); + user_manager()->SimulateUserProfileLoad(account_id); + session_manager()->NotifyUserProfileLoaded(account_id); + + ASSERT_EQ(ObservedLoginCount(), 1); + EXPECT_TRUE(profile->IsSameOrParent(logged_in_)); +} } // namespace policy
diff --git a/chrome/browser/ash/web_applications/eche_app_info.cc b/chrome/browser/ash/web_applications/eche_app_info.cc index 802506c..fe742456 100644 --- a/chrome/browser/ash/web_applications/eche_app_info.cc +++ b/chrome/browser/ash/web_applications/eche_app_info.cc
@@ -16,6 +16,13 @@ #include "third_party/blink/public/mojom/manifest/display_mode.mojom.h" #include "ui/display/screen.h" +namespace { + +constexpr float kDefaultAspectRatio = 16.0 / 9.0f; +constexpr gfx::Size kMinimumEcheSize(240, 240); + +} // namespace + std::unique_ptr<WebAppInstallInfo> CreateWebAppInfoForEcheApp() { std::unique_ptr<WebAppInstallInfo> info = std::make_unique<WebAppInstallInfo>(); @@ -35,24 +42,6 @@ return info; } -gfx::Rect GetDefaultBoundsForEche(Browser*) { - // Ensures the Eche bounds is always 16:9 portrait aspect ratio and not more - // than half of the windows. - const float aspect_ratio = 16.0f / 9.0f; - const gfx::Size min_size(240, 240); - - gfx::Rect bounds = - display::Screen::GetScreen()->GetDisplayForNewWindows().work_area(); - const float bounds_aspect_ratio = bounds.width() / bounds.height(); - const bool is_landscape = (bounds_aspect_ratio >= 1); - auto new_width = is_landscape ? (bounds.height() / 2) : bounds.width() / 2; - if (min_size.width() > new_width) { - new_width = min_size.width(); - } - bounds.ClampToCenteredSize(gfx::Size(new_width, new_width * aspect_ratio)); - return bounds; -} - EcheSystemAppDelegate::EcheSystemAppDelegate(Profile* profile) : web_app::SystemWebAppDelegate(web_app::SystemAppType::ECHE, "Eche", @@ -92,9 +81,25 @@ } gfx::Rect EcheSystemAppDelegate::GetDefaultBounds(Browser* browser) const { - return GetDefaultBoundsForEche(browser); + return GetDefaultBoundsForEche(); } bool EcheSystemAppDelegate::IsAppEnabled() const { return base::FeatureList::IsEnabled(chromeos::features::kEcheSWA); } + +gfx::Rect EcheSystemAppDelegate::GetDefaultBoundsForEche() const { + // Ensures the Eche bounds is always 16:9 portrait aspect ratio and not more + // than half of the windows. + gfx::Rect bounds = + display::Screen::GetScreen()->GetDisplayForNewWindows().work_area(); + const float bounds_aspect_ratio = bounds.width() / bounds.height(); + const bool is_landscape = (bounds_aspect_ratio >= 1); + auto new_width = is_landscape ? (bounds.height() / 2) : bounds.width() / 2; + if (kMinimumEcheSize.width() > new_width) { + new_width = kMinimumEcheSize.width(); + } + bounds.ClampToCenteredSize( + gfx::Size(new_width, new_width * kDefaultAspectRatio)); + return bounds; +}
diff --git a/chrome/browser/ash/web_applications/eche_app_info.h b/chrome/browser/ash/web_applications/eche_app_info.h index 949bb616..8e99bec5 100644 --- a/chrome/browser/ash/web_applications/eche_app_info.h +++ b/chrome/browser/ash/web_applications/eche_app_info.h
@@ -27,12 +27,10 @@ bool ShouldAllowScriptsToCloseWindows() const override; gfx::Rect GetDefaultBounds(Browser*) const override; bool IsAppEnabled() const override; + gfx::Rect GetDefaultBoundsForEche() const; }; // Return a WebAppInstallInfo used to install the app. std::unique_ptr<WebAppInstallInfo> CreateWebAppInfoForEcheApp(); -// Returns the default bounds. -gfx::Rect GetDefaultBoundsForEche(Browser*); - #endif // CHROME_BROWSER_ASH_WEB_APPLICATIONS_ECHE_APP_INFO_H_
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl_unittest.cc b/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl_unittest.cc index db99aa1..132d15e 100644 --- a/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl_unittest.cc +++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl_unittest.cc
@@ -48,6 +48,10 @@ albums_ = std::move(albums); } + // TODO: to be updated in b/219247662. + void OnTemperatureUnitChanged( + ash::AmbientModeTemperatureUnit temperature_unit) override {} + mojo::PendingRemote<ash::personalization_app::mojom::AmbientObserver> pending_remote() { if (ambient_observer_receiver_.is_bound()) {
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc index 82618784..22d8c6d 100644 --- a/chrome/browser/chrome_browser_interface_binders.cc +++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -210,6 +210,8 @@ #include "ash/webui/media_app_ui/media_app_ui.h" #include "ash/webui/media_app_ui/media_app_ui.mojom.h" #include "ash/webui/multidevice_debug/proximity_auth_ui.h" +#include "ash/webui/os_feedback_ui/mojom/os_feedback_ui.mojom.h" +#include "ash/webui/os_feedback_ui/os_feedback_ui.h" #include "ash/webui/personalization_app/mojom/personalization_app.mojom.h" #include "ash/webui/personalization_app/personalization_app_ui.h" #include "ash/webui/print_management/mojom/printing_manager.mojom.h" @@ -990,6 +992,12 @@ RegisterWebUIControllerInterfaceBinder< ash::common::mojom::AccessibilityFeatures, ash::ScanningUI>(map); + if (base::FeatureList::IsEnabled(ash::features::kOsFeedback)) { + RegisterWebUIControllerInterfaceBinder< + ash::os_feedback_ui::mojom::HelpContentProvider, ash::OSFeedbackUI>( + map); + } + // TODO(crbug.com/1218492): When boot RMA state is available disable this when // not in RMA. if (ash::features::IsShimlessRMAFlowEnabled()) {
diff --git a/chrome/browser/download/bubble/download_display.h b/chrome/browser/download/bubble/download_display.h index 558562b..e3feb1b 100644 --- a/chrome/browser/download/bubble/download_display.h +++ b/chrome/browser/download/bubble/download_display.h
@@ -21,6 +21,9 @@ virtual void Disable() = 0; // Updates the download icon according to |state|. virtual void UpdateDownloadIcon(download::DownloadIconState state) = 0; + // Shows detailed information on the download display. It can be a popup or + // dialog other than the main view. + virtual void ShowDetails() = 0; protected: virtual ~DownloadDisplay();
diff --git a/chrome/browser/download/bubble/download_display_controller.cc b/chrome/browser/download/bubble/download_display_controller.cc index 3ded524..b3a0c5f 100644 --- a/chrome/browser/download/bubble/download_display_controller.cc +++ b/chrome/browser/download/bubble/download_display_controller.cc
@@ -9,9 +9,13 @@ #include "chrome/browser/download/bubble/download_display.h" #include "chrome/browser/download/bubble/download_icon_state.h" #include "chrome/browser/download/download_item_model.h" +#include "chrome/browser/download/download_prefs.h" namespace { -constexpr int kToolbarIconVisbilityHours = 24; + +// The amount of time for the toolbar icon to be visible after a download is +// completed. +constexpr base::TimeDelta kToolbarIconVisibilityTimeInterval = base::Hours(24); } DownloadDisplayController::DownloadDisplayController( @@ -19,7 +23,9 @@ content::DownloadManager* download_manager) : display_(display), download_manager_(download_manager), - download_notifier_(download_manager, this) {} + download_notifier_(download_manager, this) { + MaybeShowButtonWhenCreated(); +} DownloadDisplayController::~DownloadDisplayController() = default; @@ -35,7 +41,7 @@ DownloadItemModel item_model(item); if (item_model.IsDone()) { - ScheduleToolbarDisappearance(base::Hours(kToolbarIconVisbilityHours)); + ScheduleToolbarDisappearance(kToolbarIconVisibilityTimeInterval); } UpdateToolbarButtonState(); } @@ -47,10 +53,13 @@ } } -void DownloadDisplayController::ShowToolbarButton() { +void DownloadDisplayController::ShowToolbarButton(bool show_details) { if (!display_->IsShowing()) { display_->Enable(); display_->Show(); + if (show_details) { + display_->ShowDetails(); + } } } @@ -64,7 +73,7 @@ download::DownloadIconState icon_state; if (download_manager_->InProgressCount() > 0) { - ShowToolbarButton(); + ShowToolbarButton(/*show_details=*/true); icon_state = download::DownloadIconState::kProgress; } else { icon_state = download::DownloadIconState::kComplete; @@ -80,6 +89,25 @@ FROM_HERE, interval, this, &DownloadDisplayController::HideToolbarButton); } +void DownloadDisplayController::MaybeShowButtonWhenCreated() { + base::Time last_complete_time = + DownloadPrefs::FromDownloadManager(download_manager_) + ->GetLastCompleteTime(); + base::Time current_time = base::Time::Now(); + base::TimeDelta time_since_last_completion = + current_time - last_complete_time; + // If the last download complete time is less than + // `kToolbarIconVisibilityTimeInterval` ago, show the button immediately. Also + // check that the current time is not smaller than the last complete time, + // this can happen if the system clock has moved backward. + if (time_since_last_completion < kToolbarIconVisibilityTimeInterval && + current_time >= last_complete_time) { + ShowToolbarButton(/*show_details=*/false); + ScheduleToolbarDisappearance(kToolbarIconVisibilityTimeInterval - + time_since_last_completion); + } +} + DownloadDisplayController::ProgressInfo DownloadDisplayController::GetProgress() { DownloadDisplayController::ProgressInfo progress_info;
diff --git a/chrome/browser/download/bubble/download_display_controller.h b/chrome/browser/download/bubble/download_display_controller.h index ba48e83..07ee292 100644 --- a/chrome/browser/download/bubble/download_display_controller.h +++ b/chrome/browser/download/bubble/download_display_controller.h
@@ -42,15 +42,29 @@ // This implementation will match the one in download_status_updater.cc ProgressInfo GetProgress(); - void ShowToolbarButton(); + // Asks `display_` to show the toolbar button. Does nothing if the toolbar + // button is already showing. If `show_details` is true, `display_` will + // show a dialog alongside with the button. + void ShowToolbarButton(bool show_details); + // Asks `display_` to hide the toolbar button. Does nothing if the toolbar + // button is already hidden. void HideToolbarButton(); + download::AllDownloadItemNotifier& get_download_notifier_for_testing() { + return download_notifier_; + } + private: + // Stops and restarts `icon_disappearance_timer_`. The toolbar button will + // be hidden after the `interval`. void ScheduleToolbarDisappearance(base::TimeDelta interval); + // Based on the information from `download_manager_`, updates the icon state + // of the `display_`. void UpdateToolbarButtonState(); - void UpdateDownloadIconDisplayState(); + // Decides whether the toolbar button should be shown when it is created. + void MaybeShowButtonWhenCreated(); // AllDownloadItemNotifier::Observer void OnDownloadCreated(content::DownloadManager* manager,
diff --git a/chrome/browser/download/bubble/download_display_controller_unittest.cc b/chrome/browser/download/bubble/download_display_controller_unittest.cc index ce75e65..217c0e5 100644 --- a/chrome/browser/download/bubble/download_display_controller_unittest.cc +++ b/chrome/browser/download/bubble/download_display_controller_unittest.cc
@@ -5,7 +5,15 @@ #include "chrome/browser/download/bubble/download_display_controller.h" #include "chrome/browser/download/bubble/download_display.h" #include "chrome/browser/download/bubble/download_icon_state.h" +#include "chrome/browser/download/chrome_download_manager_delegate.h" +#include "chrome/browser/download/download_core_service.h" +#include "chrome/browser/download/download_core_service_factory.h" +#include "chrome/browser/download/download_prefs.h" +#include "chrome/test/base/testing_browser_process.h" +#include "chrome/test/base/testing_profile.h" +#include "chrome/test/base/testing_profile_manager.h" #include "components/download/public/common/mock_download_item.h" +#include "content/public/test/browser_task_environment.h" #include "content/public/test/mock_download_manager.h" #include "content/public/test/test_utils.h" #include "testing/gtest/include/gtest/gtest.h" @@ -17,6 +25,8 @@ namespace { using StrictMockDownloadItem = testing::StrictMock<download::MockDownloadItem>; +using DownloadIconState = download::DownloadIconState; +using DownloadState = download::DownloadItem::DownloadState; class FakeDownloadDisplay : public DownloadDisplay { public: @@ -26,7 +36,10 @@ void Show() override { shown_ = true; } - void Hide() override { shown_ = false; } + void Hide() override { + shown_ = false; + detail_shown_ = false; + } bool IsShowing() override { return shown_; } @@ -34,27 +47,67 @@ void Disable() override { enabled_ = false; } - void UpdateDownloadIcon(download::DownloadIconState state) override {} + void UpdateDownloadIcon(DownloadIconState state) override { + icon_state_ = state; + } + + void ShowDetails() override { detail_shown_ = true; } + + bool IsDetailsShown() { return detail_shown_; } + + DownloadIconState GetDownloadIconState() { return icon_state_; } private: bool shown_ = false; bool enabled_ = false; + DownloadIconState icon_state_ = DownloadIconState::kComplete; + bool detail_shown_ = false; }; class DownloadDisplayControllerTest : public testing::Test { public: DownloadDisplayControllerTest() - : manager_(std::make_unique<NiceMock<content::MockDownloadManager>>()) {} + : manager_(std::make_unique<NiceMock<content::MockDownloadManager>>()), + testing_profile_manager_(TestingBrowserProcess::GetGlobal()) {} DownloadDisplayControllerTest(const DownloadDisplayControllerTest&) = delete; DownloadDisplayControllerTest& operator=( const DownloadDisplayControllerTest&) = delete; + void SetUp() override { + ASSERT_TRUE(testing_profile_manager_.SetUp()); + + Profile* profile = + testing_profile_manager_.CreateTestingProfile("testing_profile"); + EXPECT_CALL(*manager_.get(), GetBrowserContext()) + .WillRepeatedly(Return(profile)); + + // Set test delegate to get the corresponding download prefs. + auto delegate = std::make_unique<ChromeDownloadManagerDelegate>(profile); + DownloadCoreServiceFactory::GetForBrowserContext(profile) + ->SetDownloadManagerDelegateForTesting(std::move(delegate)); + + display_ = std::make_unique<FakeDownloadDisplay>(); + controller_ = std::make_unique<DownloadDisplayController>(display_.get(), + manager_.get()); + } + + void TearDown() override { + for (auto& item : items_) { + item->RemoveObserver(&controller_->get_download_notifier_for_testing()); + } + // The controller needs to be reset before download manager, because the + // download_notifier_ will unregister itself from the manager. + controller_.reset(); + } + protected: NiceMock<content::MockDownloadManager>& manager() { return *manager_.get(); } download::MockDownloadItem& item(size_t index) { return *items_[index]; } + FakeDownloadDisplay& display() { return *display_; } + DownloadDisplayController& controller() { return *controller_; } void InitDownloadItem(const base::FilePath::CharType* path, - download::DownloadItem::DownloadState state) { + DownloadState state) { size_t index = items_.size(); items_.push_back(std::make_unique<StrictMockDownloadItem>()); EXPECT_CALL(item(index), GetId()) @@ -65,6 +118,10 @@ EXPECT_CALL(item(index), GetReceivedBytes()) .WillRepeatedly(Return(received_bytes)); EXPECT_CALL(item(index), GetTotalBytes()).WillRepeatedly(Return(100)); + EXPECT_CALL(item(index), IsDone()).WillRepeatedly(Return(false)); + in_progress_count_++; + EXPECT_CALL(manager(), InProgressCount()) + .WillRepeatedly(Return(in_progress_count_)); std::vector<download::DownloadItem*> items; for (size_t i = 0; i < items_.size(); ++i) { @@ -72,11 +129,60 @@ } EXPECT_CALL(*manager_.get(), GetAllDownloads(_)) .WillRepeatedly(SetArgPointee<0>(items)); + item(index).AddObserver(&controller().get_download_notifier_for_testing()); + item(index).NotifyObserversDownloadUpdated(); } + void UpdateDownloadItem(int item_index, DownloadState state) { + DCHECK_GT(items_.size(), static_cast<size_t>(item_index)); + + EXPECT_CALL(item(item_index), GetState()).WillRepeatedly(Return(state)); + if (state == DownloadState::COMPLETE) { + EXPECT_CALL(item(item_index), IsDone()).WillRepeatedly(Return(true)); + in_progress_count_--; + EXPECT_CALL(manager(), InProgressCount()) + .WillRepeatedly(Return(in_progress_count_)); + } else { + EXPECT_CALL(item(item_index), IsDone()).WillRepeatedly(Return(false)); + } + item(item_index).NotifyObserversDownloadUpdated(); + } + + bool VerifyDisplayState(bool shown, + bool detail_shown, + DownloadIconState icon_state) { + bool success = true; + if (shown != display().IsShowing()) { + success = false; + ADD_FAILURE() << "Display should have shown state " << shown + << ", but found " << display().IsShowing(); + } + if (detail_shown != display().IsDetailsShown()) { + success = false; + ADD_FAILURE() << "Display should have detailed shown state " + << detail_shown << ", but found " + << display().IsDetailsShown(); + } + if (icon_state != display().GetDownloadIconState()) { + success = false; + ADD_FAILURE() << "Display should have detailed icon state " + << static_cast<int>(icon_state) << ", but found " + << static_cast<int>(display().GetDownloadIconState()); + } + return success; + } + + content::BrowserTaskEnvironment task_environment_{ + base::test::TaskEnvironment::TimeSource::MOCK_TIME}; + private: + int in_progress_count_ = 0; + + std::unique_ptr<DownloadDisplayController> controller_; + std::unique_ptr<FakeDownloadDisplay> display_; std::vector<std::unique_ptr<StrictMockDownloadItem>> items_; std::unique_ptr<NiceMock<content::MockDownloadManager>> manager_; + TestingProfileManager testing_profile_manager_; }; TEST_F(DownloadDisplayControllerTest, GetProgressItemsInProgress) { @@ -86,9 +192,7 @@ download::DownloadItem::COMPLETE); InitDownloadItem(FILE_PATH_LITERAL("/foo/bar4.pdf"), download::DownloadItem::IN_PROGRESS); - FakeDownloadDisplay display; - DownloadDisplayController controller(&display, &manager()); - DownloadDisplayController::ProgressInfo progress = controller.GetProgress(); + DownloadDisplayController::ProgressInfo progress = controller().GetProgress(); EXPECT_EQ(progress.download_count, 2); EXPECT_EQ(progress.progress_percentage, 50); @@ -99,11 +203,94 @@ download::DownloadItem::COMPLETE); InitDownloadItem(FILE_PATH_LITERAL("/foo/bar2.pdf"), download::DownloadItem::COMPLETE); - FakeDownloadDisplay display; - DownloadDisplayController controller(&display, &manager()); - DownloadDisplayController::ProgressInfo progress = controller.GetProgress(); + DownloadDisplayController::ProgressInfo progress = controller().GetProgress(); EXPECT_EQ(progress.download_count, 0); EXPECT_EQ(progress.progress_percentage, 0); } + +TEST_F(DownloadDisplayControllerTest, UpdateToolbarButtonState) { + EXPECT_FALSE(display().IsShowing()); + + InitDownloadItem(FILE_PATH_LITERAL("/foo/bar.pdf"), + download::DownloadItem::IN_PROGRESS); + EXPECT_TRUE(VerifyDisplayState(/*shown=*/true, /*detail_shown=*/true, + /*icon_state=*/DownloadIconState::kProgress)); + + UpdateDownloadItem(/*item_index=*/0, DownloadState::COMPLETE); + EXPECT_TRUE(VerifyDisplayState(/*shown=*/true, /*detail_shown=*/true, + /*icon_state=*/DownloadIconState::kComplete)); + + task_environment_.FastForwardBy(base::Hours(23)); + // The display is still showing because the last download is less than 1 + // day ago. + EXPECT_TRUE(VerifyDisplayState(/*shown=*/true, /*detail_shown=*/true, + /*icon_state=*/DownloadIconState::kComplete)); + + task_environment_.FastForwardBy(base::Hours(1)); + // The display should stop showing once the last download is more than 1 + // day ago. + EXPECT_TRUE(VerifyDisplayState(/*shown=*/false, /*detail_shown=*/false, + /*icon_state=*/DownloadIconState::kComplete)); +} + +TEST_F(DownloadDisplayControllerTest, + UpdateToolbarButtonState_MultipleDownloads) { + EXPECT_FALSE(display().IsShowing()); + + InitDownloadItem(FILE_PATH_LITERAL("/foo/bar.pdf"), + download::DownloadItem::IN_PROGRESS); + EXPECT_TRUE(VerifyDisplayState(/*shown=*/true, /*detail_shown=*/true, + /*icon_state=*/DownloadIconState::kProgress)); + + InitDownloadItem(FILE_PATH_LITERAL("/foo/bar2.pdf"), + download::DownloadItem::IN_PROGRESS); + EXPECT_TRUE(VerifyDisplayState(/*shown=*/true, /*detail_shown=*/true, + /*icon_state=*/DownloadIconState::kProgress)); + + UpdateDownloadItem(/*item_index=*/0, DownloadState::COMPLETE); + // The download icon state is still kProgress because not all downloads are + // completed. + EXPECT_TRUE(VerifyDisplayState(/*shown=*/true, /*detail_shown=*/true, + /*icon_state=*/DownloadIconState::kProgress)); + + UpdateDownloadItem(/*item_index=*/1, DownloadState::COMPLETE); + EXPECT_TRUE(VerifyDisplayState(/*shown=*/true, /*detail_shown=*/true, + /*icon_state=*/DownloadIconState::kComplete)); +} + +TEST_F(DownloadDisplayControllerTest, InitialState_OldLastDownload) { + base::Time current_time = base::Time::Now(); + // Set the last complete time to more than 1 day ago. + DownloadPrefs::FromDownloadManager(&manager()) + ->SetLastCompleteTime(current_time - base::Hours(25)); + + DownloadDisplayController controller(&display(), &manager()); + EXPECT_TRUE(VerifyDisplayState(/*shown=*/false, /*detail_shown=*/false, + /*icon_state=*/DownloadIconState::kComplete)); +} + +TEST_F(DownloadDisplayControllerTest, InitialState_NewLastDownload) { + base::Time current_time = base::Time::Now(); + // Set the last complete time to less than 1 day ago. + DownloadPrefs::FromDownloadManager(&manager()) + ->SetLastCompleteTime(current_time - base::Hours(23)); + + DownloadDisplayController controller(&display(), &manager()); + // The initial state should not display details. + EXPECT_TRUE(VerifyDisplayState(/*shown=*/true, /*detail_shown=*/false, + /*icon_state=*/DownloadIconState::kComplete)); + + // The display should stop showing once the last download is more than 1 day + // ago. + task_environment_.FastForwardBy(base::Hours(1)); + EXPECT_TRUE(VerifyDisplayState(/*shown=*/false, /*detail_shown=*/false, + /*icon_state=*/DownloadIconState::kComplete)); +} + +TEST_F(DownloadDisplayControllerTest, InitialState_NoLastDownload) { + DownloadDisplayController controller(&display(), &manager()); + EXPECT_TRUE(VerifyDisplayState(/*shown=*/false, /*detail_shown=*/false, + /*icon_state=*/DownloadIconState::kComplete)); +} } // namespace
diff --git a/chrome/browser/download/download_prefs.cc b/chrome/browser/download/download_prefs.cc index 673ed00..04679ec6 100644 --- a/chrome/browser/download/download_prefs.cc +++ b/chrome/browser/download/download_prefs.cc
@@ -286,6 +286,8 @@ default_download_path); registry->RegisterFilePathPref(prefs::kSaveFileDefaultDirectory, default_download_path); + registry->RegisterTimePref(prefs::kDownloadLastCompleteTime, + /*default_value=*/base::Time()); #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || \ BUILDFLAG(IS_MAC) registry->RegisterBooleanPref(prefs::kOpenPdfDownloadInSystemReader, false); @@ -370,6 +372,15 @@ save_file_type_.SetValue(type); } +base::Time DownloadPrefs::GetLastCompleteTime() { + return profile_->GetPrefs()->GetTime(prefs::kDownloadLastCompleteTime); +} + +void DownloadPrefs::SetLastCompleteTime(const base::Time& last_complete_time) { + profile_->GetPrefs()->SetTime(prefs::kDownloadLastCompleteTime, + last_complete_time); +} + bool DownloadPrefs::PromptForDownload() const { // If the DownloadDirectory policy is set, then |prompt_for_download_| should // always be false.
diff --git a/chrome/browser/download/download_prefs.h b/chrome/browser/download/download_prefs.h index 87eb569..a8b99054 100644 --- a/chrome/browser/download/download_prefs.h +++ b/chrome/browser/download/download_prefs.h
@@ -10,6 +10,7 @@ #include "base/files/file_path.h" #include "base/memory/raw_ptr.h" +#include "base/time/time.h" #include "build/build_config.h" #include "components/prefs/pref_change_registrar.h" #include "components/prefs/pref_member.h" @@ -78,6 +79,8 @@ void SetSaveFilePath(const base::FilePath& path); int save_file_type() const { return *save_file_type_; } void SetSaveFileType(int type); + base::Time GetLastCompleteTime(); + void SetLastCompleteTime(const base::Time& last_complete_time); DownloadRestriction download_restriction() const { return static_cast<DownloadRestriction>(*download_restriction_); }
diff --git a/chrome/browser/download/download_status_updater.cc b/chrome/browser/download/download_status_updater.cc index 1ea75052..80ac006 100644 --- a/chrome/browser/download/download_status_updater.cc +++ b/chrome/browser/download/download_status_updater.cc
@@ -11,6 +11,7 @@ #include "base/memory/ptr_util.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" +#include "chrome/browser/download/download_prefs.h" #include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h" #include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h" #include "chrome/browser/profiles/profile.h" @@ -114,6 +115,7 @@ void DownloadStatusUpdater::OnDownloadUpdated(content::DownloadManager* manager, download::DownloadItem* item) { + UpdatePrefsOnDownloadUpdated(manager, item); if (item->GetState() == download::DownloadItem::IN_PROGRESS) { // If the item was interrupted/cancelled and then resumed/restarted, then // set WasInProgress so that UpdateAppIconDownloadProgress() will be called @@ -183,3 +185,17 @@ // TODO(avi): Implement for Android? } #endif + +void DownloadStatusUpdater::UpdatePrefsOnDownloadUpdated( + content::DownloadManager* manager, + download::DownloadItem* download) { + if (!manager) { + // Can be null in tests. + return; + } + + if (download->GetState() == download::DownloadItem::COMPLETE) { + DownloadPrefs::FromDownloadManager(manager)->SetLastCompleteTime( + base::Time::Now()); + } +}
diff --git a/chrome/browser/download/download_status_updater.h b/chrome/browser/download/download_status_updater.h index 839af57..126f32a4 100644 --- a/chrome/browser/download/download_status_updater.h +++ b/chrome/browser/download/download_status_updater.h
@@ -61,6 +61,10 @@ // in-progress downloads, and the browser is not tearing down yet. void UpdateProfileKeepAlive(content::DownloadManager* manager); + // Updates the download prefs when downloads are updated. + void UpdatePrefsOnDownloadUpdated(content::DownloadManager* manager, + download::DownloadItem* download); + private: std::vector<std::unique_ptr<download::AllDownloadItemNotifier>> notifiers_; std::map<Profile*, std::unique_ptr<ScopedProfileKeepAlive>>
diff --git a/chrome/browser/download/download_status_updater_unittest.cc b/chrome/browser/download/download_status_updater_unittest.cc index f2c7477..53d7a93 100644 --- a/chrome/browser/download/download_status_updater_unittest.cc +++ b/chrome/browser/download/download_status_updater_unittest.cc
@@ -13,6 +13,11 @@ #include "base/test/scoped_feature_list.h" #include "chrome/browser/browser_features.h" #include "chrome/browser/browser_process.h" +#include "chrome/browser/download/chrome_download_manager_delegate.h" +#include "chrome/browser/download/download_core_service.h" +#include "chrome/browser/download/download_core_service_factory.h" +#include "chrome/browser/download/download_core_service_impl.h" +#include "chrome/browser/download/download_prefs.h" #include "chrome/browser/download/download_status_updater.h" #include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h" #include "chrome/browser/profiles/profile_manager.h" @@ -109,6 +114,9 @@ base::StringPrintf("Profile %d", i + 1)); testing_profiles_.push_back(profile); EXPECT_CALL(*mgr, GetBrowserContext()).WillRepeatedly(Return(profile)); + auto delegate = std::make_unique<ChromeDownloadManagerDelegate>(profile); + DownloadCoreServiceFactory::GetForBrowserContext(profile) + ->SetDownloadManagerDelegateForTesting(std::move(delegate)); updater_->AddManager(mgr); } @@ -200,9 +208,8 @@ // Thread so that the DownloadManager (which is a DeleteOnUIThread // object) can be deleted. - // TODO(rdsmith): This can be removed when the DownloadManager - // is no longer required to be deleted on the UI thread. - content::BrowserTaskEnvironment task_environment_; + content::BrowserTaskEnvironment task_environment_{ + base::test::TaskEnvironment::TimeSource::MOCK_TIME}; // To test ScopedProfileKeepAlive behavior. TestingProfileManager profile_manager_; @@ -398,3 +405,42 @@ EXPECT_FALSE(profile_manager->HasKeepAliveForTesting( profile1, ProfileKeepAliveOrigin::kDownloadInProgress)); } + +// Test that the last completion time is logged in pref. +TEST_F(DownloadStatusUpdaterTest, LogLastCompletionTimeInPrefs) { + SetupManagers(/*manager_count=*/1); + AddItems(/*manager_index=*/0, /*item_count=*/2, /*in_progress_count=*/0); + LinkManager(0); + + DownloadPrefs* download_prefs = + DownloadPrefs::FromDownloadManager(Manager(0)); + base::Time initial_time = download_prefs->GetLastCompleteTime(); + base::Time current_time = base::Time::Now(); + + // Set the first download to in progress and notify the update. + SetItemValues(/*manager_index=*/0, /*item_index=*/0, /*received_bytes=*/90, + /*total_bytes=*/100, /*notify=*/true); + // The last complete time is still the initial time, because the download is + // not complete yet. + EXPECT_EQ(initial_time, download_prefs->GetLastCompleteTime()); + + // The first download has completed. + CompleteItem(/*manager_index=*/0, /*item_index=*/0); + // The last complete time is updated. + EXPECT_EQ(current_time, download_prefs->GetLastCompleteTime()); + + task_environment_.FastForwardBy(base::Hours(1)); + // Set the second download item to in progress and notify the update. + SetItemValues(/*manager_index=*/0, /*item_index=*/1, /*received_bytes=*/90, + /*total_bytes=*/100, /*notify=*/true); + // The last complete time is not updated yet, because the second download is + // still in progress. + EXPECT_EQ(current_time, download_prefs->GetLastCompleteTime()); + + task_environment_.FastForwardBy(base::Hours(1)); + // The second download has completed. + CompleteItem(/*manager_index=*/0, /*item_index=*/1); + // Completed time is updated + EXPECT_EQ(current_time + base::Hours(2), + download_prefs->GetLastCompleteTime()); +}
diff --git a/chrome/browser/extensions/api/omnibox/omnibox_api.cc b/chrome/browser/extensions/api/omnibox/omnibox_api.cc index 209f4f9f..c20828b0 100644 --- a/chrome/browser/extensions/api/omnibox/omnibox_api.cc +++ b/chrome/browser/extensions/api/omnibox/omnibox_api.cc
@@ -16,7 +16,6 @@ #include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "build/build_config.h" -#include "chrome/browser/extensions/api/omnibox/suggestion_parser.h" #include "chrome/browser/extensions/tab_helper.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/search_engines/template_url_service_factory.h" @@ -304,20 +303,20 @@ } void OmniboxSetDefaultSuggestionFunction::OnParsedDescriptionAndStyles( - std::unique_ptr<DescriptionAndStyles> description_and_styles, - std::string error) { - if (!description_and_styles) { - DCHECK(!error.empty()); - Respond(Error(std::move(error))); + DescriptionAndStylesResult result) { + if (!result.error.empty()) { + Respond(Error(std::move(result.error))); return; } + DCHECK_EQ(1u, result.descriptions_and_styles.size()); + DescriptionAndStyles& single_result = result.descriptions_and_styles[0]; + omnibox::DefaultSuggestResult default_suggestion; - default_suggestion.description = - base::UTF16ToUTF8(description_and_styles->description); + default_suggestion.description = base::UTF16ToUTF8(single_result.description); default_suggestion.description_styles = std::make_unique<std::vector<api::omnibox::MatchClassification>>(); - default_suggestion.description_styles->swap(description_and_styles->styles); + default_suggestion.description_styles->swap(single_result.styles); SetDefaultSuggestion(default_suggestion); Respond(NoArguments()); }
diff --git a/chrome/browser/extensions/api/omnibox/omnibox_api.h b/chrome/browser/extensions/api/omnibox/omnibox_api.h index 866952b..3497bdf 100644 --- a/chrome/browser/extensions/api/omnibox/omnibox_api.h +++ b/chrome/browser/extensions/api/omnibox/omnibox_api.h
@@ -11,6 +11,7 @@ #include "base/memory/raw_ptr.h" #include "base/scoped_observation.h" +#include "chrome/browser/extensions/api/omnibox/suggestion_parser.h" #include "chrome/browser/extensions/extension_icon_manager.h" #include "chrome/common/extensions/api/omnibox.h" #include "components/omnibox/browser/autocomplete_match.h" @@ -35,7 +36,6 @@ } namespace extensions { -struct DescriptionAndStyles; // Event router class for events related to the omnibox API. class ExtensionOmniboxEventRouter { @@ -160,9 +160,7 @@ // Called asynchronously with the parsed description and styles for the // default suggestion. - void OnParsedDescriptionAndStyles( - std::unique_ptr<DescriptionAndStyles> description_and_styles, - std::string error); + void OnParsedDescriptionAndStyles(DescriptionAndStylesResult result); // Sets the default suggestion in the extension preferences. void SetDefaultSuggestion(
diff --git a/chrome/browser/extensions/api/omnibox/suggestion_parser.cc b/chrome/browser/extensions/api/omnibox/suggestion_parser.cc index e1dbe197..66ea40e 100644 --- a/chrome/browser/extensions/api/omnibox/suggestion_parser.cc +++ b/chrome/browser/extensions/api/omnibox/suggestion_parser.cc
@@ -20,6 +20,15 @@ namespace { +// A helper function to DCHECK() that retrieving the tag for a given XML node +// succeeds and return the tag. +std::string CheckedGetElementTag(const base::Value& node) { + std::string tag; + bool got_tag = data_decoder::GetXmlElementTagName(node, &tag); + DCHECK(got_tag); + return tag; +} + // Recursively walks an XML node, generating `result` as it goes along. void WalkNode(const base::Value& node, DescriptionAndStyles* result) { const base::Value* children = data_decoder::GetXmlElementChildren(node); @@ -58,9 +67,7 @@ WalkNode(child, result); continue; } - std::string tag; - bool got_tag = data_decoder::GetXmlElementTagName(child, &tag); - DCHECK(got_tag); + std::string tag = CheckedGetElementTag(child); omnibox::DescriptionStyleType style_type = omnibox::ParseDescriptionStyleType(tag); if (style_type == omnibox::DESCRIPTION_STYLE_TYPE_NONE) { @@ -84,42 +91,153 @@ } } -// Constructs the DescriptionAndStyles result from the parsed XML value, if +// Populates `entries` with individual suggestions contained within +// `root-node`. Used when we construct an XML document with multiple suggestions +// for parsing. +// Returns true on success. `entries_out` may be modified even if population +// fails. +bool PopulateEntriesFromNode(const base::Value& root_node, + std::vector<const base::Value*>* entries_out) { + if (!data_decoder::IsXmlElementOfType( + root_node, data_decoder::mojom::XmlParser::kElementType)) { + return false; + } + + if (CheckedGetElementTag(root_node) != "fragment") + return false; + + const base::Value* children = data_decoder::GetXmlElementChildren(root_node); + if (!children) + return false; + + DCHECK(children->is_list()); + entries_out->reserve(children->GetListDeprecated().size()); + for (const base::Value& child : children->GetListDeprecated()) { + if (!data_decoder::IsXmlElementOfType( + child, data_decoder::mojom::XmlParser::kElementType)) { + return false; + } + if (CheckedGetElementTag(child) != "internal-suggestion") + return false; + entries_out->push_back(&child); + } + + return true; +} + +// Constructs the DescriptionAndStylesResult from the parsed XML value, if // present. void ConstructResultFromValue( DescriptionAndStylesCallback callback, + bool has_multiple_entries, data_decoder::DataDecoder::ValueOrError value_or_error) { + DescriptionAndStylesResult result; + + // A helper function to set an error and run the callback. + auto run_callback_with_error = [&result, &callback](std::string error) { + DCHECK_EQ(0u, result.descriptions_and_styles.size()); + result.error = std::move(error); + std::move(callback).Run(std::move(result)); + }; + if (value_or_error.error) { - std::move(callback).Run(nullptr, *value_or_error.error); + run_callback_with_error(std::move(*value_or_error.error)); return; } DCHECK(value_or_error.value); + + // From this point on, we hope that everything is valid (e.g., that we don't + // get non-dictionary values or unexpected top-level types. But, if we did, + // emit a generic error. + constexpr char kGenericError[] = "Invalid XML"; + if (!value_or_error.value->is_dict()) { - // Hopefully, we wouldn't get a non-dictionary result from a successful - // decoding, but if we do, just emit a generic error. - std::move(callback).Run(nullptr, "Invalid XML"); + run_callback_with_error(kGenericError); return; } - auto result = std::make_unique<DescriptionAndStyles>(); - WalkNode(*value_or_error.value, result.get()); - std::move(callback).Run(std::move(result), std::string()); + std::vector<const base::Value*> entries; + const base::Value& root_node = *value_or_error.value; + if (has_multiple_entries) { + if (!PopulateEntriesFromNode(root_node, &entries)) { + run_callback_with_error(kGenericError); + return; + } + } else { + entries.push_back(&root_node); + } + + result.descriptions_and_styles.reserve(entries.size()); + for (const base::Value* entry : entries) { + DescriptionAndStyles parsed; + WalkNode(*entry, &parsed); + result.descriptions_and_styles.push_back(std::move(parsed)); + } + + std::move(callback).Run(std::move(result)); +} + +// A helper method for ParseDescriptionsAndStyles(). `contains_multiple_entries` +// indicates whether `xml_input` contains a single suggestion or multiple +// suggestions that have been wrapped in individual XML elements. +void ParseDescriptionAndStylesImpl(base::StringPiece xml_input, + bool contains_multiple_entries, + DescriptionAndStylesCallback callback) { + std::string wrapped_xml = + base::StringPrintf("<fragment>%s</fragment>", xml_input.data()); + data_decoder::DataDecoder::ParseXmlIsolated( + wrapped_xml, + data_decoder::mojom::XmlParser::WhitespaceBehavior::kPreserveSignificant, + base::BindOnce(&ConstructResultFromValue, std::move(callback), + contains_multiple_entries)); } } // namespace DescriptionAndStyles::DescriptionAndStyles() = default; +DescriptionAndStyles::DescriptionAndStyles(DescriptionAndStyles&&) = default; +DescriptionAndStyles& DescriptionAndStyles::operator=(DescriptionAndStyles&&) = + default; DescriptionAndStyles::~DescriptionAndStyles() = default; +DescriptionAndStylesResult::DescriptionAndStylesResult() = default; +DescriptionAndStylesResult::DescriptionAndStylesResult( + DescriptionAndStylesResult&&) = default; +DescriptionAndStylesResult& DescriptionAndStylesResult::operator=( + DescriptionAndStylesResult&&) = default; +DescriptionAndStylesResult::~DescriptionAndStylesResult() = default; + void ParseDescriptionAndStyles(base::StringPiece str, DescriptionAndStylesCallback callback) { - std::string wrapped_xml = - base::StringPrintf("<fragment>%s</fragment>", str.data()); - data_decoder::DataDecoder::ParseXmlIsolated( - wrapped_xml, - data_decoder::mojom::XmlParser::WhitespaceBehavior::kPreserveSignificant, - base::BindOnce(&ConstructResultFromValue, std::move(callback))); + constexpr bool kContainsMultipleEntries = false; + ParseDescriptionAndStylesImpl(str, kContainsMultipleEntries, + std::move(callback)); +} + +void ParseDescriptionsAndStyles(const std::vector<base::StringPiece>& strs, + DescriptionAndStylesCallback callback) { + // When passed multiple suggestions, we synthesize them into a single XML + // document. This allows us to parse all of them with a single call to the + // XML parser. We then separate them again when constructing the result. + // For instance, given the input: + // { "suggestion one", "suggestion two" } + // We construct the XML (once wrapped): + // <fragment> + // <internal-suggestion>suggestion one</internal-suggestion> + // <internal-suggestion>suggestion two</internal-suggestion> + // </fragment> + // It's fine for the input to have unexpected XML tags; it just results in + // either invalid XML (if they're unbalanced) or unexpected children, which + // are safely ignored in our result handling. + std::string xml_input; + for (const auto& str : strs) { + xml_input += base::StringPrintf( + "<internal-suggestion>%s</internal-suggestion>", str.data()); + } + constexpr bool kContainsMultipleEntries = true; + ParseDescriptionAndStylesImpl(xml_input, kContainsMultipleEntries, + std::move(callback)); } } // namespace extensions
diff --git a/chrome/browser/extensions/api/omnibox/suggestion_parser.h b/chrome/browser/extensions/api/omnibox/suggestion_parser.h index a3d7a34..51a9fde 100644 --- a/chrome/browser/extensions/api/omnibox/suggestion_parser.h +++ b/chrome/browser/extensions/api/omnibox/suggestion_parser.h
@@ -20,24 +20,47 @@ // common struct. struct DescriptionAndStyles { DescriptionAndStyles(); + DescriptionAndStyles(const DescriptionAndStyles&) = delete; + DescriptionAndStyles(DescriptionAndStyles&&); + DescriptionAndStyles& operator=(const DescriptionAndStyles&) = delete; + DescriptionAndStyles& operator=(DescriptionAndStyles&&); ~DescriptionAndStyles(); std::u16string description; std::vector<api::omnibox::MatchClassification> styles; }; +// The container for the result from parsing descriptions and styles. +struct DescriptionAndStylesResult { + // Chromium's clang plugin about non-trivial structs needing out-of-line + // constructors requires us to go a little ham here. + DescriptionAndStylesResult(); + DescriptionAndStylesResult(const DescriptionAndStylesResult&) = delete; + DescriptionAndStylesResult(DescriptionAndStylesResult&&); + DescriptionAndStylesResult& operator=(const DescriptionAndStylesResult&) = + delete; + DescriptionAndStylesResult& operator=(DescriptionAndStylesResult&&); + ~DescriptionAndStylesResult(); + + // The parse error, if any. If a parsing error was encountered, no results + // will be populated. + std::string error; + // The parsed descriptions and styles, if parsing was successful. + std::vector<DescriptionAndStyles> descriptions_and_styles; +}; + using DescriptionAndStylesCallback = - base::OnceCallback<void(std::unique_ptr<DescriptionAndStyles> result, - std::string error)>; + base::OnceCallback<void(DescriptionAndStylesResult)>; // Parses `str`, which is the suggestion string passed from the extension that // potentially contains XML markup (e.g., the string may be // "visit <url>https://example.com</url>"). This parses the string in an -// isolated process and asynchronously returns the description and styles via -// `callback`. On failure (e.g. due to invalid XML), invokes `callback` with -// null. +// isolated process and asynchronously returns the parse result via `callback`. void ParseDescriptionAndStyles(base::StringPiece str, DescriptionAndStylesCallback callback); +// Same as above, but takes in multiple string inputs. +void ParseDescriptionsAndStyles(const std::vector<base::StringPiece>& strs, + DescriptionAndStylesCallback callback); } // namespace extensions
diff --git a/chrome/browser/extensions/api/omnibox/suggestion_parser_unittest.cc b/chrome/browser/extensions/api/omnibox/suggestion_parser_unittest.cc index 2198551..81497b9 100644 --- a/chrome/browser/extensions/api/omnibox/suggestion_parser_unittest.cc +++ b/chrome/browser/extensions/api/omnibox/suggestion_parser_unittest.cc
@@ -47,41 +47,63 @@ // A helper method to synchronously parses `str` as input and return the // result. - std::unique_ptr<DescriptionAndStyles> ParseInput(base::StringPiece str) { - std::unique_ptr<DescriptionAndStyles> result_out; - std::string error; - ParseImpl(str, &result_out, &error); - return result_out; + DescriptionAndStyles ParseSingleInput(base::StringPiece str) { + DescriptionAndStylesResult result; + ParseImpl({str}, &result); + if (result.descriptions_and_styles.size() != 1) { + ADD_FAILURE() << "Failed to parse single input. Resulting size: " + << result.descriptions_and_styles.size(); + return DescriptionAndStyles(); + } + + return std::move(result.descriptions_and_styles[0]); + } + // Same as above, accepting multiple string inputs. + std::vector<DescriptionAndStyles> ParseInputs( + const std::vector<base::StringPiece>& strs) { + DescriptionAndStylesResult result; + ParseImpl(strs, &result); + EXPECT_EQ(std::string(), result.error); + return std::move(result.descriptions_and_styles); } // Returns the parsing error from attempting to parse `str`. std::string GetParseError(base::StringPiece str) { - std::unique_ptr<DescriptionAndStyles> result_out; - std::string error_out; - ParseImpl(str, &result_out, &error_out); - return error_out; + DescriptionAndStylesResult result; + ParseImpl({str}, &result); + return result.error; + } + // Same as above, accepting multiple string inputs. + std::string GetParseError(const std::vector<base::StringPiece>& strs) { + DescriptionAndStylesResult result; + ParseImpl(strs, &result); + return result.error; } private: - void ParseImpl(base::StringPiece str, - std::unique_ptr<DescriptionAndStyles>* result_out, - std::string* error_out) { + void ParseImpl(const std::vector<base::StringPiece>& strs, + DescriptionAndStylesResult* result_out) { base::RunLoop run_loop; - auto get_result = [&run_loop, result_out, error_out]( - std::unique_ptr<DescriptionAndStyles> result, - std::string error) { + auto get_result = [&run_loop, + result_out](DescriptionAndStylesResult result) { *result_out = std::move(result); - *error_out = std::move(error); run_loop.Quit(); }; - ParseDescriptionAndStyles(str, base::BindLambdaForTesting(get_result)); + auto get_result_callback = base::BindLambdaForTesting(get_result); + if (strs.size() == 1) { + ParseDescriptionAndStyles(strs[0], std::move(get_result_callback)); + } else { + ParseDescriptionsAndStyles(strs, std::move(get_result_callback)); + } + run_loop.Run(); // Exactly one of error and result should be populated. - bool has_result = result_out->get(); - bool has_error = !error_out->empty(); - EXPECT_TRUE(has_result ^ has_error) << has_result << ", " << has_error; + bool has_parsed_entries = !result_out->descriptions_and_styles.empty(); + bool has_error = !result_out->error.empty(); + EXPECT_TRUE(has_parsed_entries ^ has_error) + << has_parsed_entries << ", " << has_error; } content::BrowserTaskEnvironment task_environment_; @@ -91,34 +113,75 @@ // Tests a number of basic cases for XML suggestion parsing. TEST_F(SuggestionParserUnitTest, BasicCases) { { - std::unique_ptr<DescriptionAndStyles> result = - ParseInput("hello <match>match</match> world"); - ASSERT_TRUE(result); - EXPECT_EQ(u"hello match world", result->description); - EXPECT_THAT(result->styles, + DescriptionAndStyles result = + ParseSingleInput("hello <match>match</match> world"); + EXPECT_EQ(u"hello match world", result.description); + EXPECT_THAT(result.styles, testing::ElementsAre(GetStyleMatcher(kMatch, 6, 5))); } { - std::unique_ptr<DescriptionAndStyles> result = - ParseInput("<dim>hello</dim> <match>match</match> <url>world</url>"); - ASSERT_TRUE(result); - EXPECT_EQ(u"hello match world", result->description); - EXPECT_THAT(result->styles, + DescriptionAndStyles result = ParseSingleInput( + "<dim>hello</dim> <match>match</match> <url>world</url>"); + EXPECT_EQ(u"hello match world", result.description); + EXPECT_THAT(result.styles, testing::ElementsAre(GetStyleMatcher(kDim, 0, 5), GetStyleMatcher(kMatch, 6, 5), GetStyleMatcher(kUrl, 12, 5))); } { - std::unique_ptr<DescriptionAndStyles> result = - ParseInput("hello <dim>dim <match>dimmed match</match></dim>"); - ASSERT_TRUE(result); - EXPECT_EQ(u"hello dim dimmed match", result->description); - EXPECT_THAT(result->styles, + DescriptionAndStyles result = + ParseSingleInput("hello <dim>dim <match>dimmed match</match></dim>"); + EXPECT_EQ(u"hello dim dimmed match", result.description); + EXPECT_THAT(result.styles, testing::ElementsAre(GetStyleMatcher(kDim, 6, 16), GetStyleMatcher(kMatch, 10, 12))); } } +// Tests parsing multiple entries passed to the suggestion parsing. +TEST_F(SuggestionParserUnitTest, MultipleEntries) { + { + std::vector<DescriptionAndStyles> result = ParseInputs( + {"first <match>match</match> entry", "second <url>url</url> entry", + "final <dim>dim</dim> entry"}); + ASSERT_EQ(3u, result.size()); + EXPECT_EQ(u"first match entry", result[0].description); + EXPECT_THAT(result[0].styles, + testing::ElementsAre(GetStyleMatcher(kMatch, 6, 5))); + EXPECT_EQ(u"second url entry", result[1].description); + EXPECT_THAT(result[1].styles, + testing::ElementsAre(GetStyleMatcher(kUrl, 7, 3))); + EXPECT_EQ(u"final dim entry", result[2].description); + EXPECT_THAT(result[2].styles, + testing::ElementsAre(GetStyleMatcher(kDim, 6, 3))); + } + { + // A fun "hack" that extensions can pull: When parsing multiple suggestions, + // we join them together with each as an element with the + // "internal-suggestion" tag. This means that, if an extension wanted to, + // it could inject inner </internal-suggestion> tags to synthesize extra + // suggestions. This isn't a security risk at all - it can't do anything + // besides get extra suggestions, and we don't limit the number of + // suggestions extensions can provide. There's no reason for extensions to + // do this, but we add a test as documentation of this "quirk". + constexpr char kJointSuggestion[] = + "first <match>match</match></internal-suggestion><internal-suggestion>" + "second <url>url</url>"; + std::vector<DescriptionAndStyles> result = + ParseInputs({kJointSuggestion, "final <dim>dim</dim>"}); + ASSERT_EQ(3u, result.size()); + EXPECT_EQ(u"first match", result[0].description); + EXPECT_THAT(result[0].styles, + testing::ElementsAre(GetStyleMatcher(kMatch, 6, 5))); + EXPECT_EQ(u"second url", result[1].description); + EXPECT_THAT(result[1].styles, + testing::ElementsAre(GetStyleMatcher(kUrl, 7, 3))); + EXPECT_EQ(u"final dim", result[2].description); + EXPECT_THAT(result[2].styles, + testing::ElementsAre(GetStyleMatcher(kDim, 6, 3))); + } +} + // Tests cases where XML parsing is expected to fail. TEST_F(SuggestionParserUnitTest, ParsingFails) { // Note: These aren't expected to be terribly robust tests, since XML parsing @@ -127,24 +190,65 @@ testing::HasSubstr("Opening and ending tag mismatch")); EXPECT_THAT(GetParseError("<dim>hello <url>foo</dim> world</url>"), testing::HasSubstr("Opening and ending tag mismatch")); + // Test an error in one of three inputs. + EXPECT_THAT(GetParseError({"first <match>match</match> entry", + "second <url>url<url> entry", + "final <dim>dim</dim> entry"}), + testing::HasSubstr("Opening and ending tag mismatch")); + + // Test "injection" attacks. Because we synthesize XML documents for and don't + // do any escaping for the element tags we use ("fragment" and + // "internal-suggestion"), extensions can prematurely end our tags. This is + // safe; it just results in invalid XML. + EXPECT_THAT(GetParseError("first </fragment>DROP TABLE supersecret"), + testing::HasSubstr("Extra content at the end of the document")); + EXPECT_THAT( + GetParseError( + {"first " + "</internal-suggestion></fragment>fetch('https://example.com');", + "second entry"}), + testing::HasSubstr("Extra content at the end of the document")); + + // Test a suggestion that would add a second "fragment" element to the + // parsed XML. The XML that ends up being parsed is: + // <fragment>first suggestion</fragment> + // <fragment>second</fragment> + EXPECT_THAT( + GetParseError("first suggestion</fragment><fragment>second</fragment>"), + testing::HasSubstr("Extra content at the end of the document")); + + // Test an injection that inserts unexpected children in our synthesized XML + // document. The XML that ends up being parsed is: + // <fragment> + // <internal-suggestion>first</internal-suggestion> + // <other-class>Foobar</other-class> <-- This was snuck in. + // <internal-suggestion>second></internal-suggestion> + // <internal-suggestion>final</internal-suggestion> + // </fragment> + // This is actually valid XML, and we reject it with our generic error in the + // handling of the parsed value. + { + constexpr char kSneakyXML[] = + "first</internal-suggestion><other-class>Foobar</other-class>" + "<internal-suggestion>second"; + EXPECT_EQ("Invalid XML", GetParseError({kSneakyXML, "final suggestion"})); + } } // Tests that XML strings are properly sanitized from any forbidden characters. TEST_F(SuggestionParserUnitTest, Sanitization) { { - std::unique_ptr<DescriptionAndStyles> result = - ParseInput(" hello <match>match</match> world"); - ASSERT_TRUE(result); - EXPECT_EQ(u"hello match world", result->description); - EXPECT_THAT(result->styles, + DescriptionAndStyles result = + ParseSingleInput(" hello <match>match</match> world"); + EXPECT_EQ(u"hello match world", result.description); + EXPECT_THAT(result.styles, testing::ElementsAre(GetStyleMatcher(kMatch, 6, 5))); } { - std::unique_ptr<DescriptionAndStyles> result = - ParseInput("hell\ro <match>ma\ttch</match> wor\nld"); - ASSERT_TRUE(result); - EXPECT_EQ(u"hello match world", result->description); - EXPECT_THAT(result->styles, + DescriptionAndStyles result = + ParseSingleInput("hell\ro <match>ma\ttch</match> wor\nld"); + EXPECT_EQ(u"hello match world", result.description); + EXPECT_THAT(result.styles, testing::ElementsAre(GetStyleMatcher(kMatch, 6, 5))); } } @@ -152,19 +256,17 @@ // Tests that unknown tag types and attributes are properly ignored. TEST_F(SuggestionParserUnitTest, UnknownTagsAndAttributesAreIgnored) { { - std::unique_ptr<DescriptionAndStyles> result = - ParseInput("hello <match some-attr=\"foo\">match</match> world"); - ASSERT_TRUE(result); - EXPECT_EQ(u"hello match world", result->description); - EXPECT_THAT(result->styles, + DescriptionAndStyles result = + ParseSingleInput("hello <match some-attr=\"foo\">match</match> world"); + EXPECT_EQ(u"hello match world", result.description); + EXPECT_THAT(result.styles, testing::ElementsAre(GetStyleMatcher(kMatch, 6, 5))); } { - std::unique_ptr<DescriptionAndStyles> result = - ParseInput("hello <unknown>match</unknown> world"); - ASSERT_TRUE(result); - EXPECT_EQ(u"hello match world", result->description); - EXPECT_THAT(result->styles, testing::IsEmpty()); + DescriptionAndStyles result = + ParseSingleInput("hello <unknown>match</unknown> world"); + EXPECT_EQ(u"hello match world", result.description); + EXPECT_THAT(result.styles, testing::IsEmpty()); } }
diff --git a/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc b/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc index 0eeb2c2e..9ed77d1 100644 --- a/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc +++ b/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc
@@ -39,6 +39,10 @@ #include "testing/gmock/include/gmock/gmock.h" #include "url/url_constants.h" +#if BUILDFLAG(IS_MAC) +#include "base/mac/mac_util.h" +#endif + namespace extensions { namespace { @@ -86,6 +90,11 @@ // Tests API behaviors, including info queries, and constraints violations. IN_PROC_BROWSER_TEST_F(TabCaptureApiTest, ApiTests) { + // This test flakes on the MacOS 10.12 builder (see crbug.com/1299873). +#if BUILDFLAG(IS_MAC) + if (base::mac::IsOS10_12()) + GTEST_SKIP(); +#endif AddExtensionToCommandLineAllowlist(); ASSERT_TRUE( RunExtensionTest("tab_capture/api_tests", {.page_url = "api_tests.html"}))
diff --git a/chrome/browser/extensions/native_bindings_apitest.cc b/chrome/browser/extensions/native_bindings_apitest.cc index 460b14e..aa5b79d7 100644 --- a/chrome/browser/extensions/native_bindings_apitest.cc +++ b/chrome/browser/extensions/native_bindings_apitest.cc
@@ -7,6 +7,7 @@ #include "base/command_line.h" #include "base/run_loop.h" #include "base/strings/stringprintf.h" +#include "base/test/scoped_feature_list.h" #include "chrome/browser/extensions/extension_apitest.h" #include "chrome/browser/renderer_context_menu/render_view_context_menu_test_util.h" #include "chrome/browser/ui/browser.h" @@ -20,7 +21,10 @@ #include "extensions/browser/extension_action.h" #include "extensions/browser/extension_action_manager.h" #include "extensions/browser/extension_host_test_helper.h" +#include "extensions/browser/extension_util.h" #include "extensions/browser/process_manager.h" +#include "extensions/common/extension_features.h" +#include "extensions/common/features/feature_developer_mode_only.h" #include "extensions/common/mojom/view_type.mojom.h" #include "extensions/common/switches.h" #include "extensions/test/extension_test_message_listener.h" @@ -54,6 +58,27 @@ } }; +// And end-to-end test for extension APIs restricted to developer mode using +// native bindings. +class NativeBindingsRestrictedToDeveloperModeApiTest + : public NativeBindingsApiTest { + public: + NativeBindingsRestrictedToDeveloperModeApiTest() { + scoped_feature_list_.InitAndEnableFeature( + extensions_features::kRestrictDeveloperModeAPIs); + } + + NativeBindingsRestrictedToDeveloperModeApiTest( + const NativeBindingsRestrictedToDeveloperModeApiTest&) = delete; + NativeBindingsRestrictedToDeveloperModeApiTest& operator=( + const NativeBindingsRestrictedToDeveloperModeApiTest&) = delete; + + ~NativeBindingsRestrictedToDeveloperModeApiTest() override = default; + + private: + base::test::ScopedFeatureList scoped_feature_list_; +}; + IN_PROC_BROWSER_TEST_F(NativeBindingsApiTest, SimpleEndToEndTest) { embedded_test_server()->ServeFilesFromDirectory(test_data_dir_); ASSERT_TRUE(StartEmbeddedTestServer()); @@ -532,4 +557,46 @@ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message(); } +IN_PROC_BROWSER_TEST_F( + NativeBindingsRestrictedToDeveloperModeApiTest, + DeveloperModeOnlyWithAPIPermissionUserIsNotInDeveloperMode) { + // With kDeveloperModeRestriction enabled, developer mode-only APIs + // should not be available if the user is not in developer mode. + SetCustomArg("not_in_developer_mode"); + SetCurrentDeveloperMode(util::GetBrowserContextId(profile()), false); + ASSERT_TRUE(RunExtensionTest( + "native_bindings/developer_mode_only_with_api_permission")) + << message_; +} + +IN_PROC_BROWSER_TEST_F( + NativeBindingsRestrictedToDeveloperModeApiTest, + DeveloperModeOnlyWithAPIPermissionUserIsInDeveloperMode) { + // With kDeveloperModeRestriction enabled, developer mode-only APIs + // should be available if the user is in developer mode. + SetCustomArg("in_developer_mode"); + SetCurrentDeveloperMode(util::GetBrowserContextId(profile()), true); + ASSERT_TRUE(RunExtensionTest( + "native_bindings/developer_mode_only_with_api_permission")) + << message_; +} + +IN_PROC_BROWSER_TEST_F( + NativeBindingsRestrictedToDeveloperModeApiTest, + DeveloperModeOnlyWithoutAPIPermissionUserIsNotInDeveloperMode) { + SetCurrentDeveloperMode(util::GetBrowserContextId(profile()), false); + ASSERT_TRUE(RunExtensionTest( + "native_bindings/developer_mode_only_without_api_permission")) + << message_; +} + +IN_PROC_BROWSER_TEST_F( + NativeBindingsRestrictedToDeveloperModeApiTest, + DeveloperModeOnlyWithoutAPIPermissionUserIsInDeveloperMode) { + SetCurrentDeveloperMode(util::GetBrowserContextId(profile()), true); + ASSERT_TRUE(RunExtensionTest( + "native_bindings/developer_mode_only_without_api_permission")) + << message_; +} + } // namespace extensions
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json index e182eea..c80dc07 100644 --- a/chrome/browser/flag-metadata.json +++ b/chrome/browser/flag-metadata.json
@@ -1426,6 +1426,11 @@ "expiry_milestone": 90 }, { + "name": "eche-custom-widget", + "owners": [ "nayebi" ], + "expiry_milestone": 106 + }, + { "name": "eche-phone-hub-permissions-onboarding", "owners": [ "dhnishi" ], "expiry_milestone": 101 @@ -4231,17 +4236,17 @@ { "name": "ntp-chrome-cart-module", "owners": [ "wychen", "tiborg", "yuezhanggg" ], - "expiry_milestone": 100 + "expiry_milestone": 105 }, { "name": "ntp-drive-module", "owners": [ "mahmadi", "tiborg" ], - "expiry_milestone": 100 + "expiry_milestone": 105 }, { "name": "ntp-dummy-modules", "owners": [ "pauladedeji", "tiborg" ], - "expiry_milestone": 100 + "expiry_milestone": 105 }, { "name": "ntp-modules", @@ -4251,17 +4256,17 @@ { "name": "ntp-modules-drag-and-drop", "owners": [ "lydialam", "mahmadi", "tiborg" ], - "expiry_milestone": 100 + "expiry_milestone": 105 }, { "name": "ntp-modules-redesigned", "owners": [ "doei", "mahmadi", "tiborg" ], - "expiry_milestone": 100 + "expiry_milestone": 105 }, { "name": "ntp-modules-redesigned-layout", "owners": [ "pauladedeji", "tiborg" ], - "expiry_milestone": 100 + "expiry_milestone": 105 }, { "name": "ntp-photos-module", @@ -4316,12 +4321,12 @@ { "name": "ntp-recipe-tasks-module", "owners": [ "mahmadi", "tiborg" ], - "expiry_milestone": 100 + "expiry_milestone": 105 }, { "name": "ntp-safe-browsing-module", "owners": [ "bhatiarohit", "tiborg" ], - "expiry_milestone": 100 + "expiry_milestone": 105 }, { "name": "ntp-shopping-tasks-module", @@ -4578,6 +4583,11 @@ "expiry_milestone": 100 }, { + "name": "oobe-hid-detection-revamp", + "owners": [ "gordonseto", "cros-connectivity@google.com"], + "expiry_milestone": 105 + }, + { "name": "optimization-guide-debug-logs", "owners": [ "rajendrant", "robertogden", "chrome-intelligence-core@google.com" ], "expiry_milestone": 110 @@ -5120,7 +5130,7 @@ { "name": "request-desktop-site-exceptions", "owners": [ "shuyng@google.com", "twellington", "clank-app-team@google.com" ], - "expiry_milestone": 100 + "expiry_milestone": 104 }, { "name": "request-desktop-site-for-tablets", @@ -5130,7 +5140,7 @@ { "name": "request-desktop-site-global", "owners": [ "shuyng@google.com", "twellington", "clank-app-team@google.com" ], - "expiry_milestone": 100 + "expiry_milestone": 104 }, { "name": "restore-session-from-cache", @@ -5633,7 +5643,12 @@ { "name": "tabs-search-ios", "owners": [ "mrefaat", "michaeldo", "bling-flags@google.com" ], - "expiry_milestone":100 + "expiry_milestone":101 + }, + { + "name": "tabs-search-regular-results-suggested-actions-ios", + "owners": [ "mrefaat", "michaeldo", "bling-flags@google.com" ], + "expiry_milestone":101 }, { "name": "tailored-security-integration",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc index 6602924..ff4e8e6 100644 --- a/chrome/browser/flag_descriptions.cc +++ b/chrome/browser/flag_descriptions.cc
@@ -4695,6 +4695,10 @@ const char kEcheSWAName[] = "Enable Eche App SWA."; const char kEcheSWADescription[] = "Enable the SWA version of the Eche."; +const char kEcheCustomWidgetName[] = "Move Eche in a custom widget."; +const char kEcheCustomWidgetDescription[] = + "Move the Eche App into a custom widget."; + const char kEcheSWAResizingName[] = "Allow resizing Eche App."; const char kEcheSWAResizingDescription[] = "Enable a naive resize for the Eche window"; @@ -5150,6 +5154,11 @@ const char kNearbySharingWifiLanDescription[] = "Enables WifiLan as a Nearby Share transfer medium."; +const char kOobeHidDetectionRevampName[] = "OOBE HID Detection Revamp"; +const char kOobeHidDetectionRevampDescription[] = + "Enables the Chrome OS HID Detection Revamp, which updates OOBE HID " + "detection screen UI and related infrastructure."; + const char kPcieBillboardNotificationName[] = "Pcie billboard notification"; const char kPcieBillboardNotificationDescription[] = "Enable Pcie peripheral billboard notification.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h index a0bb8e8..be21e91 100644 --- a/chrome/browser/flag_descriptions.h +++ b/chrome/browser/flag_descriptions.h
@@ -2697,6 +2697,9 @@ extern const char kEcheSWAName[]; extern const char kEcheSWADescription[]; +extern const char kEcheCustomWidgetName[]; +extern const char kEcheCustomWidgetDescription[]; + extern const char kEcheSWAResizingName[]; extern const char kEcheSWAResizingDescription[]; @@ -2963,6 +2966,9 @@ extern const char kNearbySharingWifiLanName[]; extern const char kNearbySharingWifiLanDescription[]; +extern const char kOobeHidDetectionRevampName[]; +extern const char kOobeHidDetectionRevampDescription[]; + extern const char kPcieBillboardNotificationName[]; extern const char kPcieBillboardNotificationDescription[];
diff --git a/chrome/browser/media/media_engagement_contents_observer.cc b/chrome/browser/media/media_engagement_contents_observer.cc index b1167d78..fe181eb 100644 --- a/chrome/browser/media/media_engagement_contents_observer.cc +++ b/chrome/browser/media/media_engagement_contents_observer.cc
@@ -157,9 +157,6 @@ void MediaEngagementContentsObserver::DidFinishNavigation( content::NavigationHandle* navigation_handle) { - // TODO(https://crbug.com/1218946): With MPArch there may be multiple main - // frames. This caller was converted automatically to the primary main frame - // to preserve its semantics. Follow up to confirm correctness. if (!navigation_handle->IsInPrimaryMainFrame() || !navigation_handle->HasCommitted() || navigation_handle->IsSameDocument() || navigation_handle->IsErrorPage()) {
diff --git a/chrome/browser/media/media_engagement_contents_observer_unittest.cc b/chrome/browser/media/media_engagement_contents_observer_unittest.cc index f94a14bd..928a17f 100644 --- a/chrome/browser/media/media_engagement_contents_observer_unittest.cc +++ b/chrome/browser/media/media_engagement_contents_observer_unittest.cc
@@ -26,14 +26,17 @@ #include "chrome/test/base/testing_profile.h" #include "components/ukm/test_ukm_recorder.h" #include "content/public/browser/global_routing_id.h" +#include "content/public/browser/render_frame_host.h" #include "content/public/browser/web_contents.h" #include "content/public/test/mock_navigation_handle.h" +#include "content/public/test/navigation_simulator.h" #include "content/public/test/web_contents_tester.h" #include "media/base/media_switches.h" #include "services/metrics/public/cpp/ukm_builders.h" #include "services/metrics/public/cpp/ukm_source.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/abseil-cpp/absl/types/optional.h" +#include "third_party/blink/public/common/features.h" // TODO(crbug/1004580) All these tests crash on Android #if !BUILDFLAG(IS_ANDROID) @@ -1362,4 +1365,90 @@ EXPECT_EQ(0u, GetStoredPlayerStatesCount()); } +class MediaEngagementContentsObserverPrerenderTest + : public MediaEngagementContentsObserverTest { + public: + MediaEngagementContentsObserverPrerenderTest() { + scoped_feature_list_.InitWithFeatures( + {blink::features::kPrerender2}, + // Disable the memory requirement of Prerender2 so the test can run on + // any bot. + {blink::features::kPrerender2MemoryControls}); + } + ~MediaEngagementContentsObserverPrerenderTest() override = default; + + private: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +TEST_F(MediaEngagementContentsObserverPrerenderTest, + EnsureDoNotCleanupAfterNavigation_AudioContextInPrerendering) { + GURL url = GURL("https://example.com"); + content::NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), + url); + EXPECT_EQ(0u, GetAudioContextPlayersCount()); + + SimulateAudioContextStarted(0); + EXPECT_EQ(1u, GetAudioContextPlayersCount()); + + // Add a prerender page. + auto* prerender_frame = content::WebContentsTester::For(web_contents()) + ->AddPrerenderAndCommitNavigation(url); + DCHECK_NE(prerender_frame, nullptr); + EXPECT_EQ(prerender_frame->GetLifecycleState(), + content::RenderFrameHost::LifecycleState::kPrerendering); + EXPECT_EQ(1u, GetAudioContextPlayersCount()); + + // Activate the prerendered page. + content::NavigationSimulator::NavigateAndCommitFromDocument( + url, web_contents()->GetMainFrame()); + EXPECT_EQ(prerender_frame->GetLifecycleState(), + content::RenderFrameHost::LifecycleState::kActive); + EXPECT_EQ(0u, GetAudioContextPlayersCount()); +} + +class MediaEngagementContentsObserverFencedFrameTest + : public MediaEngagementContentsObserverTest { + public: + MediaEngagementContentsObserverFencedFrameTest() { + scoped_feature_list_.InitAndEnableFeatureWithParameters( + blink::features::kFencedFrames, {{"implementation_type", "mparch"}}); + } + ~MediaEngagementContentsObserverFencedFrameTest() override = default; + + content::RenderFrameHost* CreateFencedFrame( + content::RenderFrameHost* parent) { + content::RenderFrameHost* fenced_frame = + content::RenderFrameHostTester::For(parent)->AppendFencedFrame(); + return fenced_frame; + } + + private: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +TEST_F(MediaEngagementContentsObserverFencedFrameTest, + EnsureDoNotCleanupAfterNavigation_AudioContextOnFencedFrame) { + GURL url("https://example.com"); + content::NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), + url); + EXPECT_EQ(0u, GetAudioContextPlayersCount()); + + SimulateAudioContextStarted(0); + EXPECT_EQ(1u, GetAudioContextPlayersCount()); + + // Navigate a fenced frame. + content::RenderFrameHostTester::For(main_rfh()) + ->InitializeRenderFrameIfNeeded(); + content::RenderFrameHost* fenced_frame_rfh = CreateFencedFrame(main_rfh()); + std::unique_ptr<content::NavigationSimulator> navigation_simulator = + content::NavigationSimulator::CreateForFencedFrame(url, fenced_frame_rfh); + navigation_simulator->Commit(); + EXPECT_TRUE(fenced_frame_rfh->IsFencedFrameRoot()); + EXPECT_EQ(1u, GetAudioContextPlayersCount()); + + Navigate(url); + EXPECT_EQ(0u, GetAudioContextPlayersCount()); +} + #endif // !BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/metrics/per_user_state_manager_chromeos.cc b/chrome/browser/metrics/per_user_state_manager_chromeos.cc index 32efe7024..88e8c8f 100644 --- a/chrome/browser/metrics/per_user_state_manager_chromeos.cc +++ b/chrome/browser/metrics/per_user_state_manager_chromeos.cc
@@ -93,6 +93,7 @@ registry->RegisterBooleanPref(prefs::kMetricsUserConsent, false); registry->RegisterBooleanPref(prefs::kMetricsRequiresClientIdResetOnConsent, false); + registry->RegisterBooleanPref(prefs::kMetricsUserInheritOwnerConsent, true); } absl::optional<std::string> PerUserStateManagerChromeOS::GetCurrentUserId() @@ -331,11 +332,35 @@ local_state_->ClearPref(prefs::kMetricsCurrentUserId); } + auto* user_prefs = GetCurrentUserPrefs(); + + // Inherit owner consent if needed. This is to migrate existing users logging + // in for the first time since the feature was enabled. + // + // New users will inherit the device owner consent initially but will be asked + // on OOBE to set the consent. + bool should_inherit_owner_consent = + user_prefs->GetBoolean(prefs::kMetricsUserInheritOwnerConsent) && + IsUserAllowedToChangeConsent(current_user_); + + if (should_inherit_owner_consent) { + bool device_metrics_reporting = GetDeviceMetricsConsent(); + + user_prefs->SetBoolean(prefs::kMetricsUserConsent, + device_metrics_reporting); + user_prefs->SetBoolean(prefs::kMetricsUserInheritOwnerConsent, false); + + // Generate and set user ID if device owner enabled metrics reporting. + if (device_metrics_reporting) { + UpdateCurrentUserId(GenerateUserId()); + UpdateLocalStatePrefs(device_metrics_reporting); + } + } + // Only initialize metrics consent to user metrics consent if user is // allowed to change the metrics consent. if (IsUserAllowedToChangeConsent(current_user_)) { - SetReportingState( - GetCurrentUserPrefs()->GetBoolean(prefs::kMetricsUserConsent)); + SetReportingState(user_prefs->GetBoolean(prefs::kMetricsUserConsent)); } }
diff --git a/chrome/browser/metrics/per_user_state_manager_chromeos_browsertest.cc b/chrome/browser/metrics/per_user_state_manager_chromeos_browsertest.cc index 3a0962f..778ca91 100644 --- a/chrome/browser/metrics/per_user_state_manager_chromeos_browsertest.cc +++ b/chrome/browser/metrics/per_user_state_manager_chromeos_browsertest.cc
@@ -99,7 +99,7 @@ class ChromeOSPerUserRegularUserTest : public ChromeOSPerUserMetricsBrowserTestBase, - public ::testing::WithParamInterface<bool> { + public ::testing::WithParamInterface<std::pair<bool, bool>> { public: ChromeOSPerUserRegularUserTest() : owner_key_util_(new ownership::MockOwnerKeyUtil()) { @@ -111,11 +111,12 @@ protected: void SetUpInProcessBrowserTestFixture() override { ChromeOSPerUserMetricsBrowserTestBase::SetUpInProcessBrowserTestFixture(); - bool owner_consent = GetParam(); + owner_consent_ = GetParam().first; + user_consent_ = GetParam().second; // Set the owner parameter. test_cros_settings_.device_settings()->SetBoolean(ash::kStatsReportingPref, - owner_consent); + owner_consent_); // Establish ownership of the device. ash::OwnerSettingsServiceAshFactory::GetInstance() @@ -131,6 +132,9 @@ &mixin_host_, ash::DeviceStateMixin::State::OOBE_COMPLETED_CONSUMER_OWNED}; + bool owner_consent_ = false; + bool user_consent_ = false; + ash::LoginManagerMixin login_mixin_{&mixin_host_}; AccountId account_id_; }; @@ -142,7 +146,6 @@ LoginUser(account_id_); base::RunLoop().RunUntilIdle(); - bool owner_consent = GetParam(); MetricsLogStore* log_store = g_browser_process->metrics_service()->LogStoreForTest(); @@ -150,31 +153,40 @@ // owner consent. EXPECT_TRUE(log_store->has_alternate_ongoing_log_store()); - // Since user consent is always off initially, this should always return - // false regardless of owner_consent. - EXPECT_FALSE(GetLocalStateMetricsConsent()); + // Since new user consent inherits owner consent initially, this should be the + // same as owner consent. + EXPECT_THAT(GetLocalStateMetricsConsent(), Eq(owner_consent_)); - // Try and toggle user metrics consent from off->on. - ChangeUserMetricsConsent(true); + // Try and toggle user metrics consent to |user_consent_|. + ChangeUserMetricsConsent(user_consent_); // Propagating metrics consent through the services happens async. base::RunLoop().RunUntilIdle(); - // If owner consent is on, then user metrics consent should be respected. - // Otherwise, user consent being on should be overridden by owner_consent - // being off. - EXPECT_THAT(GetLocalStateMetricsConsent(), Eq(owner_consent)); + // If owner consent is on, then user metrics consent should be respected and + // metrics service should be toggled off. If owner consent is off, user + // metrics consent should not be changeable and no-op to respect that device + // owner consent is off. + if (owner_consent_) + EXPECT_THAT(GetLocalStateMetricsConsent(), Eq(user_consent_)); + else + EXPECT_FALSE(GetLocalStateMetricsConsent()); - // Users should only have a user ID if they were successfully able to toggle - // consent. + // Users should only have a user ID if both owner consent and user consent are + // on. EXPECT_THAT( g_browser_process->metrics_service()->GetCurrentUserId().has_value(), - Eq(owner_consent)); + Eq(user_consent_ && owner_consent_)); } INSTANTIATE_TEST_SUITE_P(MetricsConsentForRegularUser, ChromeOSPerUserRegularUserTest, - ::testing::Bool()); + testing::ValuesIn({ + std::make_pair(true, true), + std::make_pair(true, false), + std::make_pair(false, true), + std::make_pair(false, false), + })); class ChromeOSPerUserGuestUserWithNoOwnerTest : public ChromeOSPerUserMetricsBrowserTestBase, @@ -366,9 +378,15 @@ // Should still follow policy_consent. EXPECT_EQ(GetLocalStateMetricsConsent(), policy_consent); - // Managed users should not have a user id since they cannot control the - // policy. + // Users should not have a user id since they do not have control over the + // metrics consent. EXPECT_THAT(metrics_service->GetCurrentUserId(), Eq(absl::nullopt)); + + // Try to change the user consent. + metrics_service->UpdateCurrentUserMetricsConsent(!policy_consent); + + // Managed device users cannot control metrics consent. + EXPECT_EQ(GetLocalStateMetricsConsent(), policy_consent); } INSTANTIATE_TEST_SUITE_P(MetricsConsentForManagedUsers,
diff --git a/chrome/browser/metrics/per_user_state_manager_chromeos_unittest.cc b/chrome/browser/metrics/per_user_state_manager_chromeos_unittest.cc index 67b1d43..c1c9064 100644 --- a/chrome/browser/metrics/per_user_state_manager_chromeos_unittest.cc +++ b/chrome/browser/metrics/per_user_state_manager_chromeos_unittest.cc
@@ -152,6 +152,13 @@ profile_->GetPrefs()->SetBoolean( prefs::kMetricsRequiresClientIdResetOnConsent, has_consented_to_metrics); + profile_->GetPrefs()->SetBoolean(prefs::kMetricsUserInheritOwnerConsent, + false); + } + + void SetShouldInheritOwnerConsent(bool should_inherit) { + profile_->GetPrefs()->SetBoolean(prefs::kMetricsUserInheritOwnerConsent, + should_inherit); } void RunUntilIdle() { task_environment_.RunUntilIdle(); } @@ -330,4 +337,32 @@ EXPECT_TRUE(GetPerUserStateManager()->is_log_store_set()); } +TEST_F(PerUserStateManagerChromeOSTest, + NewOrMigratingUserInheritsOwnerConsent) { + auto* test_user = + RegisterUser(AccountId::FromUserEmailGaiaId("test@example.com", "1")); + InitializeProfileState(/*user_id=*/"", /*metrics_consent=*/false, + /*has_consented_to_metrics=*/false); + + // User should inherit owner consent if migrating or new user. + SetShouldInheritOwnerConsent(true); + + GetPerUserStateManager()->SetIsManaged(false); + GetPerUserStateManager()->SetDeviceMetricsConsent(true); + + // Simulate user login. + LoginRegularUser(test_user); + + // User log store is created async. Ensure that the log store loading + // finishes. + RunUntilIdle(); + + // User consent should be set to true since pref is true and device metrics + // consent is also true. + EXPECT_TRUE( + GetPerUserStateManager()->GetCurrentUserReportingConsentIfApplicable()); + + EXPECT_TRUE(GetPerUserStateManager()->is_log_store_set()); +} + } // namespace metrics
diff --git a/chrome/browser/metrics/profile_pref_names.cc b/chrome/browser/metrics/profile_pref_names.cc index d3c1c14..810e86ea 100644 --- a/chrome/browser/metrics/profile_pref_names.cc +++ b/chrome/browser/metrics/profile_pref_names.cc
@@ -13,12 +13,23 @@ const char kMetricsRequiresClientIdResetOnConsent[] = "metrics.requires_client_id_reset_on_consent"; +// Bool pref containing the current user metrics consent. +const char kMetricsUserConsent[] = "metrics.user_consent"; + // String pref containing pseudo-anonymous identifier of a user. Will be reset // if a user goes from a no->yes metrics consent state. const char kMetricsUserId[] = "metrics.user_id"; -// Bool pref containing the current user metrics consent. -const char kMetricsUserConsent[] = "metrics.user_consent"; +// Bool pref whether per-user consent should inherit consent from the device +// owner consent, which defaults to true. This pref is used to migrate existing +// users to the per-user consent model. Any existing user logging in for the +// first time since this feature is enabled will inherit the device owner +// consent. +// +// TODO(crbug/1295789): Delete this pref after some time after this feature +// rolls out to Stable channel. +const char kMetricsUserInheritOwnerConsent[] = + "metrics.user_inherit_owner_consent"; // Array of dictionaries that are each UMA logs that were not sent because the // user session ended before accumulated metrics were sent.
diff --git a/chrome/browser/metrics/profile_pref_names.h b/chrome/browser/metrics/profile_pref_names.h index 0d132b5..4886006 100644 --- a/chrome/browser/metrics/profile_pref_names.h +++ b/chrome/browser/metrics/profile_pref_names.h
@@ -10,8 +10,9 @@ // Alphabetical list of profile preference names. Document each in the .cc file. extern const char kMetricsRequiresClientIdResetOnConsent[]; -extern const char kMetricsUserId[]; extern const char kMetricsUserConsent[]; +extern const char kMetricsUserId[]; +extern const char kMetricsUserInheritOwnerConsent[]; extern const char kMetricsUserMetricLogs[]; extern const char kMetricsUserMetricLogsMetadata[];
diff --git a/chrome/browser/navigation_predictor/navigation_predictor_preconnect_client_browsertest.cc b/chrome/browser/navigation_predictor/navigation_predictor_preconnect_client_browsertest.cc index 76cab10..6a05811 100644 --- a/chrome/browser/navigation_predictor/navigation_predictor_preconnect_client_browsertest.cc +++ b/chrome/browser/navigation_predictor/navigation_predictor_preconnect_client_browsertest.cc
@@ -409,15 +409,15 @@ ->search_engine_preconnector() ->StartPreconnecting(/*with_startup_delay=*/false); - // There should be 2 DSE preconnects (2 NIKs). - WaitForPreresolveCount(2); - EXPECT_EQ(2, preresolve_done_count_); + // There should be a DSE preconnects. + WaitForPreresolveCount(1); + EXPECT_EQ(1, preresolve_done_count_); ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); // Now there should be an onload preconnect as well as a navigation // preconnect. - WaitForPreresolveCount(4); - EXPECT_EQ(4, preresolve_done_count_); + WaitForPreresolveCount(3); + EXPECT_EQ(3, preresolve_done_count_); } class NavigationPredictorPreconnectClientLocalURLBrowserTest
diff --git a/chrome/browser/navigation_predictor/search_engine_preconnector.cc b/chrome/browser/navigation_predictor/search_engine_preconnector.cc index 0cab384..c8571595 100644 --- a/chrome/browser/navigation_predictor/search_engine_preconnector.cc +++ b/chrome/browser/navigation_predictor/search_engine_preconnector.cc
@@ -108,10 +108,6 @@ schemeful_site); loading_predictor->PreconnectURLIfAllowed( preconnect_url, /*allow_credentials=*/true, network_isolation_key); - - loading_predictor->PreconnectURLIfAllowed(preconnect_url, - /*allow_credentials=*/false, - network_isolation_key); } // The delay beyond the idle socket timeout that net uses when
diff --git a/chrome/browser/navigation_predictor/search_engine_preconnector_browsertest.cc b/chrome/browser/navigation_predictor/search_engine_preconnector_browsertest.cc index ae62f9b..4fa30a0 100644 --- a/chrome/browser/navigation_predictor/search_engine_preconnector_browsertest.cc +++ b/chrome/browser/navigation_predictor/search_engine_preconnector_browsertest.cc
@@ -172,13 +172,13 @@ // After switching search providers, the test URL should now start being // preconnected. - WaitForPreresolveCountForURL(GetTestURL("/"), 2); + WaitForPreresolveCountForURL(GetTestURL("/"), 1); // Preconnect should occur for DSE. - EXPECT_EQ(2, preresolve_counts_[GetTestURL("/").DeprecatedGetOriginAsURL()]); + EXPECT_EQ(1, preresolve_counts_[GetTestURL("/").DeprecatedGetOriginAsURL()]); - WaitForPreresolveCountForURL(GetTestURL("/"), 4); + WaitForPreresolveCountForURL(GetTestURL("/"), 2); // Preconnect should occur again for DSE. - EXPECT_EQ(4, preresolve_counts_[GetTestURL("/").DeprecatedGetOriginAsURL()]); + EXPECT_EQ(2, preresolve_counts_[GetTestURL("/").DeprecatedGetOriginAsURL()]); } IN_PROC_BROWSER_TEST_F(SearchEnginePreconnectorNoDelaysBrowserTest, @@ -221,10 +221,10 @@ ->search_engine_preconnector() ->StartPreconnecting(/*with_startup_delay=*/false); const GURL search_url = template_url->GenerateSearchURL({}); - WaitForPreresolveCountForURL(search_url, 2); + WaitForPreresolveCountForURL(search_url, 1); - // Preconnect should occur for fake search (2 since there are 2 NIKs). - EXPECT_EQ(2, preresolve_counts_[search_url]); + // Preconnect should occur for fake search. + EXPECT_EQ(1, preresolve_counts_[search_url]); // No preconnects should have been issued for the test URL. EXPECT_EQ(0, preresolve_counts_[GetTestURL("/").DeprecatedGetOriginAsURL()]); @@ -331,12 +331,12 @@ ->StartPreconnecting(/*with_startup_delay=*/false); if (!skip_in_background() || load_page()) { - WaitForPreresolveCountForURL(fake_search_url, 2); + WaitForPreresolveCountForURL(fake_search_url, 1); } // If preconnects are skipped in background and no web contents is in // foreground, then no preconnect should happen. - EXPECT_EQ(skip_in_background() && !load_page() ? 0 : 2, + EXPECT_EQ(skip_in_background() && !load_page() ? 0 : 1, preresolve_counts_[fake_search_url]); histogram_tester.ExpectUniqueSample( "NavigationPredictor.SearchEnginePreconnector." @@ -490,10 +490,10 @@ ->StartPreconnecting(/*with_startup_delay=*/false); const GURL search_url = template_url->GenerateSearchURL({}); - WaitForPreresolveCountForURL(search_url, 2); + WaitForPreresolveCountForURL(search_url, 1); - // Preconnect should occur for Google search (2 since there are 2 NIKs). - EXPECT_EQ(2, preresolve_counts_[search_url]); + // Preconnect should occur for Google search. + EXPECT_EQ(1, preresolve_counts_[search_url]); // No preconnects should have been issued for the test URL. EXPECT_EQ(0, preresolve_counts_[GetTestURL("/").DeprecatedGetOriginAsURL()]);
diff --git a/chrome/browser/nearby_sharing/certificates/nearby_share_decrypted_public_certificate.cc b/chrome/browser/nearby_sharing/certificates/nearby_share_decrypted_public_certificate.cc index c2cc52e..80e2650a 100644 --- a/chrome/browser/nearby_sharing/certificates/nearby_share_decrypted_public_certificate.cc +++ b/chrome/browser/nearby_sharing/certificates/nearby_share_decrypted_public_certificate.cc
@@ -199,6 +199,7 @@ public_key_ = other.public_key_; id_ = other.id_; unencrypted_metadata_ = other.unencrypted_metadata_; + for_self_share_ = other.for_self_share_; return *this; }
diff --git a/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc b/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc index 82d825c..5a0b86d4 100644 --- a/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc +++ b/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc
@@ -3746,6 +3746,9 @@ target.device_name = std::move(*device_name); target.is_incoming = is_incoming; target.device_id = GetDeviceId(endpoint_id, certificate); + if (base::FeatureList::IsEnabled(features::kNearbySharingSelfShare)) { + target.for_self_share = certificate && certificate->for_self_share(); + } ShareTargetInfo& info = GetOrCreateShareTargetInfo(target, endpoint_id);
diff --git a/chrome/browser/nearby_sharing/nearby_sharing_service_impl.h b/chrome/browser/nearby_sharing/nearby_sharing_service_impl.h index 6422f0b..c9a8137 100644 --- a/chrome/browser/nearby_sharing/nearby_sharing_service_impl.h +++ b/chrome/browser/nearby_sharing/nearby_sharing_service_impl.h
@@ -61,7 +61,7 @@ class Profile; namespace NearbySharingServiceUnitTests { -class NearbySharingServiceImplTest; +class NearbySharingServiceImplTestBase; } // All methods should be called from the same sequence that created the service. @@ -152,7 +152,7 @@ } private: - friend class NearbySharingServiceUnitTests::NearbySharingServiceImplTest; + friend class NearbySharingServiceUnitTests::NearbySharingServiceImplTestBase; // nearby_share::mojom::NearbyShareSettingsObserver: void OnEnabledChanged(bool enabled) override;
diff --git a/chrome/browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc b/chrome/browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc index 99385f4..cc7761b 100644 --- a/chrome/browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc +++ b/chrome/browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc
@@ -336,6 +336,12 @@ constexpr base::TimeDelta kCertificateDownloadDuringDiscoveryPeriod = base::Seconds(10); +// We will run tests with the following feature flags enabled and disabled in +// all permutations. To add or a remove a feature you can just update this list. +const std::vector<base::Feature> kTestFeatures = { + features::kNearbySharingBackgroundScanning, + features::kNearbySharingSelfShare}; + bool FileExists(const base::FilePath& file_path) { base::ScopedAllowBlockingForTesting allow_blocking; return base::PathExists(file_path); @@ -460,18 +466,15 @@ ~MockBluetoothAdapterWithIntervals() override = default; }; -class NearbySharingServiceImplTest : public testing::Test { +class NearbySharingServiceImplTestBase : public testing::Test { public: - NearbySharingServiceImplTest() + explicit NearbySharingServiceImplTestBase(size_t feature_mask) : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) { - scoped_feature_list_.InitWithFeatures( - /*enabled_features=*/{features::kNearbySharingBackgroundScanning, - features::kNearbySharingSelfShare}, - /*disabled_features=*/{}); + CreateFeatureList(feature_mask); RegisterNearbySharingPrefs(prefs_.registry()); } - ~NearbySharingServiceImplTest() override = default; + ~NearbySharingServiceImplTestBase() override = default; void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); @@ -488,20 +491,21 @@ mock_bluetooth_adapter_ = base::MakeRefCounted<NiceMock<MockBluetoothAdapterWithIntervals>>(); ON_CALL(*mock_bluetooth_adapter_, IsPresent()) - .WillByDefault( - Invoke(this, &NearbySharingServiceImplTest::IsBluetoothPresent)); + .WillByDefault(Invoke( + this, &NearbySharingServiceImplTestBase::IsBluetoothPresent)); ON_CALL(*mock_bluetooth_adapter_, IsPowered()) - .WillByDefault( - Invoke(this, &NearbySharingServiceImplTest::IsBluetoothPowered)); + .WillByDefault(Invoke( + this, &NearbySharingServiceImplTestBase::IsBluetoothPowered)); ON_CALL(*mock_bluetooth_adapter_, AddObserver(_)) - .WillByDefault( - Invoke(this, &NearbySharingServiceImplTest::AddAdapterObserver)); + .WillByDefault(Invoke( + this, &NearbySharingServiceImplTestBase::AddAdapterObserver)); ON_CALL(*mock_bluetooth_adapter_, OnSetAdvertisingInterval(_, _)) .WillByDefault(Invoke( - this, &NearbySharingServiceImplTest::OnSetAdvertisingInterval)); + this, &NearbySharingServiceImplTestBase::OnSetAdvertisingInterval)); ON_CALL(*mock_bluetooth_adapter_, StartLowEnergyScanSession(_, _)) .WillByDefault(Invoke( - this, &NearbySharingServiceImplTest::StartLowEnergyScanSession)); + this, + &NearbySharingServiceImplTestBase::StartLowEnergyScanSession)); device::BluetoothAdapterFactory::SetAdapterForTesting( mock_bluetooth_adapter_); @@ -676,7 +680,7 @@ auto mock_scan_session = std::make_unique<device::MockBluetoothLowEnergyScanSession>( base::BindOnce( - &NearbySharingServiceImplTest::OnScanSessionDestroyed, + &NearbySharingServiceImplTestBase::OnScanSessionDestroyed, weak_ptr_factory_.GetWeakPtr())); mock_scan_session_ = mock_scan_session.get(); return mock_scan_session; @@ -938,6 +942,13 @@ return discovered_target; } + absl::optional<ShareTarget> CreateShareTarget( + const sharing::mojom::AdvertisementPtr& advertisement, + absl::optional<NearbyShareDecryptedPublicCertificate> certificate) { + return service_->CreateShareTarget(kEndpointId, advertisement, certificate, + /*is_incoming=*/true); + } + sharing::nearby::Frame GetWrittenFrame() { std::vector<uint8_t> data = connection_.GetWrittenData(); sharing::nearby::Frame frame; @@ -1274,6 +1285,23 @@ return path; } + void CreateFeatureList(size_t feature_mask) { + std::vector<base::Feature> enabled_features; + std::vector<base::Feature> disabled_features; + + // Use |feature_mask| as a bitmask to decide which features in + // |kTestFeatures| to enable or disable. + for (int i = 0; i < kTestFeatures.size(); i++) { + if (feature_mask & 1 << i) { + enabled_features.push_back(kTestFeatures[i]); + } else { + disabled_features.push_back(kTestFeatures[i]); + } + } + + scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features); + } + size_t set_advertising_interval_call_count() { return set_advertising_interval_call_count_; } @@ -1343,10 +1371,21 @@ process_stopped_callback_; base::HistogramTester histogram_tester_; - base::WeakPtrFactory<NearbySharingServiceImplTest> weak_ptr_factory_{this}; + base::WeakPtrFactory<NearbySharingServiceImplTestBase> weak_ptr_factory_{ + this}; }; -struct ValidSendSurfaceTestData { +// We parameterize these tests to run them with upcoming features enabled and +// disabled. +class NearbySharingServiceImplTest + : public NearbySharingServiceImplTestBase, + public testing::WithParamInterface<size_t> { + public: + NearbySharingServiceImplTest() + : NearbySharingServiceImplTestBase(/*feature_mask=*/GetParam()) {} +}; + +struct ValidSendSurfaceTestDataInternal { bool bluetooth_enabled; net::NetworkChangeNotifier::ConnectionType connection_type; } kValidSendSurfaceTestData[] = { @@ -1359,11 +1398,20 @@ // 3G available {true, net::NetworkChangeNotifier::CONNECTION_3G}}; -class NearbySharingServiceImplValidSendTest - : public NearbySharingServiceImplTest, - public testing::WithParamInterface<ValidSendSurfaceTestData> {}; +// size_t parameter is |feature_mask|. +using ValidSendSurfaceTestData = + std::tuple<ValidSendSurfaceTestDataInternal, size_t>; -struct InvalidSendSurfaceTestData { +class NearbySharingServiceImplValidSendTest + : public NearbySharingServiceImplTestBase, + public testing::WithParamInterface<ValidSendSurfaceTestData> { + public: + NearbySharingServiceImplValidSendTest() + : NearbySharingServiceImplTestBase( + /*feature_mask=*/std::get<1>(GetParam())) {} +}; + +struct InvalidSendSurfaceTestDataInternal { bool screen_locked; bool bluetooth_enabled; net::NetworkChangeNotifier::ConnectionType connection_type; @@ -1382,13 +1430,22 @@ {/*screen_locked=*/false, false, net::NetworkChangeNotifier::CONNECTION_ETHERNET}}; +// size_t parameter is |feature_mask|. +using InvalidSendSurfaceTestData = + std::tuple<InvalidSendSurfaceTestDataInternal, size_t>; + class NearbySharingServiceImplInvalidSendTest - : public NearbySharingServiceImplTest, - public testing::WithParamInterface<InvalidSendSurfaceTestData> {}; + : public NearbySharingServiceImplTestBase, + public testing::WithParamInterface<InvalidSendSurfaceTestData> { + public: + NearbySharingServiceImplInvalidSendTest() + : NearbySharingServiceImplTestBase( + /*feature_mask==*/std::get<1>(GetParam())) {} +}; using ResponseFrameStatus = sharing::mojom::ConnectionResponseFrame::Status; -struct SendFailureTestData { +struct SendFailureTestDataInternal { ResponseFrameStatus response_status; TransferMetadata::Status expected_status; } kSendFailureTestData[] = { @@ -1401,9 +1458,17 @@ {ResponseFrameStatus::kUnknown, TransferMetadata::Status::kFailed}, }; +// size_t parameter is |feature_mask|. +using SendFailureTestData = std::tuple<SendFailureTestDataInternal, size_t>; + class NearbySharingServiceImplSendFailureTest - : public NearbySharingServiceImplTest, - public testing::WithParamInterface<SendFailureTestData> {}; + : public NearbySharingServiceImplTestBase, + public testing::WithParamInterface<SendFailureTestData> { + public: + NearbySharingServiceImplSendFailureTest() + : NearbySharingServiceImplTestBase( + /*feature_mask=*/std::get<1>(GetParam())) {} +}; class TestObserver : public NearbySharingService::Observer { public: @@ -1446,14 +1511,14 @@ NearbySharingService* service_; }; -TEST_F(NearbySharingServiceImplTest, DisableNearbyShutdownConnections) { +TEST_P(NearbySharingServiceImplTest, DisableNearbyShutdownConnections) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); prefs_.SetBoolean(prefs::kNearbySharingEnabledPrefName, false); service_->FlushMojoForTesting(); EXPECT_TRUE(fake_nearby_connections_manager_->is_shutdown()); } -TEST_F(NearbySharingServiceImplTest, StartFastInitiationAdvertising) { +TEST_P(NearbySharingServiceImplTest, StartFastInitiationAdvertising) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback transfer_callback; MockShareTargetDiscoveredCallback discovery_callback; @@ -1472,7 +1537,7 @@ EXPECT_EQ(1u, fast_initiation_advertiser_factory_->StartAdvertisingCount()); } -TEST_F(NearbySharingServiceImplTest, StartFastInitiationAdvertisingError) { +TEST_P(NearbySharingServiceImplTest, StartFastInitiationAdvertisingError) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); SetFakeFastInitiationAdvertiserFactory(/*should_succeed_on_start=*/false); MockTransferUpdateCallback transfer_callback; @@ -1483,7 +1548,7 @@ SendSurfaceState::kForeground)); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, BackgroundStartFastInitiationAdvertisingError) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback transfer_callback; @@ -1495,7 +1560,7 @@ EXPECT_EQ(0u, fast_initiation_advertiser_factory_->StartAdvertisingCount()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, StartFastInitiationAdvertising_BluetoothNotPresent) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); is_bluetooth_present_ = false; @@ -1507,7 +1572,7 @@ SendSurfaceState::kForeground)); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, StartFastInitiationAdvertising_BluetoothNotPowered) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); is_bluetooth_powered_ = false; @@ -1519,7 +1584,7 @@ SendSurfaceState::kForeground)); } -TEST_F(NearbySharingServiceImplTest, StopFastInitiationAdvertising) { +TEST_P(NearbySharingServiceImplTest, StopFastInitiationAdvertising) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback transfer_callback; MockShareTargetDiscoveredCallback discovery_callback; @@ -1535,7 +1600,7 @@ ->StopAdvertisingCalledAndAdvertiserDestroyed()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, StopFastInitiationAdvertising_BluetoothBecomesNotPresent) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback transfer_callback; @@ -1549,7 +1614,7 @@ ->StopAdvertisingCalledAndAdvertiserDestroyed()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, StopFastInitiationAdvertising_BluetoothBecomesNotPowered) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback transfer_callback; @@ -1563,7 +1628,7 @@ ->StopAdvertisingCalledAndAdvertiserDestroyed()); } -TEST_F(NearbySharingServiceImplTest, RegisterSendSurface_BluetoothNotPresent) { +TEST_P(NearbySharingServiceImplTest, RegisterSendSurface_BluetoothNotPresent) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_NONE); is_bluetooth_present_ = false; MockTransferUpdateCallback transfer_callback; @@ -1575,7 +1640,7 @@ EXPECT_FALSE(fake_nearby_connections_manager_->IsDiscovering()); } -TEST_F(NearbySharingServiceImplTest, RegisterSendSurface_BluetoothNotPowered) { +TEST_P(NearbySharingServiceImplTest, RegisterSendSurface_BluetoothNotPowered) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_NONE); is_bluetooth_powered_ = false; MockTransferUpdateCallback transfer_callback; @@ -1587,7 +1652,7 @@ EXPECT_FALSE(fake_nearby_connections_manager_->IsDiscovering()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, ForegroundRegisterSendSurfaceStartsDiscovering) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback transfer_callback; @@ -1599,7 +1664,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->IsDiscovering()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, ForegroundRegisterSendSurfaceTwiceKeepsDiscovering) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback transfer_callback; @@ -1617,7 +1682,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->IsDiscovering()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, BluetoothBecomesNotPresentStopDiscovering) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_BLUETOOTH); MockTransferUpdateCallback transfer_callback; @@ -1634,7 +1699,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->IsDiscovering()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, BluetoothBecomesNotPoweredStopDiscovering) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_BLUETOOTH); MockTransferUpdateCallback transfer_callback; @@ -1651,7 +1716,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->IsDiscovering()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, RegisterSendSurfaceAlreadyReceivingNotDiscovering) { NiceMock<MockTransferUpdateCallback> callback; ShareTarget share_target = SetUpIncomingConnection(callback); @@ -1669,7 +1734,7 @@ service_->UnregisterReceiveSurface(&callback); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, BackgroundRegisterSendSurfaceNotDiscovering) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback transfer_callback; @@ -1682,7 +1747,7 @@ EXPECT_FALSE(fake_nearby_connections_manager_->is_shutdown()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, DifferentSurfaceRegisterSendSurfaceTwiceKeepsDiscovering) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback transfer_callback; @@ -1700,7 +1765,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->IsDiscovering()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, RegisterSendSurfaceEndpointFoundDiscoveryCallbackNotified) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); @@ -1732,6 +1797,7 @@ EXPECT_TRUE(share_target.device_id); EXPECT_NE(kEndpointId, share_target.device_id); EXPECT_EQ(kTestMetadataFullName, share_target.full_name); + EXPECT_FALSE(share_target.for_self_share); run_loop.Quit(); }); fake_nearby_connections_manager_->OnEndpointFound( @@ -1765,7 +1831,7 @@ service_.reset(); } -TEST_F(NearbySharingServiceImplTest, RegisterSendSurfaceEmptyCertificate) { +TEST_P(NearbySharingServiceImplTest, RegisterSendSurfaceEmptyCertificate) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); // Ensure decoder parses a valid endpoint advertisement. @@ -1796,6 +1862,7 @@ EXPECT_TRUE(share_target.device_id); EXPECT_EQ(kEndpointId, share_target.device_id); EXPECT_FALSE(share_target.full_name); + EXPECT_FALSE(share_target.for_self_share); run_loop.Quit(); }); fake_nearby_connections_manager_->OnEndpointFound( @@ -1831,8 +1898,8 @@ TEST_P(NearbySharingServiceImplValidSendTest, RegisterSendSurfaceIsDiscovering) { - is_bluetooth_present_ = GetParam().bluetooth_enabled; - SetConnectionType(GetParam().connection_type); + is_bluetooth_present_ = std::get<0>(GetParam()).bluetooth_enabled; + SetConnectionType(std::get<0>(GetParam()).connection_type); MockTransferUpdateCallback transfer_callback; MockShareTargetDiscoveredCallback discovery_callback; EXPECT_EQ( @@ -1842,15 +1909,17 @@ EXPECT_TRUE(fake_nearby_connections_manager_->IsDiscovering()); } -INSTANTIATE_TEST_SUITE_P(NearbySharingServiceImplTest, - NearbySharingServiceImplValidSendTest, - testing::ValuesIn(kValidSendSurfaceTestData)); +INSTANTIATE_TEST_SUITE_P( + NearbySharingServiceImplTest, + NearbySharingServiceImplValidSendTest, + testing::Combine(testing::ValuesIn(kValidSendSurfaceTestData), + testing::Range<size_t>(0, 1 << kTestFeatures.size()))); TEST_P(NearbySharingServiceImplInvalidSendTest, RegisterSendSurfaceNotDiscovering) { - session_controller_->SetScreenLocked(GetParam().screen_locked); - is_bluetooth_present_ = GetParam().bluetooth_enabled; - SetConnectionType(GetParam().connection_type); + session_controller_->SetScreenLocked(std::get<0>(GetParam()).screen_locked); + is_bluetooth_present_ = std::get<0>(GetParam()).bluetooth_enabled; + SetConnectionType(std::get<0>(GetParam()).connection_type); MockTransferUpdateCallback transfer_callback; MockShareTargetDiscoveredCallback discovery_callback; EXPECT_FALSE(fake_nearby_connections_manager_->IsDiscovering()); @@ -1868,11 +1937,13 @@ EXPECT_FALSE(fake_nearby_connections_manager_->is_shutdown()); } -INSTANTIATE_TEST_SUITE_P(NearbySharingServiceImplTest, - NearbySharingServiceImplInvalidSendTest, - testing::ValuesIn(kInvalidSendSurfaceTestData)); +INSTANTIATE_TEST_SUITE_P( + NearbySharingServiceImplTest, + NearbySharingServiceImplInvalidSendTest, + testing::Combine(testing::ValuesIn(kInvalidSendSurfaceTestData), + testing::Range<size_t>(0, 1 << kTestFeatures.size()))); -TEST_F(NearbySharingServiceImplTest, DisableFeatureSendSurfaceNotDiscovering) { +TEST_P(NearbySharingServiceImplTest, DisableFeatureSendSurfaceNotDiscovering) { prefs_.SetBoolean(prefs::kNearbySharingEnabledPrefName, false); service_->FlushMojoForTesting(); SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); @@ -1886,7 +1957,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->is_shutdown()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, DisableFeatureSendSurfaceStopsDiscovering) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback transfer_callback; @@ -1903,7 +1974,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->is_shutdown()); } -TEST_F(NearbySharingServiceImplTest, UnregisterSendSurfaceStopsDiscovering) { +TEST_P(NearbySharingServiceImplTest, UnregisterSendSurfaceStopsDiscovering) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback transfer_callback; MockShareTargetDiscoveredCallback discovery_callback; @@ -1923,7 +1994,7 @@ EXPECT_FALSE(IsBoundToProcess()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, UnregisterSendSurfaceDifferentCallbackKeepDiscovering) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback transfer_callback; @@ -1942,7 +2013,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->IsDiscovering()); } -TEST_F(NearbySharingServiceImplTest, UnregisterSendSurfaceNeverRegistered) { +TEST_P(NearbySharingServiceImplTest, UnregisterSendSurfaceNeverRegistered) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback transfer_callback; @@ -1956,7 +2027,7 @@ EXPECT_FALSE(IsBoundToProcess()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, ForegroundRegisterReceiveSurfaceIsAdvertisingAllContacts) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); SetVisibility(nearby_share::mojom::Visibility::kAllContacts); @@ -1980,7 +2051,7 @@ advertisement->encrypted_metadata_key()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, ForegroundRegisterReceiveSurfaceIsAdvertisingNoOne) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); SetVisibility(nearby_share::mojom::Visibility::kNoOne); @@ -2006,7 +2077,7 @@ advertisement->encrypted_metadata_key().size()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, BackgroundRegisterReceiveSurfaceIsAdvertisingSelectedContacts) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); SetVisibility(nearby_share::mojom::Visibility::kSelectedContacts); @@ -2031,7 +2102,7 @@ advertisement->encrypted_metadata_key()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, RegisterReceiveSurfaceTwiceSameCallbackKeepAdvertising) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback callback; @@ -2046,7 +2117,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, RegisterReceiveSurfaceTwiceKeepAdvertising) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback callback; @@ -2062,7 +2133,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, ScreenLockedRegisterReceiveSurfaceNotAdvertising) { session_controller_->SetScreenLocked(true); SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); @@ -2070,11 +2141,15 @@ NearbySharingService::StatusCodes result = service_->RegisterReceiveSurface( &callback, NearbySharingService::ReceiveSurfaceState::kForeground); EXPECT_EQ(result, NearbySharingService::StatusCodes::kOk); - EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising()); + if (base::FeatureList::IsEnabled(features::kNearbySharingSelfShare)) { + EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising()); + } else { + EXPECT_FALSE(fake_nearby_connections_manager_->IsAdvertising()); + } EXPECT_FALSE(fake_nearby_connections_manager_->is_shutdown()); } -TEST_F(NearbySharingServiceImplTest, ScreenLocksDuringAdvertising) { +TEST_P(NearbySharingServiceImplTest, ScreenLocksDuringAdvertising) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback callback; NearbySharingService::StatusCodes result = service_->RegisterReceiveSurface( @@ -2084,7 +2159,11 @@ EXPECT_FALSE(fake_nearby_connections_manager_->is_shutdown()); session_controller_->SetScreenLocked(true); - EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising()); + if (base::FeatureList::IsEnabled(features::kNearbySharingSelfShare)) { + EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising()); + } else { + EXPECT_FALSE(fake_nearby_connections_manager_->IsAdvertising()); + } EXPECT_FALSE(fake_nearby_connections_manager_->is_shutdown()); session_controller_->SetScreenLocked(false); @@ -2092,7 +2171,7 @@ EXPECT_FALSE(fake_nearby_connections_manager_->is_shutdown()); } -TEST_F(NearbySharingServiceImplTest, ScreenLocksDuringDiscovery) { +TEST_P(NearbySharingServiceImplTest, ScreenLocksDuringDiscovery) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback transfer_callback; MockShareTargetDiscoveredCallback discovery_callback; @@ -2108,7 +2187,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->IsDiscovering()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, SuspendedRegisterReceiveSurfaceNotAdvertising) { power_client_->SetSuspended(true); SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); @@ -2120,7 +2199,7 @@ EXPECT_FALSE(fake_nearby_connections_manager_->is_shutdown()); } -TEST_F(NearbySharingServiceImplTest, SuspendDuringAdvertising) { +TEST_P(NearbySharingServiceImplTest, SuspendDuringAdvertising) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback transfer_callback; MockShareTargetDiscoveredCallback discovery_callback; @@ -2136,7 +2215,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->IsDiscovering()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, DataUsageChangedRegisterReceiveSurfaceRestartsAdvertising) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); @@ -2159,7 +2238,7 @@ fake_nearby_connections_manager_->advertising_data_usage()); } -TEST_F( +TEST_P( NearbySharingServiceImplTest, UnregisterForegroundReceiveSurfaceVisibilityAllContactsRestartAdvertising) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); @@ -2205,7 +2284,7 @@ EXPECT_FALSE(service_->IsInHighVisibility()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, NoNetworkRegisterReceiveSurfaceIsAdvertising) { MockTransferUpdateCallback callback; NearbySharingService::StatusCodes result = service_->RegisterReceiveSurface( @@ -2215,7 +2294,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, NoBluetoothNoNetworkRegisterForegroundReceiveSurfaceNotAdvertising) { is_bluetooth_present_ = false; MockTransferUpdateCallback callback; @@ -2227,7 +2306,7 @@ EXPECT_FALSE(fake_nearby_connections_manager_->is_shutdown()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, NoBluetoothNoNetworkRegisterBackgroundReceiveSurfaceWorks) { is_bluetooth_present_ = false; MockTransferUpdateCallback callback; @@ -2237,7 +2316,7 @@ EXPECT_FALSE(fake_nearby_connections_manager_->IsAdvertising()); } -TEST_F(NearbySharingServiceImplTest, WifiRegisterReceiveSurfaceIsAdvertising) { +TEST_P(NearbySharingServiceImplTest, WifiRegisterReceiveSurfaceIsAdvertising) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback callback; NearbySharingService::StatusCodes result = service_->RegisterReceiveSurface( @@ -2246,7 +2325,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, EthernetRegisterReceiveSurfaceIsAdvertising) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_ETHERNET); MockTransferUpdateCallback callback; @@ -2256,7 +2335,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, ThreeGRegisterReceiveSurfaceIsAdvertising) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_3G); MockTransferUpdateCallback callback; @@ -2267,7 +2346,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, NoBluetoothWifiReceiveSurfaceIsAdvertising) { is_bluetooth_present_ = false; SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); @@ -2282,7 +2361,7 @@ EXPECT_FALSE(fake_nearby_connections_manager_->IsAdvertising()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, NoBluetoothEthernetReceiveSurfaceIsAdvertising) { is_bluetooth_present_ = false; SetConnectionType(net::NetworkChangeNotifier::CONNECTION_ETHERNET); @@ -2297,7 +2376,7 @@ EXPECT_FALSE(fake_nearby_connections_manager_->IsAdvertising()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, NoBluetoothThreeGReceiveSurfaceNotAdvertising) { is_bluetooth_present_ = false; SetConnectionType(net::NetworkChangeNotifier::CONNECTION_3G); @@ -2310,7 +2389,7 @@ EXPECT_FALSE(fake_nearby_connections_manager_->is_shutdown()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, DisableFeatureReceiveSurfaceNotAdvertising) { prefs_.SetBoolean(prefs::kNearbySharingEnabledPrefName, false); service_->FlushMojoForTesting(); @@ -2323,7 +2402,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->is_shutdown()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, DisableFeatureReceiveSurfaceStopsAdvertising) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback callback; @@ -2338,7 +2417,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->is_shutdown()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, ForegroundReceiveSurfaceNoOneVisibilityIsAdvertising) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); prefs_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName, @@ -2350,7 +2429,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, BackgroundReceiveSurfaceNoOneVisibilityNotAdvertising) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); prefs_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName, @@ -2364,7 +2443,7 @@ EXPECT_FALSE(fake_nearby_connections_manager_->is_shutdown()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, BackgroundReceiveSurfaceVisibilityToNoOneStopsAdvertising) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); prefs_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName, @@ -2383,7 +2462,7 @@ EXPECT_FALSE(fake_nearby_connections_manager_->is_shutdown()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, BackgroundReceiveSurfaceVisibilityToSelectedStartsAdvertising) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); prefs_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName, @@ -2402,7 +2481,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, ForegroundReceiveSurfaceSelectedContactsVisibilityIsAdvertising) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); prefs_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName, @@ -2414,7 +2493,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, BackgroundReceiveSurfaceSelectedContactsVisibilityIsAdvertising) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); prefs_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName, @@ -2426,7 +2505,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, ForegroundReceiveSurfaceAllContactsVisibilityIsAdvertising) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); prefs_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName, @@ -2438,7 +2517,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, BackgroundReceiveSurfaceAllContactsVisibilityNotAdvertising) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); prefs_.SetInteger(prefs::kNearbySharingBackgroundVisibilityName, @@ -2450,7 +2529,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising()); } -TEST_F(NearbySharingServiceImplTest, UnregisterReceiveSurfaceStopsAdvertising) { +TEST_P(NearbySharingServiceImplTest, UnregisterReceiveSurfaceStopsAdvertising) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback callback; NearbySharingService::StatusCodes result = service_->RegisterReceiveSurface( @@ -2468,7 +2547,7 @@ EXPECT_FALSE(IsBoundToProcess()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, UnregisterReceiveSurfaceDifferentCallbackKeepAdvertising) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback callback; @@ -2484,7 +2563,7 @@ EXPECT_TRUE(fake_nearby_connections_manager_->IsAdvertising()); } -TEST_F(NearbySharingServiceImplTest, UnregisterReceiveSurfaceNeverRegistered) { +TEST_P(NearbySharingServiceImplTest, UnregisterReceiveSurfaceNeverRegistered) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback callback; @@ -2498,7 +2577,7 @@ EXPECT_FALSE(IsBoundToProcess()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, IncomingConnection_ClosedReadingIntroduction) { fake_nearby_connections_manager_->SetRawAuthenticationToken(kEndpointId, kToken); @@ -2531,7 +2610,7 @@ service_->UnregisterReceiveSurface(&callback); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, IncomingConnection_EmptyIntroductionFrame) { fake_nearby_connections_manager_->SetRawAuthenticationToken(kEndpointId, kToken); @@ -2559,6 +2638,7 @@ EXPECT_TRUE(share_target.device_id); EXPECT_NE(kEndpointId, share_target.device_id); EXPECT_EQ(kTestMetadataFullName, share_target.full_name); + EXPECT_FALSE(share_target.for_self_share); run_loop.Quit(); })); @@ -2581,7 +2661,7 @@ service_->UnregisterReceiveSurface(&callback); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, IncomingConnection_ValidIntroductionFrame_InvalidCertificate) { fake_nearby_connections_manager_->SetRawAuthenticationToken(kEndpointId, kToken); @@ -2610,6 +2690,7 @@ EXPECT_EQ(kDeviceType, share_target.type); EXPECT_EQ(kEndpointId, share_target.device_id); EXPECT_FALSE(share_target.full_name); + EXPECT_FALSE(share_target.for_self_share); run_loop.Quit(); })); @@ -2628,7 +2709,7 @@ service_->UnregisterReceiveSurface(&callback); } -TEST_F(NearbySharingServiceImplTest, IncomingConnection_TimedOut) { +TEST_P(NearbySharingServiceImplTest, IncomingConnection_TimedOut) { NiceMock<MockTransferUpdateCallback> callback; ShareTarget share_target = SetUpIncomingConnection(callback); EXPECT_FALSE(connection_.IsClosed()); @@ -2645,7 +2726,7 @@ EXPECT_TRUE(connection_.IsClosed()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, IncomingConnection_ClosedWaitingLocalConfirmation) { NiceMock<MockTransferUpdateCallback> callback; ShareTarget share_target = SetUpIncomingConnection(callback); @@ -2667,7 +2748,7 @@ service_->UnregisterReceiveSurface(&callback); } -TEST_F(NearbySharingServiceImplTest, IncomingConnection_OutOfStorage) { +TEST_P(NearbySharingServiceImplTest, IncomingConnection_OutOfStorage) { fake_nearby_connections_manager_->SetRawAuthenticationToken(kEndpointId, kToken); SetUpAdvertisementDecoder(kValidV1EndpointInfo, @@ -2722,6 +2803,7 @@ EXPECT_TRUE(share_target.device_id); EXPECT_NE(kEndpointId, share_target.device_id); EXPECT_EQ(kTestMetadataFullName, share_target.full_name); + EXPECT_FALSE(share_target.for_self_share); EXPECT_EQ(TransferMetadata::Status::kNotEnoughSpace, metadata.status()); run_loop.Quit(); @@ -2740,7 +2822,7 @@ service_->UnregisterReceiveSurface(&callback); } -TEST_F(NearbySharingServiceImplTest, IncomingConnection_FileSizeOverflow) { +TEST_P(NearbySharingServiceImplTest, IncomingConnection_FileSizeOverflow) { fake_nearby_connections_manager_->SetRawAuthenticationToken(kEndpointId, kToken); SetUpAdvertisementDecoder(kValidV1EndpointInfo, @@ -2797,6 +2879,7 @@ EXPECT_TRUE(share_target.device_id); EXPECT_NE(kEndpointId, share_target.device_id); EXPECT_EQ(kTestMetadataFullName, share_target.full_name); + EXPECT_FALSE(share_target.for_self_share); EXPECT_EQ(TransferMetadata::Status::kNotEnoughSpace, metadata.status()); run_loop.Quit(); @@ -2815,7 +2898,7 @@ service_->UnregisterReceiveSurface(&callback); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, IncomingConnection_ValidIntroductionFrame_ValidCertificate) { fake_nearby_connections_manager_->SetRawAuthenticationToken(kEndpointId, kToken); @@ -2845,6 +2928,7 @@ EXPECT_TRUE(share_target.device_id); EXPECT_NE(kEndpointId, share_target.device_id); EXPECT_EQ(kTestMetadataFullName, share_target.full_name); + EXPECT_FALSE(share_target.for_self_share); EXPECT_FALSE(metadata.token().has_value()); run_loop.Quit(); @@ -2865,7 +2949,7 @@ service_->UnregisterReceiveSurface(&callback); } -TEST_F(NearbySharingServiceImplTest, AcceptInvalidShareTarget) { +TEST_P(NearbySharingServiceImplTest, AcceptInvalidShareTarget) { ShareTarget share_target; base::RunLoop run_loop; service_->Accept( @@ -2880,7 +2964,7 @@ run_loop.Run(); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, AcceptValidShareTarget_RegisterPayloadError) { fake_nearby_connections_manager_->SetPayloadPathStatus( kFilePayloadId, location::nearby::connections::mojom::Status::kError); @@ -2922,7 +3006,7 @@ service_->UnregisterReceiveSurface(&callback); } -TEST_F(NearbySharingServiceImplTest, AcceptValidShareTarget) { +TEST_P(NearbySharingServiceImplTest, AcceptValidShareTarget) { for (int64_t payload_id : kValidIntroductionFramePayloadIds) { fake_nearby_connections_manager_->SetPayloadPathStatus( payload_id, location::nearby::connections::mojom::Status::kSuccess); @@ -2975,11 +3059,11 @@ service_->UnregisterReceiveSurface(&callback); } -TEST_F(NearbySharingServiceImplTest, AcceptValidShareTarget_PayloadSuccessful) { +TEST_P(NearbySharingServiceImplTest, AcceptValidShareTarget_PayloadSuccessful) { SuccessfullyReceiveTransfer(); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, AcceptValidShareTarget_PayloadSuccessful_IncomingPayloadNotFound) { for (int64_t payload_id : kValidIntroductionFramePayloadIds) { fake_nearby_connections_manager_->SetPayloadPathStatus( @@ -3092,7 +3176,7 @@ service_->UnregisterReceiveSurface(&callback); } -TEST_F(NearbySharingServiceImplTest, AcceptValidShareTarget_PayloadFailed) { +TEST_P(NearbySharingServiceImplTest, AcceptValidShareTarget_PayloadFailed) { for (int64_t payload_id : kValidIntroductionFramePayloadIds) { fake_nearby_connections_manager_->SetPayloadPathStatus( payload_id, location::nearby::connections::mojom::Status::kSuccess); @@ -3166,7 +3250,7 @@ service_->UnregisterReceiveSurface(&callback); } -TEST_F(NearbySharingServiceImplTest, AcceptValidShareTarget_PayloadCancelled) { +TEST_P(NearbySharingServiceImplTest, AcceptValidShareTarget_PayloadCancelled) { for (int64_t payload_id : kValidIntroductionFramePayloadIds) { fake_nearby_connections_manager_->SetPayloadPathStatus( payload_id, location::nearby::connections::mojom::Status::kSuccess); @@ -3240,7 +3324,7 @@ service_->UnregisterReceiveSurface(&callback); } -TEST_F(NearbySharingServiceImplTest, RejectInvalidShareTarget) { +TEST_P(NearbySharingServiceImplTest, RejectInvalidShareTarget) { ShareTarget share_target; base::RunLoop run_loop; service_->Reject( @@ -3255,7 +3339,7 @@ run_loop.Run(); } -TEST_F(NearbySharingServiceImplTest, RejectValidShareTarget) { +TEST_P(NearbySharingServiceImplTest, RejectValidShareTarget) { NiceMock<MockTransferUpdateCallback> callback; ShareTarget share_target = SetUpIncomingConnection(callback); @@ -3290,7 +3374,7 @@ service_->UnregisterReceiveSurface(&callback); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, IncomingConnection_KeyVerificationRunnerStatusUnable) { fake_nearby_connections_manager_->SetRawAuthenticationToken(kEndpointId, kToken); @@ -3320,6 +3404,7 @@ EXPECT_TRUE(share_target.device_id); EXPECT_NE(kEndpointId, share_target.device_id); EXPECT_EQ(kTestMetadataFullName, share_target.full_name); + EXPECT_FALSE(share_target.for_self_share); EXPECT_EQ(kFourDigitToken, metadata.token()); run_loop.Quit(); @@ -3344,7 +3429,7 @@ service_->UnregisterReceiveSurface(&callback); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, IncomingConnection_KeyVerificationRunnerStatusUnable_LowPower) { fake_nearby_connections_manager_->SetRawAuthenticationToken(kEndpointId, kToken); @@ -3374,6 +3459,7 @@ EXPECT_TRUE(share_target.device_id); EXPECT_NE(kEndpointId, share_target.device_id); EXPECT_EQ(kTestMetadataFullName, share_target.full_name); + EXPECT_FALSE(share_target.for_self_share); EXPECT_EQ(kFourDigitToken, metadata.token()); run_loop.Quit(); @@ -3402,7 +3488,7 @@ service_->UnregisterReceiveSurface(&callback); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, IncomingConnection_KeyVerificationRunnerStatusFail) { fake_nearby_connections_manager_->SetRawAuthenticationToken(kEndpointId, kToken); @@ -3439,7 +3525,7 @@ service_->UnregisterReceiveSurface(&callback); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, IncomingConnection_EmptyAuthToken_KeyVerificationRunnerStatusFail) { SetUpAdvertisementDecoder(kValidV1EndpointInfo, /*return_empty_advertisement=*/false, @@ -3469,7 +3555,7 @@ service_->UnregisterReceiveSurface(&callback); } -TEST_F(NearbySharingServiceImplTest, RegisterReceiveSurfaceWhileSending) { +TEST_P(NearbySharingServiceImplTest, RegisterReceiveSurfaceWhileSending) { MockTransferUpdateCallback transfer_callback; MockShareTargetDiscoveredCallback discovery_callback; ShareTarget target = @@ -3495,7 +3581,7 @@ service_->UnregisterSendSurface(&transfer_callback, &discovery_callback); } -TEST_F(NearbySharingServiceImplTest, RegisterReceiveSurfaceAlreadyReceiving) { +TEST_P(NearbySharingServiceImplTest, RegisterReceiveSurfaceAlreadyReceiving) { NiceMock<MockTransferUpdateCallback> callback; ShareTarget share_target = SetUpIncomingConnection(callback); EXPECT_FALSE(connection_.IsClosed()); @@ -3511,7 +3597,7 @@ service_->UnregisterReceiveSurface(&callback); } -TEST_F(NearbySharingServiceImplTest, RegisterReceiveSurfaceWhileDiscovering) { +TEST_P(NearbySharingServiceImplTest, RegisterReceiveSurfaceWhileDiscovering) { MockTransferUpdateCallback transfer_callback; MockShareTargetDiscoveredCallback discovery_callback; EXPECT_EQ( @@ -3527,7 +3613,7 @@ NearbySharingService::StatusCodes::kTransferAlreadyInProgress); } -TEST_F(NearbySharingServiceImplTest, SendAttachments_WithoutAttachments) { +TEST_P(NearbySharingServiceImplTest, SendAttachments_WithoutAttachments) { MockTransferUpdateCallback transfer_callback; MockShareTargetDiscoveredCallback discovery_callback; ShareTarget target = @@ -3538,7 +3624,7 @@ service_->UnregisterSendSurface(&transfer_callback, &discovery_callback); } -TEST_F(NearbySharingServiceImplTest, SendText_AlreadySending) { +TEST_P(NearbySharingServiceImplTest, SendText_AlreadySending) { MockTransferUpdateCallback transfer_callback; MockShareTargetDiscoveredCallback discovery_callback; ShareTarget target = @@ -3563,14 +3649,14 @@ service_->UnregisterSendSurface(&transfer_callback, &discovery_callback); } -TEST_F(NearbySharingServiceImplTest, SendText_WithoutScanning) { +TEST_P(NearbySharingServiceImplTest, SendText_WithoutScanning) { ShareTarget target; EXPECT_EQ( NearbySharingServiceImpl::StatusCodes::kError, service_->SendAttachments(target, CreateTextAttachments({kTextPayload}))); } -TEST_F(NearbySharingServiceImplTest, SendText_UnknownTarget) { +TEST_P(NearbySharingServiceImplTest, SendText_UnknownTarget) { MockTransferUpdateCallback transfer_callback; MockShareTargetDiscoveredCallback discovery_callback; DiscoverShareTarget(transfer_callback, discovery_callback); @@ -3582,7 +3668,7 @@ service_->UnregisterSendSurface(&transfer_callback, &discovery_callback); } -TEST_F(NearbySharingServiceImplTest, SendText_FailedCreateEndpointInfo) { +TEST_P(NearbySharingServiceImplTest, SendText_FailedCreateEndpointInfo) { // Set name with too many characters. local_device_data_manager()->SetDeviceName(std::string(300, 'a')); @@ -3598,7 +3684,7 @@ service_->UnregisterSendSurface(&transfer_callback, &discovery_callback); } -TEST_F(NearbySharingServiceImplTest, SendText_FailedToConnect) { +TEST_P(NearbySharingServiceImplTest, SendText_FailedToConnect) { MockTransferUpdateCallback transfer_callback; MockShareTargetDiscoveredCallback discovery_callback; // Call DiscoverShareTarget() instead of SetUpOutgoingShareTarget() as we want @@ -3621,7 +3707,7 @@ service_->UnregisterSendSurface(&transfer_callback, &discovery_callback); } -TEST_F(NearbySharingServiceImplTest, SendText_FailedKeyVerification) { +TEST_P(NearbySharingServiceImplTest, SendText_FailedKeyVerification) { MockTransferUpdateCallback transfer_callback; MockShareTargetDiscoveredCallback discovery_callback; ShareTarget target = @@ -3648,7 +3734,7 @@ service_->UnregisterSendSurface(&transfer_callback, &discovery_callback); } -TEST_F(NearbySharingServiceImplTest, SendText_UnableToVerifyKey) { +TEST_P(NearbySharingServiceImplTest, SendText_UnableToVerifyKey) { MockTransferUpdateCallback transfer_callback; MockShareTargetDiscoveredCallback discovery_callback; ShareTarget target = @@ -3700,11 +3786,12 @@ // We're now waiting for the remote device to respond with the accept result. base::RunLoop reject_run_loop; - ExpectTransferUpdates(transfer_callback, target, {GetParam().expected_status}, + ExpectTransferUpdates(transfer_callback, target, + {std::get<0>(GetParam()).expected_status}, reject_run_loop.QuitClosure()); // Cancel the transfer by rejecting it. - SendConnectionResponse(GetParam().response_status); + SendConnectionResponse(std::get<0>(GetParam()).response_status); reject_run_loop.Run(); EXPECT_TRUE(connection_.IsClosed()); @@ -3739,11 +3826,12 @@ // We're now waiting for the remote device to respond with the accept result. base::RunLoop reject_run_loop; - ExpectTransferUpdates(transfer_callback, target, {GetParam().expected_status}, + ExpectTransferUpdates(transfer_callback, target, + {std::get<0>(GetParam()).expected_status}, reject_run_loop.QuitClosure()); // Cancel the transfer by rejecting it. - SendConnectionResponse(GetParam().response_status); + SendConnectionResponse(std::get<0>(GetParam()).response_status); reject_run_loop.Run(); EXPECT_TRUE(connection_.IsClosed()); @@ -3751,11 +3839,13 @@ service_->UnregisterSendSurface(&transfer_callback, &discovery_callback); } -INSTANTIATE_TEST_SUITE_P(NearbySharingServiceImplSendFailureTest, - NearbySharingServiceImplSendFailureTest, - testing::ValuesIn(kSendFailureTestData)); +INSTANTIATE_TEST_SUITE_P( + NearbySharingServiceImplSendFailureTest, + NearbySharingServiceImplSendFailureTest, + testing::Combine(testing::ValuesIn(kSendFailureTestData), + testing::Range<size_t>(0, 1 << kTestFeatures.size()))); -TEST_F(NearbySharingServiceImplTest, SendText_Success) { +TEST_P(NearbySharingServiceImplTest, SendText_Success) { MockTransferUpdateCallback transfer_callback; MockShareTargetDiscoveredCallback discovery_callback; ShareTarget target = @@ -3818,7 +3908,7 @@ service_->UnregisterSendSurface(&transfer_callback, &discovery_callback); } -TEST_F(NearbySharingServiceImplTest, SendText_SuccessClosedConnection) { +TEST_P(NearbySharingServiceImplTest, SendText_SuccessClosedConnection) { MockTransferUpdateCallback transfer_callback; MockShareTargetDiscoveredCallback discovery_callback; ShareTarget target = @@ -3846,7 +3936,7 @@ service_->UnregisterSendSurface(&transfer_callback, &discovery_callback); } -TEST_F(NearbySharingServiceImplTest, SendFiles_Success) { +TEST_P(NearbySharingServiceImplTest, SendFiles_Success) { MockTransferUpdateCallback transfer_callback; MockShareTargetDiscoveredCallback discovery_callback; ShareTarget target = @@ -3918,7 +4008,7 @@ service_->UnregisterSendSurface(&transfer_callback, &discovery_callback); } -TEST_F(NearbySharingServiceImplTest, Cancel_Sender_Initiator) { +TEST_P(NearbySharingServiceImplTest, Cancel_Sender_Initiator) { MockTransferUpdateCallback transfer_callback; MockShareTargetDiscoveredCallback discovery_callback; ShareTarget target = @@ -3967,7 +4057,7 @@ EXPECT_TRUE(connection_.IsClosed()); } -TEST_F(NearbySharingServiceImplTest, Cancel_Sender_Noninitiator) { +TEST_P(NearbySharingServiceImplTest, Cancel_Sender_Noninitiator) { MockTransferUpdateCallback transfer_callback; MockShareTargetDiscoveredCallback discovery_callback; ShareTarget target = @@ -3996,7 +4086,7 @@ EXPECT_TRUE(connection_.IsClosed()); } -TEST_F(NearbySharingServiceImplTest, Cancel_Receiver_Initiator) { +TEST_P(NearbySharingServiceImplTest, Cancel_Receiver_Initiator) { NiceMock<MockTransferUpdateCallback> transfer_callback; ShareTarget target = SetUpIncomingConnection(transfer_callback); ExpectPairedKeyEncryptionFrame(); @@ -4036,7 +4126,7 @@ EXPECT_TRUE(connection_.IsClosed()); } -TEST_F(NearbySharingServiceImplTest, Cancel_Receiver_Noninitiator) { +TEST_P(NearbySharingServiceImplTest, Cancel_Receiver_Noninitiator) { NiceMock<MockTransferUpdateCallback> transfer_callback; ShareTarget target = SetUpIncomingConnection(transfer_callback); ExpectPairedKeyEncryptionFrame(); @@ -4063,7 +4153,7 @@ EXPECT_TRUE(connection_.IsClosed()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, RegisterForegroundReceiveSurfaceEntersHighVisibility) { TestObserver observer(service_.get()); NiceMock<MockTransferUpdateCallback> callback; @@ -4097,7 +4187,7 @@ service_->RemoveObserver(&observer); } -TEST_F(NearbySharingServiceImplTest, ProcessStoppedCallsObservers) { +TEST_P(NearbySharingServiceImplTest, ProcessStoppedCallsObservers) { TestObserver observer(service_.get()); NiceMock<MockTransferUpdateCallback> callback; @@ -4123,7 +4213,7 @@ service_->RemoveObserver(&observer); } -TEST_F(NearbySharingServiceImplTest, ShutdownCallsObservers) { +TEST_P(NearbySharingServiceImplTest, ShutdownCallsObservers) { TestObserver observer(service_.get()); EXPECT_FALSE(observer.shutdown_called_); @@ -4136,7 +4226,7 @@ service_.reset(); } -TEST_F(NearbySharingServiceImplTest, SendPayloadWithArcCallback) { +TEST_P(NearbySharingServiceImplTest, SendPayloadWithArcCallback) { MockTransferUpdateCallback transfer_callback; MockShareTargetDiscoveredCallback discovery_callback; FakeArcNearbyShareSession arc_session; @@ -4210,7 +4300,7 @@ service_->UnregisterSendSurface(&transfer_callback, &discovery_callback); } -TEST_F(NearbySharingServiceImplTest, ShutdownCallsObserversWithArcCallback) { +TEST_P(NearbySharingServiceImplTest, ShutdownCallsObserversWithArcCallback) { TestObserver observer(service_.get()); FakeArcNearbyShareSession arc_session; @@ -4233,7 +4323,7 @@ service_.reset(); } -TEST_F(NearbySharingServiceImplTest, RotateBackgroundAdvertisement_Periodic) { +TEST_P(NearbySharingServiceImplTest, RotateBackgroundAdvertisement_Periodic) { certificate_manager()->set_next_salt({0x00, 0x01}); SetVisibility(nearby_share::mojom::Visibility::kAllContacts); NiceMock<MockTransferUpdateCallback> callback; @@ -4252,7 +4342,7 @@ EXPECT_NE(endpoint_info_initial, endpoint_info_rotated); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, RotateBackgroundAdvertisement_PrivateCertificatesChange) { certificate_manager()->set_next_salt({0x00, 0x01}); SetVisibility(nearby_share::mojom::Visibility::kAllContacts); @@ -4272,7 +4362,7 @@ EXPECT_NE(endpoint_info_initial, endpoint_info_rotated); } -TEST_F(NearbySharingServiceImplTest, OrderedEndpointDiscoveryEvents) { +TEST_P(NearbySharingServiceImplTest, OrderedEndpointDiscoveryEvents) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback transfer_callback; @@ -4354,7 +4444,7 @@ } } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, RetryDiscoveredEndpoints_NoDownloadIfDecryption) { // Start discovery. SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); @@ -4391,7 +4481,7 @@ service_.reset(); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, RetryDiscoveredEndpoints_DownloadCertsAndRetryDecryption) { // Start discovery. SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); @@ -4456,7 +4546,7 @@ service_.reset(); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, RetryDiscoveredEndpoints_DiscoveryRestartClearsCache) { // Start discovery. SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); @@ -4502,7 +4592,7 @@ service_.reset(); } -TEST_F(NearbySharingServiceImplTest, RetryDiscoveredEndpoints_DownloadLimit) { +TEST_P(NearbySharingServiceImplTest, RetryDiscoveredEndpoints_DownloadLimit) { // Start discovery. SetConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI); MockTransferUpdateCallback transfer_callback; @@ -4555,19 +4645,19 @@ service_.reset(); } -TEST_F(NearbySharingServiceImplTest, NotBoundToProcessIfDisabled) { +TEST_P(NearbySharingServiceImplTest, NotBoundToProcessIfDisabled) { SetIsEnabled(false); EXPECT_FALSE(IsBoundToProcess()); } -TEST_F(NearbySharingServiceImplTest, UnbindsFromProcessWhenDisabled) { +TEST_P(NearbySharingServiceImplTest, UnbindsFromProcessWhenDisabled) { SetIsEnabled(true); EXPECT_TRUE(IsBoundToProcess()); SetIsEnabled(false); EXPECT_FALSE(IsBoundToProcess()); } -TEST_F(NearbySharingServiceImplTest, BindsProcessWhenReenabled) { +TEST_P(NearbySharingServiceImplTest, BindsProcessWhenReenabled) { SetIsEnabled(true); EXPECT_TRUE(IsBoundToProcess()); SetIsEnabled(false); @@ -4576,14 +4666,21 @@ EXPECT_TRUE(IsBoundToProcess()); } +// Parameters are |is_enabled|, |shutdown_reason|, |recent_shutdown_count|, and +// |feature_mask|. using ServiceRestartTestParams = - std::tuple<bool, NearbyProcessShutdownReason, int>; + std::tuple<bool, NearbyProcessShutdownReason, int, size_t>; -class NearbySharingServiceRestartTest - : public NearbySharingServiceImplTest, - public testing::WithParamInterface<ServiceRestartTestParams> {}; +class NearbySharingServiceImplRestartTest + : public NearbySharingServiceImplTestBase, + public testing::WithParamInterface<ServiceRestartTestParams> { + public: + NearbySharingServiceImplRestartTest() + : NearbySharingServiceImplTestBase( + /*feature_mask=*/std::get<3>(GetParam())) {} +}; -TEST_P(NearbySharingServiceRestartTest, RestartsServiceWhenAppropriate) { +TEST_P(NearbySharingServiceImplRestartTest, RestartsServiceWhenAppropriate) { bool is_enabled = std::get<0>(GetParam()); NearbyProcessShutdownReason shutdown_reason = std::get<1>(GetParam()); int recent_shutdown_count = std::get<2>(GetParam()); @@ -4622,7 +4719,7 @@ INSTANTIATE_TEST_SUITE_P( NearbySharingServiceImplTest, - NearbySharingServiceRestartTest, + NearbySharingServiceImplRestartTest, testing::Combine( testing::Bool(), testing::Values( @@ -4635,9 +4732,10 @@ kMaxRecentNearbyProcessUnexpectedShutdownCount - 1, NearbySharingServiceImpl:: - kMaxRecentNearbyProcessUnexpectedShutdownCount))); + kMaxRecentNearbyProcessUnexpectedShutdownCount), + testing::Range<size_t>(0, 1 << kTestFeatures.size()))); -TEST_F(NearbySharingServiceImplTest, ProcessShutdownTimerDoesNotRestart) { +TEST_P(NearbySharingServiceImplTest, ProcessShutdownTimerDoesNotRestart) { EXPECT_TRUE(IsBoundToProcess()); EXPECT_TRUE(IsProcessShutdownTimerRunning()); @@ -4664,7 +4762,7 @@ EXPECT_FALSE(IsProcessShutdownTimerRunning()); } -TEST_F(NearbySharingServiceImplTest, NoShutdownTimerWithoutProcessRef) { +TEST_P(NearbySharingServiceImplTest, NoShutdownTimerWithoutProcessRef) { EXPECT_TRUE(IsBoundToProcess()); EXPECT_TRUE(IsProcessShutdownTimerRunning()); FireProcessShutdownIfRunning(); @@ -4678,8 +4776,15 @@ EXPECT_FALSE(IsProcessShutdownTimerRunning()); } -TEST_F(NearbySharingServiceImplTest, FastInitiationScanning_StartAndStop) { +TEST_P(NearbySharingServiceImplTest, FastInitiationScanning_StartAndStop) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_BLUETOOTH); + if (!base::FeatureList::IsEnabled( + features::kNearbySharingBackgroundScanning)) { + EXPECT_EQ(0u, fast_initiation_scanner_factory_->scanner_created_count()); + EXPECT_EQ(0u, fast_initiation_scanner_factory_->scanner_destroyed_count()); + return; + } + EXPECT_EQ(1u, fast_initiation_scanner_factory_->scanner_created_count()); EXPECT_EQ(0u, fast_initiation_scanner_factory_->scanner_destroyed_count()); @@ -4694,8 +4799,15 @@ EXPECT_EQ(1u, fast_initiation_scanner_factory_->scanner_destroyed_count()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, FastInitiationScanning_DisallowedByPolicy) { + if (!base::FeatureList::IsEnabled( + features::kNearbySharingBackgroundScanning)) { + EXPECT_EQ(0u, fast_initiation_scanner_factory_->scanner_created_count()); + EXPECT_EQ(0u, fast_initiation_scanner_factory_->scanner_destroyed_count()); + return; + } + EXPECT_EQ(1u, fast_initiation_scanner_factory_->scanner_created_count()); EXPECT_EQ(0u, fast_initiation_scanner_factory_->scanner_destroyed_count()); @@ -4707,8 +4819,15 @@ EXPECT_EQ(1u, fast_initiation_scanner_factory_->scanner_destroyed_count()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, FastInitiationScanning_OnFastInitiationNotificationStateChanged) { + if (!base::FeatureList::IsEnabled( + features::kNearbySharingBackgroundScanning)) { + EXPECT_EQ(0u, fast_initiation_scanner_factory_->scanner_created_count()); + EXPECT_EQ(0u, fast_initiation_scanner_factory_->scanner_destroyed_count()); + return; + } + // Fast init notifications are enabled by default so a scanner is created on // initialization of the service. EXPECT_EQ(1u, fast_initiation_scanner_factory_->scanner_created_count()); @@ -4726,9 +4845,16 @@ EXPECT_EQ(1u, fast_initiation_scanner_factory_->scanner_destroyed_count()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, FastInitiationScanning_MultipleReceiveSurfaces) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_BLUETOOTH); + if (!base::FeatureList::IsEnabled( + features::kNearbySharingBackgroundScanning)) { + EXPECT_EQ(0u, fast_initiation_scanner_factory_->scanner_created_count()); + EXPECT_EQ(0u, fast_initiation_scanner_factory_->scanner_destroyed_count()); + return; + } + EXPECT_EQ(1u, fast_initiation_scanner_factory_->scanner_created_count()); EXPECT_EQ(0u, fast_initiation_scanner_factory_->scanner_destroyed_count()); @@ -4741,8 +4867,15 @@ EXPECT_EQ(0u, fast_initiation_scanner_factory_->scanner_destroyed_count()); } -TEST_F(NearbySharingServiceImplTest, FastInitiationScanning_NotifyObservers) { +TEST_P(NearbySharingServiceImplTest, FastInitiationScanning_NotifyObservers) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_BLUETOOTH); + if (!base::FeatureList::IsEnabled( + features::kNearbySharingBackgroundScanning)) { + EXPECT_EQ(0u, fast_initiation_scanner_factory_->scanner_created_count()); + EXPECT_EQ(0u, fast_initiation_scanner_factory_->scanner_destroyed_count()); + return; + } + TestObserver observer(service_.get()); ASSERT_EQ(1u, fast_initiation_scanner_factory_->scanner_created_count()); @@ -4759,8 +4892,14 @@ service_->RemoveObserver(&observer); } -TEST_F(NearbySharingServiceImplTest, FastInitiationScanning_NoHardwareSupport) { +TEST_P(NearbySharingServiceImplTest, FastInitiationScanning_NoHardwareSupport) { SetConnectionType(net::NetworkChangeNotifier::CONNECTION_BLUETOOTH); + if (!base::FeatureList::IsEnabled( + features::kNearbySharingBackgroundScanning)) { + EXPECT_EQ(0u, fast_initiation_scanner_factory_->scanner_created_count()); + EXPECT_EQ(0u, fast_initiation_scanner_factory_->scanner_destroyed_count()); + return; + } // Hardware support is enabled by default in these tests, so we expect that a // scanner has been created. @@ -4778,10 +4917,17 @@ EXPECT_EQ(1u, fast_initiation_scanner_factory_->scanner_destroyed_count()); } -TEST_F(NearbySharingServiceImplTest, +TEST_P(NearbySharingServiceImplTest, FastInitiationScanning_PostTransferCooldown) { - // Make sure we started scanning once SetConnectionType(net::NetworkChangeNotifier::CONNECTION_BLUETOOTH); + if (!base::FeatureList::IsEnabled( + features::kNearbySharingBackgroundScanning)) { + EXPECT_EQ(0u, fast_initiation_scanner_factory_->scanner_created_count()); + EXPECT_EQ(0u, fast_initiation_scanner_factory_->scanner_destroyed_count()); + return; + } + + // Make sure we started scanning once EXPECT_EQ(1u, fast_initiation_scanner_factory_->scanner_created_count()); EXPECT_EQ(0u, fast_initiation_scanner_factory_->scanner_destroyed_count()); @@ -4799,4 +4945,46 @@ EXPECT_EQ(1u, fast_initiation_scanner_factory_->scanner_destroyed_count()); } +TEST_P(NearbySharingServiceImplTest, CreateShareTarget) { + sharing::mojom::AdvertisementPtr advertisement = + sharing::mojom::Advertisement::New( + GetNearbyShareTestEncryptedMetadataKey().salt(), + GetNearbyShareTestEncryptedMetadataKey().encrypted_key(), kDeviceType, + kDeviceName); + + // Flip |for_self_share| to true to ensure the resulting ShareTarget picks + // this up. + nearbyshare::proto::PublicCertificate certificate_proto = + GetNearbyShareTestPublicCertificate( + nearby_share::mojom::Visibility::kAllContacts); + certificate_proto.set_for_self_share(true); + + absl::optional<NearbyShareDecryptedPublicCertificate> certificate = + NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate( + certificate_proto, GetNearbyShareTestEncryptedMetadataKey()); + ASSERT_TRUE(certificate.has_value()); + ASSERT_EQ(certificate_proto.for_self_share(), certificate->for_self_share()); + + absl::optional<ShareTarget> share_target = + CreateShareTarget(advertisement, certificate); + ASSERT_TRUE(share_target.has_value()); + EXPECT_EQ(kDeviceName, share_target->device_name); + EXPECT_EQ(kDeviceType, share_target->type); + if (base::FeatureList::IsEnabled(features::kNearbySharingSelfShare)) { + EXPECT_EQ(certificate_proto.for_self_share(), share_target->for_self_share); + } + + // Test when |certificate| is null. + share_target = + CreateShareTarget(advertisement, /*certificate=*/absl::nullopt); + ASSERT_TRUE(share_target.has_value()); + EXPECT_EQ(kDeviceName, share_target->device_name); + EXPECT_EQ(kDeviceType, share_target->type); + EXPECT_FALSE(share_target->for_self_share); +} + +INSTANTIATE_TEST_SUITE_P(NearbySharingServiceImplTest, + NearbySharingServiceImplTest, + testing::Range<size_t>(0, 1 << kTestFeatures.size())); + } // namespace NearbySharingServiceUnitTests
diff --git a/chrome/browser/nearby_sharing/share_target.cc b/chrome/browser/nearby_sharing/share_target.cc index 166d84e..d841d8a 100644 --- a/chrome/browser/nearby_sharing/share_target.cc +++ b/chrome/browser/nearby_sharing/share_target.cc
@@ -18,7 +18,8 @@ bool is_incoming, absl::optional<std::string> full_name, bool is_known, - absl::optional<std::string> device_id) + absl::optional<std::string> device_id, + bool for_self_share) : device_name(std::move(device_name)), image_url(std::move(image_url)), type(type), @@ -28,7 +29,8 @@ is_incoming(is_incoming), full_name(std::move(full_name)), is_known(is_known), - device_id(std::move(device_id)) {} + device_id(std::move(device_id)), + for_self_share(for_self_share) {} ShareTarget::ShareTarget(const ShareTarget&) = default;
diff --git a/chrome/browser/nearby_sharing/share_target.h b/chrome/browser/nearby_sharing/share_target.h index 09dff01..298a2b3 100644 --- a/chrome/browser/nearby_sharing/share_target.h +++ b/chrome/browser/nearby_sharing/share_target.h
@@ -31,7 +31,8 @@ bool is_incoming, absl::optional<std::string> full_name, bool is_known, - absl::optional<std::string> device_id); + absl::optional<std::string> device_id, + bool for_self_share); ShareTarget(const ShareTarget&); ShareTarget(ShareTarget&&); ShareTarget& operator=(const ShareTarget&); @@ -59,6 +60,8 @@ // True if local device has the PublicCertificate this target is advertising. bool is_known = false; absl::optional<std::string> device_id; + // True if the remote device is also owned by the current user. + bool for_self_share = false; }; #endif // CHROME_BROWSER_NEARBY_SHARING_SHARE_TARGET_H_
diff --git a/chrome/browser/optimization_guide/prediction/prediction_manager.cc b/chrome/browser/optimization_guide/prediction/prediction_manager.cc index fe466814..2330d60b 100644 --- a/chrome/browser/optimization_guide/prediction/prediction_manager.cc +++ b/chrome/browser/optimization_guide/prediction/prediction_manager.cc
@@ -342,6 +342,24 @@ void PredictionManager::FetchModels() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // The histogram that gets recorded here is used for integration tests that + // pass in a model override. For simplicity, we place the recording of this + // histogram here rather than somewhere else earlier in the session + // initialization flow since the model engine version needs to continuously be + // updated for the fetch. + proto::ModelInfo base_model_info; + if (features::IsModelDownloadingEnabled()) { + // There should only be one supported model engine version at a time. + base_model_info.add_supported_model_engine_versions( + proto::MODEL_ENGINE_VERSION_TFLITE_2_9_0_1); + // This histogram is used for integration tests. Do not remove. + // Update this to be 10000 if/when we exceed 100 model engine versions. + LOCAL_HISTOGRAM_COUNTS_100( + "OptimizationGuide.PredictionManager.SupportedModelEngineVersion", + static_cast<int>( + *base_model_info.supported_model_engine_versions().begin())); + } + if (switches::IsModelOverridePresent()) return; @@ -396,19 +414,7 @@ } std::vector<proto::ModelInfo> models_info = std::vector<proto::ModelInfo>(); - - proto::ModelInfo base_model_info; - if (features::IsModelDownloadingEnabled()) { - // There should only be one supported model engine version at a time. - base_model_info.add_supported_model_engine_versions( - proto::MODEL_ENGINE_VERSION_TFLITE_2_9_0_1); - // This histogram is used for integration tests. Do not remove. - // Update this to be 10000 if/when we exceed 100 model engine versions. - LOCAL_HISTOGRAM_COUNTS_100( - "OptimizationGuide.PredictionManager.SupportedModelEngineVersion", - static_cast<int>( - *base_model_info.supported_model_engine_versions().begin())); - } + models_info.reserve(registered_optimization_targets_and_metadata_.size()); std::string debug_msg; // For now, we will fetch for all registered optimization targets.
diff --git a/chrome/browser/optimization_guide/prediction/prediction_manager_unittest.cc b/chrome/browser/optimization_guide/prediction/prediction_manager_unittest.cc index c8e96013..9aea2cd 100644 --- a/chrome/browser/optimization_guide/prediction/prediction_manager_unittest.cc +++ b/chrome/browser/optimization_guide/prediction/prediction_manager_unittest.cc
@@ -698,6 +698,8 @@ #if !BUILDFLAG(IS_WIN) TEST_F(PredictionManagerTest, AddObserverForOptimizationTargetModelCommandLineOverride) { + base::HistogramTester histogram_tester; + optimization_guide::proto::Any metadata; metadata.set_type_url( "type.googleapis.com/" @@ -730,6 +732,12 @@ // Make sure no models are fetched. EXPECT_FALSE(prediction_model_fetcher()->models_fetched()); + // However, expect that the histogram for model engine version is recorded. + // We don't check for value here since that is too much toil for someone + // whenever they add a new version. + histogram_tester.ExpectTotalCount( + "OptimizationGuide.PredictionManager.SupportedModelEngineVersion", + features::IsModelDownloadingEnabled() ? 1 : 0); EXPECT_TRUE(prediction_manager()->GetRegisteredOptimizationTargets().contains( proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD));
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation_ui_test.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation_ui_test.js index 4a56070..81a0c48 100644 --- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation_ui_test.js +++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation_ui_test.js
@@ -57,16 +57,32 @@ // Poll until the updateDictationBubble() API gets called with // `targetProps`. return new Promise(resolve => { + const printErrorMessageTimeoutId = setTimeout(() => { + this.printErrorMessage(targetProps); + }, DictationUIE2ETest.PRINT_ERROR_MESSAGE_DELAY_MS); const intervalId = setInterval(() => { if (this.uiPropertiesMatch(targetProps)) { + clearTimeout(printErrorMessageTimeoutId); clearInterval(intervalId); resolve(); } }); }); } + + /** @param {DictationBubbleProperties} props */ + printErrorMessage(props) { + console.error(`Still waiting for UI properties + visible: ${props.visible} + icon: ${props.icon} + text: ${props.text} + hints: ${props.hints}`); + } }; +/** @const {number} */ +DictationUIE2ETest.PRINT_ERROR_MESSAGE_DELAY_MS = 3.5 * 1000; + SYNC_TEST_F( 'DictationUIE2ETest', 'ShownWhenSpeechRecognitionStarts', async function() { await this.waitForDictationWithCommandsAndHints();
diff --git a/chrome/browser/site_isolation/chrome_site_per_process_browsertest.cc b/chrome/browser/site_isolation/chrome_site_per_process_browsertest.cc index 2c6ec16e..4c7d9a3 100644 --- a/chrome/browser/site_isolation/chrome_site_per_process_browsertest.cc +++ b/chrome/browser/site_isolation/chrome_site_per_process_browsertest.cc
@@ -1187,52 +1187,6 @@ EXPECT_FALSE(frame_c_popup_opened); } -// Test that opening a window with `noopener` consumes user activation. -// crbug.com/1264543, crbug.com/1291210 -IN_PROC_BROWSER_TEST_F(ChromeSitePerProcessTest, - UserActivationConsumptionNoopener) { - // Start on a page a.com. - GURL main_url(embedded_test_server()->GetURL( - "a.com", "/cross_site_iframe_factory.html?a")); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url)); - content::WebContents* web_contents = - browser()->tab_strip_model()->GetActiveWebContents(); - - // Activate the frame by executing a dummy script. - const std::string no_op_script = "// No-op script"; - EXPECT_TRUE(ExecuteScript(web_contents, no_op_script)); - - // Add a popup observer. - content::TestNavigationObserver popup_observer(nullptr); - popup_observer.StartWatchingNewWebContents(); - - // Open a popup from the frame, with `noopener`. This should consume - // transient user activation. - GURL popup_url(embedded_test_server()->GetURL("popup.com", "/")); - EXPECT_TRUE(ExecuteScriptWithoutUserGesture( - web_contents, - base::StringPrintf( - "window.w = window.open('%s'+'title1.html', '_blank', 'noopener');", - popup_url.spec().c_str()))); - - // Try to open another popup. - EXPECT_TRUE(ExecuteScriptWithoutUserGesture( - web_contents, - base::StringPrintf( - "window.w = window.open('%s'+'title2.html', '_blank', 'noopener');", - popup_url.spec().c_str()))); - - // Wait and check that only one popup was opened. - popup_observer.Wait(); - EXPECT_EQ(2, browser()->tab_strip_model()->count()); - - content::WebContents* popup = - browser()->tab_strip_model()->GetActiveWebContents(); - EXPECT_EQ(embedded_test_server()->GetURL("popup.com", "/title1.html"), - popup->GetLastCommittedURL()); - EXPECT_NE(popup, web_contents); -} - // TODO(crbug.com/1021895): Flaky. // Tests that a cross-site iframe runs its beforeunload handler when closing a // tab. See https://crbug.com/853021.
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn index 4d6bd75..805bc06 100644 --- a/chrome/browser/ui/BUILD.gn +++ b/chrome/browser/ui/BUILD.gn
@@ -3886,8 +3886,6 @@ "startup/web_app_info_recorder_utils.h", "startup/web_app_url_handling_startup_utils.cc", "startup/web_app_url_handling_startup_utils.h", - "webui/settings/url_handlers_handler.cc", - "webui/settings/url_handlers_handler.h", ] }
diff --git a/chrome/browser/ui/android/layouts/test/java/src/org/chromium/chrome/browser/layouts/LayoutTestUtils.java b/chrome/browser/ui/android/layouts/test/java/src/org/chromium/chrome/browser/layouts/LayoutTestUtils.java index f2bdb9d..90da600 100644 --- a/chrome/browser/ui/android/layouts/test/java/src/org/chromium/chrome/browser/layouts/LayoutTestUtils.java +++ b/chrome/browser/ui/android/layouts/test/java/src/org/chromium/chrome/browser/layouts/LayoutTestUtils.java
@@ -24,6 +24,9 @@ LayoutStateObserver observer = new LayoutStateObserver() { @Override public void onFinishedShowing(int layoutType) { + // Ensure the layout is the one we're actually looking for. + if (type != layoutType) return; + finishedShowingCallbackHelper.notifyCalled(); } }; @@ -40,6 +43,7 @@ } catch (TimeoutException e) { assert false : "Timed out waiting for layout (@LayoutType " + type + ") to show!"; } + TestThreadUtils.runOnUiThreadBlocking(() -> layoutManager.removeObserver(observer)); }
diff --git a/chrome/browser/ui/views/apps/app_dialog/app_dialog_view_browsertest.cc b/chrome/browser/ui/views/apps/app_dialog/app_dialog_view_browsertest.cc index 513180c..4002cab 100644 --- a/chrome/browser/ui/views/apps/app_dialog/app_dialog_view_browsertest.cc +++ b/chrome/browser/ui/views/apps/app_dialog/app_dialog_view_browsertest.cc
@@ -8,9 +8,9 @@ #include "ash/components/arc/test/arc_util_test_support.h" #include "ash/components/arc/test/connection_holder_util.h" #include "ash/components/arc/test/fake_app_instance.h" -#include "base/bind.h" #include "base/command_line.h" #include "base/run_loop.h" +#include "base/test/bind.h" #include "chrome/browser/apps/app_service/app_service_proxy.h" #include "chrome/browser/apps/app_service/app_service_proxy_factory.h" #include "chrome/browser/ash/arc/arc_util.h"
diff --git a/chrome/browser/ui/views/apps/app_dialog/app_uninstall_dialog_view_browsertest.cc b/chrome/browser/ui/views/apps/app_dialog/app_uninstall_dialog_view_browsertest.cc index 441d556..bbfbf74 100644 --- a/chrome/browser/ui/views/apps/app_dialog/app_uninstall_dialog_view_browsertest.cc +++ b/chrome/browser/ui/views/apps/app_dialog/app_uninstall_dialog_view_browsertest.cc
@@ -12,6 +12,7 @@ #include "base/run_loop.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" +#include "base/test/bind.h" #include "chrome/browser/apps/app_service/app_service_proxy.h" #include "chrome/browser/apps/app_service/app_service_proxy_factory.h" #include "chrome/browser/ash/arc/arc_util.h" @@ -47,8 +48,9 @@ ASSERT_TRUE(app_service_proxy); base::RunLoop run_loop; - app_service_proxy->UninstallForTesting(app_id_, nullptr, - run_loop.QuitClosure()); + app_service_proxy->UninstallForTesting( + app_id_, nullptr, + base::BindLambdaForTesting([&](bool) { run_loop.Quit(); })); run_loop.Run(); ASSERT_NE(nullptr, ActiveView()); @@ -203,3 +205,66 @@ CreateApp(); ShowUi("cancel"); } + +IN_PROC_BROWSER_TEST_F(WebAppsUninstallDialogViewBrowserTest, + PreventDuplicateUninstallDialogs) { + CreateApp(); + + EXPECT_EQ(nullptr, ActiveView()); + + auto* app_service_proxy = + apps::AppServiceProxyFactory::GetForProfile(browser()->profile()); + ASSERT_TRUE(app_service_proxy); + + // First call to uninstall should return true in callback for successful. + { + base::RunLoop run_loop; + app_service_proxy->UninstallForTesting( + app_id_, nullptr, base::BindLambdaForTesting([&](bool dialog_opened) { + EXPECT_TRUE(dialog_opened); + run_loop.Quit(); + })); + run_loop.Run(); + } + // Second call should be unsuccessful. + { + base::RunLoop run_loop; + app_service_proxy->UninstallForTesting( + app_id_, nullptr, base::BindLambdaForTesting([&](bool dialog_opened) { + EXPECT_FALSE(dialog_opened); + run_loop.Quit(); + })); + + run_loop.Run(); + } + + ASSERT_NE(nullptr, ActiveView()); + EXPECT_EQ(ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL, + ActiveView()->GetDialogButtons()); + std::u16string title = + u"Uninstall \"" + base::ASCIIToUTF16(app_name_) + u"\"?"; + EXPECT_EQ(title, ActiveView()->GetWindowTitle()); + + // Cancelling the active dialog should not uninstall the web app. + ActiveView()->CancelDialog(); + app_service_proxy->FlushMojoCallsForTesting(); + bool is_installed = true; + app_service_proxy->AppRegistryCache().ForOneApp( + app_id_, [&is_installed](const apps::AppUpdate& update) { + is_installed = update.Readiness() == apps::mojom::Readiness::kReady; + }); + EXPECT_TRUE(is_installed); + + // Uninstall dialog should be reopenable. + EXPECT_EQ(nullptr, ActiveView()); + { + base::RunLoop run_loop; + app_service_proxy->UninstallForTesting( + app_id_, nullptr, base::BindLambdaForTesting([&](bool dialog_opened) { + EXPECT_TRUE(dialog_opened); + run_loop.Quit(); + })); + run_loop.Run(); + } + ASSERT_NE(nullptr, ActiveView()); +}
diff --git a/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc b/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc index 0c09a1f..d069e6f2 100644 --- a/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc +++ b/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc
@@ -63,12 +63,12 @@ SetTooltipText(l10n_util::GetStringUTF16(IDS_TOOLTIP_DOWNLOAD_ICON)); Profile* profile = browser_->profile(); content::DownloadManager* manager = profile->GetDownloadManager(); - // The display starts hidden and isn't shown until a download is initiated. - // TODO(crbug.com/1282240): Use pref service to determine what the initial - // state should be. SetVisible(false); - controller_ = std::make_unique<DownloadDisplayController>(this, manager); + bubble_controller_ = std::make_unique<DownloadBubbleUIController>(manager); + // Wait until we're done with everything else before creating `controller_` + // since it can call `Show()` synchronously. + controller_ = std::make_unique<DownloadDisplayController>(this, manager); } DownloadToolbarButtonView::~DownloadToolbarButtonView() { @@ -102,7 +102,6 @@ void DownloadToolbarButtonView::Show() { SetVisible(true); - ButtonPressed(); PreferredSizeChanged(); } @@ -129,6 +128,10 @@ UpdateIcon(); } +void DownloadToolbarButtonView::ShowDetails() { + ButtonPressed(); +} + void DownloadToolbarButtonView::UpdateIcon() { if (!GetWidget()) return;
diff --git a/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.h b/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.h index 89d74c5..228ac217 100644 --- a/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.h +++ b/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.h
@@ -36,6 +36,7 @@ void Enable() override; void Disable() override; void UpdateDownloadIcon(download::DownloadIconState state) override; + void ShowDetails() override; // ToolbarButton: void UpdateIcon() override;
diff --git a/chrome/browser/ui/views/extensions/extensions_tabbed_menu_view.cc b/chrome/browser/ui/views/extensions/extensions_tabbed_menu_view.cc index 6b7a064..b8d0542 100644 --- a/chrome/browser/ui/views/extensions/extensions_tabbed_menu_view.cc +++ b/chrome/browser/ui/views/extensions/extensions_tabbed_menu_view.cc
@@ -59,24 +59,25 @@ int title_string_id, std::unique_ptr<views::View> contents, std::unique_ptr<views::View> footer) { - auto tab_container = std::make_unique<views::View>(); - tab_container->SetLayoutManager(std::make_unique<views::BoxLayout>( - views::BoxLayout::Orientation::kVertical)); - // This is set so that the extensions menu doesn't fall outside the monitor in // a maximized window in 1024x768. See https://crbug.com/1096630. // TODO(pbos): Consider making this dynamic and handled by views. Ideally we // wouldn't ever pop up so that they pop outside the screen. constexpr int kMaxExtensionButtonsHeightDp = 448; - auto scroll_view = std::make_unique<views::ScrollView>(); - scroll_view->ClipHeightTo(0, kMaxExtensionButtonsHeightDp); - scroll_view->SetDrawOverflowIndicator(false); - scroll_view->SetHorizontalScrollBarMode( - views::ScrollView::ScrollBarMode::kDisabled); - scroll_view->SetContents(std::move(contents)); - tab_container->AddChildView(std::move(scroll_view)); - tab_container->AddChildView(std::move(footer)); + auto tab_container = + views::Builder<views::View>() + .SetLayoutManager(std::make_unique<views::BoxLayout>( + views::BoxLayout::Orientation::kVertical)) + .AddChildren( + views::Builder<views::ScrollView>() + .SetContents(views::Builder<views::View>(std::move(contents))) + .ClipHeightTo(0, kMaxExtensionButtonsHeightDp) + .SetDrawOverflowIndicator(false) + .SetHorizontalScrollBarMode( + views::ScrollView::ScrollBarMode::kDisabled), + views::Builder<views::View>(std::move(footer))) + .Build(); tabbed_pane->AddTabAtIndex(index, l10n_util::GetStringUTF16(title_string_id), std::move(tab_container)); @@ -403,99 +404,90 @@ } void ExtensionsTabbedMenuView::CreateSiteAccessTab() { - auto site_access_items = std::make_unique<views::View>(); - site_access_items->SetLayoutManager(std::make_unique<views::BoxLayout>( - views::BoxLayout::Orientation::kVertical)); - auto current_site = base::UTF8ToUTF16(browser_->tab_strip_model() ->GetActiveWebContents() ->GetLastCommittedURL() .host()); - // Create the site access items divided in requests access and has access - // sections. - auto create_section = + auto create_section_builder = [current_site](ExtensionsTabbedMenuView::SiteAccessSection* section) { - auto section_container = std::make_unique<views::View>(); - section->container = section_container.get(); - section_container->SetLayoutManager(std::make_unique<views::BoxLayout>( - views::BoxLayout::Orientation::kVertical)); - const int horizontal_spacing = ChromeLayoutProvider::Get()->GetDistanceMetric( views::DISTANCE_BUTTON_HORIZONTAL_PADDING); - // Add an emphasized short header explaining the section. - auto header = std::make_unique<views::Label>( - l10n_util::GetStringFUTF16(section->header_string_id, current_site), - ChromeTextContext::CONTEXT_DIALOG_BODY_TEXT_SMALL, - ChromeTextStyle::STYLE_EMPHASIZED); - header->SetHorizontalAlignment(gfx::ALIGN_LEFT); - header->SetBorder(views::CreateEmptyBorder( - ChromeLayoutProvider::Get()->GetDistanceMetric( - DISTANCE_CONTROL_LIST_VERTICAL), - horizontal_spacing, 0, horizontal_spacing)); - section_container->AddChildView(std::move(header)); - - // Add an empty section for the menu items of the section. Items will be - // populated later. - auto items = std::make_unique<views::View>(); - items->SetLayoutManager(std::make_unique<views::BoxLayout>( - views::BoxLayout::Orientation::kVertical)); - section->items = items.get(); - section_container->AddChildView(std::move(items)); - - // Start off with the section invisible. We'll update it as we add items - // if necessary. - section_container->SetVisible(false); - - return section_container; + return views::Builder<views::BoxLayoutView>() + .CopyAddressTo(§ion->container) + .SetOrientation(views::BoxLayout::Orientation::kVertical) + // Start off with the section invisible. We'll update it as we + // add items if necessary. + .SetVisible(false) + .AddChildren( + // Emphasized short header explaining the section. + views::Builder<views::Label>( + std::make_unique<views::Label>( + l10n_util::GetStringFUTF16(section->header_string_id, + current_site), + ChromeTextContext::CONTEXT_DIALOG_BODY_TEXT_SMALL, + ChromeTextStyle::STYLE_EMPHASIZED)) + .SetHorizontalAlignment(gfx::ALIGN_LEFT) + .SetBorder(views::CreateEmptyBorder( + ChromeLayoutProvider::Get()->GetDistanceMetric( + DISTANCE_CONTROL_LIST_VERTICAL), + horizontal_spacing, 0, horizontal_spacing)), + // Empty section for the menu items. Items + // will be populated later. + views::Builder<views::BoxLayoutView>() + .CopyAddressTo(§ion->items) + .SetOrientation(views::BoxLayout::Orientation::kVertical)); }; - site_access_items->AddChildView(create_section(&requests_access_)); - site_access_items->AddChildView(create_section(&has_access_)); + auto site_access_items = + views::Builder<views::BoxLayoutView>() + .SetOrientation(views::BoxLayout::Orientation::kVertical) + .AddChildren(create_section_builder(&requests_access_), + create_section_builder(&has_access_)) + .Build(); - // Create the site access footer with site settings and a button that opens - // them. + const auto create_radio_button_builder = + [current_site](int label_id, bool replace_current_site) { + auto label = replace_current_site + ? l10n_util::GetStringFUTF16(label_id, current_site) + : l10n_util::GetStringUTF16(label_id); + // TODO(crbug.com/1263310): Add callback. Differentiate between types + // with a SiteSettings enum. + return views::Builder<views::RadioButton>( + std::make_unique<views::RadioButton>(label, kGroupId)); + }; + auto site_access_footer = views::Builder<views::BoxLayoutView>() .SetOrientation(views::BoxLayout::Orientation::kVertical) + .AddChildren( + // The following bind is safe because the button will be owned by + // the parent views and therefore callback can only happen if the + // button exists and can be clicked. + views::Builder<SiteSettingsExpandButton>( + std::make_unique< + SiteSettingsExpandButton>(base::BindRepeating( + &ExtensionsTabbedMenuView::OnSiteSettingsButtonPressed, + base::Unretained(this)))) + .CopyAddressTo(&site_settings_button_), + views::Builder<views::BoxLayoutView>() + .CopyAddressTo(&site_settings_) + .SetOrientation(views::BoxLayout::Orientation::kVertical) + .SetVisible(show_site_settings_) + .AddChildren( + create_radio_button_builder( + IDS_EXTENSIONS_MENU_SITE_ACCESS_TAB_USER_SETTINGS_ALLOW_ALL_TEXT, + true), + create_radio_button_builder( + IDS_EXTENSIONS_MENU_SITE_ACCESS_TAB_USER_SETTINGS_BLOCK_ALL_TEXT, + true), + create_radio_button_builder( + IDS_EXTENSIONS_MENU_SITE_ACCESS_TAB_USER_SETTINGS_CUSTOMIZE_EACH_TEXT, + false))) .Build(); - // The following bind is safe because the button will be owned by the parent - // views and therefore callback can only happen if the button exists and can - // be clicked. - auto site_settings_button = - std::make_unique<SiteSettingsExpandButton>(base::BindRepeating( - &ExtensionsTabbedMenuView::OnSiteSettingsButtonPressed, - base::Unretained(this))); - - auto site_settings = - views::Builder<views::BoxLayoutView>() - .SetOrientation(views::BoxLayout::Orientation::kVertical) - .SetVisible(show_site_settings_) - .Build(); - - const auto create_radio_button = [](std::u16string label) { - auto radio_button = std::make_unique<views::RadioButton>(label, kGroupId); - // TODO(crbug.com/1263310): Add callback. Differentiate between types with a - // SiteSettings enum. - return radio_button; - }; - - site_settings->AddChildView(create_radio_button(l10n_util::GetStringFUTF16( - IDS_EXTENSIONS_MENU_SITE_ACCESS_TAB_USER_SETTINGS_ALLOW_ALL_TEXT, - current_site))); - site_settings->AddChildView(create_radio_button(l10n_util::GetStringFUTF16( - IDS_EXTENSIONS_MENU_SITE_ACCESS_TAB_USER_SETTINGS_BLOCK_ALL_TEXT, - current_site))); - site_settings->AddChildView(create_radio_button(l10n_util::GetStringUTF16( - IDS_EXTENSIONS_MENU_SITE_ACCESS_TAB_USER_SETTINGS_CUSTOMIZE_EACH_TEXT))); - - site_settings_button_ = - site_access_footer->AddChildView(std::move(site_settings_button)); - site_settings_ = site_access_footer->AddChildView(std::move(site_settings)); - CreateTab(tabbed_pane_, 0, IDS_EXTENSIONS_MENU_SITE_ACCESS_TAB_TITLE, std::move(site_access_items), std::move(site_access_footer)); } @@ -511,10 +503,11 @@ } void ExtensionsTabbedMenuView::CreateExtensionsTab() { - auto installed_items = std::make_unique<views::View>(); - installed_items->SetLayoutManager(std::make_unique<views::BoxLayout>( - views::BoxLayout::Orientation::kVertical)); - installed_items_ = installed_items.get(); + auto installed_items = + views::Builder<views::BoxLayoutView>() + .CopyAddressTo(&installed_items_) + .SetOrientation(views::BoxLayout::Orientation::kVertical) + .Build(); auto webstore_icon = std::make_unique<views::ImageView>( ui::ImageModel::FromResourceId(IDR_WEBSTORE_ICON_16)); @@ -522,13 +515,17 @@ std::make_unique<views::ImageView>(ui::ImageModel::FromVectorIcon( vector_icons::kOpenInNewIcon, ui::kColorIcon, webstore_icon->GetImageModel().Size().width())); - auto installed_tab_footer = std::make_unique<HoverButton>( - base::BindRepeating(&chrome::ShowWebStore, browser_), - std::move(webstore_icon), - l10n_util::GetStringUTF16( - IDS_EXTENSIONS_MENU_EXTENSIONS_TAB_DISCOVER_MORE_TITLE), - /*subtitle=*/std::u16string(), std::move(open_icon)); - discover_more_button_ = installed_tab_footer.get(); + + auto installed_tab_footer = + views::Builder<HoverButton>( + std::make_unique<HoverButton>( + base::BindRepeating(&chrome::ShowWebStore, browser_), + std::move(webstore_icon), + l10n_util::GetStringUTF16( + IDS_EXTENSIONS_MENU_EXTENSIONS_TAB_DISCOVER_MORE_TITLE), + /*subtitle=*/std::u16string(), std::move(open_icon))) + .CopyAddressTo(&discover_more_button_) + .Build(); CreateTab(tabbed_pane_, 1, IDS_EXTENSIONS_MENU_EXTENSIONS_TAB_TITLE, std::move(installed_items), std::move(installed_tab_footer));
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc index bf93789..c7b50bc6e 100644 --- a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc +++ b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
@@ -1084,8 +1084,9 @@ apps::AppServiceProxy* app_service_proxy = apps::AppServiceProxyFactory::GetForProfile(profile()); base::RunLoop run_loop; - app_service_proxy->UninstallForTesting(app_state->id, nullptr, - run_loop.QuitClosure()); + app_service_proxy->UninstallForTesting( + app_state->id, nullptr, + base::BindLambdaForTesting([&](bool) { run_loop.Quit(); })); run_loop.Run(); ASSERT_NE(nullptr, AppUninstallDialogView::GetActiveViewForTesting());
diff --git a/chrome/browser/ui/webui/settings/settings_ui.cc b/chrome/browser/ui/webui/settings/settings_ui.cc index 2d9eb72..36bb5bd4 100644 --- a/chrome/browser/ui/webui/settings/settings_ui.cc +++ b/chrome/browser/ui/webui/settings/settings_ui.cc
@@ -17,7 +17,6 @@ #include "build/branding_buildflags.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" -#include "chrome/browser/browser_process.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/signin/identity_manager_factory.h" #include "chrome/browser/ui/hats/hats_service.h" @@ -114,6 +113,7 @@ #include "chrome/browser/ash/multidevice_setup/multidevice_setup_client_factory.h" #include "chrome/browser/ash/phonehub/phone_hub_manager_factory.h" #include "chrome/browser/ash/profiles/profile_helper.h" +#include "chrome/browser/browser_process.h" #include "chrome/browser/browser_process_platform_part.h" #include "chrome/browser/ui/webui/certificate_provisioning_ui_handler.h" #include "chrome/browser/ui/webui/settings/chromeos/account_manager_handler.h" @@ -140,12 +140,6 @@ #include "chrome/browser/ui/webui/settings/native_certificates_handler.h" #endif // BUILDFLAG(USE_NSS_CERTS) -#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || \ - (BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CHROMEOS_LACROS)) -#include "chrome/browser/ui/webui/settings/url_handlers_handler.h" -#include "chrome/browser/web_applications/web_app_provider.h" -#endif - namespace settings { // static @@ -247,14 +241,6 @@ AddSettingsPageUIHandler(std::make_unique<ChromeCleanupHandler>(profile)); #endif // BUILDFLAG(IS_WIN) -#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || \ - (BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CHROMEOS_LACROS)) - if (web_app::WebAppProvider::GetForWebApps(profile) != nullptr) { - AddSettingsPageUIHandler(std::make_unique<UrlHandlersHandler>( - g_browser_process->local_state(), profile)); - } -#endif - #if BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING) bool has_incompatible_applications = IncompatibleApplicationsUpdater::HasCachedApplications();
diff --git a/chrome/browser/ui/webui/settings/url_handlers_handler.cc b/chrome/browser/ui/webui/settings/url_handlers_handler.cc deleted file mode 100644 index 93b0d6e..0000000 --- a/chrome/browser/ui/webui/settings/url_handlers_handler.cc +++ /dev/null
@@ -1,423 +0,0 @@ -// 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 "chrome/browser/ui/webui/settings/url_handlers_handler.h" - -#include <memory> -#include <utility> - -#include "base/bind.h" -#include "base/check.h" -#include "base/containers/flat_map.h" -#include "base/containers/flat_set.h" -#include "base/strings/utf_string_conversions.h" -#include "base/values.h" -#include "chrome/browser/profiles/profile.h" -#include "chrome/browser/web_applications/url_handler_launch_params.h" -#include "chrome/browser/web_applications/url_handler_prefs.h" -#include "chrome/browser/web_applications/web_app_provider.h" -#include "chrome/browser/web_applications/web_app_registrar.h" -#include "chrome/common/pref_names.h" -#include "components/prefs/pref_change_registrar.h" -#include "components/prefs/pref_service.h" -#include "components/url_formatter/elide_url.h" -#include "content/public/browser/web_ui.h" -#include "third_party/abseil-cpp/absl/types/optional.h" -#include "url/gurl.h" -#include "url/origin.h" - -namespace settings { - -namespace { - -struct EnabledRow { - std::string origin_key; // Eg. "https://contoso.com" - std::string app_id; - std::string short_name; - std::u16string publisher; - bool has_origin_wildcard; - std::string path; // Eg. "/*" or "/news" - absl::optional<std::u16string> display_url; // Eg. "contoso.com/news" - - bool operator<(const EnabledRow& other) const { - if (this == &other) - return false; - - if (short_name < other.short_name) - return true; - if (origin_key < other.origin_key) - return true; - if (has_origin_wildcard < other.has_origin_wildcard) - return true; - if (path < other.path) - return true; - - return false; - } -}; - -struct DisabledRow { - std::string origin_key; - bool has_origin_wildcard; - std::string path; // Eg. "/*" or "/news" - std::u16string display_url; - - bool operator<(const DisabledRow& other) const { - if (this == &other) - return false; - - if (origin_key < other.origin_key) - return true; - if (has_origin_wildcard < other.has_origin_wildcard) - return true; - if (path < other.path) - return true; - - return false; - } -}; - -// Example of returned Value: -// [ -// { -// "app_entries": [ -// { -// "app_id": "jncifgjpfigpfjphlanoeonmiedopibl", -// "display_url": "example.com/abc", // optional -// "has_origin_wildcard": true, -// "origin_key": "https://example.com", -// "path": "/abc", -// "publisher": "example.com", -// "short_name": "Example App -// } -// ], -// "display_origin": "*.example.com" -// } -// ] -base::Value SerializeEnabledHandlersList( - const base::flat_map<std::u16string, base::flat_set<EnabledRow>>& - organizer) { - base::Value result_list(base::Value::Type::LIST); - - for (const auto& kv : organizer) { - const std::u16string& origin_str = kv.first; - const base::flat_set<EnabledRow>& app_rows = kv.second; - - base::Value result_entry(base::Value::Type::DICTIONARY); - result_entry.SetStringKey("display_origin", origin_str); - base::Value app_entries(base::Value::Type::LIST); - for (const auto& app_row : app_rows) { - base::Value app_entry(base::Value::Type::DICTIONARY); - app_entry.SetStringKey("origin_key", app_row.origin_key); - app_entry.SetStringKey("app_id", app_row.app_id); - app_entry.SetStringKey("short_name", app_row.short_name); - app_entry.SetStringKey("publisher", app_row.publisher); - app_entry.SetBoolKey("has_origin_wildcard", app_row.has_origin_wildcard); - app_entry.SetStringKey("path", app_row.path); - // "display_url" is optional. It's only needed when path != "/*". - if (app_row.display_url.has_value()) - app_entry.SetStringKey("display_url", app_row.display_url.value()); - - app_entries.Append(std::move(app_entry)); - } - result_entry.SetKey("app_entries", std::move(app_entries)); - result_list.Append(std::move(result_entry)); - } - return result_list; -} - -// Example of returned value: -// [ -// { -// "display_url": "*.example.com/abc", -// "has_origin_wildcard": true, -// "origin_key": "https://example.com", -// "path": "/abc" -// } -// ] -base::Value SerializeDisabledHandlersList( - const base::flat_set<DisabledRow>& organizer) { - base::Value disabled_entries(base::Value::Type::LIST); - - for (const DisabledRow& row : organizer) { - base::Value entry(base::Value::Type::DICTIONARY); - entry.SetStringKey("origin_key", row.origin_key); - entry.SetBoolKey("has_origin_wildcard", row.has_origin_wildcard); - entry.SetStringKey("path", row.path); - entry.SetStringKey("display_url", row.display_url); - disabled_entries.Append(std::move(entry)); - } - return disabled_entries; -} - -std::u16string FormatOriginString(const url::Origin& origin, - bool has_origin_wildcard) { - std::u16string origin_str = url_formatter::FormatOriginForSecurityDisplay( - origin, url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS); - - if (has_origin_wildcard) - origin_str = u"*." + origin_str; - - return origin_str; -} - -} // namespace - -UrlHandlersHandler::UrlHandlersHandler( - PrefService* local_state, - Profile* profile, - web_app::WebAppRegistrar* web_app_registrar) - : local_state_(local_state), - profile_(profile), - web_app_registrar_(web_app_registrar) { - DCHECK(local_state_); - DCHECK(profile_); - DCHECK(web_app_registrar_); -} - -UrlHandlersHandler::UrlHandlersHandler(PrefService* local_state, - Profile* profile) - : UrlHandlersHandler( - local_state, - profile, - &web_app::WebAppProvider::GetForWebApps(profile)->registrar()) {} - -UrlHandlersHandler::~UrlHandlersHandler() = default; - -void UrlHandlersHandler::OnJavascriptAllowed() { - DCHECK(local_state_); - - local_state_pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>(); - local_state_pref_change_registrar_->Init(local_state_); - local_state_pref_change_registrar_->Add( - prefs::kWebAppsUrlHandlerInfo, - base::BindRepeating( - &UrlHandlersHandler::OnUrlHandlersLocalStatePrefChanged, - // Using base::Unretained(this) here is safe because the lifetime of - // this UrlHandlersHandler instance is the same or longer than that of - // local_state_pref_change_registrar_. - // local_state_pref_change_registrar_ is destroyed either in - // |OnJavascriptDisallowed| or when this UrlHandlersHandler is - // destroyed. - base::Unretained(this))); -} - -void UrlHandlersHandler::OnJavascriptDisallowed() { - local_state_pref_change_registrar_.reset(); -} - -void UrlHandlersHandler::RegisterMessages() { - web_ui()->RegisterMessageCallback( - "getUrlHandlers", - base::BindRepeating(&UrlHandlersHandler::HandleGetUrlHandlers, - base::Unretained(this))); - - web_ui()->RegisterMessageCallback( - "resetUrlHandlerSavedChoice", - base::BindRepeating(&UrlHandlersHandler::HandleResetUrlHandlerSavedChoice, - base::Unretained(this))); -} - -void UrlHandlersHandler::OnUrlHandlersLocalStatePrefChanged() { - UpdateModel(); -} - -void UrlHandlersHandler::UpdateModel() { - base::Value enabled_handlers_list = GetEnabledHandlersList(); - base::Value disabled_handlers_list = GetDisabledHandlersList(); - - // TODO(crbug.com/1217423): Implement a handler on the WebUI side to accept - // this data and update the UI. - FireWebUIListener("updateUrlHandlers", enabled_handlers_list, - disabled_handlers_list); -} - -void UrlHandlersHandler::HandleGetUrlHandlers(base::Value::ConstListView args) { - CHECK_EQ(1U, args.size()); - const base::Value& callback_id = args[0]; - AllowJavascript(); - - base::Value result(base::Value::Type::DICTIONARY); - result.SetKey("enabled", base::Value(GetEnabledHandlersList())); - result.SetKey("disabled", base::Value(GetDisabledHandlersList())); - ResolveJavascriptCallback(callback_id, result); -} - -void UrlHandlersHandler::HandleResetUrlHandlerSavedChoice( - base::Value::ConstListView args) { - CHECK_EQ(4U, args.size()); - const std::string& origin = args[0].GetString(); - bool has_origin_wildcard = args[1].GetBool(); - const std::string& path = args[2].GetString(); - // If app_id is an empty string, reset saved choices for all applicable - // entries regardless of app_id. - const std::string& app_id = args[3].GetString(); - absl::optional<std::string> app_id_opt = - app_id.empty() ? absl::nullopt : absl::make_optional(app_id); - - web_app::url_handler_prefs::ResetSavedChoice(local_state_, app_id_opt, - profile_->GetPath(), origin, - has_origin_wildcard, path); - - // No need to call UpdateModel() here - we should receive a notification - // that local state prefs have changed and we will update the view - // then. -} - -// Example of returned value: -// [ -// { -// "display_origin": "example.com", -// "app_entries": [ -// { -// "app_id": "jncifgjpfigpfjphlanoeonmiedopibl", -// "has_origin_wildcard": false, -// "origin_key": "https://example.com", -// "path": "/abc", -// "publisher": "example.com", -// "short_name": "Example App", -// "display_url": "example.com/abc" // optional -// } -// ] -// } -// ] -base::Value UrlHandlersHandler::GetEnabledHandlersList() { - const base::Value* const pref_value = - local_state_->Get(prefs::kWebAppsUrlHandlerInfo); - if (!pref_value || !pref_value->is_dict()) - return base::Value(base::Value::Type::LIST); - - // Ensure that both keys and values are sorted. - base::flat_map<std::u16string, base::flat_set<EnabledRow>> organizer; - for (const auto kv : pref_value->DictItems()) { - const auto& origin_key = kv.first; - auto origin = url::Origin::Create(GURL(kv.first)); - - for (const auto& handler : kv.second.GetListDeprecated()) { - // Only process handlers from current profile. - if (!web_app::url_handler_prefs::IsHandlerForProfile( - handler, profile_->GetPath())) { - continue; - } - - absl::optional<const web_app::url_handler_prefs::HandlerView> - handler_view = - web_app::url_handler_prefs::GetConstHandlerView(handler); - - const std::string& short_name = - web_app_registrar_->GetAppShortName(handler_view->app_id); - - url::Origin app_origin = url::Origin::Create( - GURL(web_app_registrar_->GetAppStartUrl(handler_view->app_id))); - - std::u16string publisher = url_formatter::FormatOriginForSecurityDisplay( - app_origin, url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS); - - // For every include_path that has kInApp choice, add a "row" to - // app_entries. Each "row" has the same app_id, etc, but different path - // information. This create a list that is easy to process in WebUI. - for (const auto& include_path_dict : - handler_view->include_paths.GetListDeprecated()) { - const std::string* path = include_path_dict.FindStringKey("path"); - absl::optional<int> choice = include_path_dict.FindIntKey("choice"); - if (!path || !choice) - continue; - - // Only show entries that open in app. - if (*choice != static_cast<int>(web_app::UrlHandlerSavedChoice::kInApp)) - continue; - - EnabledRow app_row; - app_row.origin_key = origin_key; - app_row.app_id = handler_view->app_id; - app_row.short_name = short_name; - app_row.publisher = publisher; - app_row.has_origin_wildcard = handler_view->has_origin_wildcard; - app_row.path = *path; - - std::u16string origin_str = - FormatOriginString(origin, handler_view->has_origin_wildcard); - - // Only include a formatted URL with path if the path is not /*. - // Eg. example.com/* only needs to display example.com, while - // example.com/abc needs to be displayed in full. - if (*path != "/*") - app_row.display_url = origin_str + base::UTF8ToUTF16(*path); - - // Add app_row to the correct bucket according to displayed origin. - organizer[origin_str].insert(app_row); - } - } - } - return SerializeEnabledHandlersList(organizer); -} - -// Example of returned Value: -// [ -// { -// "display_url": "https://example.com/", -// "has_origin_wildcard": true, -// "origin_key": "https://example.com", -// "path": "/*" -// } -// ] -base::Value UrlHandlersHandler::GetDisabledHandlersList() { - const base::Value* const pref_value = - local_state_->Get(prefs::kWebAppsUrlHandlerInfo); - if (!pref_value || !pref_value->is_dict()) - return base::Value(base::Value::Type::LIST); - - // Ensure that values are deduplicated and sorted. - base::flat_set<DisabledRow> organizer; - for (const auto kv : pref_value->DictItems()) { - const auto& origin_key = kv.first; - url::Origin origin = url::Origin::Create(GURL(kv.first)); - - for (const auto& handler : kv.second.GetListDeprecated()) { - // Only process handlers from current profile. - if (!web_app::url_handler_prefs::IsHandlerForProfile( - handler, profile_->GetPath())) { - continue; - } - - absl::optional<const web_app::url_handler_prefs::HandlerView> - handler_view = - web_app::url_handler_prefs::GetConstHandlerView(handler); - if (!handler_view) - continue; - - for (const base::Value& include_path_dict : - handler_view->include_paths.GetListDeprecated()) { - if (!include_path_dict.is_dict()) - continue; - - absl::optional<int> choice = include_path_dict.FindIntKey("choice"); - if (!choice || - *choice != - static_cast<int>(web_app::UrlHandlerSavedChoice::kInBrowser)) - continue; - - const std::string* path = include_path_dict.FindStringKey("path"); - if (!path) - continue; - - DisabledRow row; - row.origin_key = origin_key; - row.has_origin_wildcard = handler_view->has_origin_wildcard; - row.path = *path; - - std::u16string origin_str = - FormatOriginString(origin, handler_view->has_origin_wildcard); - std::u16string display_url = - *path == "/*" ? origin_str : origin_str + base::UTF8ToUTF16(*path); - row.display_url = display_url; - organizer.insert(row); - } - } - } - - return SerializeDisabledHandlersList(organizer); -} - -} // namespace settings
diff --git a/chrome/browser/ui/webui/settings/url_handlers_handler.h b/chrome/browser/ui/webui/settings/url_handlers_handler.h deleted file mode 100644 index 9d7c6ac..0000000 --- a/chrome/browser/ui/webui/settings/url_handlers_handler.h +++ /dev/null
@@ -1,78 +0,0 @@ -// 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 CHROME_BROWSER_UI_WEBUI_SETTINGS_URL_HANDLERS_HANDLER_H_ -#define CHROME_BROWSER_UI_WEBUI_SETTINGS_URL_HANDLERS_HANDLER_H_ - -#include "base/memory/raw_ptr.h" -#include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h" - -class PrefChangeRegistrar; -class PrefService; -class Profile; - -namespace base { -class Value; -} - -namespace web_app { -class WebAppRegistrar; -} // namespace web_app - -namespace settings { - -class UrlHandlersHandlerTest; - -class UrlHandlersHandler : public SettingsPageUIHandler { - public: - UrlHandlersHandler(PrefService* local_state, Profile* profile); - ~UrlHandlersHandler() override; - UrlHandlersHandler(const UrlHandlersHandler&) = delete; - UrlHandlersHandler& operator=(const UrlHandlersHandler&) = delete; - - // SettingsPageUIHandler: - void OnJavascriptAllowed() override; - void OnJavascriptDisallowed() override; - void RegisterMessages() override; - - protected: - // Protected for tests. - UrlHandlersHandler(PrefService* local_state, - Profile* profile, - web_app::WebAppRegistrar* web_app_registrar); - - private: - friend class ::settings::UrlHandlersHandlerTest; - - // Handles calls from WebUI to update the model data of URL handlers settings. - // Takes no args. - void HandleGetUrlHandlers(base::Value::ConstListView args); - - // Handles calls from WebUI to reset the user's saved choice for one or more - // URL handler entries. - // When reset, the choice becomes kNone and the timestamp is updated. - // |args| is a list of [app_id, origin_key, has_origin_wildcard, url_path]. - void HandleResetUrlHandlerSavedChoice(base::Value::ConstListView args); - - // Reads and formats data from UrlHandlerPrefs then sends it to the WebUI - // frontend. - void UpdateModel(); - - // Listens to relevant prefs changes and updates WebUI with new data. - void OnUrlHandlersLocalStatePrefChanged(); - - // Helper functions that read and format UrlHandlerPrefs data for settings - // page in WebUI. - base::Value GetEnabledHandlersList(); - base::Value GetDisabledHandlersList(); - - std::unique_ptr<PrefChangeRegistrar> local_state_pref_change_registrar_; - const raw_ptr<PrefService> local_state_; - const raw_ptr<Profile> profile_; - const raw_ptr<web_app::WebAppRegistrar> web_app_registrar_; -}; - -} // namespace settings - -#endif // CHROME_BROWSER_UI_WEBUI_SETTINGS_URL_HANDLERS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/settings/url_handlers_handler_unittest.cc b/chrome/browser/ui/webui/settings/url_handlers_handler_unittest.cc deleted file mode 100644 index 5f238b7..0000000 --- a/chrome/browser/ui/webui/settings/url_handlers_handler_unittest.cc +++ /dev/null
@@ -1,366 +0,0 @@ -// 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 "chrome/browser/ui/webui/settings/url_handlers_handler.h" - -#include <memory> -#include <string> -#include <utility> - -#include "base/test/values_test_util.h" -#include "base/time/time.h" -#include "base/values.h" -#include "chrome/browser/web_applications/test/fake_web_app_registry_controller.h" -#include "chrome/browser/web_applications/url_handler_prefs.h" -#include "chrome/browser/web_applications/web_app.h" -#include "chrome/browser/web_applications/web_app_helpers.h" -#include "chrome/browser/web_applications/web_app_registrar.h" -#include "chrome/common/pref_names.h" -#include "chrome/test/base/scoped_testing_local_state.h" -#include "chrome/test/base/testing_browser_process.h" -#include "chrome/test/base/testing_profile.h" -#include "components/services/app_service/public/cpp/url_handler_info.h" -#include "content/public/browser/web_ui_message_handler.h" -#include "content/public/test/browser_task_environment.h" -#include "content/public/test/test_web_ui.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "third_party/blink/public/mojom/manifest/display_mode.mojom.h" -#include "url/gurl.h" -#include "url/origin.h" - -namespace settings { -namespace { - -class TestUrlHandlersHandler : public settings::UrlHandlersHandler { - public: - TestUrlHandlersHandler(PrefService* local_state, - Profile* profile, - web_app::WebAppRegistrar* web_app_registrar) - : UrlHandlersHandler(local_state, profile, web_app_registrar) {} - ~TestUrlHandlersHandler() override = default; - TestUrlHandlersHandler(const TestUrlHandlersHandler&) = delete; - TestUrlHandlersHandler& operator=(const TestUrlHandlersHandler&) = delete; - - using settings::UrlHandlersHandler::set_web_ui; - - private: - friend class ::settings::UrlHandlersHandlerTest; -}; - -constexpr char kTestCallbackId[] = "test-callback-id"; -constexpr char kEmptyList[] = R"([ ])"; - -} // namespace - -class UrlHandlersHandlerTest : public testing::Test { - public: - UrlHandlersHandlerTest() - : scoped_testing_local_state_(TestingBrowserProcess::GetGlobal()) { - EXPECT_TRUE(base::Time::FromString("1 Jan 2000 00:00:00 GMT", &time_1_)); - } - - ~UrlHandlersHandlerTest() override = default; - UrlHandlersHandlerTest(const UrlHandlersHandlerTest&) = delete; - UrlHandlersHandlerTest& operator=(const UrlHandlersHandlerTest&) = delete; - - void SetUp() override { - profile_ = std::make_unique<TestingProfile>(); - - fake_registry_controller_ = - std::make_unique<web_app::FakeWebAppRegistryController>(); - fake_registry_controller_->SetUp(profile_.get()); - - handler_ = std::make_unique<TestUrlHandlersHandler>( - local_state(), profile(), &test_app_registrar()); - handler_->set_web_ui(web_ui()); - handler_->RegisterMessages(); - handler_->AllowJavascriptForTesting(); - - web_ui()->ClearTrackedCalls(); - - controller().Init(); - } - - const web_app::WebApp* RegisterWebAppWithUrlHandlers( - const GURL& app_url, - const apps::UrlHandlers& url_handlers) { - std::unique_ptr<web_app::WebApp> web_app = - std::make_unique<web_app::WebApp>(web_app::GenerateAppId( - /*manifest_id=*/absl::nullopt, GURL(app_url))); - web_app->AddSource(web_app::Source::kDefault); - web_app->SetName("App Name"); - web_app->SetDisplayMode(web_app::DisplayMode::kStandalone); - web_app->SetUserDisplayMode(web_app::DisplayMode::kStandalone); - web_app->SetStartUrl(app_url); - web_app->SetUrlHandlers(url_handlers); - const web_app::AppId app_id = web_app->app_id(); - web_app::url_handler_prefs::AddWebApp( - local_state(), app_id, profile()->GetPath(), web_app->url_handlers()); - controller().RegisterApp(std::move(web_app)); - return registrar().GetAppById(app_id); - } - - void TearDown() override { - profile_.reset(); - handler_.reset(); - } - - web_app::FakeWebAppRegistryController& controller() { - return *fake_registry_controller_; - } - - web_app::WebAppRegistrar& registrar() { return controller().registrar(); } - - web_app::WebAppRegistrar& test_app_registrar() { - return controller().registrar(); - } - - TestingProfile* profile() { return profile_.get(); } - - content::TestWebUI* web_ui() { return &web_ui_; } - - PrefService* local_state() { - return TestingBrowserProcess::GetGlobal()->local_state(); - } - - TestUrlHandlersHandler* handler() { return handler_.get(); } - - void ParseAndExpectValue(const base::Value& value, - const std::string& expectation) { - base::Value expected_value = base::test::ParseJson(expectation); - EXPECT_EQ(value, expected_value); - } - - void CallAndExpectGetUrlHandlers( - const std::string& expected_enabled_handlers = "", - const std::string& expected_disabled_handlers = "") { - base::ListValue list_args; - list_args.Append(kTestCallbackId); - web_ui()->HandleReceivedMessage("getUrlHandlers", &list_args); - - ASSERT_EQ(1u, web_ui()->call_data().size()); - const auto& data = *web_ui()->call_data()[0]; - EXPECT_EQ("cr.webUIResponse", data.function_name()); - - ASSERT_TRUE(data.arg1()->is_string()); - EXPECT_EQ(kTestCallbackId, data.arg1()->GetString()); - - // Check that ResolveJavascriptCallback was called by the handler. - ASSERT_TRUE(data.arg2()->is_bool()); - EXPECT_TRUE(data.arg2()->GetBool()) - << "Callback should be resolved successfully by HandleGetUrlHandlers"; - - // Check data given to ResolveJavascriptCallback. - ASSERT_TRUE(data.arg3()->is_dict()); - const base::Value* callback_data = data.arg3(); - ASSERT_TRUE(callback_data != nullptr); - const base::Value* enabled_handlers = callback_data->FindKey("enabled"); - const base::Value* disabled_handlers = callback_data->FindKey("disabled"); - ASSERT_TRUE(enabled_handlers != nullptr); - ASSERT_TRUE(disabled_handlers != nullptr); - if (!expected_enabled_handlers.empty()) - ParseAndExpectValue(*enabled_handlers, expected_enabled_handlers); - if (!expected_disabled_handlers.empty()) - ParseAndExpectValue(*disabled_handlers, expected_disabled_handlers); - - web_ui()->ClearTrackedCalls(); - } - - void ExpectUpdateUrlHandlers( - const std::string& expected_enabled_handlers = "", - const std::string& expected_disabled_handlers = "") { - ASSERT_EQ(web_ui()->call_data().size(), 1u); - const content::TestWebUI::CallData& data = *web_ui()->call_data()[0]; - EXPECT_EQ("cr.webUIListenerCallback", data.function_name()); - ASSERT_TRUE(data.arg1()->is_string()); - EXPECT_EQ("updateUrlHandlers", data.arg1()->GetString()); - - if (!expected_enabled_handlers.empty()) - ParseAndExpectValue(*data.arg2(), expected_enabled_handlers); - if (!expected_disabled_handlers.empty()) - ParseAndExpectValue(*data.arg3(), expected_disabled_handlers); - - web_ui()->ClearTrackedCalls(); - } - - void ExpectEnabledHandlersList(const std::string& expected) { - const base::Value expected_value = base::test::ParseJson(expected); - EXPECT_EQ(handler()->GetEnabledHandlersList(), expected_value); - } - - void ExpectDisabledHandlersList(const std::string& expected) { - const base::Value expected_value = base::test::ParseJson(expected); - EXPECT_EQ(handler()->GetDisabledHandlersList(), expected_value); - } - - void ExpectUrlHandlerPrefs(const std::string& expected_prefs) { - const base::Value* const stored_prefs = - local_state()->Get(prefs::kWebAppsUrlHandlerInfo); - ASSERT_TRUE(stored_prefs); - const base::Value expected_prefs_value = - base::test::ParseJson(expected_prefs); - EXPECT_EQ(*stored_prefs, expected_prefs_value); - } - - protected: - content::BrowserTaskEnvironment task_environment_; - ScopedTestingLocalState scoped_testing_local_state_; - std::unique_ptr<TestingProfile> profile_; - content::TestWebUI web_ui_; - std::unique_ptr<web_app::FakeWebAppRegistryController> - fake_registry_controller_; - std::unique_ptr<TestUrlHandlersHandler> handler_; - base::Time time_1_; - const GURL app_url_ = GURL("https://app.com"); - const GURL app2_url_ = GURL("https://app2.com"); - const url::Origin target_origin_ = - url::Origin::Create(GURL("https://target-1.com")); - const GURL url_in_target_origin_ = - target_origin_.GetURL().Resolve("/index.html"); -}; - -TEST_F(UrlHandlersHandlerTest, HandleGetUrlHandlers) { - // Trigger HandleGetUrlHandlers and observe that it calls - // ResolveJavascriptCallback successfully. - CallAndExpectGetUrlHandlers(kEmptyList, kEmptyList); - - // Install app. - const auto* web_app = RegisterWebAppWithUrlHandlers( - app_url_, {apps::UrlHandlerInfo(target_origin_)}); - ExpectUpdateUrlHandlers(kEmptyList, kEmptyList); - - // Save user choice to open in app. - web_app::url_handler_prefs::SaveOpenInApp(local_state(), web_app->app_id(), - profile()->GetPath(), - url_in_target_origin_, time_1_); - constexpr char kEnabledHandlers[] = R"([ { - "app_entries": [ { - "app_id": "ahkofokocdmhbhhpkeohpoocnniagaac", - "has_origin_wildcard": false, - "origin_key": "https://target-1.com", - "path": "/*", - "publisher": "app.com", - "short_name": "App Name" - } ], - "display_origin": "target-1.com" - } ])"; - ExpectUpdateUrlHandlers(kEnabledHandlers, kEmptyList); - // Trigger HandleGetUrlHandlers and observe that it calls - // ResolveJavascriptCallback successfully. This isn't necessary but we call - // "getUrlHandlers" here to check that its values are identical to values from - // "updateUrlHandlers". - CallAndExpectGetUrlHandlers(kEnabledHandlers, kEmptyList); - - // Save user choice to open in browser. - web_app::url_handler_prefs::SaveOpenInBrowser(local_state(), - url_in_target_origin_, time_1_); - constexpr char kDisabledHandlers[] = R"([ { - "display_url": "target-1.com", - "has_origin_wildcard": false, - "origin_key": "https://target-1.com", - "path": "/*" - }])"; - ExpectUpdateUrlHandlers(kEmptyList, kDisabledHandlers); - CallAndExpectGetUrlHandlers(kEmptyList, kDisabledHandlers); -} - -TEST_F(UrlHandlersHandlerTest, HandleResetUrlHandlerSavedChoice) { - const auto* web_app = RegisterWebAppWithUrlHandlers( - app_url_, {apps::UrlHandlerInfo(target_origin_)}); - // Prefs changes should cause data to be sent to WebUI. - ExpectUpdateUrlHandlers(kEmptyList, kEmptyList); - - // Save user choice to open in app. - web_app::url_handler_prefs::SaveOpenInApp(local_state(), web_app->app_id(), - profile()->GetPath(), - url_in_target_origin_, time_1_); - ExpectUpdateUrlHandlers(R"([ { - "app_entries": [ { - "app_id": "ahkofokocdmhbhhpkeohpoocnniagaac", - "has_origin_wildcard": false, - "origin_key": "https://target-1.com", - "path": "/*", - "publisher": "app.com", - "short_name": "App Name" - } ], - "display_origin": "target-1.com" - } ])", - kEmptyList); - - // Trigger resetUrlHandlerSavedChoice event directly. That should result - // in local state prefs being updated and then update to WebUI. - base::ListValue list_args; - list_args.Append("https://target-1.com"); // origin - list_args.Append(false); // has_origin_wildcard - list_args.Append("/*"); // url_path - list_args.Append(web_app->app_id()); // app_id - web_ui()->HandleReceivedMessage("resetUrlHandlerSavedChoice", &list_args); - ExpectUpdateUrlHandlers(kEmptyList, kEmptyList); -} - -TEST_F(UrlHandlersHandlerTest, EnabledHandlers) { - const auto* web_app = RegisterWebAppWithUrlHandlers( - app_url_, {apps::UrlHandlerInfo(target_origin_)}); - ExpectUpdateUrlHandlers(kEmptyList, kEmptyList); - // Save user choice to open in app. - web_app::url_handler_prefs::SaveOpenInApp(local_state(), web_app->app_id(), - profile()->GetPath(), - url_in_target_origin_, time_1_); - - ExpectUpdateUrlHandlers(R"([ { - "app_entries": [ { - "app_id": "ahkofokocdmhbhhpkeohpoocnniagaac", - "has_origin_wildcard": false, - "origin_key": "https://target-1.com", - "path": "/*", - "publisher": "app.com", - "short_name": "App Name" - } ], - "display_origin": "target-1.com" - } ])", - kEmptyList); -} - -TEST_F(UrlHandlersHandlerTest, DisabledHandlers) { - RegisterWebAppWithUrlHandlers(app_url_, - {apps::UrlHandlerInfo(target_origin_)}); - ExpectUpdateUrlHandlers(kEmptyList, kEmptyList); - - // Save user choice to open in browser. - web_app::url_handler_prefs::SaveOpenInBrowser(local_state(), - url_in_target_origin_, time_1_); - ExpectUpdateUrlHandlers(kEmptyList, - R"([ { - "display_url": "target-1.com", - "has_origin_wildcard": false, - "origin_key": "https://target-1.com", - "path": "/*" - } ])"); -} - -TEST_F(UrlHandlersHandlerTest, GetDisabledHandlersList_MultipleApps) { - RegisterWebAppWithUrlHandlers(app_url_, - {apps::UrlHandlerInfo(target_origin_)}); - ExpectUpdateUrlHandlers(kEmptyList, kEmptyList); - - RegisterWebAppWithUrlHandlers(app2_url_, - {apps::UrlHandlerInfo(target_origin_)}); - ExpectUpdateUrlHandlers(kEmptyList, kEmptyList); - - // Save user choice to open in browser. - web_app::url_handler_prefs::SaveOpenInBrowser(local_state(), - url_in_target_origin_, time_1_); - - // WebUI should show the entry to open this origin+path in the browser only - // once even though it is written to multiple apps. - constexpr char kDisabledHandlers[] = R"([ { - "display_url": "target-1.com", - "has_origin_wildcard": false, - "origin_key": "https://target-1.com", - "path": "/*" - }])"; - ExpectUpdateUrlHandlers(kEmptyList, kDisabledHandlers); -} - -} // namespace settings
diff --git a/chrome/browser/vr/chrome_xr_integration_client.cc b/chrome/browser/vr/chrome_xr_integration_client.cc index 681c5ce..74ee91e 100644 --- a/chrome/browser/vr/chrome_xr_integration_client.cc +++ b/chrome/browser/vr/chrome_xr_integration_client.cc
@@ -4,6 +4,7 @@ #include "chrome/browser/vr/chrome_xr_integration_client.h" +#include <memory> #include <utility> #include "base/command_line.h" @@ -11,11 +12,16 @@ #include "base/metrics/user_metrics.h" #include "base/metrics/user_metrics_action.h" #include "build/build_config.h" +#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h" +#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h" +#include "content/public/browser/browser_xr_runtime.h" +#include "content/public/browser/media_stream_request.h" #include "content/public/browser/xr_install_helper.h" #include "content/public/common/content_features.h" #include "device/vr/buildflags/buildflags.h" #include "device/vr/public/cpp/vr_device_provider.h" #include "device/vr/public/mojom/vr_service.mojom-shared.h" +#include "third_party/blink/public/common/mediastream/media_stream_request.h" #if BUILDFLAG(IS_WIN) #include "chrome/browser/vr/ui_host/vr_ui_host_impl.h" @@ -30,6 +36,58 @@ #endif // BUILDFLAG(ENABLE_ARCORE) #endif // BUILDFLAG(IS_WIN) +namespace { + +constexpr char kWebXrVideoCaptureDeviceId[] = "WebXRVideoCaptureDevice:-1"; +constexpr char kWebXrVideoCaptureDeviceName[] = "WebXRVideoCaptureDevice"; +constexpr char kWebXrMediaStreamLabel[] = "WebXR Raw Camera Access"; + +class CameraIndicationObserver : public content::BrowserXRRuntime::Observer { + public: + void WebXRCameraInUseChanged(content::WebContents* web_contents, + bool in_use) override { + DVLOG(3) << __func__ << ": web_contents=" << web_contents + << ", in_use=" << in_use << ", num_runtimes_with_camera_in_use_=" + << num_runtimes_with_camera_in_use_ << ", ui_=" << ui_; + // If `in_use` is true, we need to have a non-null `web_contents` to be able + // to register the media stream: + DCHECK(!in_use || web_contents); + + if (in_use) { + num_runtimes_with_camera_in_use_++; + } else { + DCHECK_GT(num_runtimes_with_camera_in_use_, 0u); + num_runtimes_with_camera_in_use_--; + } + + if (num_runtimes_with_camera_in_use_ && !ui_) { + DCHECK(web_contents); + + blink::MediaStreamDevice device( + blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, + kWebXrVideoCaptureDeviceId, kWebXrVideoCaptureDeviceName); + + ui_ = MediaCaptureDevicesDispatcher::GetInstance() + ->GetMediaStreamCaptureIndicator() + ->RegisterMediaStream(web_contents, {std::move(device)}); + DCHECK(ui_); + } + + if (num_runtimes_with_camera_in_use_) { + ui_->OnStarted({}, {}, kWebXrMediaStreamLabel, {}, {}); + } else { + ui_->OnDeviceStopped(kWebXrMediaStreamLabel, {}); + ui_ = nullptr; + } + } + + private: + size_t num_runtimes_with_camera_in_use_ = 0; + std::unique_ptr<content::MediaStreamUI> ui_; +}; + +} // namespace + namespace vr { std::unique_ptr<content::XrInstallHelper> @@ -72,6 +130,12 @@ return providers; } +std::unique_ptr<content::BrowserXRRuntime::Observer> +ChromeXrIntegrationClient::CreateRuntimeObserver() { + DVLOG(3) << __func__; + return std::make_unique<CameraIndicationObserver>(); +} + #if BUILDFLAG(IS_WIN) std::unique_ptr<content::VrUiHost> ChromeXrIntegrationClient::CreateVrUiHost( device::mojom::XRDeviceId device_id,
diff --git a/chrome/browser/vr/chrome_xr_integration_client.h b/chrome/browser/vr/chrome_xr_integration_client.h index 4841771..b5e1ea7d 100644 --- a/chrome/browser/vr/chrome_xr_integration_client.h +++ b/chrome/browser/vr/chrome_xr_integration_client.h
@@ -9,6 +9,7 @@ #include "base/types/pass_key.h" #include "build/build_config.h" +#include "content/public/browser/browser_xr_runtime.h" #include "content/public/browser/xr_integration_client.h" class ChromeContentBrowserClient; @@ -28,6 +29,8 @@ std::unique_ptr<content::XrInstallHelper> GetInstallHelper( device::mojom::XRDeviceId device_id) override; content::XRProviderList GetAdditionalProviders() override; + std::unique_ptr<content::BrowserXRRuntime::Observer> CreateRuntimeObserver() + override; // The only class that we have which implements VrUiHost is Win-only. #if BUILDFLAG(IS_WIN)
diff --git a/chrome/browser/vr/ui_host/vr_ui_host_impl.cc b/chrome/browser/vr/ui_host/vr_ui_host_impl.cc index 311f517e..90b64ef 100644 --- a/chrome/browser/vr/ui_host/vr_ui_host_impl.cc +++ b/chrome/browser/vr/ui_host/vr_ui_host_impl.cc
@@ -153,7 +153,7 @@ // destroyed, it means the corresponding device has been removed from // XRRuntimeManager, and the BrowserXRRuntime has been destroyed. if (web_contents_) - SetWebXRWebContents(nullptr); + WebXRWebContentsChanged(nullptr); } bool IsValidInfo(device::mojom::VRDisplayInfoPtr& info) { @@ -168,7 +168,7 @@ &device::mojom::XRView::eye); } -void VRUiHostImpl::SetWebXRWebContents(content::WebContents* contents) { +void VRUiHostImpl::WebXRWebContentsChanged(content::WebContents* contents) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); if (!IsValidInfo(info_)) { @@ -237,7 +237,7 @@ } } -void VRUiHostImpl::SetFramesThrottled(bool throttled) { +void VRUiHostImpl::WebXRFramesThrottledChanged(bool throttled) { frames_throttled_ = throttled; if (!ui_rendering_thread_) { @@ -248,7 +248,7 @@ ui_rendering_thread_->SetFramesThrottled(frames_throttled_); } -void VRUiHostImpl::SetVRDisplayInfo( +void VRUiHostImpl::VRDisplayInfoChanged( device::mojom::VRDisplayInfoPtr display_info) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); // On Windows this is getting logged every frame, so set to 3.
diff --git a/chrome/browser/vr/ui_host/vr_ui_host_impl.h b/chrome/browser/vr/ui_host/vr_ui_host_impl.h index 89b9acbc..acfa91d 100644 --- a/chrome/browser/vr/ui_host/vr_ui_host_impl.h +++ b/chrome/browser/vr/ui_host/vr_ui_host_impl.h
@@ -71,9 +71,10 @@ }; // content::BrowserXRRuntime::Observer implementation. - void SetWebXRWebContents(content::WebContents* contents) override; - void SetVRDisplayInfo(device::mojom::VRDisplayInfoPtr display_info) override; - void SetFramesThrottled(bool throttled) override; + void WebXRWebContentsChanged(content::WebContents* contents) override; + void VRDisplayInfoChanged( + device::mojom::VRDisplayInfoPtr display_info) override; + void WebXRFramesThrottledChanged(bool throttled) override; // Internal methods used to start/stop the UI rendering thread that is used // for drawing browser UI (such as permission prompts) for display in VR.
diff --git a/chrome/browser/web_applications/system_web_apps/system_web_app_manager.cc b/chrome/browser/web_applications/system_web_apps/system_web_app_manager.cc index a638d99..df569ca4a 100644 --- a/chrome/browser/web_applications/system_web_apps/system_web_app_manager.cc +++ b/chrome/browser/web_applications/system_web_apps/system_web_app_manager.cc
@@ -597,7 +597,7 @@ for (const auto& it : system_app_delegates_) { absl::optional<SystemAppBackgroundTaskInfo> background_info = it.second->GetTimerInfo(); - if (background_info) { + if (background_info && it.second->IsAppEnabled()) { tasks_.push_back(std::make_unique<SystemAppBackgroundTask>( profile_, background_info.value())); }
diff --git a/chrome/browser/web_applications/system_web_apps/test/system_web_app_manager_unittest.cc b/chrome/browser/web_applications/system_web_apps/test/system_web_app_manager_unittest.cc index 7da5847..5bca480d 100644 --- a/chrome/browser/web_applications/system_web_apps/test/system_web_app_manager_unittest.cc +++ b/chrome/browser/web_applications/system_web_apps/test/system_web_app_manager_unittest.cc
@@ -1088,6 +1088,36 @@ } }; +TEST_F(SystemWebAppManagerTimerTest, BackgroundTaskDisabled) { + InitEmptyRegistrar(); + + // 1) Disabled app should not push to background tasks. + SystemAppMapType system_apps; + std::unique_ptr<TimerSystemAppDelegate> sys_app_delegate = + std::make_unique<TimerSystemAppDelegate>( + SystemAppType::SETTINGS, kSettingsAppInternalName, AppUrl1(), + GetApp1WebAppInfoFactory(), base::Seconds(60), false); + + sys_app_delegate->SetIsAppEnabled(false); + + system_apps.emplace(SystemAppType::SETTINGS, std::move(sys_app_delegate)); + system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps)); + StartAndWaitForAppsToSynchronize(); + + EXPECT_EQ(0u, system_web_app_manager().GetBackgroundTasksForTesting().size()); + + // 2) Enabled app should push to background tasks. + sys_app_delegate = std::make_unique<TimerSystemAppDelegate>( + SystemAppType::SETTINGS, kSettingsAppInternalName, AppUrl1(), + GetApp1WebAppInfoFactory(), base::Seconds(60), false); + + system_apps.emplace(SystemAppType::SETTINGS, std::move(sys_app_delegate)); + system_web_app_manager().SetSystemAppsForTesting(std::move(system_apps)); + StartAndWaitForAppsToSynchronize(); + + EXPECT_EQ(1u, system_web_app_manager().GetBackgroundTasksForTesting().size()); +} + TEST_F(SystemWebAppManagerTimerTest, TestTimer) { ui::ScopedSetIdleState idle(ui::IDLE_STATE_IDLE); SetupTimer(base::Seconds(60), false);
diff --git a/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.cc b/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.cc index ff0ec723..95d32367 100644 --- a/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.cc +++ b/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.cc
@@ -167,7 +167,7 @@ return gfx::Rect(); } bool UnittestingSystemAppDelegate::IsAppEnabled() const { - return true; + return is_app_enabled; } bool UnittestingSystemAppDelegate::IsUrlInSystemAppScope( const GURL& url) const { @@ -244,6 +244,9 @@ base::RepeatingCallback<gfx::Rect(Browser*)> lambda) { get_default_bounds_ = std::move(lambda); } +void UnittestingSystemAppDelegate::SetIsAppEnabled(bool value) { + is_app_enabled = value; +} void UnittestingSystemAppDelegate::SetUrlInSystemAppScope(const GURL& url) { url_in_system_app_scope_ = url; }
diff --git a/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.h b/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.h index bac4f4fa..23f895e7 100644 --- a/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.h +++ b/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.h
@@ -74,6 +74,7 @@ void SetShouldAllowScriptsToCloseWindows(bool); void SetTimerInfo(const SystemAppBackgroundTaskInfo&); void SetDefaultBounds(base::RepeatingCallback<gfx::Rect(Browser*)>); + void SetIsAppEnabled(bool); void SetUrlInSystemAppScope(const GURL& url); void SetPreferManifestBackgroundColor(bool); #if BUILDFLAG(IS_CHROMEOS) @@ -98,6 +99,7 @@ bool has_tab_strip_ = false; bool should_have_reload_button_in_minimal_ui_ = true; bool allow_scripts_to_close_windows_ = false; + bool is_app_enabled = true; GURL url_in_system_app_scope_; bool prefer_manifest_background_color_ = false; #if BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/browser/web_applications/web_app_install_manager.cc b/chrome/browser/web_applications/web_app_install_manager.cc index 055927ac..17442eb6 100644 --- a/chrome/browser/web_applications/web_app_install_manager.cc +++ b/chrome/browser/web_applications/web_app_install_manager.cc
@@ -486,9 +486,13 @@ } void WebAppInstallManager::DeleteTask(WebAppInstallTask* task) { - DCHECK(tasks_.contains(task)); TakeTaskErrorLog(task); - tasks_.erase(task); + // If this happens after/during the call to Shutdown(), then ignore deletion + // as `tasks_` is emptied already. + if (started_) { + DCHECK(tasks_.contains(task)); + tasks_.erase(task); + } } void WebAppInstallManager::OnInstallTaskCompleted(
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt index 3f6a631a..50c452d2 100644 --- a/chrome/build/linux.pgo.txt +++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@ -chrome-linux-main-1645552472-dff82300689788600581e0c5620f9cdf7aa20a65.profdata +chrome-linux-main-1645574159-4f68b0ac4c0ad4f9971762f66286704eaa122288.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt index b7d20fca..b3b0e11 100644 --- a/chrome/build/mac-arm.pgo.txt +++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@ -chrome-mac-arm-main-1645552472-8da75fd00e85025a920bd93921f23abd95747050.profdata +chrome-mac-arm-main-1645574159-9b0952a0e6f8425b04314d4ce625a5b4c219dcfc.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt index 3cd87d34..dabfd4c5 100644 --- a/chrome/build/mac.pgo.txt +++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@ -chrome-mac-main-1645552472-814468c805e18fadb8b63b034445166fb7ced9d5.profdata +chrome-mac-main-1645574159-84ac20ae2b3921896fbf677a762cbe6c60e5d3d8.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt index defcf35..139f86d2 100644 --- a/chrome/build/win32.pgo.txt +++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@ -chrome-win32-main-1645552472-d30ff9a4075104712b56882ed53a4d4029b64133.profdata +chrome-win32-main-1645574159-895357cd8bedc9dc31ba86d30478d3731468f622.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt index 661047f1..7bb2d0a6 100644 --- a/chrome/build/win64.pgo.txt +++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@ -chrome-win64-main-1645552472-38feed80a11c993b85a949a5df69c21b3f8765ba.profdata +chrome-win64-main-1645574159-8ba46068808193f24883e0c5d0b484d8961428e9.profdata
diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/extensions/api/_api_features.json index cc5d7a0..6e1e9d75 100644 --- a/chrome/common/extensions/api/_api_features.json +++ b/chrome/common/extensions/api/_api_features.json
@@ -300,7 +300,8 @@ }], "debugger": { "dependencies": ["permission:debugger"], - "contexts": ["blessed_extension"] + "contexts": ["blessed_extension"], + "developer_mode_only": true }, "declarativeContent": { "dependencies": ["permission:declarativeContent"],
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc index c4d5a74e..3fede71 100644 --- a/chrome/common/pref_names.cc +++ b/chrome/common/pref_names.cc
@@ -1779,6 +1779,9 @@ // upgrade a unsafe location to a safe location. const char kDownloadDirUpgraded[] = "download.directory_upgrade"; +// base::Time value indicating the last timestamp when a download is completed. +const char kDownloadLastCompleteTime[] = "download.last_complete_time"; + #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || \ BUILDFLAG(IS_MAC) const char kOpenPdfDownloadInSystemReader[] =
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h index 257a98c..32df3c4b 100644 --- a/chrome/common/pref_names.h +++ b/chrome/common/pref_names.h
@@ -583,6 +583,7 @@ extern const char kDownloadExtensionsToOpenByPolicy[]; extern const char kDownloadAllowedURLsForOpenByPolicy[]; extern const char kDownloadDirUpgraded[]; +extern const char kDownloadLastCompleteTime[]; #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || \ BUILDFLAG(IS_MAC) extern const char kOpenPdfDownloadInSystemReader[];
diff --git a/chrome/renderer/extensions/chrome_native_extension_bindings_system_unittest.cc b/chrome/renderer/extensions/chrome_native_extension_bindings_system_unittest.cc index 331ad4c1..dde4209 100644 --- a/chrome/renderer/extensions/chrome_native_extension_bindings_system_unittest.cc +++ b/chrome/renderer/extensions/chrome_native_extension_bindings_system_unittest.cc
@@ -2,8 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/test/scoped_feature_list.h" #include "extensions/common/extension_builder.h" +#include "extensions/common/extension_features.h" +#include "extensions/common/features/feature_developer_mode_only.h" #include "extensions/renderer/bindings/api_binding_test_util.h" +#include "extensions/renderer/dispatcher.h" #include "extensions/renderer/native_extension_bindings_system.h" #include "extensions/renderer/native_extension_bindings_system_test_base.h" #include "extensions/renderer/script_context.h" @@ -43,4 +47,137 @@ ASSERT_TRUE(query->IsFunction()); } +TEST_F(NativeExtensionBindingsSystemUnittest, + RestrictDeveloperModeAPIsUserIsInDeveloperMode) { + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitAndEnableFeature( + extensions_features::kRestrictDeveloperModeAPIs); + + // With kDeveloperModeRestriction enabled, developer mode-only APIs + // should be available if and only if the user is in dev mode. + SetCurrentDeveloperMode(kRendererProfileId, true); + + scoped_refptr<const Extension> extension = + ExtensionBuilder("foo").AddPermissions({"debugger"}).Build(); + RegisterExtension(extension); + + v8::HandleScope handle_scope(isolate()); + v8::Local<v8::Context> context = MainContext(); + + ScriptContext* script_context = CreateScriptContext( + context, extension.get(), Feature::BLESSED_EXTENSION_CONTEXT); + script_context->set_url(extension->url()); + + bindings_system()->UpdateBindingsForContext(script_context); + + // chrome.debugger.getTargets should exist. + v8::Local<v8::Value> chrome = + GetPropertyFromObject(context->Global(), context, "chrome"); + ASSERT_FALSE(chrome.IsEmpty()); + ASSERT_TRUE(chrome->IsObject()); + + v8::Local<v8::Value> debugger = GetPropertyFromObject( + v8::Local<v8::Object>::Cast(chrome), context, "debugger"); + ASSERT_FALSE(debugger.IsEmpty()); + ASSERT_TRUE(debugger->IsObject()); + + v8::Local<v8::Object> debugger_object = v8::Local<v8::Object>::Cast(debugger); + v8::Local<v8::Value> debugger_getTargets = + GetPropertyFromObject(debugger_object, context, "getTargets"); + ASSERT_FALSE(debugger_getTargets.IsEmpty()); + + { + // Call the function correctly. + const char kCallDebuggerGetTargets[] = + R"((function() { + chrome.debugger.getTargets(function() {}); + });)"; + + v8::Local<v8::Function> call_debugger_getTargets = + FunctionFromString(context, kCallDebuggerGetTargets); + RunFunctionOnGlobal(call_debugger_getTargets, context, 0, nullptr); + } + + // Validate the params that would be sent to the browser. + EXPECT_EQ(extension->id(), last_params().extension_id); + EXPECT_EQ("debugger.getTargets", last_params().name); + EXPECT_EQ(extension->url(), last_params().source_url); + EXPECT_TRUE(last_params().has_callback); + EXPECT_EQ(last_params().arguments, *ListValueFromString("[ ]")); +} + +TEST_F(NativeExtensionBindingsSystemUnittest, + RestrictDeveloperModeAPIsUserIsNotInDeveloperModeAndHasPermission) { + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitAndEnableFeature( + extensions_features::kRestrictDeveloperModeAPIs); + + // With kDeveloperModeRestriction enabled, developer mode-only APIs + // should not be available if the user is not in dev mode. + SetCurrentDeveloperMode(kRendererProfileId, false); + + scoped_refptr<const Extension> extension = + ExtensionBuilder("foo").AddPermissions({"debugger"}).Build(); + RegisterExtension(extension); + + v8::HandleScope handle_scope(isolate()); + v8::Local<v8::Context> context = MainContext(); + + ScriptContext* script_context = CreateScriptContext( + context, extension.get(), Feature::BLESSED_EXTENSION_CONTEXT); + script_context->set_url(extension->url()); + + bindings_system()->UpdateBindingsForContext(script_context); + + v8::Local<v8::Value> chrome = + GetPropertyFromObject(context->Global(), context, "chrome"); + ASSERT_FALSE(chrome.IsEmpty()); + ASSERT_TRUE(chrome->IsObject()); + + { + const char kCallDebuggerGetTargets[] = + R"((function() { + chrome.debugger(function() {}); + });)"; + + v8::Local<v8::Function> call_debugger_getTargets = + FunctionFromString(context, kCallDebuggerGetTargets); + RunFunctionAndExpectError(call_debugger_getTargets, context, 0, nullptr, + "Uncaught Error: The 'debugger' API is only " + "available for users in developer mode."); + } +} + +TEST_F( + NativeExtensionBindingsSystemUnittest, + RestrictDeveloperModeAPIsUserIsNotInDeveloperModeAndDoesNotHavePermission) { + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitAndEnableFeature( + extensions_features::kRestrictDeveloperModeAPIs); + + SetCurrentDeveloperMode(kRendererProfileId, false); + + scoped_refptr<const Extension> extension = ExtensionBuilder("foo").Build(); + RegisterExtension(extension); + + v8::HandleScope handle_scope(isolate()); + v8::Local<v8::Context> context = MainContext(); + + ScriptContext* script_context = CreateScriptContext( + context, extension.get(), Feature::BLESSED_EXTENSION_CONTEXT); + script_context->set_url(extension->url()); + + bindings_system()->UpdateBindingsForContext(script_context); + + v8::Local<v8::Value> chrome = + GetPropertyFromObject(context->Global(), context, "chrome"); + ASSERT_FALSE(chrome.IsEmpty()); + ASSERT_TRUE(chrome->IsObject()); + + v8::Local<v8::Value> debugger = GetPropertyFromObject( + v8::Local<v8::Object>::Cast(chrome), context, "debugger"); + ASSERT_FALSE(debugger.IsEmpty()); + EXPECT_TRUE(debugger->IsUndefined()); +} + } // namespace extensions
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index 933968d..5e738bb 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn
@@ -6428,10 +6428,6 @@ "../browser/ui/webui/help/version_updater_chromeos_unittest.cc", ] } - if (is_win || is_mac || (is_linux && !is_chromeos_lacros)) { - sources += - [ "../browser/ui/webui/settings/url_handlers_handler_unittest.cc" ] - } if (use_gtk) { deps += [ "//ui/gtk" ] }
diff --git a/chrome/test/data/extensions/api_test/native_bindings/developer_mode_only_with_api_permission/manifest.json b/chrome/test/data/extensions/api_test/native_bindings/developer_mode_only_with_api_permission/manifest.json new file mode 100644 index 0000000..96df9ed --- /dev/null +++ b/chrome/test/data/extensions/api_test/native_bindings/developer_mode_only_with_api_permission/manifest.json
@@ -0,0 +1,8 @@ +{ + "name": "Test developer_mode_only feature with API permission", + "description": "Test developer_mode_only with native bindings", + "version": "0.1", + "manifest_version": 3, + "permissions": ["debugger"], + "background": {"service_worker": "service_worker_background.js"} +}
diff --git a/chrome/test/data/extensions/api_test/native_bindings/developer_mode_only_with_api_permission/service_worker_background.js b/chrome/test/data/extensions/api_test/native_bindings/developer_mode_only_with_api_permission/service_worker_background.js new file mode 100644 index 0000000..f26f9a8 --- /dev/null +++ b/chrome/test/data/extensions/api_test/native_bindings/developer_mode_only_with_api_permission/service_worker_background.js
@@ -0,0 +1,24 @@ +// Copyright 2022 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. + +chrome.test.getConfig(function(config) { + var inDeveloperMode = config.customArg == 'in_developer_mode'; + + chrome.test.runTests([ + function testDebuggerApiAccess() { + if (inDeveloperMode) { + chrome.test.assertTrue(!!chrome.debugger); + chrome.test.assertTrue(!!chrome.debugger.getTargets); + } else { + var expectedError = 'The \'debugger\' API is only ' + + 'available for users in developer mode.'; + var functionThatThrows = function() { + chrome.debugger; + }; + chrome.test.assertThrows(functionThatThrows, [], expectedError); + } + chrome.test.succeed(); + } + ]); +});
diff --git a/chrome/test/data/extensions/api_test/native_bindings/developer_mode_only_without_api_permission/manifest.json b/chrome/test/data/extensions/api_test/native_bindings/developer_mode_only_without_api_permission/manifest.json new file mode 100644 index 0000000..916374c --- /dev/null +++ b/chrome/test/data/extensions/api_test/native_bindings/developer_mode_only_without_api_permission/manifest.json
@@ -0,0 +1,7 @@ +{ + "name": "Test developer_mode_only feature without API access permission", + "description": "Test developer_mode_only with native bindings", + "version": "0.1", + "manifest_version": 3, + "background": {"service_worker": "service_worker_background.js"} +}
diff --git a/chrome/test/data/extensions/api_test/native_bindings/developer_mode_only_without_api_permission/service_worker_background.js b/chrome/test/data/extensions/api_test/native_bindings/developer_mode_only_without_api_permission/service_worker_background.js new file mode 100644 index 0000000..80f8462 --- /dev/null +++ b/chrome/test/data/extensions/api_test/native_bindings/developer_mode_only_without_api_permission/service_worker_background.js
@@ -0,0 +1,10 @@ +// Copyright 2022 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. + +chrome.test.runTests([ + function testGetTargetsThrows() { + chrome.test.assertEq(undefined, chrome.debugger); + chrome.test.succeed(); + } +]);
diff --git a/chrome/test/data/webui/chromeos/personalization_app/ambient_subpage_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/ambient_subpage_element_test.ts index 43cc2713..f4cd1c1bc 100644 --- a/chrome/test/data/webui/chromeos/personalization_app/ambient_subpage_element_test.ts +++ b/chrome/test/data/webui/chromeos/personalization_app/ambient_subpage_element_test.ts
@@ -2,13 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import {AmbientActionName, SetAmbientModeEnabledAction, SetTopicSourceAction} from 'chrome://personalization/trusted/ambient/ambient_actions.js'; +import {AmbientActionName, SetAmbientModeEnabledAction, SetTemperatureUnitAction, SetTopicSourceAction} from 'chrome://personalization/trusted/ambient/ambient_actions.js'; import {AmbientObserver} from 'chrome://personalization/trusted/ambient/ambient_observer.js'; import {AmbientSubpage} from 'chrome://personalization/trusted/ambient/ambient_subpage_element.js'; import {ToggleRow} from 'chrome://personalization/trusted/ambient/toggle_row_element.js'; import {TopicSourceItem} from 'chrome://personalization/trusted/ambient/topic_source_item_element.js'; -import {TopicSource} from 'chrome://personalization/trusted/personalization_app.mojom-webui.js'; +import {TemperatureUnit, TopicSource} from 'chrome://personalization/trusted/personalization_app.mojom-webui.js'; import {emptyState} from 'chrome://personalization/trusted/personalization_state.js'; +import {CrRadioButtonElement} from 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js'; import {CrToggleElement} from 'chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js'; import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js'; import {waitAfterNextRender} from 'chrome://webui-test/test_util.js'; @@ -200,4 +201,51 @@ AmbientActionName.SET_TOPIC_SOURCE) as SetTopicSourceAction; assertEquals(TopicSource.kArtGallery, action.topicSource); }); + + test('has correct temperature unit on load', async () => { + ambientSubpageElement = initElement(AmbientSubpage); + + personalizationStore.setReducersEnabled(true); + personalizationStore.expectAction(AmbientActionName.SET_TEMPERATURE_UNIT); + let action = + await personalizationStore.waitForAction( + AmbientActionName.SET_TEMPERATURE_UNIT) as SetTemperatureUnitAction; + assertEquals(TemperatureUnit.kFahrenheit, action.temperatureUnit); + }); + + test('sets temperature unit when temperature unit item clicked', async () => { + ambientSubpageElement = initElement(AmbientSubpage); + personalizationStore.data.ambient.temperatureUnit = + TemperatureUnit.kCelsius; + personalizationStore.notifyObservers(); + await waitAfterNextRender(ambientSubpageElement); + + const weatherUnit = + ambientSubpageElement.shadowRoot!.querySelector('ambient-weather-unit'); + assertTrue(!!weatherUnit); + + const temperatureUnitItems = + weatherUnit!.shadowRoot!.querySelectorAll('cr-radio-button'); + assertEquals(2, temperatureUnitItems!.length); + + const fahrenheitUnitButton = + temperatureUnitItems[0] as CrRadioButtonElement; + const celsiusUnitButton = temperatureUnitItems[1] as CrRadioButtonElement; + + personalizationStore.expectAction(AmbientActionName.SET_TEMPERATURE_UNIT); + fahrenheitUnitButton!.click(); + assertTrue(fahrenheitUnitButton.checked); + let action = + await personalizationStore.waitForAction( + AmbientActionName.SET_TEMPERATURE_UNIT) as SetTemperatureUnitAction; + assertEquals(TemperatureUnit.kFahrenheit, action.temperatureUnit); + + personalizationStore.expectAction(AmbientActionName.SET_TEMPERATURE_UNIT); + celsiusUnitButton!.click(); + assertTrue(celsiusUnitButton.checked); + action = + await personalizationStore.waitForAction( + AmbientActionName.SET_TEMPERATURE_UNIT) as SetTemperatureUnitAction; + assertEquals(TemperatureUnit.kCelsius, action.temperatureUnit); + }); }
diff --git a/chrome/test/data/webui/chromeos/personalization_app/test_ambient_interface_provider.ts b/chrome/test/data/webui/chromeos/personalization_app/test_ambient_interface_provider.ts index d597bca..aab8bba 100644 --- a/chrome/test/data/webui/chromeos/personalization_app/test_ambient_interface_provider.ts +++ b/chrome/test/data/webui/chromeos/personalization_app/test_ambient_interface_provider.ts
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import {AmbientObserverInterface, AmbientObserverRemote, AmbientProviderInterface, TopicSource} from 'chrome://personalization/trusted/personalization_app.mojom-webui.js'; +import {AmbientObserverInterface, AmbientObserverRemote, AmbientProviderInterface, TemperatureUnit, TopicSource} from 'chrome://personalization/trusted/personalization_app.mojom-webui.js'; import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js'; export class TestAmbientProvider extends TestBrowserProxy implements @@ -13,6 +13,7 @@ 'setAmbientObserver', 'setAmbientModeEnabled', 'setTopicSource', + 'setTemperatureUnit', ]); } @@ -35,6 +36,11 @@ window.setTimeout(() => { this.ambientObserverRemote!.onTopicSourceChanged(TopicSource.kArtGallery); }, 10); + + window.setTimeout(() => { + this.ambientObserverRemote!.onTemperatureUnitChanged( + TemperatureUnit.kFahrenheit); + }, 10); } setAmbientModeEnabled(ambientModeEnabled: boolean) { @@ -44,4 +50,8 @@ setTopicSource(topic_source: TopicSource) { this.methodCalled('setTopicSource', topic_source); } + + setTemperatureUnit(temperature_unit: TemperatureUnit) { + this.methodCalled('setTemperatureUnit', temperature_unit); + } }
diff --git a/chromeos/components/quick_answers/utils/language_detector_unittest.cc b/chromeos/components/quick_answers/utils/language_detector_unittest.cc index e484858..88641cf 100644 --- a/chromeos/components/quick_answers/utils/language_detector_unittest.cc +++ b/chromeos/components/quick_answers/utils/language_detector_unittest.cc
@@ -20,11 +20,12 @@ namespace quick_answers { namespace { +using ::chromeos::machine_learning::mojom:: + REMOVED_TextSuggestSelectionRequestPtr; using ::chromeos::machine_learning::mojom::TextAnnotationRequestPtr; using ::chromeos::machine_learning::mojom::TextClassifier; using ::chromeos::machine_learning::mojom::TextLanguage; using ::chromeos::machine_learning::mojom::TextLanguagePtr; -using ::chromeos::machine_learning::mojom::TextSuggestSelectionRequestPtr; TextLanguagePtr DefaultLanguage() { return TextLanguage::New("en", /*confidence=*/1); @@ -42,9 +43,6 @@ void Annotate(TextAnnotationRequestPtr request, AnnotateCallback callback) override {} - void SuggestSelection(TextSuggestSelectionRequestPtr request, - SuggestSelectionCallback callback) override {} - void FindLanguages(const std::string& text, FindLanguagesCallback callback) override { std::vector<TextLanguagePtr> languages; @@ -63,6 +61,9 @@ detection_results_[text] = std::move(language); } + void REMOVED_1(REMOVED_TextSuggestSelectionRequestPtr request, + REMOVED_1Callback callback) override {} + private: std::map<std::string, TextLanguagePtr> detection_results_; };
diff --git a/chromeos/crosapi/mojom/arc.mojom b/chromeos/crosapi/mojom/arc.mojom index 83aa3f4..edac26a0 100644 --- a/chromeos/crosapi/mojom/arc.mojom +++ b/chromeos/crosapi/mojom/arc.mojom
@@ -38,6 +38,21 @@ kArcNotAvailable, }; +[Stable, Extensible] +enum IsInstallableResult { + // The app is available for installation in ARC. + kInstallable, + + // The app is not available for installation in ARC or is already installed. + kNotInstallable, + + // ARC is not running (e.g. disabled). + kArcIsNotRunning, + + // ARC mojo API version is too low. + kArcIsTooOld, +}; + // This struct reflects components/arc/mojom/intent_common.mojom. // Describes an activity. [Stable] @@ -152,8 +167,8 @@ }; // Interacts with ARC. -// Next version: 4 -// Next method id: 7 +// Next version: 5 +// Next method id: 8 [Stable, Uuid="a39a22dd-2f5c-4c99-b0ea-d83d5b1c987f"] interface Arc { // Registers ArcObserver in Lacros-chrome to Ash-chrome @@ -201,4 +216,9 @@ // the ResolverActivity, this package will be picked if it is on the list. // When multiple packages are set as preferred, the most recent setting wins. [MinVersion=3] AddPreferredPackage@6(string package_name); + + // Checks if the |package_name| is available for installation. + // See |arc.mojom.AppInstance.IsInstallable|. + [MinVersion=4] IsInstallable@7(string package_name) => + (IsInstallableResult result); };
diff --git a/chromeos/services/machine_learning/public/cpp/fake_service_connection.cc b/chromeos/services/machine_learning/public/cpp/fake_service_connection.cc index 5c4a336..a308c65 100644 --- a/chromeos/services/machine_learning/public/cpp/fake_service_connection.cc +++ b/chromeos/services/machine_learning/public/cpp/fake_service_connection.cc
@@ -305,13 +305,6 @@ std::move(callback).Run(std::move(annotations)); } -void FakeServiceConnectionImpl::HandleSuggestSelectionCall( - mojom::TextSuggestSelectionRequestPtr request, - mojom::TextClassifier::SuggestSelectionCallback callback) { - auto selection = suggest_selection_result_.Clone(); - std::move(callback).Run(std::move(selection)); -} - void FakeServiceConnectionImpl::HandleFindLanguagesCall( std::string request, mojom::TextClassifier::FindLanguagesCallback callback) { @@ -330,11 +323,6 @@ } } -void FakeServiceConnectionImpl::SetOutputSelection( - const mojom::CodepointSpanPtr& selection) { - suggest_selection_result_ = selection.Clone(); -} - void FakeServiceConnectionImpl::SetOutputLanguages( const std::vector<mojom::TextLanguagePtr>& languages) { find_languages_result_.clear(); @@ -385,14 +373,6 @@ std::move(callback))); } -void FakeServiceConnectionImpl::SuggestSelection( - mojom::TextSuggestSelectionRequestPtr request, - mojom::TextClassifier::SuggestSelectionCallback callback) { - ScheduleCall(base::BindOnce( - &FakeServiceConnectionImpl::HandleSuggestSelectionCall, - base::Unretained(this), std::move(request), std::move(callback))); -} - void FakeServiceConnectionImpl::FindLanguages( const std::string& text, mojom::TextClassifier::FindLanguagesCallback callback) { @@ -401,6 +381,12 @@ base::Unretained(this), text, std::move(callback))); } +void FakeServiceConnectionImpl::REMOVED_1( + mojom::REMOVED_TextSuggestSelectionRequestPtr request, + mojom::TextClassifier::REMOVED_1Callback callback) { + NOTIMPLEMENTED(); +} + void FakeServiceConnectionImpl::Recognize( mojom::HandwritingRecognitionQueryPtr query, mojom::HandwritingRecognizer::RecognizeCallback callback) {
diff --git a/chromeos/services/machine_learning/public/cpp/fake_service_connection.h b/chromeos/services/machine_learning/public/cpp/fake_service_connection.h index 4aad6b5..c575be6 100644 --- a/chromeos/services/machine_learning/public/cpp/fake_service_connection.h +++ b/chromeos/services/machine_learning/public/cpp/fake_service_connection.h
@@ -35,8 +35,6 @@ // a previous call to SetOutputValue. // Handles TextClassifier::Annotate by always returning the value specified by // a previous call to SetOutputAnnotation. -// Handles TextClassifier::SuggestSelection by always returning the value -// specified by a previous call to SetOutputSelection. // For use with ServiceConnection::UseFakeServiceConnectionForTesting(). class COMPONENT_EXPORT(CHROMEOS_MLSERVICE) FakeServiceConnectionImpl : public ServiceConnection, @@ -127,11 +125,13 @@ void REMOVED_0(mojo::PendingReceiver<mojom::GraphExecutor> receiver, mojom::Model::REMOVED_0Callback callback) override; + // mojom::Model: void REMOVED_4(mojom::HandwritingRecognizerSpecPtr spec, mojo::PendingReceiver<mojom::HandwritingRecognizer> receiver, mojom::MachineLearningService::REMOVED_4Callback result_callback) override; + // mojom::Model: void CreateGraphExecutor( mojom::GraphExecutorOptionsPtr options, mojo::PendingReceiver<mojom::GraphExecutor> receiver, @@ -153,8 +153,8 @@ void SetExecuteSuccess(); // Reset all the TextClassifier related failures and make LoadTextClassifier // succeed. - // Currently, there are three interfaces related to TextClassifier - // (|LoadTextClassifier|, |Annotate| and |SuggestSelection|) but only + // Currently, there are two interfaces related to TextClassifier + // (|LoadTextClassifier|, |Annotate|) but only // |LoadTextClassifier| can fail. void SetTextClassifierSuccess(); @@ -175,10 +175,6 @@ void SetOutputAnnotation( const std::vector<mojom::TextAnnotationPtr>& annotation); - // Call SetOutputSelection() before SuggestSelection() to set the output - // selection. - void SetOutputSelection(const mojom::CodepointSpanPtr& selection); - // Call SetOutputLanguages() before FindLanguages() to set the output // languages. void SetOutputLanguages(const std::vector<mojom::TextLanguagePtr>& languages); @@ -219,15 +215,15 @@ mojom::TextClassifier::AnnotateCallback callback) override; // mojom::TextClassifier: - void SuggestSelection( - mojom::TextSuggestSelectionRequestPtr request, - mojom::TextClassifier::SuggestSelectionCallback callback) override; - - // mojom::TextClassifier: void FindLanguages( const std::string& text, mojom::TextClassifier::FindLanguagesCallback callback) override; + // mojom::TextClassifier: + void REMOVED_1( + mojom::REMOVED_TextSuggestSelectionRequestPtr request, + mojom::TextClassifier::REMOVED_1Callback callback) override; + // mojom::HandwritingRecognizer: void Recognize( mojom::HandwritingRecognitionQueryPtr query, @@ -290,9 +286,6 @@ mojom::MachineLearningService::LoadTextClassifierCallback callback); void HandleAnnotateCall(mojom::TextAnnotationRequestPtr request, mojom::TextClassifier::AnnotateCallback callback); - void HandleSuggestSelectionCall( - mojom::TextSuggestSelectionRequestPtr request, - mojom::TextClassifier::SuggestSelectionCallback callback); void HandleFindLanguagesCall( std::string text, mojom::TextClassifier::FindLanguagesCallback callback);
diff --git a/chromeos/services/machine_learning/public/cpp/service_connection_unittest.cc b/chromeos/services/machine_learning/public/cpp/service_connection_unittest.cc index 3f4d022..60b9b3f 100644 --- a/chromeos/services/machine_learning/public/cpp/service_connection_unittest.cc +++ b/chromeos/services/machine_learning/public/cpp/service_connection_unittest.cc
@@ -447,56 +447,6 @@ ASSERT_TRUE(infer_callback_done); } -// Tests the fake ML service for text classifier suggest selection. -TEST_F(ServiceConnectionTest, - FakeServiceConnectionForTextClassifierSuggestSelection) { - mojo::Remote<mojom::TextClassifier> text_classifier; - bool callback_done = false; - FakeServiceConnectionImpl fake_service_connection; - ServiceConnection::UseFakeServiceConnectionForTesting( - &fake_service_connection); - ServiceConnection::GetInstance()->Initialize(); - - auto span = mojom::CodepointSpan::New(); - span->start_offset = 1; - span->end_offset = 2; - fake_service_connection.SetOutputSelection(span); - - std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>(); - ServiceConnection::GetInstance() - ->GetMachineLearningService() - .LoadTextClassifier( - text_classifier.BindNewPipeAndPassReceiver(), - base::BindOnce( - [](bool* callback_done, mojom::LoadModelResult result) { - EXPECT_EQ(result, mojom::LoadModelResult::OK); - *callback_done = true; - }, - &callback_done) - .Then(run_loop->QuitClosure())); - run_loop->Run(); - ASSERT_TRUE(callback_done); - ASSERT_TRUE(text_classifier.is_bound()); - - auto request = mojom::TextSuggestSelectionRequest::New(); - request->user_selection = mojom::CodepointSpan::New(); - bool infer_callback_done = false; - run_loop.reset(new base::RunLoop); - text_classifier->SuggestSelection( - std::move(request), base::BindOnce( - [](bool* infer_callback_done, - mojom::CodepointSpanPtr suggested_span) { - *infer_callback_done = true; - // Check if the suggestion is correct. - EXPECT_EQ(suggested_span->start_offset, 1u); - EXPECT_EQ(suggested_span->end_offset, 2u); - }, - &infer_callback_done) - .Then(run_loop->QuitClosure())); - run_loop->Run(); - ASSERT_TRUE(infer_callback_done); -} - // Tests the fake ML service for text classifier language identification. TEST_F(ServiceConnectionTest, FakeServiceConnectionForTextClassifierFindLanguages) {
diff --git a/chromeos/services/machine_learning/public/mojom/text_classifier.mojom b/chromeos/services/machine_learning/public/mojom/text_classifier.mojom index 6712ed48e6..ace78e2 100644 --- a/chromeos/services/machine_learning/public/mojom/text_classifier.mojom +++ b/chromeos/services/machine_learning/public/mojom/text_classifier.mojom
@@ -114,11 +114,20 @@ uint32 end_offset@1; }; +// Represent a language detection result. +[Stable] +struct TextLanguage { + // The BCP-47 language code like "en", "fr", "zh" etc. + string locale; + // The confidence score of the language detected (range: 0~1). + float confidence; +}; + // Contains the input and parameters used to suggest selection. // This is a combination of the inputs of the `SuggestSelection` function // of tclib. (See "tclib/annotator/annotate.h"). -[Stable] -struct TextSuggestSelectionRequest { +[Stable, RenamedFrom="chromeos.machine_learning.mojom.TextSuggestSelectionRequest"] +struct REMOVED_TextSuggestSelectionRequest { // The candidate text. string text@0; // Where the user selects. @@ -131,15 +140,6 @@ AnnotationUsecase annotation_usecase@4 = ANNOTATION_USECASE_SMART; }; -// Represent a language detection result. -[Stable] -struct TextLanguage { - // The BCP-47 language code like "en", "fr", "zh" etc. - string locale; - // The confidence score of the language detected (range: 0~1). - float confidence; -}; - // Used to annotate entities within text strings. // Next ordinal: 3 [Stable] @@ -148,17 +148,13 @@ // entities. Annotate@0(TextAnnotationRequest request) => (array<TextAnnotation> outputs); - // Suggest a selection based on user's selection. - // If the inputs are invalid (e.g., user selection's start point is behind - // the end point), the input user selection will be returned. - // NOTE: The selection indices are passed in and returned in terms of - // UTF8 codepoints (not bytes). - SuggestSelection@1(TextSuggestSelectionRequest request) => - (CodepointSpan outputs); // Identify the languages the text is possibly written in. // The returned results are sorted according to the confidence score, from the // highest to the lowest. // The maximum number of results returned is determined internally. // Will return an empty array if the language can not be determined. FindLanguages@2(string text) => (array<TextLanguage> outputs); + // Deprecated `SuggestSelection` + REMOVED_1@1(REMOVED_TextSuggestSelectionRequest request) => + (CodepointSpan outputs); };
diff --git a/components/autofill_payments_strings.grdp b/components/autofill_payments_strings.grdp index 0b17fc9..d7a1c60 100644 --- a/components/autofill_payments_strings.grdp +++ b/components/autofill_payments_strings.grdp
@@ -539,6 +539,9 @@ <message name="IDS_AUTOFILL_VIRTUAL_CARD_ENROLLMENT_ACCEPT_BUTTON_LABEL" desc="Accept enrolling card as a virtual card." formatter_data="android_java"> Yes </message> + <message name="IDS_AUTOFILL_VIRTUAL_CARD_ENROLLMENT_DIALOG_TITLE_LABEL" desc="Title encouraging users to enroll their card to VCN." formatter_data="android_java"> + Make it more secure with a virtual card? + </message> <message name="IDS_AUTOFILL_CARD_UNMASK_AUTHENTICATION_SELECTION_DIALOG_ISSUER_CONFIRMATION_TEXT" desc="The issuer confirmation text shown in the Autofill card unmask authentication selection dialog on Desktop. This is the header text of the dialog, and it is the text stating that the issuer wants additional authentication. This dialog lets the user choose the method of authentication when unmasking a server card, including a virtual card."> Your bank wants to confirm it's you. </message> @@ -599,9 +602,6 @@ <message name="IDS_AUTOFILL_VIRTUAL_CARD_NUMBER_SWITCH_LABEL" desc="The text shown as the label for virtual card enrollment switch in the server card edit page." formatter_data="android_java"> Virtual card </message> - <message name="IDS_AUTOFILL_VIRTUAL_CARD_ENROLLMENT_DIALOG_TITLE_LABEL" desc="Title encouraging users to enroll their card to VCN."> - Make it more secure with a virtual card? - </message> <message name="IDS_AUTOFILL_VIRTUAL_CARD_ENROLLMENT_DIALOG_CONTENT_LABEL" desc="Text explaining the benefit of enrolling a credit card as a virtual card. Also contains a link to learn more about virtual cards from IDS_AUTOFILL_VIRTUAL_CARD_ENROLLMENT_LEARN_MORE_LINK_LABEL."> A virtual card disguises your actual card to help protect you from potential fraud. <ph name="IDS_AUTOFILL_VIRTUAL_CARD_ENROLLMENT_LEARN_MORE_LINK_LABEL">$1<ex>Learn about virtual cards</ex></ph> </message>
diff --git a/components/browser_ui/styles/android/BUILD.gn b/components/browser_ui/styles/android/BUILD.gn index 4e2cd83..2963e0a 100644 --- a/components/browser_ui/styles/android/BUILD.gn +++ b/components/browser_ui/styles/android/BUILD.gn
@@ -23,6 +23,8 @@ android_resources("java_resources") { sources = [ + "java/res/color-night/new_tab_button_pressed_tint.xml", + "java/res/color-night/new_tab_button_tint.xml", "java/res/color-night/switch_thumb_tint.xml", "java/res/color-night/switch_track_tint.xml", "java/res/color/chip_text_color_secondary.xml", @@ -42,6 +44,8 @@ "java/res/color/default_text_color_on_accent1_list.xml", "java/res/color/default_text_color_secondary_list.xml", "java/res/color/icon_animated_faded_color.xml", + "java/res/color/new_tab_button_pressed_tint.xml", + "java/res/color/new_tab_button_tint.xml", "java/res/color/progress_bar_bg_color.xml", "java/res/color/selection_control_button_tint.xml", "java/res/color/switch_thumb_tint.xml",
diff --git a/components/browser_ui/styles/android/java/res/color-night/new_tab_button_pressed_tint.xml b/components/browser_ui/styles/android/java/res/color-night/new_tab_button_pressed_tint.xml new file mode 100644 index 0000000..420e7302 --- /dev/null +++ b/components/browser_ui/styles/android/java/res/color-night/new_tab_button_pressed_tint.xml
@@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2022 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. --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="?attr/colorPrimary"/> +</selector> \ No newline at end of file
diff --git a/components/browser_ui/styles/android/java/res/color-night/new_tab_button_tint.xml b/components/browser_ui/styles/android/java/res/color-night/new_tab_button_tint.xml new file mode 100644 index 0000000..94907f6 --- /dev/null +++ b/components/browser_ui/styles/android/java/res/color-night/new_tab_button_tint.xml
@@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2022 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. --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="?attr/colorOnSurface"/> +</selector> \ No newline at end of file
diff --git a/components/browser_ui/styles/android/java/res/color/new_tab_button_pressed_tint.xml b/components/browser_ui/styles/android/java/res/color/new_tab_button_pressed_tint.xml new file mode 100644 index 0000000..01301d3 --- /dev/null +++ b/components/browser_ui/styles/android/java/res/color/new_tab_button_pressed_tint.xml
@@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2022 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. --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="?attr/colorPrimaryInverse"/> +</selector> \ No newline at end of file
diff --git a/components/browser_ui/styles/android/java/res/color/new_tab_button_tint.xml b/components/browser_ui/styles/android/java/res/color/new_tab_button_tint.xml new file mode 100644 index 0000000..065c368 --- /dev/null +++ b/components/browser_ui/styles/android/java/res/color/new_tab_button_tint.xml
@@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2022 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. --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="?attr/colorOnSurfaceInverse"/> +</selector> \ No newline at end of file
diff --git a/components/certificate_transparency/chrome_ct_policy_enforcer.cc b/components/certificate_transparency/chrome_ct_policy_enforcer.cc index 9648ae2..25417ff 100644 --- a/components/certificate_transparency/chrome_ct_policy_enforcer.cc +++ b/components/certificate_transparency/chrome_ct_policy_enforcer.cc
@@ -346,7 +346,7 @@ // ... AND the certificate embeds SCTs from AT LEAST the number of logs // once or currently qualified shown in Table 1 of the CT Policy. base::TimeDelta lifetime = cert.valid_expiry() - cert.valid_start(); - if (lifetime >= base::Days(180)) { + if (lifetime > base::Days(180)) { num_required_embedded_scts = 3; } else { num_required_embedded_scts = 2;
diff --git a/components/certificate_transparency/chrome_ct_policy_enforcer_unittest.cc b/components/certificate_transparency/chrome_ct_policy_enforcer_unittest.cc index a6628a22..e9525db8 100644 --- a/components/certificate_transparency/chrome_ct_policy_enforcer_unittest.cc +++ b/components/certificate_transparency/chrome_ct_policy_enforcer_unittest.cc
@@ -848,6 +848,9 @@ base::Time time_2015_9_0_21_11_25_0_0 = CreateTime({2015, 9, 0, 21, 11, 25, 0, 0}); + base::Time time_2015_9_0_21_11_25_1_0 = + CreateTime({2015, 9, 0, 21, 11, 25, 1, 0}); + base::Time time_2016_3_0_25_11_25_0_0 = CreateTime({2016, 3, 0, 25, 11, 25, 0, 0}); @@ -859,8 +862,10 @@ time_2016_3_0_25_11_25_0_0, time_2015_3_0_25_11_25_0_0, 2}, {// Cert valid for 179 days, needs 2 SCTs. time_2015_3_0_25_11_25_0_0, time_2015_9_0_20_11_25_0_0, 2}, - {// Cert valid for exactly 180 days, needs 3 SCTs. - time_2015_3_0_25_11_25_0_0, time_2015_9_0_21_11_25_0_0, 3}, + {// Cert valid for exactly 180 days, needs only 2 SCTs. + time_2015_3_0_25_11_25_0_0, time_2015_9_0_21_11_25_0_0, 2}, + {// Cert valid for barely over 180 days, needs 3 SCTs. + time_2015_3_0_25_11_25_0_0, time_2015_9_0_21_11_25_1_0, 3}, {// Cert valid for over 180 days, needs 3 SCTs. time_2015_3_0_25_11_25_0_0, time_2016_3_0_25_11_25_0_0, 3}}; @@ -880,7 +885,7 @@ ASSERT_TRUE(cert); std::map<std::string, OperatorHistoryEntry> operator_history; - for (size_t j = 0; j <= scts_required - 1; ++j) { + for (size_t j = 0; j <= scts_required; ++j) { SCTList scts; FillListWithSCTsOfOrigin(SignedCertificateTimestamp::SCT_EMBEDDED, j, std::vector<std::string>(), false, &scts);
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerMediator.java b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerMediator.java index 5fc9cc4b..7e8adf9 100644 --- a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerMediator.java +++ b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerMediator.java
@@ -214,18 +214,15 @@ final float velocity = isVertical ? velocityY : velocityX; float translateTo; if (isVertical) { - translateTo = velocity < 0 ? -mMaxTranslationYSupplier.get() : 0; + final float translationY = mModel.get(TRANSLATION_Y); + translateTo = translationY < 0 ? -mMaxTranslationYSupplier.get() : 0; } else { final float translationX = mModel.get(TRANSLATION_X); - - if (velocity < 0) { - translateTo = translationX > mHorizontalHideThresholdPx - ? 0 - : -mMaxHorizontalTranslationPx.get(); + if (Math.abs(translationX) < mHorizontalHideThresholdPx) { + translateTo = 0; } else { - translateTo = translationX < -mHorizontalHideThresholdPx - ? 0 - : mMaxHorizontalTranslationPx.get(); + translateTo = + MathUtils.flipSignIf(mMaxHorizontalTranslationPx.get(), translationX < 0); } }
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerMediatorUnitTest.java b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerMediatorUnitTest.java index 5511997..ac254f9 100644 --- a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerMediatorUnitTest.java +++ b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerMediatorUnitTest.java
@@ -279,7 +279,7 @@ } @Test - public void testHorizontalFlingFromOutsideThresholdToCenterNotDismissed() { + public void testHorizontalFlingFromOutsideThresholdToCenterDismissed() { mMediator.show(mShownRunnable); shadowOf(getMainLooper()).idle(); @@ -295,19 +295,8 @@ shadowOf(getMainLooper()).idle(); - assertModelState(0, 0, 1, "animated to idle position after fling."); - verify(mDismissedRunnable, times(0)).run(); - - // More than the threshold to dismiss, fling back to center - swipeHorizontal(-30, 100); - - // Alpha .75 is 1 (fully opaque) - 30 (translationY) / 120 (maxTranslation) - assertModelState(-30, 0, .75f, "after swipe."); - - shadowOf(getMainLooper()).idle(); - - assertModelState(0, 0, 1, "animated to idle position after fling."); - verify(mDismissedRunnable, times(0)).run(); + assertModelState(120, 0, 0, "after swipe"); + verify(mDismissedRunnable).run(); } @Test @@ -327,19 +316,8 @@ shadowOf(getMainLooper()).idle(); - assertModelState(0, 0, 1, "animated to idle position after fling."); - verify(mDismissedRunnable, times(0)).run(); - - // Less than the threshold to dismiss, fling back to center - swipeVertical(-10, 100); - - // .9 is 1 (fully opaque) - 10 (translationY) / 100 (maxTranslation) - assertModelState(0, -10, .9f, "after swipe."); - - shadowOf(getMainLooper()).idle(); - - assertModelState(0, 0, 1, "animated to idle position after fling."); - verify(mDismissedRunnable, times(0)).run(); + assertModelState(0, -100, 0, "after swipe"); + verify(mDismissedRunnable, times(1)).run(); } @Test @@ -398,7 +376,7 @@ } @Test - public void testLeftFlingWithinThresholdPositiveXDismisses() { + public void testLeftFlingWithinThresholdPositiveXNoDismisses() { mMediator.show(mShownRunnable); shadowOf(getMainLooper()).idle(); @@ -414,12 +392,12 @@ shadowOf(getMainLooper()).idle(); - assertModelState(-120, 0, 0, "dismissed to left after fling."); - verify(mDismissedRunnable, times(1)).run(); + assertModelState(0, 0, 1, "animate back to center."); + verify(mDismissedRunnable, times(0)).run(); } @Test - public void testLeftFlingWithinThresholdNegativeXDismisses() { + public void testLeftFlingWithinThresholdNegativeXNoDismisses() { mMediator.show(mShownRunnable); shadowOf(getMainLooper()).idle(); @@ -435,12 +413,12 @@ shadowOf(getMainLooper()).idle(); - assertModelState(-120, 0, 0, "dismissed to left after fling."); - verify(mDismissedRunnable, times(1)).run(); + assertModelState(0, 0, 1, "animate back to center."); + verify(mDismissedRunnable, times(0)).run(); } @Test - public void testRightFlingWithinThresholdNegativeXDismisses() { + public void testRightFlingWithinThresholdNegativeXNoDismisses() { mMediator.show(mShownRunnable); shadowOf(getMainLooper()).idle(); @@ -456,12 +434,12 @@ shadowOf(getMainLooper()).idle(); - assertModelState(120, 0, 0, "dismissed to right after fling."); - verify(mDismissedRunnable, times(1)).run(); + assertModelState(0, 0, 1, "animate back to center."); + verify(mDismissedRunnable, times(0)).run(); } @Test - public void testRightFlingWithinThresholdPositiveXDismisses() { + public void testRightFlingWithinThresholdPositiveXNoDismisses() { mMediator.show(mShownRunnable); shadowOf(getMainLooper()).idle(); @@ -477,8 +455,8 @@ shadowOf(getMainLooper()).idle(); - assertModelState(120, 0, 0, "dismissed to right after fling."); - verify(mDismissedRunnable, times(1)).run(); + assertModelState(0, 0, 1, "animate back to center."); + verify(mDismissedRunnable, times(0)).run(); } @Test
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerRenderTest.java b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerRenderTest.java index 1b6ffd2..59ca4aa2 100644 --- a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerRenderTest.java +++ b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerRenderTest.java
@@ -251,4 +251,94 @@ () -> { getActivity().setContentView(view, params); }); mRenderTestRule.render(view, "message_banner_large_icon_with_radius"); } + + @Test + @SmallTest + @Feature({"RenderTest", "Messages"}) + public void testDescriptionIconWithDefaultSize() throws Exception { + Activity activity = getActivity(); + Drawable messageIcon = ApiCompatibilityUtils.getDrawable( + activity.getResources(), android.R.drawable.ic_delete); + Drawable descriptionIcon = ApiCompatibilityUtils.getDrawable( + activity.getResources(), R.drawable.ic_photo_camera_black); + PropertyModel model = + new PropertyModel.Builder(MessageBannerProperties.ALL_KEYS) + .with(MessageBannerProperties.MESSAGE_IDENTIFIER, + MessageIdentifier.TEST_MESSAGE) + .with(MessageBannerProperties.ICON, messageIcon) + .with(MessageBannerProperties.TITLE, "Primary Title") + .with(MessageBannerProperties.DESCRIPTION_ICON, descriptionIcon) + .with(MessageBannerProperties.PRIMARY_BUTTON_TEXT, "Action") + .build(); + MessageBannerView view = (MessageBannerView) LayoutInflater.from(activity).inflate( + R.layout.message_banner_view, null, false); + PropertyModelChangeProcessor.create(model, view, MessageBannerViewBinder::bind); + LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, + activity.getResources().getDimensionPixelSize(R.dimen.message_banner_height)); + + TestThreadUtils.runOnUiThreadBlocking( + () -> { getActivity().setContentView(view, params); }); + mRenderTestRule.render(view, "message_banner_description_icon_with_default_size"); + } + + @Test + @SmallTest + @Feature({"RenderTest", "Messages"}) + public void testDescriptionIconWithResizing() throws Exception { + Activity activity = getActivity(); + Drawable messageIcon = ApiCompatibilityUtils.getDrawable( + activity.getResources(), android.R.drawable.ic_delete); + Drawable descriptionIcon = ApiCompatibilityUtils.getDrawable( + activity.getResources(), R.drawable.ic_photo_camera_black); + PropertyModel model = + new PropertyModel.Builder(MessageBannerProperties.ALL_KEYS) + .with(MessageBannerProperties.MESSAGE_IDENTIFIER, + MessageIdentifier.TEST_MESSAGE) + .with(MessageBannerProperties.ICON, messageIcon) + .with(MessageBannerProperties.TITLE, "Primary Title") + .with(MessageBannerProperties.DESCRIPTION_ICON, descriptionIcon) + .with(MessageBannerProperties.RESIZE_DESCRIPTION_ICON, true) + .with(MessageBannerProperties.PRIMARY_BUTTON_TEXT, "Action") + .build(); + MessageBannerView view = (MessageBannerView) LayoutInflater.from(activity).inflate( + R.layout.message_banner_view, null, false); + PropertyModelChangeProcessor.create(model, view, MessageBannerViewBinder::bind); + LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, + activity.getResources().getDimensionPixelSize(R.dimen.message_banner_height)); + + TestThreadUtils.runOnUiThreadBlocking( + () -> { getActivity().setContentView(view, params); }); + mRenderTestRule.render(view, "message_banner_description_icon_with_resizing"); + } + + @Test + @SmallTest + @Feature({"RenderTest", "Messages"}) + public void testDescriptionIconWithText() throws Exception { + Activity activity = getActivity(); + Drawable messageIcon = ApiCompatibilityUtils.getDrawable( + activity.getResources(), android.R.drawable.ic_delete); + Drawable descriptionIcon = ApiCompatibilityUtils.getDrawable( + activity.getResources(), R.drawable.ic_photo_camera_black); + PropertyModel model = + new PropertyModel.Builder(MessageBannerProperties.ALL_KEYS) + .with(MessageBannerProperties.MESSAGE_IDENTIFIER, + MessageIdentifier.TEST_MESSAGE) + .with(MessageBannerProperties.ICON, messageIcon) + .with(MessageBannerProperties.TITLE, "Primary Title") + .with(MessageBannerProperties.DESCRIPTION_ICON, descriptionIcon) + .with(MessageBannerProperties.RESIZE_DESCRIPTION_ICON, true) + .with(MessageBannerProperties.DESCRIPTION, "Secondary Title") + .with(MessageBannerProperties.PRIMARY_BUTTON_TEXT, "Action") + .build(); + MessageBannerView view = (MessageBannerView) LayoutInflater.from(activity).inflate( + R.layout.message_banner_view, null, false); + PropertyModelChangeProcessor.create(model, view, MessageBannerViewBinder::bind); + LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, + activity.getResources().getDimensionPixelSize(R.dimen.message_banner_height)); + + TestThreadUtils.runOnUiThreadBlocking( + () -> { getActivity().setContentView(view, params); }); + mRenderTestRule.render(view, "message_banner_description_icon_with_text"); + } } \ No newline at end of file
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerView.java b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerView.java index c19da98..d0a1de3 100644 --- a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerView.java +++ b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerView.java
@@ -18,6 +18,7 @@ import androidx.annotation.ColorInt; import androidx.annotation.Nullable; +import androidx.appcompat.content.res.AppCompatResources; import androidx.core.graphics.drawable.RoundedBitmapDrawable; import org.chromium.base.ApiCompatibilityUtils; @@ -86,6 +87,8 @@ void setDescriptionIcon(Drawable drawable) { mDescription.setVisibility(drawable == null ? GONE : VISIBLE); mDescriptionDrawable = drawable; + mDescription.setDrawableTintColor(AppCompatResources.getColorStateList( + getContext(), R.color.default_icon_color_secondary_tint_list)); ((TextView) mDescription).setCompoundDrawablesRelative(drawable, null, null, null); }
diff --git a/components/omnibox/browser/history_fuzzy_provider.cc b/components/omnibox/browser/history_fuzzy_provider.cc index 16a30ac374..f6c46ab 100644 --- a/components/omnibox/browser/history_fuzzy_provider.cc +++ b/components/omnibox/browser/history_fuzzy_provider.cc
@@ -66,7 +66,12 @@ struct Node { int relevance = 0; - std::unordered_map<char16_t, std::reference_wrapper<Node>> next; + + // Note: Some C++ implementations of unordered_map support using the + // containing struct (Node) as element type, but some do not. To avoid + // potential build issues in downstream projects that use Chromium code, + // ensure the element type is of known size (a fully declared type). + std::unordered_map<char16_t, std::unique_ptr<Node>> next; void Insert(const std::u16string& text, size_t from) { if (from >= text.length()) { @@ -74,17 +79,11 @@ return; } char16_t c = text[from]; - Node& node = next.at(c); - node.Insert(text, from + 1); - } - - int Walk(const std::u16string& text, size_t from) const { - if (from >= text.length()) { - return relevance; + std::unique_ptr<Node>& node = next[c]; + if (!node) { + node = std::make_unique<Node>(); } - char16_t c = text[from]; - const Node& node = next.at(c); - return node.Walk(text, from + 1); + node->Insert(text, from + 1); } // Produce corrections necessary to get `text` back on trie. Each correction @@ -114,7 +113,7 @@ // insertion, not only replacement. Change `from` parameter and modify // correction accordingly. std::vector<Correction> subcorrections; - bool found = entry.second.get().FindCorrections( + bool found = entry.second->FindCorrections( text, from + 1, tolerance - 1, subcorrections); if (found) { // Remaining input without further correction is on trie. @@ -135,8 +134,8 @@ return false; } else { // Found; proceed with tolerance. - return it->second.get().FindCorrections(text, from + 1, tolerance, - corrections); + return it->second->FindCorrections(text, from + 1, tolerance, + corrections); } } @@ -148,7 +147,7 @@ DVLOG(1) << " <" << built << ">"; } for (const auto& entry : next) { - entry.second.get().Log(built + entry.first); + entry.second->Log(built + entry.first); } } };
diff --git a/components/optimization_guide/content/browser/page_content_annotations_service.cc b/components/optimization_guide/content/browser/page_content_annotations_service.cc index 95d210bb..b82a8ad 100644 --- a/components/optimization_guide/content/browser/page_content_annotations_service.cc +++ b/components/optimization_guide/content/browser/page_content_annotations_service.cc
@@ -117,6 +117,10 @@ } if (features::BatchAnnotationsValidationEnabled()) { + // Normally the caller would do this, but we are our own caller. + RequestAndNotifyWhenModelAvailable(AnnotationType::kContentVisibility, + base::DoNothing()); + validation_timer_ = std::make_unique<base::OneShotTimer>( base::DefaultTickClock::GetInstance()); validation_timer_->Start(
diff --git a/components/security_interstitials/content/DEPS b/components/security_interstitials/content/DEPS index 640379c..bab5c54 100644 --- a/components/security_interstitials/content/DEPS +++ b/components/security_interstitials/content/DEPS
@@ -24,6 +24,7 @@ "+services/cert_verifier/public/mojom/trial_comparison_cert_verifier.mojom.h", "+services/network/public", "+services/network/test", + "+third_party/blink/public/common/features.h", "+third_party/blink/public/mojom", "+third_party/protobuf", "+third_party/re2",
diff --git a/components/security_interstitials/content/security_interstitial_tab_helper_unittest.cc b/components/security_interstitials/content/security_interstitial_tab_helper_unittest.cc index c6db9a0c..40b4a146 100644 --- a/components/security_interstitials/content/security_interstitial_tab_helper_unittest.cc +++ b/components/security_interstitials/content/security_interstitial_tab_helper_unittest.cc
@@ -9,6 +9,7 @@ #include "base/bind.h" #include "base/i18n/rtl.h" #include "base/memory/raw_ptr.h" +#include "base/test/scoped_feature_list.h" #include "base/time/time.h" #include "components/security_interstitials/content/security_interstitial_controller_client.h" #include "components/security_interstitials/content/security_interstitial_page.h" @@ -16,11 +17,14 @@ #include "components/security_interstitials/core/controller_client.h" #include "components/security_interstitials/core/metrics_helper.h" #include "content/public/browser/certificate_request_result_type.h" +#include "content/public/browser/render_frame_host.h" #include "content/public/test/mock_navigation_handle.h" +#include "content/public/test/navigation_simulator.h" #include "content/public/test/test_renderer_host.h" #include "net/base/net_errors.h" #include "net/test/cert_test_util.h" #include "net/test/test_data_directory.h" +#include "third_party/blink/public/common/features.h" #include "url/gurl.h" namespace security_interstitials { @@ -215,4 +219,60 @@ EXPECT_TRUE(committed_blocking_page_destroyed); } +class SecurityInterstitialTabHelperFencedFrameTest + : public SecurityInterstitialTabHelperTest { + public: + SecurityInterstitialTabHelperFencedFrameTest() { + scoped_feature_list_.InitAndEnableFeatureWithParameters( + blink::features::kFencedFrames, {{"implementation_type", "mparch"}}); + } + ~SecurityInterstitialTabHelperFencedFrameTest() override = default; + + content::RenderFrameHost* CreateFencedFrame( + content::RenderFrameHost* parent) { + content::RenderFrameHost* fenced_frame = + content::RenderFrameHostTester::For(parent)->AppendFencedFrame(); + return fenced_frame; + } + + private: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +// Tests that a FencedFrame does not close the interstitial page. +TEST_F(SecurityInterstitialTabHelperFencedFrameTest, + FencedFrameDoesNotCloseInterstitialPage) { + std::unique_ptr<content::NavigationHandle> blocking_page_handle = + CreateHandle(true, false); + bool blocking_page_destroyed = false; + CreateAssociatedBlockingPage(blocking_page_handle.get(), + &blocking_page_destroyed); + SecurityInterstitialTabHelper* helper = + SecurityInterstitialTabHelper::FromWebContents(web_contents()); + helper->DidFinishNavigation(blocking_page_handle.get()); + EXPECT_FALSE(blocking_page_destroyed); + EXPECT_TRUE(helper->IsDisplayingInterstitial()); + + // Navigate a fenced frame and the interstitial page should be kept visible on + // the fenced frame. + GURL fenced_frame_url = GURL("https://fencedframe.com"); + content::RenderFrameHostTester::For(main_rfh()) + ->InitializeRenderFrameIfNeeded(); + content::RenderFrameHost* fenced_frame_rfh = CreateFencedFrame(main_rfh()); + std::unique_ptr<content::NavigationSimulator> navigation_simulator = + content::NavigationSimulator::CreateForFencedFrame(fenced_frame_url, + fenced_frame_rfh); + navigation_simulator->Commit(); + EXPECT_TRUE(fenced_frame_rfh->IsFencedFrameRoot()); + EXPECT_FALSE(blocking_page_destroyed); + EXPECT_TRUE(helper->IsDisplayingInterstitial()); + + // When a navigation does commit, the fenced frame one should be cleaned up. + std::unique_ptr<content::NavigationHandle> next_committed_handle = + CreateHandle(true, false); + helper->DidFinishNavigation(next_committed_handle.get()); + EXPECT_TRUE(blocking_page_destroyed); + EXPECT_FALSE(helper->IsDisplayingInterstitial()); +} + } // namespace security_interstitials
diff --git a/components/segmentation_platform/components_unittests.filter b/components/segmentation_platform/components_unittests.filter index 776a40f..36bd497 100644 --- a/components/segmentation_platform/components_unittests.filter +++ b/components/segmentation_platform/components_unittests.filter
@@ -26,6 +26,7 @@ SignalKeyTest.* SignalStorageConfigTest.* StatsTest.* +TrainingDataCollectorTest.* UkmConfigTest.* UrlSignalHandlerTest.* UserActionSignalHandlerTest.*
diff --git a/components/segmentation_platform/internal/BUILD.gn b/components/segmentation_platform/internal/BUILD.gn index 47279254..54d9403 100644 --- a/components/segmentation_platform/internal/BUILD.gn +++ b/components/segmentation_platform/internal/BUILD.gn
@@ -169,6 +169,8 @@ "execution/feature_list_query_processor_unittest.cc", "execution/mock_feature_aggregator.cc", "execution/mock_feature_aggregator.h", + "execution/mock_feature_list_query_processor.cc", + "execution/mock_feature_list_query_processor.h", "execution/model_execution_manager_factory_unittest.cc", "mock_ukm_data_manager.cc", "mock_ukm_data_manager.h", @@ -181,6 +183,8 @@ "service_proxy_impl_unittest.cc", "signals/histogram_signal_handler_unittest.cc", "signals/history_delegate_impl_unittest.cc", + "signals/mock_histogram_signal_handler.cc", + "signals/mock_histogram_signal_handler.h", "signals/signal_filter_processor_unittest.cc", "signals/ukm_config_unittest.cc", "signals/url_signal_handler_unittest.cc",
diff --git a/components/segmentation_platform/internal/data_collection/training_data_collector.cc b/components/segmentation_platform/internal/data_collection/training_data_collector.cc index 27822a2..e34fc6a 100644 --- a/components/segmentation_platform/internal/data_collection/training_data_collector.cc +++ b/components/segmentation_platform/internal/data_collection/training_data_collector.cc
@@ -10,10 +10,18 @@ namespace segmentation_platform { TrainingDataCollector::TrainingDataCollector( - FeatureListQueryProcessor* processor) - : feature_list_query_processor_(processor) {} + FeatureListQueryProcessor* processor, + HistogramSignalHandler* histogram_signal_handler) + : feature_list_query_processor_(processor), + histogram_signal_handler_(histogram_signal_handler) { + DCHECK(histogram_signal_handler_); + histogram_signal_handler_->AddObserver(this); +} -TrainingDataCollector::~TrainingDataCollector() = default; +TrainingDataCollector::~TrainingDataCollector() { + DCHECK(histogram_signal_handler_); + histogram_signal_handler_->RemoveObserver(this); +} void TrainingDataCollector::OnModelMetadataUpdated() { NOTIMPLEMENTED(); @@ -23,4 +31,12 @@ NOTIMPLEMENTED(); } +void TrainingDataCollector::OnHistogramSignalUpdated( + const std::string& histogram_name, + base::HistogramBase::Sample) { + // TODO(xingliu): Check whether the histogram needs to trigger a data + // collection, and report to UKM. + NOTIMPLEMENTED(); +} + } // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/data_collection/training_data_collector.h b/components/segmentation_platform/internal/data_collection/training_data_collector.h index 30d0260..0b3cb14b 100644 --- a/components/segmentation_platform/internal/data_collection/training_data_collector.h +++ b/components/segmentation_platform/internal/data_collection/training_data_collector.h
@@ -6,18 +6,22 @@ #define COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_DATA_COLLECTION_TRAINING_DATA_COLLECTOR_H_ #include "base/memory/raw_ptr.h" +#include "base/metrics/histogram_base.h" +#include "components/segmentation_platform/internal/signals/histogram_signal_handler.h" namespace segmentation_platform { class FeatureListQueryProcessor; +class HistogramSignalHandler; // Collect training data and report as Ukm message. Live on main thread. // TODO(xingliu): Make a new class that owns the training data collector and // model execution collector. -class TrainingDataCollector { +class TrainingDataCollector : public HistogramSignalHandler::Observer { public: - explicit TrainingDataCollector(FeatureListQueryProcessor* processor); - ~TrainingDataCollector(); + TrainingDataCollector(FeatureListQueryProcessor* processor, + HistogramSignalHandler* histogram_signal_handler); + ~TrainingDataCollector() override; // Disallow copy/assign. TrainingDataCollector(const TrainingDataCollector&) = delete; @@ -31,8 +35,13 @@ // to Ukm that has a non-zero |duration| field in |UMAOutput|. void OnServiceInitialized(); + // HistogramSignalHandler::Observer overrides. + void OnHistogramSignalUpdated(const std::string& histogram_name, + base::HistogramBase::Sample) override; + private: raw_ptr<FeatureListQueryProcessor> feature_list_query_processor_; + raw_ptr<HistogramSignalHandler> histogram_signal_handler_; }; } // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/data_collection/training_data_collector_unittest.cc b/components/segmentation_platform/internal/data_collection/training_data_collector_unittest.cc new file mode 100644 index 0000000..0c792a92 --- /dev/null +++ b/components/segmentation_platform/internal/data_collection/training_data_collector_unittest.cc
@@ -0,0 +1,41 @@ +// Copyright 2022 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/segmentation_platform/internal/data_collection/training_data_collector.h" + +#include "components/segmentation_platform/internal/execution/mock_feature_list_query_processor.h" +#include "components/segmentation_platform/internal/signals/mock_histogram_signal_handler.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace segmentation_platform { +namespace { + +class TrainingDataCollectorTest : public ::testing::Test { + public: + TrainingDataCollectorTest() = default; + ~TrainingDataCollectorTest() override = default; + + void SetUp() override { + collector_ = std::make_unique<TrainingDataCollector>( + &feature_list_processor_, &histogram_signal_handler_); + } + + protected: + TrainingDataCollector* collector() { return collector_.get(); } + + private: + MockFeatureListQueryProcessor feature_list_processor_; + MockHistogramSignalHandler histogram_signal_handler_; + std::unique_ptr<TrainingDataCollector> collector_; +}; + +// Place holder test case that will be replaced to test real implementation +// logic. +TEST_F(TrainingDataCollectorTest, Construction) { + // TODO(xingliu): Remove this once read test cases are added. + EXPECT_NE(nullptr, collector()); +} + +} // namespace +} // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/execution/feature_list_query_processor.h b/components/segmentation_platform/internal/execution/feature_list_query_processor.h index 1606552..d1cfc2d 100644 --- a/components/segmentation_platform/internal/execution/feature_list_query_processor.h +++ b/components/segmentation_platform/internal/execution/feature_list_query_processor.h
@@ -44,7 +44,7 @@ // |segment_id| is only used for recording performance metrics. This class // does not need to know about the segment itself. |prediction_time| is the // time at which we predict the model execution should happen. - void ProcessFeatureList( + virtual void ProcessFeatureList( const proto::SegmentationModelMetadata& model_metadata, OptimizationTarget segment_id, base::Time prediction_time,
diff --git a/components/segmentation_platform/internal/execution/mock_feature_list_query_processor.cc b/components/segmentation_platform/internal/execution/mock_feature_list_query_processor.cc new file mode 100644 index 0000000..69673a8 --- /dev/null +++ b/components/segmentation_platform/internal/execution/mock_feature_list_query_processor.cc
@@ -0,0 +1,14 @@ +// Copyright 2022 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/segmentation_platform/internal/execution/mock_feature_list_query_processor.h" + +namespace segmentation_platform { + +MockFeatureListQueryProcessor::MockFeatureListQueryProcessor() + : FeatureListQueryProcessor(nullptr, nullptr) {} + +MockFeatureListQueryProcessor::~MockFeatureListQueryProcessor() = default; + +} // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/execution/mock_feature_list_query_processor.h b/components/segmentation_platform/internal/execution/mock_feature_list_query_processor.h new file mode 100644 index 0000000..c969bcd --- /dev/null +++ b/components/segmentation_platform/internal/execution/mock_feature_list_query_processor.h
@@ -0,0 +1,30 @@ +// Copyright 2022 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_SEGMENTATION_PLATFORM_INTERNAL_EXECUTION_MOCK_FEATURE_LIST_QUERY_PROCESSOR_H_ +#define COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_EXECUTION_MOCK_FEATURE_LIST_QUERY_PROCESSOR_H_ + +#include "components/segmentation_platform/internal/execution/feature_list_query_processor.h" + +#include "components/segmentation_platform/internal/execution/feature_aggregator.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace segmentation_platform { + +class MockFeatureListQueryProcessor : public FeatureListQueryProcessor { + public: + MockFeatureListQueryProcessor(); + ~MockFeatureListQueryProcessor() override; + MOCK_METHOD(void, + ProcessFeatureList, + (const proto::SegmentationModelMetadata&, + OptimizationTarget, + base::Time, + FeatureProcessorCallback), + (override)); +}; + +} // namespace segmentation_platform + +#endif // COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_EXECUTION_MOCK_FEATURE_LIST_QUERY_PROCESSOR_H_
diff --git a/components/segmentation_platform/internal/segmentation_platform_service_impl.cc b/components/segmentation_platform/internal/segmentation_platform_service_impl.cc index 49b8790..b177f83 100644 --- a/components/segmentation_platform/internal/segmentation_platform_service_impl.cc +++ b/components/segmentation_platform/internal/segmentation_platform_service_impl.cc
@@ -230,7 +230,7 @@ signal_database_.get(), std::make_unique<FeatureAggregatorImpl>()); training_data_collector_ = std::make_unique<TrainingDataCollector>( - feature_list_query_processor_.get()); + feature_list_query_processor_.get(), histogram_signal_handler_.get()); model_execution_manager_ = CreateModelExecutionManager( model_provider_, task_runner_, all_segment_ids_, clock_,
diff --git a/components/segmentation_platform/internal/signals/histogram_signal_handler.cc b/components/segmentation_platform/internal/signals/histogram_signal_handler.cc index f2adb38..3427d5a 100644 --- a/components/segmentation_platform/internal/signals/histogram_signal_handler.cc +++ b/components/segmentation_platform/internal/signals/histogram_signal_handler.cc
@@ -17,7 +17,7 @@ HistogramSignalHandler::~HistogramSignalHandler() = default; void HistogramSignalHandler::SetRelevantHistograms( - const std::set<std::pair<std::string, proto::SignalType>>& histograms) { + const RelevantHistograms& histograms) { histogram_observers_.clear(); for (const auto& pair : histograms) { const auto& histogram_name = pair.first; @@ -49,7 +49,7 @@ db_->WriteSample(signal_type, name_hash, sample, base::BindOnce(&HistogramSignalHandler::OnSampleWritten, weak_ptr_factory_.GetWeakPtr(), - std::string(histogram_name))); + std::string(histogram_name), sample)); } void HistogramSignalHandler::AddObserver(Observer* observer) { @@ -61,12 +61,13 @@ } void HistogramSignalHandler::OnSampleWritten(const std::string& histogram_name, + base::HistogramBase::Sample sample, bool success) { if (!success) return; for (Observer& ob : observers_) - ob.OnHistogramSignalUpdated(histogram_name); + ob.OnHistogramSignalUpdated(histogram_name, sample); } } // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/signals/histogram_signal_handler.h b/components/segmentation_platform/internal/signals/histogram_signal_handler.h index a704990..a0ed73d 100644 --- a/components/segmentation_platform/internal/signals/histogram_signal_handler.h +++ b/components/segmentation_platform/internal/signals/histogram_signal_handler.h
@@ -27,12 +27,15 @@ // persisting them to the internal database for future processing. class HistogramSignalHandler { public: + using RelevantHistograms = + std::set<std::pair<std::string, proto::SignalType>>; + class Observer : public base::CheckedObserver { public: // Called when a histogram signal tracked by segmentation platform is // updated and written to database. - virtual void OnHistogramSignalUpdated( - const std::string& histogram_name) = 0; + virtual void OnHistogramSignalUpdated(const std::string& histogram_name, + base::HistogramBase::Sample) = 0; ~Observer() override = default; protected: @@ -48,8 +51,7 @@ // Called to notify about a set of histograms which the segmentation models // care about. - virtual void SetRelevantHistograms( - const std::set<std::pair<std::string, proto::SignalType>>& histograms); + virtual void SetRelevantHistograms(const RelevantHistograms& histograms); // Called to enable or disable metrics collection for segmentation platform. virtual void EnableMetrics(bool enable_metrics); @@ -64,7 +66,9 @@ uint64_t name_hash, base::HistogramBase::Sample sample); - void OnSampleWritten(const std::string& histogram_name, bool success); + void OnSampleWritten(const std::string& histogram_name, + base::HistogramBase::Sample sample, + bool success); // The database storing relevant histogram samples. raw_ptr<SignalDatabase> db_;
diff --git a/components/segmentation_platform/internal/signals/histogram_signal_handler_unittest.cc b/components/segmentation_platform/internal/signals/histogram_signal_handler_unittest.cc index a6bc8ae..7fde3bc 100644 --- a/components/segmentation_platform/internal/signals/histogram_signal_handler_unittest.cc +++ b/components/segmentation_platform/internal/signals/histogram_signal_handler_unittest.cc
@@ -28,7 +28,10 @@ public: MockObserver() = default; ~MockObserver() override = default; - MOCK_METHOD(void, OnHistogramSignalUpdated, (const std::string&), (override)); + MOCK_METHOD(void, + OnHistogramSignalUpdated, + (const std::string&, base::HistogramBase::Sample), + (override)); }; class HistogramSignalHandlerTest : public testing::Test { @@ -124,7 +127,7 @@ std::move(callback).Run(true); }))); EXPECT_CALL(observer_, - OnHistogramSignalUpdated(std::string(kExpectedHistogram))); + OnHistogramSignalUpdated(std::string(kExpectedHistogram), Eq(1))); // Record a registered histogram sample. |observer_| should be notified. UMA_HISTOGRAM_BOOLEAN(kExpectedHistogram, true);
diff --git a/components/segmentation_platform/internal/signals/mock_histogram_signal_handler.cc b/components/segmentation_platform/internal/signals/mock_histogram_signal_handler.cc new file mode 100644 index 0000000..7c6c1f7c --- /dev/null +++ b/components/segmentation_platform/internal/signals/mock_histogram_signal_handler.cc
@@ -0,0 +1,14 @@ +// Copyright 2022 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/segmentation_platform/internal/signals/mock_histogram_signal_handler.h" + +namespace segmentation_platform { + +MockHistogramSignalHandler::MockHistogramSignalHandler() + : HistogramSignalHandler(nullptr) {} + +MockHistogramSignalHandler::~MockHistogramSignalHandler() = default; + +} // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/signals/mock_histogram_signal_handler.h b/components/segmentation_platform/internal/signals/mock_histogram_signal_handler.h new file mode 100644 index 0000000..7ad0619 --- /dev/null +++ b/components/segmentation_platform/internal/signals/mock_histogram_signal_handler.h
@@ -0,0 +1,26 @@ +// Copyright 2022 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_SEGMENTATION_PLATFORM_INTERNAL_SIGNALS_MOCK_HISTOGRAM_SIGNAL_HANDLER_H_ +#define COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_SIGNALS_MOCK_HISTOGRAM_SIGNAL_HANDLER_H_ + +#include "components/segmentation_platform/internal/signals/histogram_signal_handler.h" + +#include "components/segmentation_platform/internal/proto/types.pb.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace segmentation_platform { + +class MockHistogramSignalHandler : public HistogramSignalHandler { + public: + MockHistogramSignalHandler(); + ~MockHistogramSignalHandler() override; + + MOCK_METHOD(void, SetRelevantHistograms, (const RelevantHistograms&)); + MOCK_METHOD(void, EnableMetrics, (bool)); +}; + +} // namespace segmentation_platform + +#endif // COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_SIGNALS_MOCK_HISTOGRAM_SIGNAL_HANDLER_H_
diff --git a/components/segmentation_platform/internal/signals/signal_filter_processor_unittest.cc b/components/segmentation_platform/internal/signals/signal_filter_processor_unittest.cc index a2316245..d7d0051 100644 --- a/components/segmentation_platform/internal/signals/signal_filter_processor_unittest.cc +++ b/components/segmentation_platform/internal/signals/signal_filter_processor_unittest.cc
@@ -13,6 +13,7 @@ #include "components/segmentation_platform/internal/proto/aggregation.pb.h" #include "components/segmentation_platform/internal/proto/types.pb.h" #include "components/segmentation_platform/internal/signals/histogram_signal_handler.h" +#include "components/segmentation_platform/internal/signals/mock_histogram_signal_handler.h" #include "components/segmentation_platform/internal/signals/user_action_signal_handler.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -41,15 +42,6 @@ MOCK_METHOD(void, EnableMetrics, (bool)); }; -class MockHistogramSignalHandler : public HistogramSignalHandler { - public: - MockHistogramSignalHandler() : HistogramSignalHandler(nullptr) {} - using HistogramAndSignalTypeSet = - const std::set<std::pair<std::string, proto::SignalType>>&; - MOCK_METHOD(void, SetRelevantHistograms, (HistogramAndSignalTypeSet)); - MOCK_METHOD(void, EnableMetrics, (bool)); -}; - class SignalFilterProcessorTest : public testing::Test { public: SignalFilterProcessorTest() = default;
diff --git a/components/services/app_service/public/cpp/app_update.cc b/components/services/app_service/public/cpp/app_update.cc index 4b3c3c24..5e384c36 100644 --- a/components/services/app_service/public/cpp/app_update.cc +++ b/components/services/app_service/public/cpp/app_update.cc
@@ -299,18 +299,19 @@ return apps::mojom::Readiness::kUnknown; } -apps::mojom::Readiness AppUpdate::PriorReadiness() const { - return mojom_state_ ? mojom_state_->readiness - : apps::mojom::Readiness::kUnknown; +apps::Readiness AppUpdate::PriorReadiness() const { + if (base::FeatureList::IsEnabled(kAppServiceOnAppUpdateWithoutMojom)) { + return state_ ? state_->readiness : apps::Readiness::kUnknown; + } + + return ConvertMojomReadinessToReadiness( + mojom_state_ ? mojom_state_->readiness + : apps::mojom::Readiness::kUnknown); } apps::Readiness AppUpdate::GetReadiness() const { GET_VALUE_WITH_DEFAULT_VALUE(readiness, apps::Readiness::kUnknown)} -apps::Readiness AppUpdate::GetPriorReadiness() const { - return state_ ? state_->readiness : apps::Readiness::kUnknown; -} - bool AppUpdate::ReadinessChanged() const { if (base::FeatureList::IsEnabled(kAppServiceOnAppUpdateWithoutMojom)) { IS_VALUE_CHANGED_WITH_DEFAULT_VALUE(readiness, Readiness::kUnknown)
diff --git a/components/services/app_service/public/cpp/app_update.h b/components/services/app_service/public/cpp/app_update.h index bc7e9db..aacb9be 100644 --- a/components/services/app_service/public/cpp/app_update.h +++ b/components/services/app_service/public/cpp/app_update.h
@@ -82,9 +82,8 @@ const std::string& GetAppId() const; apps::mojom::Readiness Readiness() const; - apps::mojom::Readiness PriorReadiness() const; + apps::Readiness PriorReadiness() const; apps::Readiness GetReadiness() const; - apps::Readiness GetPriorReadiness() const; bool ReadinessChanged() const; const std::string& Name() const;
diff --git a/components/services/app_service/public/cpp/app_update_mojom_unittest.cc b/components/services/app_service/public/cpp/app_update_mojom_unittest.cc index ce12093b..c370e649 100644 --- a/components/services/app_service/public/cpp/app_update_mojom_unittest.cc +++ b/components/services/app_service/public/cpp/app_update_mojom_unittest.cc
@@ -27,7 +27,7 @@ } apps::mojom::Readiness expect_readiness_; - apps::mojom::Readiness expect_prior_readiness_; + apps::Readiness expect_prior_readiness_; bool expect_readiness_changed_; std::string expect_name_; @@ -259,7 +259,7 @@ EXPECT_EQ(state == nullptr, u.StateIsNull()); expect_readiness_ = apps::mojom::Readiness::kUnknown; - expect_prior_readiness_ = apps::mojom::Readiness::kUnknown; + expect_prior_readiness_ = apps::Readiness::kUnknown; expect_name_ = ""; expect_short_name_ = ""; expect_publisher_id_ = ""; @@ -319,7 +319,8 @@ if (state) { apps::AppUpdate::Merge(state, delta); - expect_prior_readiness_ = state->readiness; + expect_prior_readiness_ = + apps::ConvertMojomReadinessToReadiness(state->readiness); ExpectNoChange(); CheckExpects(u); } @@ -352,7 +353,8 @@ if (state) { apps::AppUpdate::Merge(state, delta); - expect_prior_readiness_ = state->readiness; + expect_prior_readiness_ = + apps::ConvertMojomReadinessToReadiness(state->readiness); ExpectNoChange(); CheckExpects(u); }
diff --git a/components/services/app_service/public/cpp/app_update_unittest.cc b/components/services/app_service/public/cpp/app_update_unittest.cc index 5b26894..2a827437 100644 --- a/components/services/app_service/public/cpp/app_update_unittest.cc +++ b/components/services/app_service/public/cpp/app_update_unittest.cc
@@ -169,7 +169,7 @@ void CheckExpects(const AppUpdate& u) { EXPECT_EQ(expect_readiness_, u.GetReadiness()); - EXPECT_EQ(expect_prior_readiness_, u.GetPriorReadiness()); + EXPECT_EQ(expect_prior_readiness_, u.PriorReadiness()); EXPECT_EQ(expect_readiness_changed_, u.ReadinessChanged()); EXPECT_EQ(expect_name_, u.Name());
diff --git a/components/viz/service/display/surface_aggregator.cc b/components/viz/service/display/surface_aggregator.cc index ef0eb39..18399989 100644 --- a/components/viz/service/display/surface_aggregator.cc +++ b/components/viz/service/display/surface_aggregator.cc
@@ -1060,9 +1060,8 @@ render_pass_id_generator_.GenerateNextId(); } - AddRenderPassHelper(output_rect, color_conversion_render_pass_id_, - output_rect, root_render_pass->damage_rect, - root_content_color_usage_, + AddRenderPassHelper(color_conversion_render_pass_id_, output_rect, + root_render_pass->damage_rect, root_content_color_usage_, root_render_pass->has_transparent_background, /*pass_is_color_conversion_pass=*/true, /*quad_state_to_target_transform=*/gfx::Transform(), @@ -1119,7 +1118,7 @@ bool has_transparent_background = root_render_pass->has_transparent_background; root_render_pass->has_transparent_background = true; - AddRenderPassHelper(output_rect, readback_render_pass_id_, output_rect, + AddRenderPassHelper(readback_render_pass_id_, output_rect, root_render_pass->damage_rect, root_content_color_usage_, has_transparent_background, /*pass_is_color_conversion_pass=*/false, @@ -1149,7 +1148,7 @@ } AddRenderPassHelper( - root_render_pass->output_rect, display_transform_render_pass_id_, + display_transform_render_pass_id_, cc::MathUtil::MapEnclosedRectWith2dAxisAlignedTransform( root_surface_transform_, root_render_pass->output_rect), cc::MathUtil::MapEnclosedRectWith2dAxisAlignedTransform( @@ -1161,10 +1160,9 @@ } void SurfaceAggregator::AddRenderPassHelper( - const gfx::Rect& output_rect, AggregatedRenderPassId render_pass_id, const gfx::Rect& render_pass_output_rect, - const gfx::Rect& pass_damage_rect, + const gfx::Rect& render_pass_damage_rect, gfx::ContentColorUsage pass_color_usage, bool pass_has_transparent_background, bool pass_is_color_conversion_pass, @@ -1172,9 +1170,11 @@ bool quad_state_contents_opaque, SkBlendMode quad_state_blend_mode, AggregatedRenderPassId quad_pass_id) { + gfx::Rect current_output_rect = dest_pass_list_->back()->output_rect; + auto render_pass = std::make_unique<AggregatedRenderPass>(1, 1); - render_pass->SetAll(render_pass_id, output_rect, pass_damage_rect, - gfx::Transform(), + render_pass->SetAll(render_pass_id, render_pass_output_rect, + render_pass_damage_rect, gfx::Transform(), /*filters=*/cc::FilterOperations(), /*backdrop_filters=*/cc::FilterOperations(), /*backdrop_filter_bounds=*/gfx::RRectF(), @@ -1187,16 +1187,16 @@ auto* shared_quad_state = render_pass->CreateAndAppendSharedQuadState(); shared_quad_state->SetAll( quad_state_to_target_transform, - /*layer_rect=*/output_rect, - /*visible_layer_rect=*/output_rect, gfx::MaskFilterInfo(), + /*layer_rect=*/current_output_rect, + /*visible_layer_rect=*/current_output_rect, gfx::MaskFilterInfo(), /*clip=*/absl::nullopt, quad_state_contents_opaque, /*opacity_f=*/1.f, quad_state_blend_mode, /*sorting_context=*/0); auto* quad = render_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>(); - quad->SetNew(shared_quad_state, output_rect, output_rect, quad_pass_id, - kInvalidResourceId, gfx::RectF(), gfx::Size(), gfx::Vector2dF(), - gfx::PointF(), gfx::RectF(output_rect), + quad->SetNew(shared_quad_state, current_output_rect, current_output_rect, + quad_pass_id, kInvalidResourceId, gfx::RectF(), gfx::Size(), + gfx::Vector2dF(), gfx::PointF(), gfx::RectF(current_output_rect), /*force_anti_aliasing_off=*/false, /*backdrop_filter_quality*/ 1.0f); dest_pass_list_->push_back(std::move(render_pass));
diff --git a/components/viz/service/display/surface_aggregator.h b/components/viz/service/display/surface_aggregator.h index fc48ff7..4ca7c473 100644 --- a/components/viz/service/display/surface_aggregator.h +++ b/components/viz/service/display/surface_aggregator.h
@@ -256,10 +256,9 @@ void AddColorConversionPass(); void AddRootReadbackPass(); void AddDisplayTransformPass(); - void AddRenderPassHelper(const gfx::Rect& output_rect, - AggregatedRenderPassId render_pass_id, + void AddRenderPassHelper(AggregatedRenderPassId render_pass_id, const gfx::Rect& render_pass_output_rect, - const gfx::Rect& pass_damage_rect, + const gfx::Rect& render_pass_damage_rect, gfx::ContentColorUsage pass_color_usage, bool pass_has_transparent_background, bool pass_is_color_conversion_pass,
diff --git a/components/webapps/browser/android/BUILD.gn b/components/webapps/browser/android/BUILD.gn index 094d561..53ce5c93 100644 --- a/components/webapps/browser/android/BUILD.gn +++ b/components/webapps/browser/android/BUILD.gn
@@ -74,18 +74,23 @@ android_resources("java_resources") { sources = [ "java/res/drawable-hdpi/google_play.png", + "java/res/drawable-hdpi/google_play_dark.png", "java/res/drawable-hdpi/star_gray.png", "java/res/drawable-hdpi/star_green.png", "java/res/drawable-mdpi/google_play.png", + "java/res/drawable-mdpi/google_play_dark.png", "java/res/drawable-mdpi/star_gray.png", "java/res/drawable-mdpi/star_green.png", "java/res/drawable-xhdpi/google_play.png", + "java/res/drawable-xhdpi/google_play_dark.png", "java/res/drawable-xhdpi/star_gray.png", "java/res/drawable-xhdpi/star_green.png", "java/res/drawable-xxhdpi/google_play.png", + "java/res/drawable-xxhdpi/google_play_dark.png", "java/res/drawable-xxhdpi/star_gray.png", "java/res/drawable-xxhdpi/star_green.png", "java/res/drawable-xxxhdpi/google_play.png", + "java/res/drawable-xxxhdpi/google_play_dark.png", "java/res/drawable-xxxhdpi/star_gray.png", "java/res/drawable-xxxhdpi/star_green.png", "java/res/drawable/rating_bar.xml",
diff --git a/components/webapps/browser/android/java/res/drawable-hdpi/google_play_dark.png b/components/webapps/browser/android/java/res/drawable-hdpi/google_play_dark.png new file mode 100644 index 0000000..efd3f11d --- /dev/null +++ b/components/webapps/browser/android/java/res/drawable-hdpi/google_play_dark.png Binary files differ
diff --git a/components/webapps/browser/android/java/res/drawable-mdpi/google_play_dark.png b/components/webapps/browser/android/java/res/drawable-mdpi/google_play_dark.png new file mode 100644 index 0000000..1cecf5e --- /dev/null +++ b/components/webapps/browser/android/java/res/drawable-mdpi/google_play_dark.png Binary files differ
diff --git a/components/webapps/browser/android/java/res/drawable-xhdpi/google_play_dark.png b/components/webapps/browser/android/java/res/drawable-xhdpi/google_play_dark.png new file mode 100644 index 0000000..9a5d27d --- /dev/null +++ b/components/webapps/browser/android/java/res/drawable-xhdpi/google_play_dark.png Binary files differ
diff --git a/components/webapps/browser/android/java/res/drawable-xxhdpi/google_play_dark.png b/components/webapps/browser/android/java/res/drawable-xxhdpi/google_play_dark.png new file mode 100644 index 0000000..d48e6bb --- /dev/null +++ b/components/webapps/browser/android/java/res/drawable-xxhdpi/google_play_dark.png Binary files differ
diff --git a/components/webapps/browser/android/java/res/drawable-xxxhdpi/google_play_dark.png b/components/webapps/browser/android/java/res/drawable-xxxhdpi/google_play_dark.png new file mode 100644 index 0000000..12f11c9 --- /dev/null +++ b/components/webapps/browser/android/java/res/drawable-xxxhdpi/google_play_dark.png Binary files differ
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn index 778cc50..1571aa5f4 100644 --- a/content/browser/BUILD.gn +++ b/content/browser/BUILD.gn
@@ -411,6 +411,8 @@ "attribution_reporting/attribution_random_generator.h", "attribution_reporting/attribution_report.cc", "attribution_reporting/attribution_report.h", + "attribution_reporting/attribution_report_scheduler.cc", + "attribution_reporting/attribution_report_scheduler.h", "attribution_reporting/attribution_reporting.cc", "attribution_reporting/attribution_storage.cc", "attribution_reporting/attribution_storage.h",
diff --git a/content/browser/attribution_reporting/attribution_manager_impl.cc b/content/browser/attribution_reporting/attribution_manager_impl.cc index 8ae0b8ce..49924c8 100644 --- a/content/browser/attribution_reporting/attribution_manager_impl.cc +++ b/content/browser/attribution_reporting/attribution_manager_impl.cc
@@ -35,7 +35,6 @@ #include "content/public/browser/attribution_reporting.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/content_browser_client.h" -#include "content/public/browser/network_service_instance.h" #include "content/public/browser/storage_partition.h" #include "content/public/browser/web_contents.h" #include "content/public/common/content_client.h" @@ -121,10 +120,6 @@ time_from_conversion_to_report_send.InHours()); } -bool IsOffline() { - return content::GetNetworkConnectionTracker()->IsOffline(); -} - std::unique_ptr<AttributionStorageDelegate> MakeStorageDelegate() { bool debug_mode = base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kConversionsDebugMode); @@ -216,6 +211,9 @@ g_storage_task_runner.Get(), user_data_directory, std::move(storage_delegate))), + scheduler_(base::BindRepeating(&AttributionManagerImpl::GetReportsToSend, + base::Unretained(this)), + attribution_storage_), data_host_manager_(std::move(data_host_manager)), special_storage_policy_(std::move(special_storage_policy)), cookie_checker_(std::move(cookie_checker)), @@ -224,15 +222,9 @@ DCHECK(is_report_allowed_callback_); DCHECK(cookie_checker_); DCHECK(network_sender_); - - content::GetNetworkConnectionTracker()->AddNetworkConnectionObserver(this); - - OnConnectionChanged(network::mojom::ConnectionType::CONNECTION_UNKNOWN); } AttributionManagerImpl::~AttributionManagerImpl() { - content::GetNetworkConnectionTracker()->RemoveNetworkConnectionObserver(this); - // Browser contexts are not required to have a special storage policy. if (!special_storage_policy_ || !special_storage_policy_->HasSessionOnlyOrigins()) { @@ -289,10 +281,7 @@ for (Observer& observer : manager->observers_) observer.OnSourceHandled(source, result.status); - if (result.min_fake_report_time.has_value()) { - manager->UpdateGetReportsToSendTimer( - *result.min_fake_report_time); - } + manager->scheduler_.ScheduleSend(result.min_fake_report_time); manager->NotifySourcesChanged(); @@ -411,7 +400,7 @@ void AttributionManagerImpl::OnReportStored(CreateReportResult result) { RecordCreateReportStatus(result.status()); - UpdateGetReportsToSendTimer(result.report_time()); + scheduler_.ScheduleSend(result.report_time()); if (result.status() != AttributionTrigger::Result::kInternalError) { // Sources are changed here because storing a report can cause sources to be @@ -465,7 +454,7 @@ std::move(done).Run(); if (manager) { - manager->StartGetReportsToSendTimer(); + manager->scheduler_.Refresh(); manager->NotifySourcesChanged(); manager->NotifyReportsChanged(); } @@ -473,25 +462,6 @@ std::move(done), weak_factory_.GetWeakPtr())); } -void AttributionManagerImpl::OnConnectionChanged( - network::mojom::ConnectionType connection_type) { - if (IsOffline()) { - get_reports_to_send_timer_.Stop(); - } else { - DCHECK(!get_reports_to_send_timer_.IsRunning()); - - // Add delay to all reports that should have been sent while the browser was - // offline so they are not temporally joinable. We do this in storage to - // avoid pulling an unbounded number of reports into memory, only to - // immediately issue async storage calls to modify their report times. - attribution_storage_ - .AsyncCall(&AttributionStorage::AdjustOfflineReportTimes) - .Then( - base::BindOnce(&AttributionManagerImpl::UpdateGetReportsToSendTimer, - weak_factory_.GetWeakPtr())); - } -} - void AttributionManagerImpl::GetAndHandleReports( ReportsHandlerFunc handler_function, base::Time max_report_time, @@ -501,31 +471,7 @@ .Then(std::move(handler_function)); } -void AttributionManagerImpl::UpdateGetReportsToSendTimer( - absl::optional<base::Time> time) { - if (!time.has_value() || IsOffline()) - return; - - if (!get_reports_to_send_timer_.IsRunning() || - *time < get_reports_to_send_timer_.desired_run_time()) { - get_reports_to_send_timer_.Start(FROM_HERE, *time, this, - &AttributionManagerImpl::GetReportsToSend); - } -} - -void AttributionManagerImpl::StartGetReportsToSendTimer() { - if (IsOffline()) - return; - - attribution_storage_.AsyncCall(&AttributionStorage::GetNextReportTime) - .WithArgs(base::Time::Now()) - .Then(base::BindOnce(&AttributionManagerImpl::UpdateGetReportsToSendTimer, - weak_factory_.GetWeakPtr())); -} - void AttributionManagerImpl::GetReportsToSend() { - DCHECK(!IsOffline()); - // We only get the next report time strictly after now, because if we are // sending a report now but haven't finished doing so and it is still present // in storage, storage will return the report time for the same report. @@ -542,17 +488,17 @@ void AttributionManagerImpl::OnGetReportsToSend( std::vector<AttributionReport> reports) { - if (reports.empty() || IsOffline()) + if (reports.empty()) return; SendReports(std::move(reports), /*log_metrics=*/true, base::DoNothing()); - StartGetReportsToSendTimer(); + scheduler_.Refresh(); } void AttributionManagerImpl::OnGetReportsToSendFromWebUI( base::OnceClosure done, std::vector<AttributionReport> reports) { - if (reports.empty() || IsOffline()) { + if (reports.empty()) { std::move(done).Run(); return; } @@ -654,7 +600,7 @@ if (manager && success) { manager->MarkReportCompleted(report_id); - manager->UpdateGetReportsToSendTimer(new_report_time); + manager->scheduler_.ScheduleSend(new_report_time); manager->NotifyReportsChanged(); } },
diff --git a/content/browser/attribution_reporting/attribution_manager_impl.h b/content/browser/attribution_reporting/attribution_manager_impl.h index a5a26ef..fd1269c 100644 --- a/content/browser/attribution_reporting/attribution_manager_impl.h +++ b/content/browser/attribution_reporting/attribution_manager_impl.h
@@ -17,13 +17,12 @@ #include "base/memory/weak_ptr.h" #include "base/observer_list.h" #include "base/threading/sequence_bound.h" -#include "base/timer/wall_clock_timer.h" #include "content/browser/attribution_reporting/attribution_data_host_manager.h" #include "content/browser/attribution_reporting/attribution_manager.h" #include "content/browser/attribution_reporting/attribution_report.h" +#include "content/browser/attribution_reporting/attribution_report_scheduler.h" #include "content/browser/attribution_reporting/attribution_storage.h" #include "content/common/content_export.h" -#include "services/network/public/cpp/network_connection_tracker.h" #include "storage/browser/quota/special_storage_policy.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/abseil-cpp/absl/types/variant.h" @@ -67,9 +66,7 @@ // UI thread class that manages the lifetime of the underlying attribution // storage and coordinates sending attribution reports. Owned by the storage // partition. -class CONTENT_EXPORT AttributionManagerImpl - : public AttributionManager, - public network::NetworkConnectionTracker::NetworkConnectionObserver { +class CONTENT_EXPORT AttributionManagerImpl : public AttributionManager { public: using IsReportAllowedCallback = base::RepeatingCallback<bool(const AttributionReport&)>; @@ -135,10 +132,6 @@ std::unique_ptr<AttributionNetworkSender> network_sender, std::unique_ptr<AttributionDataHostManager> data_host_manager); - // network::NetworkConnectionTracker::NetworkConnectionObserver: - void OnConnectionChanged( - network::mojom::ConnectionType connection_type) override; - void MaybeEnqueueEvent(SourceOrTrigger event); void ProcessEvents(); void ProcessNextEvent(bool is_debug_cookie_set); @@ -154,8 +147,6 @@ base::Time max_report_time, int limit); - void UpdateGetReportsToSendTimer(absl::optional<base::Time> time); - void StartGetReportsToSendTimer(); void GetReportsToSend(); void OnGetReportsToSend(std::vector<AttributionReport> reports); @@ -196,6 +187,8 @@ base::SequenceBound<AttributionStorage> attribution_storage_; + AttributionReportScheduler scheduler_; + std::unique_ptr<AttributionDataHostManager> data_host_manager_; // Storage policy for the browser context |this| is in. May be nullptr. @@ -205,8 +198,6 @@ std::unique_ptr<AttributionNetworkSender> network_sender_; - base::WallClockTimer get_reports_to_send_timer_; - // Set of all conversion IDs that are currently being sent, deleted, or // updated. The number of concurrent conversion reports being sent at any time // is expected to be small, so a `flat_set` is used.
diff --git a/content/browser/attribution_reporting/attribution_report_scheduler.cc b/content/browser/attribution_reporting/attribution_report_scheduler.cc new file mode 100644 index 0000000..868a77c --- /dev/null +++ b/content/browser/attribution_reporting/attribution_report_scheduler.cc
@@ -0,0 +1,81 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/attribution_reporting/attribution_report_scheduler.h" + +#include <utility> + +#include "base/bind.h" +#include "base/check.h" +#include "base/time/time.h" +#include "content/browser/attribution_reporting/attribution_storage.h" +#include "content/public/browser/network_service_instance.h" + +namespace content { + +namespace { + +bool IsOffline() { + return GetNetworkConnectionTracker()->IsOffline(); +} + +} // namespace + +AttributionReportScheduler::AttributionReportScheduler( + base::RepeatingClosure send_reports, + base::SequenceBound<AttributionStorage>& attribution_storage) + : send_reports_(std::move(send_reports)), + attribution_storage_(attribution_storage) { + content::GetNetworkConnectionTracker()->AddNetworkConnectionObserver(this); + OnConnectionChanged(network::mojom::ConnectionType::CONNECTION_UNKNOWN); +} + +AttributionReportScheduler::~AttributionReportScheduler() { + content::GetNetworkConnectionTracker()->RemoveNetworkConnectionObserver(this); +} + +void AttributionReportScheduler::Refresh() { + if (IsOffline()) + return; + + attribution_storage_.AsyncCall(&AttributionStorage::GetNextReportTime) + .WithArgs(base::Time::Now()) + .Then(base::BindOnce(&AttributionReportScheduler::ScheduleSend, + weak_factory_.GetWeakPtr())); +} + +void AttributionReportScheduler::ScheduleSend(absl::optional<base::Time> time) { + if (!time.has_value() || IsOffline()) + return; + + if (!get_reports_to_send_timer_.IsRunning() || + *time < get_reports_to_send_timer_.desired_run_time()) { + get_reports_to_send_timer_.Start( + FROM_HERE, *time, this, &AttributionReportScheduler::InvokeCallback); + } +} + +void AttributionReportScheduler::InvokeCallback() { + send_reports_.Run(); +} + +void AttributionReportScheduler::OnConnectionChanged( + network::mojom::ConnectionType connection_type) { + if (IsOffline()) { + get_reports_to_send_timer_.Stop(); + } else { + DCHECK(!get_reports_to_send_timer_.IsRunning()); + + // Add delay to all reports that should have been sent while the browser was + // offline so they are not temporally joinable. We do this in storage to + // avoid pulling an unbounded number of reports into memory, only to + // immediately issue async storage calls to modify their report times. + attribution_storage_ + .AsyncCall(&AttributionStorage::AdjustOfflineReportTimes) + .Then(base::BindOnce(&AttributionReportScheduler::ScheduleSend, + weak_factory_.GetWeakPtr())); + } +} + +} // namespace content
diff --git a/content/browser/attribution_reporting/attribution_report_scheduler.h b/content/browser/attribution_reporting/attribution_report_scheduler.h new file mode 100644 index 0000000..75b82de3 --- /dev/null +++ b/content/browser/attribution_reporting/attribution_report_scheduler.h
@@ -0,0 +1,68 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_REPORT_SCHEDULER_H_ +#define CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_REPORT_SCHEDULER_H_ + +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/sequence_bound.h" +#include "base/timer/wall_clock_timer.h" +#include "services/network/public/cpp/network_connection_tracker.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace base { +class Time; +} // namespace base + +namespace content { + +class AttributionStorage; + +// This class consolidates logic regarding when to schedule the browser to send +// attribution reports. It talks directly to the `AttributionStorage` to help +// make these decisions. +// +// While the class does not make large changes to the underlying database, it +// is responsible for notifying the `AttributionStorage` when the browser comes +// back online, which mutates report times for some scheduled reports. +// +// TODO(apaseltiner): Consider making this class an observer to allow it to +// manage when to schedule things. +class AttributionReportScheduler + : public network::NetworkConnectionTracker::NetworkConnectionObserver { + public: + AttributionReportScheduler( + base::RepeatingClosure send_reports_, + base::SequenceBound<AttributionStorage>& attribution_storage); + ~AttributionReportScheduler() override; + + AttributionReportScheduler(const AttributionReportScheduler& other) = delete; + AttributionReportScheduler& operator=( + const AttributionReportScheduler& other) = delete; + AttributionReportScheduler(AttributionReportScheduler&& other) = delete; + AttributionReportScheduler& operator=(AttributionReportScheduler&& other) = + delete; + + void Refresh(); + void ScheduleSend(absl::optional<base::Time> time); + + private: + // Needed to avoid requiring `send_reports_` being safe to call after the + // manager or `this` is destroyed. + void InvokeCallback(); + + // network::NetworkConnectionTracker::NetworkConnectionObserver: + void OnConnectionChanged( + network::mojom::ConnectionType connection_type) override; + + base::RepeatingClosure send_reports_; + base::WallClockTimer get_reports_to_send_timer_; + base::SequenceBound<AttributionStorage>& attribution_storage_; + base::WeakPtrFactory<AttributionReportScheduler> weak_factory_{this}; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_REPORT_SCHEDULER_H_
diff --git a/content/browser/back_forward_cache_features_browsertest.cc b/content/browser/back_forward_cache_features_browsertest.cc index 1ac051c3..58a38d4 100644 --- a/content/browser/back_forward_cache_features_browsertest.cc +++ b/content/browser/back_forward_cache_features_browsertest.cc
@@ -3825,7 +3825,8 @@ } // TODO(https://crbug.com/1286474): This test is flaking on some Android bots. -#if BUILDFLAG(IS_ANDROID) +// TODO(crbug.com/1297406): Also flaky on Mac and Linux. +#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) #define MAYBE_PresentationConnectionClosed DISABLED_PresentationConnectionClosed #else #define MAYBE_PresentationConnectionClosed PresentationConnectionClosed
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc index 462f685d..5a2cdd90 100644 --- a/content/browser/renderer_host/render_frame_host_impl.cc +++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -6722,23 +6722,18 @@ effective_transient_activation_state, params->opener_suppressed, &no_javascript_access); - // If this frame isn't allowed to create a window, return early (before we - // consume transient user activation). - if (!can_create_window) { - std::move(callback).Run(mojom::CreateNewWindowStatus::kBlocked, nullptr); + bool was_consumed = false; + if (can_create_window) { + // Consume activation even w/o User Activation v2, to sync other renderers + // with calling renderer. + was_consumed = frame_tree_node_->UpdateUserActivationState( + blink::mojom::UserActivationUpdateType::kConsumeTransientActivation, + blink::mojom::UserActivationNotificationType::kNone); + } else { + std::move(callback).Run(mojom::CreateNewWindowStatus::kIgnore, nullptr); return; } - // Otherwise, consume user activation before we proceed. In particular, it is - // important to do this before we return from the |opener_suppressed| case - // below. - // NB: This call will consume activations in the browser and the remote frame - // proxies for this frame. The initiating renderer will consume its view of - // the activations after we return. - bool was_consumed = frame_tree_node_->UpdateUserActivationState( - blink::mojom::UserActivationUpdateType::kConsumeTransientActivation, - blink::mojom::UserActivationNotificationType::kNone); - // For Android WebView, we support a pop-up like behavior for window.open() // even if the embedding app doesn't support multiple windows. In this case, // window.open() will return "window" and navigate it to whatever URL was
diff --git a/content/browser/xr/service/browser_xr_runtime_impl.cc b/content/browser/xr/service/browser_xr_runtime_impl.cc index 9b343f6..635d405 100644 --- a/content/browser/xr/service/browser_xr_runtime_impl.cc +++ b/content/browser/xr/service/browser_xr_runtime_impl.cc
@@ -8,7 +8,10 @@ #include <memory> #include <utility> +#include "base/logging.h" #include "build/build_config.h" +#include "content/public/browser/browser_xr_runtime.h" +#include "device/vr/public/mojom/vr_service.mojom-shared.h" #if BUILDFLAG(IS_ANDROID) #include "base/android/android_hardware_buffer_compat.h" @@ -156,12 +159,21 @@ if (integration_client) { install_helper_ = integration_client->GetInstallHelper(id_); + runtime_observer_ = integration_client->CreateRuntimeObserver(); + + if (runtime_observer_) { + AddObserver(runtime_observer_.get()); + } } } BrowserXRRuntimeImpl::~BrowserXRRuntimeImpl() { DVLOG(2) << __func__ << ": id=" << id_; + if (runtime_observer_) { + RemoveObserver(runtime_observer_.get()); + } + if (install_finished_callback_) { std::move(install_finished_callback_).Run(false); } @@ -244,13 +256,21 @@ // Notify observers of the new display info. for (Observer& observer : observers_) { - observer.SetVRDisplayInfo(display_info_.Clone()); + observer.VRDisplayInfoChanged(display_info_.Clone()); } } void BrowserXRRuntimeImpl::StopImmersiveSession( VRServiceImpl::ExitPresentCallback on_exited) { DVLOG(2) << __func__; + + if (immersive_session_has_camera_access_) { + for (Observer& observer : observers_) { + observer.WebXRCameraInUseChanged(nullptr, false); + } + immersive_session_has_camera_access_ = false; + } + if (immersive_session_controller_) { immersive_session_controller_.reset(); if (presenting_service_) { @@ -259,7 +279,7 @@ } for (Observer& observer : observers_) { - observer.SetWebXRWebContents(nullptr); + observer.WebXRWebContentsChanged(nullptr); } } std::move(on_exited).Run(); @@ -318,7 +338,7 @@ bool throttled) { if (service == presenting_service_) { for (Observer& observer : observers_) { - observer.SetFramesThrottled(throttled); + observer.WebXRFramesThrottledChanged(throttled); } } } @@ -360,7 +380,16 @@ // Notify observers that we have started presentation. content::WebContents* web_contents = service->GetWebContents(); for (Observer& observer : observers_) { - observer.SetWebXRWebContents(web_contents); + observer.WebXRWebContentsChanged(web_contents); + } + + immersive_session_has_camera_access_ = + base::Contains(session_result->session->enabled_features, + device::mojom::XRSessionFeature::CAMERA_ACCESS); + if (immersive_session_has_camera_access_) { + for (Observer& observer : observers_) { + observer.WebXRCameraInUseChanged(web_contents, true); + } } } @@ -421,7 +450,7 @@ void BrowserXRRuntimeImpl::AddObserver(Observer* observer) { observers_.AddObserver(observer); - observer->SetVRDisplayInfo(display_info_.Clone()); + observer->VRDisplayInfoChanged(display_info_.Clone()); } void BrowserXRRuntimeImpl::RemoveObserver(Observer* observer) {
diff --git a/content/browser/xr/service/browser_xr_runtime_impl.h b/content/browser/xr/service/browser_xr_runtime_impl.h index f8b4c3a9..5982ad2 100644 --- a/content/browser/xr/service/browser_xr_runtime_impl.h +++ b/content/browser/xr/service/browser_xr_runtime_impl.h
@@ -121,6 +121,7 @@ mojo::Remote<device::mojom::XRRuntime> runtime_; mojo::Remote<device::mojom::XRSessionController> immersive_session_controller_; + bool immersive_session_has_camera_access_; std::set<VRServiceImpl*> services_; device::mojom::VRDisplayInfoPtr display_info_; @@ -132,6 +133,7 @@ base::ObserverList<Observer> observers_; std::unique_ptr<content::XrInstallHelper> install_helper_; + std::unique_ptr<content::BrowserXRRuntime::Observer> runtime_observer_; base::OnceCallback<void(bool)> install_finished_callback_; base::WeakPtrFactory<BrowserXRRuntimeImpl> weak_ptr_factory_{this};
diff --git a/content/browser/xr/service/vr_service_impl.cc b/content/browser/xr/service/vr_service_impl.cc index a1fbc68..d9eb2744 100644 --- a/content/browser/xr/service/vr_service_impl.cc +++ b/content/browser/xr/service/vr_service_impl.cc
@@ -9,8 +9,10 @@ #include "base/bind.h" #include "base/containers/contains.h" #include "base/containers/cxx20_erase.h" +#include "base/dcheck_is_on.h" #include "base/feature_list.h" #include "base/metrics/histogram_macros.h" +#include "base/stl_util.h" #include "base/trace_event/common/trace_event_common.h" #include "build/build_config.h" #include "components/viz/common/surfaces/frame_sink_id.h" @@ -30,6 +32,7 @@ #include "device/base/features.h" #include "device/vr/buildflags/buildflags.h" #include "device/vr/public/cpp/session_mode.h" +#include "device/vr/public/mojom/vr_service.mojom-shared.h" namespace { @@ -500,19 +503,29 @@ const std::vector<PermissionType> permissions = GetRequiredPermissions(request.options->mode, request.required_features, request.optional_features); + permission_controller->RequestPermissions( permissions, render_frame_host_, render_frame_host_->GetLastCommittedURL(), true, base::BindOnce(&VRServiceImpl::OnPermissionResults, - weak_ptr_factory_.GetWeakPtr(), std::move(request))); + weak_ptr_factory_.GetWeakPtr(), std::move(request), + permissions)); } void VRServiceImpl::OnPermissionResults( SessionRequestData request, + const std::vector<content::PermissionType>& permissions, const std::vector<blink::mojom::PermissionStatus>& permission_statuses) { DVLOG(2) << __func__; + DCHECK_EQ(permissions.size(), permission_statuses.size()); + bool is_consent_granted = true; - for (auto& permission_status : permission_statuses) { + for (size_t i = 0; i < permission_statuses.size(); ++i) { + const blink::mojom::PermissionStatus& permission_status = + permission_statuses[i]; + DVLOG(3) << __func__ << ": index=" << i + << ", permission=" << base::to_underlying(permissions[i]) + << ", status=" << permission_status; if (permission_status != blink::mojom::PermissionStatus::GRANTED) { is_consent_granted = false; break;
diff --git a/content/browser/xr/service/vr_service_impl.h b/content/browser/xr/service/vr_service_impl.h index 61ab19bb..ae5d9ac 100644 --- a/content/browser/xr/service/vr_service_impl.h +++ b/content/browser/xr/service/vr_service_impl.h
@@ -15,6 +15,7 @@ #include "build/build_config.h" #include "content/browser/xr/metrics/session_metrics_helper.h" #include "content/common/content_export.h" +#include "content/public/browser/permission_type.h" #include "content/public/browser/web_contents_observer.h" #include "device/vr/public/mojom/isolated_xr_service.mojom-forward.h" #include "device/vr/public/mojom/vr_service.mojom.h" @@ -147,6 +148,7 @@ void OnPermissionResults( SessionRequestData request, + const std::vector<content::PermissionType>& permissions, const std::vector<blink::mojom::PermissionStatus>& permission_statuses); void EnsureRuntimeInstalled(SessionRequestData request,
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc index 28b33adb..5c85cd0 100644 --- a/content/child/runtime_features.cc +++ b/content/child/runtime_features.cc
@@ -419,6 +419,7 @@ {"ClientHintPartitiondCookies", blink::features::kClientHintsPartitionedCookies}, {"WindowPlacement", blink::features::kWindowPlacement}, + {"EventPath", blink::features::kEventPath}, }; for (const auto& mapping : runtimeFeatureNameToChromiumFeatureMapping) { SetRuntimeFeatureFromChromiumFeature(
diff --git a/content/common/frame.mojom b/content/common/frame.mojom index afc0dc3..ace032d 100644 --- a/content/common/frame.mojom +++ b/content/common/frame.mojom
@@ -554,10 +554,8 @@ // Operation result when the renderer asks the browser to create a new window. enum CreateNewWindowStatus { - // Creation of the new window was blocked, e.g. because the source frame - // doesn't have user activation. - kBlocked, - // Ignore creation of the new window, e.g. because noopener is in effect. + // Ignore creation of the new window. This can happen because creation is + // blocked or because the new window should have no opener relationship. kIgnore, // Reuse the current window rather than creating a new window. kReuse,
diff --git a/content/public/browser/browser_xr_runtime.h b/content/public/browser/browser_xr_runtime.h index c1c5e278..b88c0a2 100644 --- a/content/public/browser/browser_xr_runtime.h +++ b/content/public/browser/browser_xr_runtime.h
@@ -7,13 +7,12 @@ #include "base/observer_list_types.h" #include "content/common/content_export.h" -#include "device/vr/public/mojom/vr_service.mojom-forward.h" +#include "device/vr/public/mojom/vr_service.mojom.h" namespace content { + class WebContents; -} -namespace content { // This interface allows observing the state of the XR service for a particular // runtime. In particular, observers may currently know when the browser // considers a WebContents presenting to an immersive headset. Implementers of @@ -23,15 +22,25 @@ public: class Observer : public base::CheckedObserver { public: - virtual void SetVRDisplayInfo( + virtual void VRDisplayInfoChanged( device::mojom::VRDisplayInfoPtr display_info) {} - // The parameter |contents| is set when a page starts an immersive WebXR - // session. There can only be at most one active immersive session for the - // XRRuntime. Set to null when there is no active immersive session. - virtual void SetWebXRWebContents(content::WebContents* contents) {} + // Called when a page starts or ends an immersive WebXR session. When a + // session was started, |web_contents| will be non-null. There can only be + // at most one active immersive session for the XRRuntime. When there is no + // active immersive session, this method will be called with |web_contents| + // set to null. + virtual void WebXRWebContentsChanged(WebContents* web_contents) {} - virtual void SetFramesThrottled(bool throttled) {} + // Called when the currently active immersive WebXR session has its frames + // [un/]throttled by the compositor. + virtual void WebXRFramesThrottledChanged(bool throttled) {} + + // Called when the observed runtime's camera in use state changes for the + // currently active immersive WebXR session. When |in_use| is true, + // |web_contents| will not be null. + virtual void WebXRCameraInUseChanged(WebContents* web_contents, + bool in_use) {} }; virtual void AddObserver(Observer* observer) = 0;
diff --git a/content/public/browser/xr_integration_client.cc b/content/public/browser/xr_integration_client.cc index 364a14e..ac4d315 100644 --- a/content/public/browser/xr_integration_client.cc +++ b/content/public/browser/xr_integration_client.cc
@@ -16,6 +16,11 @@ return nullptr; } +std::unique_ptr<BrowserXRRuntime::Observer> +XrIntegrationClient::CreateRuntimeObserver() { + return nullptr; +} + XRProviderList XrIntegrationClient::GetAdditionalProviders() { return {}; }
diff --git a/content/public/browser/xr_integration_client.h b/content/public/browser/xr_integration_client.h index e13527c..d76105fa 100644 --- a/content/public/browser/xr_integration_client.h +++ b/content/public/browser/xr_integration_client.h
@@ -9,6 +9,7 @@ #include "build/build_config.h" #include "content/common/content_export.h" +#include "content/public/browser/browser_xr_runtime.h" #include "device/vr/public/mojom/vr_service.mojom-forward.h" #if !BUILDFLAG(IS_ANDROID) @@ -59,6 +60,11 @@ // any default providers built-in to //content. virtual XRProviderList GetAdditionalProviders(); + // Creates a runtime observer that will respond to browser XR runtime state + // changes. May return null if the integraton client does not need to observe + // state changes. + virtual std::unique_ptr<BrowserXRRuntime::Observer> CreateRuntimeObserver(); + #if !BUILDFLAG(IS_ANDROID) // Creates a VrUiHost object for the specified device_id, and takes ownership // of any XRCompositor supplied from the runtime.
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc index 36d9571c..8351788 100644 --- a/content/renderer/render_view_impl.cc +++ b/content/renderer/render_view_impl.cc
@@ -307,18 +307,6 @@ mojom::CreateNewWindowReplyPtr reply; auto* frame_host = creator_frame->GetFrameHost(); bool err = !frame_host->CreateNewWindow(std::move(params), &status, &reply); - - // If creation of the window was blocked, return before consuming user - // activation. A frame that isn't itself activated shouldn't be able to - // consume the activation for the rest of the frame tree. - if (status == mojom::CreateNewWindowStatus::kBlocked) - return nullptr; - - consumed_user_gesture = creator->ConsumeTransientUserActivation( - blink::UserActivationUpdateSource::kBrowser); - - // If there was an error or we should ignore the new window (e.g. because of - // `noopener`), return now that user activation was consumed. if (err || status == mojom::CreateNewWindowStatus::kIgnore) return nullptr; @@ -339,6 +327,11 @@ DCHECK_NE(MSG_ROUTING_NONE, reply->main_frame_route_id); DCHECK_NE(MSG_ROUTING_NONE, reply->widget_params->routing_id); + // The browser allowed creation of a new window and consumed the user + // activation. + consumed_user_gesture = creator->ConsumeTransientUserActivation( + blink::UserActivationUpdateSource::kBrowser); + // While this view may be a background extension page, it can spawn a visible // render view. So we just assume that the new one is not another background // page instead of passing on our own value.
diff --git a/content/test/data/accessibility/html/body-tabindex-expected-blink.txt b/content/test/data/accessibility/html/body-tabindex-expected-blink.txt index 0794f7c..07da43f 100644 --- a/content/test/data/accessibility/html/body-tabindex-expected-blink.txt +++ b/content/test/data/accessibility/html/body-tabindex-expected-blink.txt
@@ -1,6 +1,6 @@ rootWebArea ++genericContainer ignored -++++genericContainer ignored name='This test is for the body tag with a tabindex' +++++genericContainer ignored ++++++paragraph ++++++++staticText name='This test is for the body tag with a tabindex' -++++++++++inlineTextBox name='This test is for the body tag with a tabindex' \ No newline at end of file +++++++++++inlineTextBox name='This test is for the body tag with a tabindex'
diff --git a/content/test/data/accessibility/html/inert-attribute-expected-blink.txt b/content/test/data/accessibility/html/inert-attribute-expected-blink.txt index 426eff2b..99e1aef 100644 --- a/content/test/data/accessibility/html/inert-attribute-expected-blink.txt +++ b/content/test/data/accessibility/html/inert-attribute-expected-blink.txt
@@ -1,6 +1,6 @@ rootWebArea focusable htmlTag='#document' -++genericContainer focusable ignored htmlTag='html' name='Lorem ipsum sit dolor amet consectetur adipiscing tempor sed do eiusmod tempor ' -++++genericContainer focusable ignored htmlTag='body' name='Lorem ipsum sit dolor amet consectetur adipiscing tempor sed do eiusmod tempor ' +++genericContainer focusable ignored htmlTag='html' +++++genericContainer focusable ignored htmlTag='body' ++++++genericContainer ignored invisible htmlTag='div' notUserSelectableStyle=true ++++++++staticText ignored invisible name='Lorem ' notUserSelectableStyle=true ++++++++genericContainer ignored invisible htmlTag='span' notUserSelectableStyle=true
diff --git a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt index 6d2dd72..9865b03 100644 --- a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt +++ b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
@@ -307,6 +307,7 @@ crbug.com/1213170 [ mac asan intel-0x3e9b ] Pixel_WebGPUCopyExternalImage* [ Skip ] crbug.com/1213170 [ mac asan intel-0xd26 ] Pixel_WebGPUCopyExternalImage* [ Skip ] crbug.com/1213170 [ mac nvidia-0xfe9 ] Pixel_WebGPUCopyExternalImage* [ Skip ] +crbug.com/1213170 [ mac nvidia-0xfe9 ] Pixel_WebGPUWebGLTexImage2D [ Skip ] # WebGPU pixel tests fail on Linux+SkiaRenderer+Vulkan crbug.com/1213657 [ linux skia-renderer-vulkan ] Pixel_WebGPU* [ Skip ] @@ -362,9 +363,6 @@ # Producing blank images on Macmini and Macbook Pro crbug.com/974380 [ mac ] Pixel_OffscreenCanvasUnaccelerated2DGPUCompositingWorker [ Failure ] -# Flaky test on SkiaRenderer vulkan android bot. -crbug.com/1252081 [ android skia-renderer-vulkan android-chromium ] Pixel_OffscreenCanvasWebGLPaintAfterResize [ RetryOnFailure ] - # Producing incorrect image on Win10 Intel HD 630 and UHD 630 w/ 26.20.100.6912 # or later drivers. crbug.com/997313 [ win intel-0x5912 skia-renderer-gl ] Pixel_WebGL_PremultipliedAlpha_False [ Failure ] @@ -470,13 +468,16 @@ crbug.com/1213542 [ skia-renderer-gl mac amd-0x6821 passthrough ] Pixel_Video_Media_Stream_Incompatible_Stride [ RetryOnFailure ] # Flaky on Pixel 4 Android WebView -crbug.com/1278893 [ android android-pixel-4 android-webview-instrumentation skia-renderer-gl ] Pixel_OffscreenCanvasWebGLPaintAfterResize [ RetryOnFailure ] crbug.com/1285084 [ android android-pixel-4 android-webview-instrumentation skia-renderer-gl ] Pixel_CSS3DBlueBox [ RetryOnFailure ] +crbug.com/1273033 [ android android-pixel-4 android-webview-instrumentation skia-renderer-gl ] Pixel_OffscreenCanvasTransferBeforeStyleResize [ Failure ] # Flaky failures on Win10 AMD RX 5500 XT crbug.com/1288134 [ win10 amd-0x7340 skia-renderer-gl ] Pixel_DirectComposition_Video_VP9_YUY2 [ Failure ] crbug.com/1288134 [ win10 amd-0x7340 skia-renderer-gl ] Pixel_DirectComposition_Video_VP9_BGRA [ Failure ] +# Pixel 4 Flaky Failures - Passthrough, WebView and Validating +crbug.com/1273033 [ android android-pixel-4 ] Pixel_OffscreenCanvasWebGLPaintAfterResize [ Failure ] + # Pixel 6 failures. crbug.com/1286915 [ android android-pixel-6 ] Pixel_CanvasLowLatencyWebGLAlphaFalse [ Failure ] crbug.com/1286919 [ android android-pixel-6 skia-renderer-vulkan android-chromium ] Pixel_Video_MP4 [ Failure ]
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt index ff56419..adb786e 100644 --- a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt +++ b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
@@ -623,7 +623,7 @@ # Flakily returning RGBA(0,0,0,0) for all pixels crbug.com/1295386 [ android android-shield-android-tv ] conformance/textures/misc/texture-corner-case-videos.html [ RetryOnFailure ] crbug.com/1295386 [ android android-shield-android-tv ] conformance/textures/video/tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html [ Failure ] -crbug.com/1295386 [ android android-shield-android-tv ] conformance/textures/video/tex-2d-rgba-rgba-unsigned_short_5_5_5_1.html [ RetryOnFailure ] +crbug.com/1295386 [ android android-shield-android-tv ] conformance/textures/video/tex-2d-rgba-rgba-unsigned_short_5_5_5_1.html [ Failure ] # Pixel 4 crbug.com/1175223 [ android android-pixel-4 angle-opengles passthrough ] conformance/extensions/angle-instanced-arrays-out-of-bounds.html [ Failure ]
diff --git a/crypto/OWNERS b/crypto/OWNERS index e3db9ed..c1da9e3 100644 --- a/crypto/OWNERS +++ b/crypto/OWNERS
@@ -1,4 +1,5 @@ set noparent agl@chromium.org davidben@chromium.org +mattm@chromium.org rsleevi@chromium.org
diff --git a/extensions/common/features/complex_feature.cc b/extensions/common/features/complex_feature.cc index 6263c94..00fd0c22 100644 --- a/extensions/common/features/complex_feature.cc +++ b/extensions/common/features/complex_feature.cc
@@ -56,20 +56,22 @@ return first_availability; } -Feature::Availability ComplexFeature::IsAvailableToContext( +Feature::Availability ComplexFeature::IsAvailableToContextImpl( const Extension* extension, Context context, const GURL& url, Platform platform, - int context_id) const { - Feature::Availability first_availability = features_[0]->IsAvailableToContext( - extension, context, url, platform, context_id); + int context_id, + bool check_developer_mode) const { + Feature::Availability first_availability = + features_[0]->IsAvailableToContextImpl(extension, context, url, platform, + context_id, check_developer_mode); if (first_availability.is_available()) return first_availability; for (auto it = features_.cbegin() + 1; it != features_.cend(); ++it) { - Availability availability = (*it)->IsAvailableToContext( - extension, context, url, platform, context_id); + Availability availability = (*it)->IsAvailableToContextImpl( + extension, context, url, platform, context_id, check_developer_mode); if (availability.is_available()) return availability; }
diff --git a/extensions/common/features/complex_feature.h b/extensions/common/features/complex_feature.h index d03420f8..9936f50 100644 --- a/extensions/common/features/complex_feature.h +++ b/extensions/common/features/complex_feature.h
@@ -38,17 +38,20 @@ int manifest_version, Platform platform, int context_id) const override; - Availability IsAvailableToContext(const Extension* extension, - Context context, - const GURL& url, - Platform platform, - int context_id) const override; Availability IsAvailableToEnvironment(int context_id) const override; bool IsIdInBlocklist(const HashedExtensionId& hashed_id) const override; bool IsIdInAllowlist(const HashedExtensionId& hashed_id) const override; protected: // Feature: + Availability IsAvailableToContextImpl( + const Extension* extension, + Context context, + const GURL& url, + Platform platform, + int context_id, + bool check_developer_mode) const override; + bool IsInternal() const override; private:
diff --git a/extensions/common/features/feature.h b/extensions/common/features/feature.h index a5cb791..58023a3 100644 --- a/extensions/common/features/feature.h +++ b/extensions/common/features/feature.h
@@ -143,12 +143,24 @@ return IsAvailableToContext(extension, context, url, GetCurrentPlatform(), context_id); } - virtual Availability IsAvailableToContext(const Extension* extension, - Context context, - const GURL& url, - Platform platform, - int context_id) const = 0; + Availability IsAvailableToContext(const Extension* extension, + Context context, + const GURL& url, + Platform platform, + int context_id) const { + return IsAvailableToContextImpl(extension, context, url, platform, + context_id, true); + } + + Availability IsAvailableToContextIgnoringDevMode(const Extension* extension, + Context context, + const GURL& url, + Platform platform, + int context_id) const { + return IsAvailableToContextImpl(extension, context, url, platform, + context_id, false); + } // Returns true if the feature is available to the current environment, // without needing to know information about an Extension or any other // contextual information. Typically used when the Feature is purely @@ -164,6 +176,16 @@ virtual bool IsIdInAllowlist(const HashedExtensionId& hashed_id) const = 0; protected: + friend class SimpleFeature; + friend class ComplexFeature; + virtual Availability IsAvailableToContextImpl( + const Extension* extension, + Context context, + const GURL& url, + Platform platform, + int context_id, + bool check_developer_mode) const = 0; + std::string name_; std::string alias_; std::string source_;
diff --git a/extensions/common/features/manifest_feature.cc b/extensions/common/features/manifest_feature.cc index 03d8ca4b..9829af92 100644 --- a/extensions/common/features/manifest_feature.cc +++ b/extensions/common/features/manifest_feature.cc
@@ -15,14 +15,15 @@ ManifestFeature::~ManifestFeature() { } -Feature::Availability ManifestFeature::IsAvailableToContext( +Feature::Availability ManifestFeature::IsAvailableToContextImpl( const Extension* extension, Feature::Context context, const GURL& url, Feature::Platform platform, - int context_id) const { - Availability availability = SimpleFeature::IsAvailableToContext( - extension, context, url, platform, context_id); + int context_id, + bool check_developer_mode) const { + Availability availability = SimpleFeature::IsAvailableToContextImpl( + extension, context, url, platform, context_id, check_developer_mode); if (!availability.is_available()) return availability;
diff --git a/extensions/common/features/manifest_feature.h b/extensions/common/features/manifest_feature.h index e58b262f..371b15d 100644 --- a/extensions/common/features/manifest_feature.h +++ b/extensions/common/features/manifest_feature.h
@@ -18,11 +18,14 @@ // that a permission or manifest feature can declare dependency on other // manifest features. - Feature::Availability IsAvailableToContext(const Extension* extension, - Feature::Context context, - const GURL& url, - Feature::Platform platform, - int context_id) const override; + protected: + Feature::Availability IsAvailableToContextImpl( + const Extension* extension, + Feature::Context context, + const GURL& url, + Feature::Platform platform, + int context_id, + bool check_developer_mode) const override; }; } // namespace extensions
diff --git a/extensions/common/features/permission_feature.cc b/extensions/common/features/permission_feature.cc index c67e5d9..37e544a 100644 --- a/extensions/common/features/permission_feature.cc +++ b/extensions/common/features/permission_feature.cc
@@ -15,14 +15,15 @@ PermissionFeature::~PermissionFeature() { } -Feature::Availability PermissionFeature::IsAvailableToContext( +Feature::Availability PermissionFeature::IsAvailableToContextImpl( const Extension* extension, Feature::Context context, const GURL& url, Feature::Platform platform, - int context_id) const { - Availability availability = SimpleFeature::IsAvailableToContext( - extension, context, url, platform, context_id); + int context_id, + bool check_developer_mode) const { + Availability availability = SimpleFeature::IsAvailableToContextImpl( + extension, context, url, platform, context_id, check_developer_mode); if (!availability.is_available()) return availability;
diff --git a/extensions/common/features/permission_feature.h b/extensions/common/features/permission_feature.h index 5a36b3cb..57c36af 100644 --- a/extensions/common/features/permission_feature.h +++ b/extensions/common/features/permission_feature.h
@@ -18,11 +18,13 @@ // that a permission or manifest feature can declare dependency on other // permission features. - Feature::Availability IsAvailableToContext(const Extension* extension, - Feature::Context context, - const GURL& url, - Feature::Platform platform, - int context_id) const override; + Feature::Availability IsAvailableToContextImpl( + const Extension* extension, + Feature::Context context, + const GURL& url, + Feature::Platform platform, + int context_id, + bool check_developer_mode) const override; }; } // namespace extensions
diff --git a/extensions/common/features/simple_feature.cc b/extensions/common/features/simple_feature.cc index 86d8df6..f34cb5c 100644 --- a/extensions/common/features/simple_feature.cc +++ b/extensions/common/features/simple_feature.cc
@@ -69,16 +69,6 @@ manifest_version, platform); } -Feature::Availability IsAvailableToContextForBind(const Extension* extension, - Feature::Context context, - const GURL& url, - Feature::Platform platform, - int context_id, - const Feature* feature) { - return feature->IsAvailableToContext(extension, context, url, platform, - context_id); -} - Feature::Availability IsAvailableToEnvironmentForBind(int context_id, const Feature* feature) { return feature->IsAvailableToEnvironment(context_id); @@ -242,9 +232,9 @@ int manifest_version, Platform platform, int context_id) const { - Availability environment_availability = - GetEnvironmentAvailability(platform, GetCurrentChannel(), - GetCurrentFeatureSessionType(), context_id); + Availability environment_availability = GetEnvironmentAvailability( + platform, GetCurrentChannel(), GetCurrentFeatureSessionType(), context_id, + true); if (!environment_availability.is_available()) return environment_availability; Availability manifest_availability = @@ -257,15 +247,27 @@ location, manifest_version, platform, context_id)); } -Feature::Availability SimpleFeature::IsAvailableToContext( +Feature::Availability SimpleFeature::IsAvailableToContextForBind( + const Extension* extension, + Feature::Context context, + const GURL& url, + Feature::Platform platform, + int context_id, + const Feature* feature) { + return feature->IsAvailableToContextImpl(extension, context, url, platform, + context_id, true); +} + +Feature::Availability SimpleFeature::IsAvailableToContextImpl( const Extension* extension, Feature::Context context, const GURL& url, Platform platform, - int context_id) const { - Availability environment_availability = - GetEnvironmentAvailability(platform, GetCurrentChannel(), - GetCurrentFeatureSessionType(), context_id); + int context_id, + bool check_developer_mode) const { + Availability environment_availability = GetEnvironmentAvailability( + platform, GetCurrentChannel(), GetCurrentFeatureSessionType(), context_id, + check_developer_mode); if (!environment_availability.is_available()) return environment_availability; @@ -301,9 +303,9 @@ Feature::Availability SimpleFeature::IsAvailableToEnvironment( int context_id) const { - Availability environment_availability = - GetEnvironmentAvailability(GetCurrentPlatform(), GetCurrentChannel(), - GetCurrentFeatureSessionType(), context_id); + Availability environment_availability = GetEnvironmentAvailability( + GetCurrentPlatform(), GetCurrentChannel(), GetCurrentFeatureSessionType(), + context_id, true); if (!environment_availability.is_available()) return environment_availability; return CheckDependencies( @@ -605,7 +607,8 @@ Platform platform, version_info::Channel channel, mojom::FeatureSessionType session_type, - int context_id) const { + int context_id, + bool check_developer_mode) const { base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); if (!platforms_.empty() && !base::Contains(platforms_, platform)) return CreateAvailability(INVALID_PLATFORM); @@ -632,7 +635,8 @@ if (!MatchesSessionTypes(session_type)) return CreateAvailability(INVALID_SESSION_TYPE, session_type); - if (base::FeatureList::IsEnabled( + if (check_developer_mode && + base::FeatureList::IsEnabled( extensions_features::kRestrictDeveloperModeAPIs) && developer_mode_only_ && !GetCurrentDeveloperMode(context_id)) { return CreateAvailability(REQUIRES_DEVELOPER_MODE);
diff --git a/extensions/common/features/simple_feature.h b/extensions/common/features/simple_feature.h index e40eebdc..48c764a 100644 --- a/extensions/common/features/simple_feature.h +++ b/extensions/common/features/simple_feature.h
@@ -65,15 +65,23 @@ Context context, Platform platform, int context_id) const { - return IsAvailableToContext(extension, context, GURL(), platform, - context_id); + return IsAvailableToContextImpl(extension, context, GURL(), platform, + context_id, true); } Availability IsAvailableToContext(const Extension* extension, Context context, const GURL& url, int context_id) const { - return IsAvailableToContext(extension, context, url, GetCurrentPlatform(), - context_id); + return IsAvailableToContextImpl(extension, context, url, + GetCurrentPlatform(), context_id, true); + } + Availability IsAvailableToContext(const Extension* extension, + Context context, + const GURL& url, + Platform platform, + int context_id) const { + return IsAvailableToContextImpl(extension, context, url, platform, + context_id, true); } // extension::Feature: @@ -83,11 +91,6 @@ int manifest_version, Platform platform, int context_id) const override; - Availability IsAvailableToContext(const Extension* extension, - Context context, - const GURL& url, - Platform platform, - int context_id) const override; Availability IsAvailableToEnvironment(int context_id) const override; bool IsInternal() const override; bool IsIdInBlocklist(const HashedExtensionId& hashed_id) const override; @@ -197,6 +200,14 @@ Availability CreateAvailability(AvailabilityResult result, mojom::FeatureSessionType session_type) const; + Availability IsAvailableToContextImpl( + const Extension* extension, + Context context, + const GURL& url, + Platform platform, + int context_id, + bool check_developer_mode) const override; + private: friend struct FeatureComparator; FRIEND_TEST_ALL_PREFIXES(FeatureProviderTest, ManifestFeatureTypes); @@ -207,6 +218,14 @@ // Holds String to Enum value mappings. struct Mappings; + static Feature::Availability IsAvailableToContextForBind( + const Extension* extension, + Feature::Context context, + const GURL& url, + Feature::Platform platform, + int context_id, + const Feature* feature); + static bool IsIdInList(const HashedExtensionId& hashed_id, const std::vector<std::string>& list); @@ -229,7 +248,8 @@ Platform platform, version_info::Channel channel, mojom::FeatureSessionType session_type, - int context_id) const; + int context_id, + bool check_developer_mode) const; // Returns the availability of the feature with respect to a given extension's // properties.
diff --git a/extensions/renderer/feature_cache.cc b/extensions/renderer/feature_cache.cc index 695ab5bd..9b2ed97 100644 --- a/extensions/renderer/feature_cache.cc +++ b/extensions/renderer/feature_cache.cc
@@ -10,12 +10,18 @@ #include "content/public/common/content_switches.h" #include "extensions/common/extension.h" #include "extensions/common/extension_api.h" +#include "extensions/common/features/feature_developer_mode_only.h" #include "extensions/common/features/feature_provider.h" #include "extensions/renderer/dispatcher.h" namespace extensions { -FeatureCache::FeatureCache() {} +FeatureCache::ExtensionFeatureData::ExtensionFeatureData() = default; +FeatureCache::ExtensionFeatureData::ExtensionFeatureData( + const ExtensionFeatureData&) = default; +FeatureCache::ExtensionFeatureData::~ExtensionFeatureData() = default; + +FeatureCache::FeatureCache() = default; FeatureCache::~FeatureCache() = default; FeatureCache::FeatureNameVector FeatureCache::GetAvailableFeatures( @@ -32,12 +38,12 @@ DCHECK_NE(Feature::UNSPECIFIED_CONTEXT, context_type) << "FeatureCache shouldn't be used for unspecified contexts."; - const FeatureVector& features = + const ExtensionFeatureData& features = GetFeaturesFromCache(context_type, extension, url.DeprecatedGetOriginAsURL(), kRendererProfileId); FeatureNameVector names; - names.reserve(features.size()); - for (const Feature* feature : features) { + names.reserve(features.available_features.size()); + for (const Feature* feature : features.available_features) { // Since we only cache based on extension id and context type, instead of // all attributes of a context (like URL), we need to double-check if the // feature is actually available to the context. This is still a win, since @@ -54,6 +60,22 @@ return names; } +FeatureCache::FeatureNameVector +FeatureCache::GetDeveloperModeRestrictedFeatures(Feature::Context context_type, + const Extension* extension, + const GURL& url) { + const ExtensionFeatureData& features = + GetFeaturesFromCache(context_type, extension, + url.DeprecatedGetOriginAsURL(), kRendererProfileId); + FeatureNameVector names; + names.reserve(features.dev_mode_restricted_features.size()); + for (const Feature* feature : features.dev_mode_restricted_features) { + names.push_back(feature->name()); + } + + return names; +} + void FeatureCache::InvalidateExtension(const ExtensionId& extension_id) { for (auto iter = extension_cache_.begin(); iter != extension_cache_.end();) { if (iter->first.first == extension_id) @@ -63,7 +85,7 @@ } } -const FeatureCache::FeatureVector& FeatureCache::GetFeaturesFromCache( +const FeatureCache::ExtensionFeatureData& FeatureCache::GetFeaturesFromCache( Feature::Context context_type, const Extension* extension, const GURL& origin, @@ -90,12 +112,12 @@ .first->second; } -FeatureCache::FeatureVector FeatureCache::CreateCacheEntry( +FeatureCache::ExtensionFeatureData FeatureCache::CreateCacheEntry( Feature::Context context_type, const Extension* extension, const GURL& origin, int context_id) { - FeatureVector features; + ExtensionFeatureData features; const FeatureProvider* api_feature_provider = FeatureProvider::GetAPIFeatures(); GURL empty_url; @@ -131,16 +153,32 @@ if (!ExtensionAPI::GetSharedInstance()->IsAnyFeatureAvailableToContext( *feature, extension, context_type, url_to_use, CheckAliasStatus::NOT_ALLOWED, context_id)) { + if (feature + ->IsAvailableToContextIgnoringDevMode( + extension, context_type, url_to_use, + Feature::GetCurrentPlatform(), context_id) + .is_available()) { + features.dev_mode_restricted_features.push_back(feature); + } continue; } - features.push_back(feature); + features.available_features.push_back(feature); } std::sort( - features.begin(), features.end(), + features.dev_mode_restricted_features.begin(), + features.dev_mode_restricted_features.end(), [](const Feature* a, const Feature* b) { return a->name() < b->name(); }); - DCHECK(std::unique(features.begin(), features.end()) == features.end()); + std::sort( + features.available_features.begin(), features.available_features.end(), + [](const Feature* a, const Feature* b) { return a->name() < b->name(); }); + DCHECK(std::unique(features.dev_mode_restricted_features.begin(), + features.dev_mode_restricted_features.end()) == + features.dev_mode_restricted_features.end()); + DCHECK(std::unique(features.available_features.begin(), + features.available_features.end()) == + features.available_features.end()); return features; }
diff --git a/extensions/renderer/feature_cache.h b/extensions/renderer/feature_cache.h index 4392546..95a63d66 100644 --- a/extensions/renderer/feature_cache.h +++ b/extensions/renderer/feature_cache.h
@@ -42,34 +42,55 @@ const Extension* extension, const GURL& url); + // Returns the names of features restricted to developer mode in a + // lexicographically sorted vector. + FeatureNameVector GetDeveloperModeRestrictedFeatures( + Feature::Context context_type, + const Extension* extension, + const GURL& url); + // Invalidates the cache for the specified extension. void InvalidateExtension(const ExtensionId& extension_id); private: using FeatureVector = std::vector<const Feature*>; + struct ExtensionFeatureData { + public: + ExtensionFeatureData(); + ExtensionFeatureData(const ExtensionFeatureData&); + ~ExtensionFeatureData(); + + // Features that are restricted to developer mode. + FeatureVector dev_mode_restricted_features; + // Available features that are not restricted to developer mode. + FeatureVector available_features; + }; + // Note: We use a key of ExtensionId, Feature::Context to maximize cache hits. // Unfortunately, this won't always be perfectly accurate, since some features // may have other context-dependent restrictions (such as URLs), but caching // by extension id + context + url would result in significantly fewer hits. using ExtensionCacheMapKey = std::pair<ExtensionId, Feature::Context>; - using ExtensionCacheMap = std::map<ExtensionCacheMapKey, FeatureVector>; + using ExtensionCacheMap = + std::map<ExtensionCacheMapKey, ExtensionFeatureData>; // Cache by origin. - using WebUICacheMap = std::map<GURL, FeatureVector>; + using WebUICacheMap = std::map<GURL, ExtensionFeatureData>; // Returns the features available to the given context from the cache, // creating a new entry if one doesn't exist. - const FeatureVector& GetFeaturesFromCache(Feature::Context context_type, - const Extension* extension, - const GURL& origin, - int context_id); + const ExtensionFeatureData& GetFeaturesFromCache( + Feature::Context context_type, + const Extension* extension, + const GURL& origin, + int context_id); - // Creates a FeatureVector to be entered into a cache for the specified + // Creates ExtensionFeatureData to be entered into a cache for the specified // context data. - FeatureVector CreateCacheEntry(Feature::Context context_type, - const Extension* extension, - const GURL& origin, - int context_id); + ExtensionFeatureData CreateCacheEntry(Feature::Context context_type, + const Extension* extension, + const GURL& origin, + int context_id); // The cache of extension-related contexts. These may be invalidated, since // extension permissions change.
diff --git a/extensions/renderer/native_extension_bindings_system.cc b/extensions/renderer/native_extension_bindings_system.cc index cbfc1869..6e41915 100644 --- a/extensions/renderer/native_extension_bindings_system.cc +++ b/extensions/renderer/native_extension_bindings_system.cc
@@ -11,6 +11,7 @@ #include "base/command_line.h" #include "base/metrics/histogram_macros.h" #include "base/strings/string_piece.h" +#include "base/strings/stringprintf.h" #include "base/timer/elapsed_timer.h" #include "components/crx_file/id_util.h" #include "content/public/common/content_switches.h" @@ -507,6 +508,15 @@ return success.IsJust() && success.FromJust(); }; + auto set_restricted_accessor = [chrome, isolate, + v8_context](base::StringPiece accessor_name) { + v8::Local<v8::String> api_name = + gin::StringToSymbol(isolate, accessor_name); + v8::Maybe<bool> success = chrome->SetLazyDataProperty( + v8_context, api_name, &ThrowDeveloperModeRestrictedError, api_name); + return success.IsJust() && success.FromJust(); + }; + bool is_webpage = false; switch (context->context_type()) { case Feature::UNSPECIFIED_CONTEXT: @@ -575,6 +585,23 @@ return; } } + + FeatureCache::FeatureNameVector dev_mode_features = + feature_cache_.GetDeveloperModeRestrictedFeatures( + context->context_type(), context->extension(), context->url()); + + for (const std::string& feature : dev_mode_features) { + base::StringPiece accessor_name = + GetFirstDifferentAPIName(feature, base::StringPiece()); + // This code only works for restricting top-level features to developer + // mode. For sub-features, this would result in overwriting the accessor + // for the root API object and restricting the whole API. + DCHECK_EQ(accessor_name, feature); + if (!set_restricted_accessor(accessor_name)) { + LOG(ERROR) << "Failed to create API on Chrome object."; + return; + } + } } void NativeExtensionBindingsSystem::DispatchEventInContext( @@ -660,6 +687,18 @@ info.GetReturnValue().Set(binding); } +void NativeExtensionBindingsSystem::ThrowDeveloperModeRestrictedError( + v8::Local<v8::Name> name, + const v8::PropertyCallbackInfo<v8::Value>& info) { + v8::Isolate* isolate = info.GetIsolate(); + isolate->ThrowException(v8::Exception::Error(gin::StringToV8( + isolate, + base::StringPrintf( + "The '%s' API is only available for users in developer mode.", + gin::V8ToString(isolate, info.Data()).c_str())))); + return; +} + // static v8::Local<v8::Object> NativeExtensionBindingsSystem::GetAPIHelper( v8::Local<v8::Context> context,
diff --git a/extensions/renderer/native_extension_bindings_system.h b/extensions/renderer/native_extension_bindings_system.h index 15c615d..a010376 100644 --- a/extensions/renderer/native_extension_bindings_system.h +++ b/extensions/renderer/native_extension_bindings_system.h
@@ -115,6 +115,12 @@ static void BindingAccessor(v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info); + // Callback for accessing a restricted extension API. Access to the API is + // restricted to the developer mode only. + static void ThrowDeveloperModeRestrictedError( + v8::Local<v8::Name> name, + const v8::PropertyCallbackInfo<v8::Value>& info); + // Creates and returns the API binding for the given |name|. static v8::Local<v8::Object> GetAPIHelper(v8::Local<v8::Context> context, v8::Local<v8::String> name);
diff --git a/headless/BUILD.gn b/headless/BUILD.gn index 98bf51b9..b46f106 100644 --- a/headless/BUILD.gn +++ b/headless/BUILD.gn
@@ -454,6 +454,7 @@ } public_deps = [ + ":headless_shared_sources", "//base", "//net", ] @@ -468,7 +469,6 @@ deps = [ ":backend_cdp_bindings", - ":headless_shared_sources", ":version_header", "//base:base_static", "//build:branding_buildflags", @@ -661,8 +661,9 @@ ] } + public_deps = [ ":headless_shared_sources" ] + deps = [ - ":headless_shared_sources", "//build:chromeos_buildflags", "//components/crash/core/common", "//components/security_state/content", @@ -713,7 +714,7 @@ # For component builds all dependencies are already included in the headless # component. group("headless_renderer") { - deps = [ ":headless_non_renderer" ] + public_deps = [ ":headless_non_renderer" ] } } @@ -862,6 +863,7 @@ "//gin", "//net:test_support", "//printing/buildflags", + "//services/device/public/cpp:test_support", "//services/network/public/mojom", "//testing/gmock", "//testing/gtest", @@ -944,7 +946,6 @@ } deps = [ - ":headless_renderer", "//build:branding_buildflags", "//components/embedder_support", "//components/security_state/content", @@ -958,6 +959,7 @@ ] public_deps = [ + ":headless_renderer", "//base", "//third_party/inspector_protocol:crdtp", ]
diff --git a/headless/test/headless_browser_browsertest.cc b/headless/test/headless_browser_browsertest.cc index bfc9e75..eaca9d1 100644 --- a/headless/test/headless_browser_browsertest.cc +++ b/headless/test/headless_browser_browsertest.cc
@@ -15,9 +15,6 @@ #include "base/threading/thread_restrictions.h" #include "base/values.h" #include "build/build_config.h" -#include "components/policy/core/browser/browser_policy_connector_base.h" -#include "components/policy/core/common/mock_configuration_policy_provider.h" -#include "components/policy/core/common/policy_map.h" #include "content/public/browser/navigation_controller.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/permission_controller_delegate.h" @@ -32,7 +29,6 @@ #include "headless/lib/browser/headless_browser_context_impl.h" #include "headless/lib/browser/headless_browser_impl.h" #include "headless/lib/browser/headless_web_contents_impl.h" -#include "headless/lib/browser/policy/headless_mode_policy.h" #include "headless/public/devtools/domains/inspector.h" #include "headless/public/devtools/domains/network.h" #include "headless/public/devtools/domains/page.h"
diff --git a/infra/config/generated/builders/ci/android-weblayer-11-x86-rel-tests/properties.json b/infra/config/generated/builders/ci/android-weblayer-11-x86-rel-tests/properties.json new file mode 100644 index 0000000..c6534f7 --- /dev/null +++ b/infra/config/generated/builders/ci/android-weblayer-11-x86-rel-tests/properties.json
@@ -0,0 +1,17 @@ +{ + "$build/goma": { + "enable_ats": true, + "rpc_extra_params": "?prod", + "server_host": "goma.chromium.org", + "use_luci_auth": true + }, + "$recipe_engine/resultdb/test_presentation": { + "column_keys": [], + "grouping_keys": [ + "status", + "v.test_suite" + ] + }, + "builder_group": "chromium.android.fyi", + "recipe": "chromium" +} \ No newline at end of file
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg index c00b9b2..79e7703 100644 --- a/infra/config/generated/luci/cr-buildbucket.cfg +++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -26161,6 +26161,85 @@ } } builders { + name: "android-weblayer-11-x86-rel-tests" + swarming_host: "chromium-swarm.appspot.com" + dimensions: "builderless:1" + dimensions: "cores:8" + dimensions: "cpu:x86-64" + dimensions: "os:Ubuntu-18.04" + dimensions: "pool:luci.chromium.ci" + dimensions: "ssd:0" + exe { + cipd_package: "infra/chromium/bootstrapper/${platform}" + cipd_version: "latest" + cmd: "bootstrapper" + } + properties: + '{' + ' "$bootstrap/exe": {' + ' "exe": {' + ' "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",' + ' "cipd_version": "refs/heads/main",' + ' "cmd": [' + ' "luciexe"' + ' ]' + ' }' + ' },' + ' "$bootstrap/properties": {' + ' "properties_file": "infra/config/generated/builders/ci/android-weblayer-11-x86-rel-tests/properties.json",' + ' "top_level_project": {' + ' "ref": "refs/heads/main",' + ' "repo": {' + ' "host": "chromium.googlesource.com",' + ' "project": "chromium/src"' + ' }' + ' }' + ' },' + ' "builder_group": "chromium.android.fyi",' + ' "led_builder_is_bootstrapped": true,' + ' "recipe": "chromium"' + '}' + execution_timeout_secs: 10800 + build_numbers: YES + service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + experiments { + key: "luci.recipes.use_python3" + value: 100 + } + resultdb { + enable: true + bq_exports { + project: "chrome-luci-data" + dataset: "chromium" + table: "ci_test_results" + test_results {} + } + bq_exports { + project: "chrome-luci-data" + dataset: "chromium" + table: "gpu_ci_test_results" + test_results { + predicate { + test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+" + } + } + } + bq_exports { + project: "chrome-luci-data" + dataset: "chromium" + table: "blink_web_tests_ci_test_results" + test_results { + predicate { + test_id_regexp: "ninja://[^/]*blink_web_tests/.+" + } + } + } + history_options { + use_invocation_timestamp: true + } + } + } + builders { name: "android-weblayer-marshmallow-x86-rel-tests" swarming_host: "chromium-swarm.appspot.com" dimensions: "builderless:1"
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg index 3d921a1..7ab2241 100644 --- a/infra/config/generated/luci/luci-milo.cfg +++ b/infra/config/generated/luci/luci-milo.cfg
@@ -3707,6 +3707,11 @@ category: "tester|10" short_name: "10" } + builders { + name: "buildbucket/luci.chromium.ci/android-weblayer-11-x86-rel-tests" + category: "tester|weblayer" + short_name: "11" + } header { oncalls { name: "Chromium"
diff --git a/infra/config/generated/luci/luci-notify.cfg b/infra/config/generated/luci/luci-notify.cfg index 8b5ae3c..4893b20d2 100644 --- a/infra/config/generated/luci/luci-notify.cfg +++ b/infra/config/generated/luci/luci-notify.cfg
@@ -2575,6 +2575,18 @@ } builders { bucket: "ci" + name: "android-weblayer-11-x86-rel-tests" + } +} +notifiers { + notifications { + on_new_status: FAILURE + email { + recipients: "weblayer-sheriff@grotations.appspotmail.com" + } + } + builders { + bucket: "ci" name: "android-weblayer-marshmallow-x86-rel-tests" } }
diff --git a/infra/config/generated/luci/luci-scheduler.cfg b/infra/config/generated/luci/luci-scheduler.cfg index 3e99607..7b128e3 100644 --- a/infra/config/generated/luci/luci-scheduler.cfg +++ b/infra/config/generated/luci/luci-scheduler.cfg
@@ -4523,6 +4523,20 @@ } } job { + id: "android-weblayer-11-x86-rel-tests" + realm: "ci" + acls { + role: TRIGGERER + granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com" + } + acl_sets: "ci" + buildbucket { + server: "cr-buildbucket.appspot.com" + bucket: "ci" + builder: "android-weblayer-11-x86-rel-tests" + } +} +job { id: "android-weblayer-marshmallow-x86-rel-tests" realm: "ci" acls {
diff --git a/infra/config/generated/luci/realms.cfg b/infra/config/generated/luci/realms.cfg index 688dde8..bc2ccba 100644 --- a/infra/config/generated/luci/realms.cfg +++ b/infra/config/generated/luci/realms.cfg
@@ -144,6 +144,7 @@ values: "android-cronet-x86-dbg-pie-tests" values: "android-pie-arm64-dbg" values: "android-weblayer-10-x86-rel-tests" + values: "android-weblayer-11-x86-rel-tests" values: "android-weblayer-marshmallow-x86-rel-tests" values: "android-weblayer-oreo-x86-rel-tests" values: "android-weblayer-pie-x86-rel-tests"
diff --git a/infra/config/subprojects/chromium/ci/chromium.android.fyi.star b/infra/config/subprojects/chromium/ci/chromium.android.fyi.star index 5ca28dfd..9c1576f 100644 --- a/infra/config/subprojects/chromium/ci/chromium.android.fyi.star +++ b/infra/config/subprojects/chromium/ci/chromium.android.fyi.star
@@ -56,6 +56,16 @@ ) ci.builder( + name = "android-weblayer-11-x86-rel-tests", + console_view_entry = consoles.console_view_entry( + category = "tester|weblayer", + short_name = "11", + ), + triggered_by = ["android-weblayer-with-aosp-webview-x86-fyi-rel"], + notifies = ["weblayer-sheriff"], +) + +ci.builder( name = "android-weblayer-pie-x86-wpt-fyi-rel", console_view_entry = consoles.console_view_entry( category = "builder_tester|weblayer",
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd index d4caaa7..20e594a 100644 --- a/ios/chrome/app/strings/ios_strings.grd +++ b/ios/chrome/app/strings/ios_strings.grd
@@ -2854,6 +2854,9 @@ <message name="IDS_IOS_TABS_SEARCH_SUGGESTED_ACTION_SEARCH_OPEN_TABS" desc="Title for a suggested action presented at the end of search results to search through open tabs for the same search term."> Search Open Tabs </message> + <message name="IDS_IOS_TABS_SEARCH_SUGGESTED_ACTION_SEARCH_RECENT_TABS" desc="Title for a suggested action presented at the end of search results to search through recent tabs for the same search term."> + Search Recent Tabs + </message> <message name="IDS_IOS_TABS_SEARCH_SUGGESTED_ACTION_SEARCH_WEB" desc="Title for a suggested action presented at the end of search results to search the web for the same search term."> Search on the Web </message>
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TABS_SEARCH_SUGGESTED_ACTION_SEARCH_RECENT_TABS.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TABS_SEARCH_SUGGESTED_ACTION_SEARCH_RECENT_TABS.png.sha1 new file mode 100644 index 0000000..1d3ccdd --- /dev/null +++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TABS_SEARCH_SUGGESTED_ACTION_SEARCH_RECENT_TABS.png.sha1
@@ -0,0 +1 @@ +05700e97f44d39a174242285483b6beff2e8dd85 \ No newline at end of file
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm index b7cab3a0..9882767 100644 --- a/ios/chrome/browser/flags/about_flags.mm +++ b/ios/chrome/browser/flags/about_flags.mm
@@ -583,6 +583,11 @@ {"tabs-search-ios", flag_descriptions::kTabsSearchName, flag_descriptions::kTabsSearchDescription, flags_ui::kOsIos, FEATURE_VALUE_TYPE(kTabsSearch)}, + {"tabs-search-regular-results-suggested-actions-ios", + flag_descriptions::kTabsSearchRegularResultsSuggestedActionsName, + flag_descriptions::kTabsSearchRegularResultsSuggestedActionsDescription, + flags_ui::kOsIos, + FEATURE_VALUE_TYPE(kTabsSearchRegularResultsSuggestedActions)}, {"incognito-brand-consistency-for-ios", flag_descriptions::kIncognitoBrandConsistencyForIOSName, flag_descriptions::kIncognitoBrandConsistencyForIOSDescription,
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc index 4f3cdeb..049028df 100644 --- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc +++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -535,6 +535,12 @@ "Enables the search mode in the Tab grid where users can search open tabs " "or recent tabs."; +const char kTabsSearchRegularResultsSuggestedActionsName[] = + "Enable Tabs Search regular results suggested actions section"; +const char kTabsSearchRegularResultsSuggestedActionsDescription[] = + "Enables the suggested actions section in the regular tabs page when the " + "search mode is enabled."; + const char kToolbarContainerName[] = "Use Toolbar Containers"; const char kToolbarContainerDescription[] = "When enabled, the toolbars and their fullscreen animations will be "
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h index 7e33add0..733f97e 100644 --- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h +++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -483,6 +483,11 @@ extern const char kTabsSearchName[]; extern const char kTabsSearchDescription[]; +// Title and description for the flag to enable tabs search regular results +// suggested actions feature. +extern const char kTabsSearchRegularResultsSuggestedActionsName[]; +extern const char kTabsSearchRegularResultsSuggestedActionsDescription[]; + // Title and description for the flag to enable the toolbar container // implementation. extern const char kToolbarContainerName[];
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/BUILD.gn b/ios/chrome/browser/ui/tab_switcher/tab_grid/BUILD.gn index fe822d4..08065a03 100644 --- a/ios/chrome/browser/ui/tab_switcher/tab_grid/BUILD.gn +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/BUILD.gn
@@ -24,6 +24,7 @@ "//components/bookmarks/browser", "//components/favicon/ios", "//components/prefs", + "//components/search_engines", "//components/sessions", "//components/strings", "//ios/chrome/app/strings", @@ -35,6 +36,7 @@ "//ios/chrome/browser/main", "//ios/chrome/browser/policy:feature_flags", "//ios/chrome/browser/policy:policy_util", + "//ios/chrome/browser/search_engines", "//ios/chrome/browser/sessions", "//ios/chrome/browser/sessions:restoration_agent", "//ios/chrome/browser/sessions:serialisation", @@ -64,6 +66,7 @@ "//ios/chrome/browser/ui/sharing", "//ios/chrome/browser/ui/snackbar", "//ios/chrome/browser/ui/tab_switcher", + "//ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions", "//ios/chrome/browser/ui/tab_switcher/tab_grid/transitions", "//ios/chrome/browser/ui/thumb_strip", "//ios/chrome/browser/ui/thumb_strip:feature_flags", @@ -146,6 +149,7 @@ "//ios/chrome/browser/ui/incognito_reauth:incognito_reauth_scene_agent", "//ios/chrome/browser/ui/menu", "//ios/chrome/browser/ui/recent_tabs:recent_tabs_ui", + "//ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions", "//ios/chrome/browser/ui/tab_switcher/tab_grid/transitions", "//ios/chrome/browser/ui/table_view:styler", "//ios/chrome/browser/ui/thumb_strip",
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/features.h b/ios/chrome/browser/ui/tab_switcher/tab_grid/features.h index 62769bbd..f09952d 100644 --- a/ios/chrome/browser/ui/tab_switcher/tab_grid/features.h +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/features.h
@@ -9,8 +9,15 @@ // Feature flag to enable Tabs Search. extern const base::Feature kTabsSearch; +// Feature flag to enable suggested actions in the regular tabs search results +// page. +extern const base::Feature kTabsSearchRegularResultsSuggestedActions; // Whether the kTabsSearch flag is enabled. bool IsTabsSearchEnabled(); +// Whether the tabs search and regular results suggestedActions section is +// enabled. +bool IsTabsSearchRegularResultsSuggestedActionsEnabled(); + #endif // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_GRID_FEATURES_H_
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/features.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/features.mm index 95276d4d..f851b0c 100644 --- a/ios/chrome/browser/ui/tab_switcher/tab_grid/features.mm +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/features.mm
@@ -11,6 +11,16 @@ const base::Feature kTabsSearch{"TabsSearch", base::FEATURE_DISABLED_BY_DEFAULT}; +const base::Feature kTabsSearchRegularResultsSuggestedActions{ + "TabsSearchRegularResultsSuggestedActions", + base::FEATURE_ENABLED_BY_DEFAULT}; + bool IsTabsSearchEnabled() { return base::FeatureList::IsEnabled(kTabsSearch); } + +bool IsTabsSearchRegularResultsSuggestedActionsEnabled() { + return IsTabsSearchEnabled() && + base::FeatureList::IsEnabled( + kTabsSearchRegularResultsSuggestedActions); +}
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/BUILD.gn b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/BUILD.gn index 98c12d0..275bfbd 100644 --- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/BUILD.gn +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/BUILD.gn
@@ -70,6 +70,7 @@ "//ios/chrome/browser/ui/tab_switcher", "//ios/chrome/browser/ui/tab_switcher/tab_grid:features", "//ios/chrome/browser/ui/tab_switcher/tab_grid:tab_grid_paging", + "//ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions", "//ios/chrome/browser/ui/tab_switcher/tab_grid/transitions", "//ios/chrome/browser/ui/thumb_strip", "//ios/chrome/browser/ui/thumb_strip:feature_flags",
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_commands.h b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_commands.h index 36f3e8e..0d751bd0 100644 --- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_commands.h +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_commands.h
@@ -63,6 +63,12 @@ // Tells the receiver to reset grid to contain all the items and select the // active item. - (void)resetToAllItems; + +// Tells the receiver to fetch the search history results count for |searchText| +// and provide it to the |completion| block. +- (void)fetchSearchHistoryResultsCountForText:(NSString*)searchText + completion:(void (^)(size_t))completion; + @end #endif // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_GRID_GRID_GRID_COMMANDS_H_
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.h b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.h index d224f76..fbfbc77 100644 --- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.h +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.h
@@ -24,6 +24,7 @@ @protocol IncognitoReauthCommands; @protocol PriceCardDataSource; @protocol ThumbStripCommands; +@protocol SuggestedActionsDelegate; // Protocol used to relay relevant user interactions from a grid UI. @protocol GridViewControllerDelegate @@ -85,12 +86,18 @@ @property(nonatomic, readonly, getter=isGridEmpty) BOOL gridEmpty; // The visual look of the grid. @property(nonatomic, assign) GridTheme theme; -// The current mode (normal, selection) for the grid. +// The current mode for the grid. @property(nonatomic, assign) TabGridMode mode; +// The current search text to use for filtering results when the search mode is +// active. +@property(nonatomic, copy) NSString* searchText; // Handler for reauth commands. @property(nonatomic, weak) id<IncognitoReauthCommands> reauthHandler; // Handler for thumbstrip commands. @property(nonatomic, weak) id<ThumbStripCommands> thumbStripHandler; +// Delegate for search results suggested actions. +@property(nonatomic, weak) id<SuggestedActionsDelegate> + suggestedActionsDelegate; // Delegate is informed of user interactions in the grid UI. @property(nonatomic, weak) id<GridViewControllerDelegate> delegate; // Handles drag and drop interactions that involved the model layer. @@ -102,7 +109,7 @@ // YES if the selected cell is visible in the grid. @property(nonatomic, readonly, getter=isSelectedCellVisible) BOOL selectedCellVisible; -// YES if the gid should show cell selection updates. This would be set to NO, +// YES if the grid should show cell selection updates. This would be set to NO, // for example, if the grid was about to be transitioned out of. @property(nonatomic, assign) BOOL showsSelectionUpdates; // The fraction of the last item of the grid that is visible.
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.mm index 62a0d46..47009dd7 100644 --- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.mm +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.mm
@@ -33,6 +33,9 @@ #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_shareable_items_provider.h" #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/horizontal_layout.h" #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/plus_sign_cell.h" +#import "ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_delegate.h" +#import "ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_grid_cell.h" +#import "ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_view_controller.h" #import "ios/chrome/browser/ui/tab_switcher/tab_grid/transitions/grid_transition_layout.h" #import "ios/chrome/browser/ui/tab_switcher/tab_switcher_item.h" #import "ios/chrome/browser/ui/util/rtl_geometry.h" @@ -51,11 +54,18 @@ constexpr CGFloat kSpringAnimationDuration = 0.4; constexpr CGFloat kSpringAnimationDamping = 0.6; constexpr CGFloat kSpringAnimationInitialVelocity = 1.0; +constexpr int kSuggestedActionsSectionIndex = 1; NSString* const kCellIdentifier = @"GridCellIdentifier"; NSString* const kPlusSignCellIdentifier = @"PlusSignCellIdentifier"; +NSString* const kSuggestedActionsCellIdentifier = + @"SuggestedActionsCellIdentifier"; + +NSString* const kSuggestedActionsSectionIdentifier = + @"SuggestedActionsSectionIdentifier"; + // Creates an NSIndexPath with |index| in section 0. NSIndexPath* CreateIndexPath(NSInteger index) { return [NSIndexPath indexPathForItem:index inSection:0]; @@ -74,8 +84,10 @@ @end @interface GridViewController () <GridCellDelegate, + SuggestedActionsViewControllerDelegate, UICollectionViewDataSource, UICollectionViewDelegate, + UICollectionViewDelegateFlowLayout, UICollectionViewDragDelegate, UICollectionViewDropDelegate, UIPointerInteractionDelegate> @@ -111,6 +123,9 @@ // By how much the user scrolled past the view's content size. A negative value // means the user hasn't scrolled past the end of the scroll view. @property(nonatomic, assign, readonly) CGFloat offsetPastEndOfScrollView; +// The view controller that holds the view of the suggested saerch actions. +@property(nonatomic, strong) + SuggestedActionsViewController* suggestedActionsViewController; // Cells for which pointer interactions have been added. Pointer interactions // should only be added to displayed cells (not transition cells). This is only // expected to get as large as the number of reusable cells in memory. @@ -137,6 +152,9 @@ // YES while batch updates and the batch update completion are being performed. @property(nonatomic) BOOL updating; +// YES while the grid has the suggested actions section. +@property(nonatomic) BOOL showingSuggestedActions; + @end @implementation GridViewController @@ -169,6 +187,10 @@ forCellWithReuseIdentifier:kCellIdentifier]; [collectionView registerClass:[PlusSignCell class] forCellWithReuseIdentifier:kPlusSignCellIdentifier]; + if (IsTabsSearchRegularResultsSuggestedActionsEnabled()) { + [collectionView registerClass:[SuggestedActionsGridCell class] + forCellWithReuseIdentifier:kSuggestedActionsCellIdentifier]; + } // During deletion (in horizontal layout) the backgroundView can resize, // revealing temporarily the collectionView background. This makes sure // both are the same color. @@ -227,6 +249,8 @@ - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection { [super traitCollectionDidChange:previousTraitCollection]; [self.collectionView.collectionViewLayout invalidateLayout]; + [self.suggestedActionsViewController + traitCollectionDidChange:previousTraitCollection]; } #pragma mark - Public @@ -270,6 +294,16 @@ _mode = mode; + if (IsTabsSearchRegularResultsSuggestedActionsEnabled()) { + if (mode == TabGridModeSearch && self.suggestedActionsDelegate) { + if (!self.suggestedActionsViewController) { + self.suggestedActionsViewController = + [[SuggestedActionsViewController alloc] initWithDelegate:self]; + } + } + [self updateSuggestedActionsSection]; + } + // Reloading specific sections in a |performBatchUpdates| fades the changes in // rather than reloads the collection view with a harsh flash. __weak GridViewController* weakSelf = self; @@ -303,6 +337,12 @@ } } +- (void)setSearchText:(NSString*)searchText { + _searchText = searchText; + _suggestedActionsViewController.searchText = searchText; + [self updateSuggestedActionsSection]; +} + - (BOOL)isSelectedCellVisible { // The collection view's selected item may not have updated yet, so use the // selected index. @@ -364,7 +404,7 @@ self.currentLayout.animatesItemUpdates = YES; [self.collectionView reloadData]; // Selection is invalid if there are no items. - if (self.items.count == 0) { + if ([self shouldShowEmptyState]) { [self animateEmptyStateIn]; return; } @@ -386,8 +426,25 @@ #pragma mark - UICollectionViewDataSource +- (NSInteger)numberOfSectionsInCollectionView: + (UICollectionView*)collectionView { + if (IsTabsSearchRegularResultsSuggestedActionsEnabled() && + self.showingSuggestedActions) { + return kSuggestedActionsSectionIndex + 1; + } + return 1; +} + - (NSInteger)collectionView:(UICollectionView*)collectionView numberOfItemsInSection:(NSInteger)section { + if (IsTabsSearchRegularResultsSuggestedActionsEnabled() && + section == kSuggestedActionsSectionIndex) { + // In the search mode there there is only one item in the suggested actions + // section which contains the table for the suggested actions. + if (self.showingSuggestedActions) + return 1; + return 0; + } if (self.thumbStripEnabled) { // The PlusSignCell (new item button) is always appended at the end of the // collection. @@ -401,33 +458,46 @@ NSUInteger itemIndex = base::checked_cast<NSUInteger>(indexPath.item); UICollectionViewCell* cell; - if ([self isIndexPathForPlusSignCell:indexPath]) { + if (IsTabsSearchRegularResultsSuggestedActionsEnabled() && + indexPath.section == kSuggestedActionsSectionIndex) { + DCHECK(self.suggestedActionsViewController); cell = [collectionView - dequeueReusableCellWithReuseIdentifier:kPlusSignCellIdentifier + dequeueReusableCellWithReuseIdentifier:kSuggestedActionsCellIdentifier forIndexPath:indexPath]; - PlusSignCell* plusSignCell = base::mac::ObjCCastStrict<PlusSignCell>(cell); - plusSignCell.theme = self.theme; + SuggestedActionsGridCell* suggestedActionsCell = + base::mac::ObjCCastStrict<SuggestedActionsGridCell>(cell); + suggestedActionsCell.suggestedActionsView = + self.suggestedActionsViewController.view; } else { - // In some cases this is called with an indexPath.item that's beyond (by 1) - // the bounds of self.items -- see crbug.com/1068136. Presumably this is a - // race condition where an item has been deleted at the same time as the - // collection is doing layout (potentially during rotation?). DCHECK to - // catch this in debug, and then in production fudge by duplicating the last - // cell. The assumption is that there will be another, correct layout - // shortly after the incorrect one. - DCHECK_LT(itemIndex, self.items.count); - // Outside of debug builds, keep array bounds valid. - if (itemIndex >= self.items.count) - itemIndex = self.items.count - 1; + if ([self isIndexPathForPlusSignCell:indexPath]) { + cell = [collectionView + dequeueReusableCellWithReuseIdentifier:kPlusSignCellIdentifier + forIndexPath:indexPath]; + PlusSignCell* plusSignCell = + base::mac::ObjCCastStrict<PlusSignCell>(cell); + plusSignCell.theme = self.theme; + } else { + // In some cases this is called with an indexPath.item that's beyond (by + // 1) the bounds of self.items -- see crbug.com/1068136. Presumably this + // is a race condition where an item has been deleted at the same time as + // the collection is doing layout (potentially during rotation?). DCHECK + // to catch this in debug, and then in production fudge by duplicating the + // last cell. The assumption is that there will be another, correct layout + // shortly after the incorrect one. + DCHECK_LT(itemIndex, self.items.count); + // Outside of debug builds, keep array bounds valid. + if (itemIndex >= self.items.count) + itemIndex = self.items.count - 1; - TabSwitcherItem* item = self.items[itemIndex]; - cell = - [collectionView dequeueReusableCellWithReuseIdentifier:kCellIdentifier - forIndexPath:indexPath]; - cell.accessibilityIdentifier = [NSString - stringWithFormat:@"%@%ld", kGridCellIdentifierPrefix, itemIndex]; - GridCell* gridCell = base::mac::ObjCCastStrict<GridCell>(cell); - [self configureCell:gridCell withItem:item]; + TabSwitcherItem* item = self.items[itemIndex]; + cell = + [collectionView dequeueReusableCellWithReuseIdentifier:kCellIdentifier + forIndexPath:indexPath]; + cell.accessibilityIdentifier = [NSString + stringWithFormat:@"%@%ld", kGridCellIdentifierPrefix, itemIndex]; + GridCell* gridCell = base::mac::ObjCCastStrict<GridCell>(cell); + [self configureCell:gridCell withItem:item]; + } } // Set the z index of cells so that lower rows are moving behind the upper // rows during transitions between grid and horizontal layouts. @@ -444,6 +514,31 @@ #pragma mark - UICollectionViewDelegate +- (CGSize)collectionView:(UICollectionView*)collectionView + layout:(UICollectionViewLayout*)collectionViewLayout + sizeForItemAtIndexPath:(NSIndexPath*)indexPath { + // |collectionViewLayout| should always be a flow layout. + DCHECK( + [collectionViewLayout isKindOfClass:[UICollectionViewFlowLayout class]]); + UICollectionViewFlowLayout* layout = + (UICollectionViewFlowLayout*)collectionViewLayout; + CGSize itemSize = layout.itemSize; + // The SuggestedActions cell can't use the item size that is set in + // |prepareLayout| of the layout class. For that specific cell calculate the + // anticipated size from the layout section insets and the content view insets + // and return it. + if (IsTabsSearchRegularResultsSuggestedActionsEnabled() && + indexPath.section == kSuggestedActionsSectionIndex) { + UIEdgeInsets sectionInset = layout.sectionInset; + CGFloat width = CGRectGetWidth(collectionView.bounds) - + self.gridView.contentInset.left - sectionInset.right - + self.gridView.contentInset.right; + CGFloat height = self.suggestedActionsViewController.contentHeight; + return CGSizeMake(width, height); + } + return itemSize; +} + // This prevents the user from dragging a cell past the plus sign cell (the last // cell in the collection view). - (NSIndexPath*)collectionView:(UICollectionView*)collectionView @@ -480,6 +575,12 @@ if (_mode == TabGridModeSelection) { return nil; } + + // No context menu on suggested actions section. + if (indexPath.section == kSuggestedActionsSectionIndex) { + return nil; + } + // No context menu on plus sign cell. if ([self isIndexPathForPlusSignCell:indexPath]) { return nil; @@ -540,6 +641,11 @@ // Return an empty array because the plus sign cell should not be dragged. return @[]; } + if (indexPath.section == kSuggestedActionsSectionIndex) { + // Return an empty array because ther suggested actions cell should not be + // dragged. + return @[]; + } if (_mode != TabGridModeSelection) { TabSwitcherItem* item = self.items[indexPath.item]; return @[ [self.dragDropHandler dragItemForItemWithID:item.identifier] ]; @@ -575,6 +681,12 @@ // Return nil so that the plus sign cell doesn't superpose the dragged cell. return nil; } + if (indexPath.section == kSuggestedActionsSectionIndex) { + // Return nil so that the suggested actions cell doesn't superpose the + // dragged cell. + return nil; + } + GridCell* gridCell = base::mac::ObjCCastStrict<GridCell>( [self.collectionView cellForItemAtIndexPath:indexPath]); return gridCell.dragPreviewParameters; @@ -698,6 +810,36 @@ } } +#pragma mark-- SuggestedActionsViewControllerDelegate + +- (void)suggestedActionsViewController: + (SuggestedActionsViewController*)viewController + fetchHistoryResultsCountWithCompletion:(void (^)(size_t))completion { + [self.suggestedActionsDelegate + fetchSearchHistoryResultsCountForText:self.searchText + completion:completion]; +} + +- (void)didSelectSearchHistoryInSuggestedActionsViewController: + (SuggestedActionsViewController*)viewController { + base::RecordAction( + base::UserMetricsAction("TabsSearch.SuggestedActions.SearchHistory")); + [self.suggestedActionsDelegate searchHistoryForText:self.searchText]; +} + +- (void)didSelectSearchRecentTabsInSuggestedActionsViewController: + (SuggestedActionsViewController*)viewController { + // TODO(crbug.com/1297859): Log the user action. + [self.suggestedActionsDelegate searchRecentTabsForText:self.searchText]; +} + +- (void)didSelectSearchWebInSuggestedActionsViewController: + (SuggestedActionsViewController*)viewController { + base::RecordAction( + base::UserMetricsAction("TabsSearch.SuggestedActions.SearchOnWeb")); + [self.suggestedActionsDelegate searchWebForText:self.searchText]; +} + #pragma mark - IncognitoReauthConsumer - (void)setItemsRequireAuthentication:(BOOL)require { @@ -760,10 +902,10 @@ [self.collectionView selectItemAtIndexPath:CreateIndexPath(self.selectedIndex) animated:NO scrollPosition:UICollectionViewScrollPositionTop]; - if (self.items.count > 0) { - [self removeEmptyStateAnimated:YES]; - } else { + if ([self shouldShowEmptyState]) { [self animateEmptyStateIn]; + } else { + [self removeEmptyStateAnimated:YES]; } // Whether the view is visible or not, the delegate must be updated. [self.delegate gridViewController:self didChangeItemCount:self.items.count]; @@ -827,7 +969,7 @@ }; auto collectionViewUpdates = ^{ [self.collectionView deleteItemsAtIndexPaths:@[ CreateIndexPath(index) ]]; - if (self.items.count == 0) { + if ([self shouldShowEmptyState]) { [self animateEmptyStateIn]; } }; @@ -1309,6 +1451,43 @@ } } +- (BOOL)shouldShowEmptyState { + if (IsTabsSearchRegularResultsSuggestedActionsEnabled() && + self.showingSuggestedActions) { + return NO; + } + return self.items.count == 0; +} + +#pragma mark Suggested Actions Section + +- (void)updateSuggestedActionsSection { + DCHECK(IsTabsSearchEnabled()); + if (!self.suggestedActionsDelegate) + return; + // In search mode if there is already a search query, and the suggested + // actions section section is not yet added, add it. otherwise remove the + // section if it exists and the search mode is not active. + auto updateSectionBlock = ^{ + NSIndexSet* sections = + [NSIndexSet indexSetWithIndex:kSuggestedActionsSectionIndex]; + if (self.mode == TabGridModeSearch && self.searchText.length) { + if (!self.showingSuggestedActions) { + [self.collectionView insertSections:sections]; + self.showingSuggestedActions = YES; + } + } else { + if (self.showingSuggestedActions) { + [self.collectionView deleteSections:sections]; + self.showingSuggestedActions = NO; + } + } + }; + [UIView performWithoutAnimation:^{ + [self.collectionView performBatchUpdates:updateSectionBlock completion:nil]; + }]; +} + #pragma mark - Public Editing Mode Selection - (void)selectAllItemsForEditing { if (_mode != TabGridModeSelection) {
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/BUILD.gn b/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/BUILD.gn new file mode 100644 index 0000000..e4eccd32 --- /dev/null +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/BUILD.gn
@@ -0,0 +1,44 @@ +# Copyright 2022 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("//ios/public/provider/chrome/browser/build_config.gni") + +source_set("suggested_actions") { + sources = [ + "suggested_actions_delegate.h", + "suggested_actions_grid_cell.h", + "suggested_actions_grid_cell.mm", + "suggested_actions_view_controller.h", + "suggested_actions_view_controller.mm", + ] + + configs += [ "//build/config/compiler:enable_arc" ] + + deps = [ + "//base", + "//ios/chrome/app/strings:ios_strings_grit", + "//ios/chrome/browser/ui/tab_switcher/tab_grid/grid:grid_ui_constants", + "//ios/chrome/browser/ui/table_view", + "//ios/chrome/browser/ui/table_view:styler", + "//ios/chrome/browser/ui/table_view:utils", + "//ios/chrome/common/ui/util:util", + "//ui/base:base", + ] +} + +source_set("unit_tests") { + configs += [ "//build/config/compiler:enable_arc" ] + testonly = true + sources = [ "suggested_actions_view_controller_unittest.mm" ] + deps = [ + ":suggested_actions", + "//base", + "//base/test:test_support", + "//ios/chrome/app/strings:ios_strings_grit", + "//ios/chrome/browser/ui/table_view", + "//ios/chrome/browser/ui/table_view:test_support", + "//third_party/ocmock", + "//ui/base:base", + ] +}
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_delegate.h b/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_delegate.h new file mode 100644 index 0000000..7019777 --- /dev/null +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_delegate.h
@@ -0,0 +1,32 @@ +// Copyright 2022 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 IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_GRID_SUGGESTED_ACTIONS_SUGGESTED_ACTIONS_DELEGATE_H_ +#define IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_GRID_SUGGESTED_ACTIONS_SUGGESTED_ACTIONS_DELEGATE_H_ + +#import <Foundation/Foundation.h> + +// Protocol used to issue commands related to search suggested actions. +@protocol SuggestedActionsDelegate <NSObject> + +// Tells the delegate to fetch the search history results count for +// |searchText| and provide it to the |completion| block. +- (void)fetchSearchHistoryResultsCountForText:(NSString*)searchText + completion:(void (^)(size_t))completion; + +// Asks the delegate to open a history modal and filter the history +// |searchText|. +- (void)searchHistoryForText:(NSString*)searchText; + +// Asks the delegate to open a new tab and use the default search engine to +// search for |searchText| in web. +- (void)searchWebForText:(NSString*)searchText; + +// Asks the delegate to switch the tab grid page to recent tabs and filter the +// page content by |searchText|. +- (void)searchRecentTabsForText:(NSString*)searchText; + +@end + +#endif // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_GRID_SUGGESTED_ACTIONS_SUGGESTED_ACTIONS_DELEGATE_H_
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_grid_cell.h b/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_grid_cell.h new file mode 100644 index 0000000..e5003d6 --- /dev/null +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_grid_cell.h
@@ -0,0 +1,16 @@ +// Copyright 2022 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 IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_GRID_SUGGESTED_ACTIONS_SUGGESTED_ACTIONS_GRID_CELL_H_ +#define IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_GRID_SUGGESTED_ACTIONS_SUGGESTED_ACTIONS_GRID_CELL_H_ + +#import <UIKit/UIKit.h> + +// A cell in a grid that contains a table of suggested actions. +@interface SuggestedActionsGridCell : UICollectionViewCell +// The view to be added in the content of the cell. +@property(nonatomic, weak) UIView* suggestedActionsView; +@end + +#endif // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_GRID_SUGGESTED_ACTIONS_SUGGESTED_ACTIONS_GRID_CELL_H_
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_grid_cell.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_grid_cell.mm new file mode 100644 index 0000000..d156ec6 --- /dev/null +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_grid_cell.mm
@@ -0,0 +1,61 @@ +// Copyright 2022 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 "ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_grid_cell.h" + +#import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_constants.h" +#import "ios/chrome/common/ui/util/constraints_ui_util.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +@implementation SuggestedActionsGridCell + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.layer.cornerRadius = kGridCellCornerRadius; + self.layer.masksToBounds = YES; + self.overrideUserInterfaceStyle = UIUserInterfaceStyleDark; + self.backgroundView = [[UIView alloc] init]; + self.backgroundView.backgroundColor = + [UIColor colorNamed:kGridBackgroundColor]; + } + return self; +} + +#pragma mark UICollectionViewCell Overrides + +- (void)prepareForReuse { + [super prepareForReuse]; + _suggestedActionsView = nil; +} + +#pragma mark - Public + +- (void)setSuggestedActionsView:(UIView*)view { + if (view == _suggestedActionsView) + return; + if (_suggestedActionsView) + [_suggestedActionsView removeFromSuperview]; + _suggestedActionsView = view; + _suggestedActionsView.translatesAutoresizingMaskIntoConstraints = NO; + [self.contentView addSubview:_suggestedActionsView]; + NSArray* constraints = @[ + [_suggestedActionsView.centerYAnchor + constraintEqualToAnchor:self.contentView.centerYAnchor], + [_suggestedActionsView.topAnchor + constraintEqualToAnchor:self.contentView.topAnchor], + [_suggestedActionsView.bottomAnchor + constraintEqualToAnchor:self.contentView.bottomAnchor], + [_suggestedActionsView.leadingAnchor + constraintEqualToAnchor:self.contentView.leadingAnchor], + [_suggestedActionsView.widthAnchor + constraintEqualToAnchor:self.contentView.widthAnchor] + ]; + [NSLayoutConstraint activateConstraints:constraints]; +} + +@end
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_view_controller.h b/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_view_controller.h new file mode 100644 index 0000000..dc088a3f6 --- /dev/null +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_view_controller.h
@@ -0,0 +1,53 @@ +// Copyright 2022 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 IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_GRID_SUGGESTED_ACTIONS_SUGGESTED_ACTIONS_VIEW_CONTROLLER_H_ +#define IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_GRID_SUGGESTED_ACTIONS_SUGGESTED_ACTIONS_VIEW_CONTROLLER_H_ + +#import "ios/chrome/browser/ui/table_view/chrome_table_view_controller.h" + +@protocol SuggestedActionsDelegate; +@class SuggestedActionsViewController; + +// Protocol used to relay relevant user interactions from the view. +@protocol SuggestedActionsViewControllerDelegate +// Tells the delegate that the user tapped on the search in history item. +- (void)didSelectSearchHistoryInSuggestedActionsViewController: + (SuggestedActionsViewController*)viewController; +// Tells the delegate that the user tapped on the search in reecent tabs item. +- (void)didSelectSearchRecentTabsInSuggestedActionsViewController: + (SuggestedActionsViewController*)viewController; +// Tells the delegate that the user tapped on search in web item. +- (void)didSelectSearchWebInSuggestedActionsViewController: + (SuggestedActionsViewController*)viewController; +// Asks the delegate to fetch the history results count and execute |completion| +// with it. +- (void)suggestedActionsViewController: + (SuggestedActionsViewController*)viewController + fetchHistoryResultsCountWithCompletion:(void (^)(size_t))completion; + +@end + +// SuggestedActionsViewController represents the suggestions will appear on +// the tab grid to extend the users search journey beyond the current active +// page of the tab grid. +@interface SuggestedActionsViewController : ChromeTableViewController + +- (instancetype)initWithDelegate: + (id<SuggestedActionsViewControllerDelegate>)delegate + NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithStyle:(UITableViewStyle)style NS_UNAVAILABLE; + +// The delegate to handle interactions with view. +@property(nonatomic, weak) id<SuggestedActionsViewControllerDelegate> delegate; +// The current search term associated that the suggestion actions are being +// presented for. +@property(nonatomic, copy) NSString* searchText; +@property(nonatomic, readonly) CGFloat contentHeight; + +@end + +#endif // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_GRID_SUGGESTED_ACTIONS_SUGGESTED_ACTIONS_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_view_controller.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_view_controller.mm new file mode 100644 index 0000000..1929544 --- /dev/null +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_view_controller.mm
@@ -0,0 +1,188 @@ +// Copyright 2022 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 "ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_view_controller.h" + +#import <utility> + +#include "base/check_op.h" +#include "base/mac/foundation_util.h" +#import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_constants.h" +#import "ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_delegate.h" +#import "ios/chrome/browser/ui/table_view/cells/table_view_disclosure_header_footer_item.h" +#import "ios/chrome/browser/ui/table_view/cells/table_view_image_item.h" +#import "ios/chrome/browser/ui/table_view/cells/table_view_item.h" +#import "ios/chrome/browser/ui/table_view/cells/table_view_tabs_search_suggested_history_item.h" +#import "ios/chrome/browser/ui/table_view/cells/table_view_text_button_item.h" +#import "ios/chrome/browser/ui/table_view/cells/table_view_text_header_footer_item.h" +#import "ios/chrome/browser/ui/table_view/cells/table_view_text_item.h" +#import "ios/chrome/browser/ui/table_view/cells/table_view_url_item.h" +#import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h" +#import "ios/chrome/browser/ui/table_view/table_view_utils.h" +#import "ios/chrome/browser/ui/util/uikit_ui_util.h" +#include "ios/chrome/grit/ios_strings.h" +#include "ui/base/l10n/l10n_util.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +namespace { + +const CGFloat kEstimatedRowHeight = 56; +NSString* const kSuggestedActionsViewControllerAccessibilityIdentifier = + @"search_suggestions_view_controller"; +const int kSectionIdentifierSuggestedActions = kSectionIdentifierEnumZero + 1; + +} // namespace + +typedef NS_ENUM(NSInteger, ItemType) { + ItemTypeSuggestedActionsHeader = kItemTypeEnumZero, + ItemTypeSuggestedActionSearchRecentTabs, + ItemTypeSuggestedActionSearchWeb, + ItemTypeSuggestedActionSearchHistory, +}; + +@interface SuggestedActionsViewController () + +// Delegate to handle the execution of the suggested actions. +@property(nonatomic, weak) id<SuggestedActionsDelegate> + suggestedActionsDelegate; + +@end + +@implementation SuggestedActionsViewController + +- (instancetype)initWithDelegate: + (id<SuggestedActionsViewControllerDelegate>)delegate { + self = [super initWithStyle:ChromeTableViewStyle()]; + + if (self) { + _delegate = delegate; + self.styler.tableViewBackgroundColor = + [UIColor colorNamed:kGridBackgroundColor]; + } + return self; +} + +#pragma mark - ViewController Lifecycle + +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.accessibilityIdentifier = + kSuggestedActionsViewControllerAccessibilityIdentifier; + self.tableView.cellLayoutMarginsFollowReadableWidth = YES; + self.tableView.estimatedRowHeight = kEstimatedRowHeight; + self.tableView.estimatedSectionHeaderHeight = kEstimatedRowHeight; + self.tableView.rowHeight = UITableViewAutomaticDimension; + self.tableView.sectionFooterHeight = 0.0; + self.tableView.alwaysBounceVertical = NO; + self.tableView.scrollEnabled = NO; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + [self loadModel]; + [self.tableView reloadData]; +} + +#pragma mark - TableViewModel + +- (void)loadModel { + [super loadModel]; + + TableViewModel* model = self.tableViewModel; + [model addSectionWithIdentifier:kSectionIdentifierSuggestedActions]; + TableViewTextHeaderFooterItem* header = [[TableViewTextHeaderFooterItem alloc] + initWithType:ItemTypeSuggestedActionsHeader]; + header.text = l10n_util::GetNSString(IDS_IOS_TABS_SEARCH_SUGGESTED_ACTIONS); + [model setHeader:header + forSectionWithIdentifier:kSectionIdentifierSuggestedActions]; + + TableViewImageItem* searchWebItem = [[TableViewImageItem alloc] + initWithType:ItemTypeSuggestedActionSearchWeb]; + searchWebItem.title = + l10n_util::GetNSString(IDS_IOS_TABS_SEARCH_SUGGESTED_ACTION_SEARCH_WEB); + searchWebItem.image = [[UIImage imageNamed:@"popup_menu_search"] + imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + [model addItem:searchWebItem + toSectionWithIdentifier:kSectionIdentifierSuggestedActions]; + + TableViewImageItem* searchRecentTabsItem = [[TableViewImageItem alloc] + initWithType:ItemTypeSuggestedActionSearchRecentTabs]; + searchRecentTabsItem.title = l10n_util::GetNSString( + IDS_IOS_TABS_SEARCH_SUGGESTED_ACTION_SEARCH_RECENT_TABS); + searchRecentTabsItem.image = [[UIImage imageNamed:@"popup_menu_search"] + imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + [model addItem:searchRecentTabsItem + toSectionWithIdentifier:kSectionIdentifierSuggestedActions]; + + TableViewTabsSearchSuggestedHistoryItem* searchHistoryItem = + [[TableViewTabsSearchSuggestedHistoryItem alloc] + initWithType:ItemTypeSuggestedActionSearchHistory]; + [model addItem:searchHistoryItem + toSectionWithIdentifier:kSectionIdentifierSuggestedActions]; +} + +#pragma mark - UITableViewDataSource + +- (UITableViewCell*)tableView:(UITableView*)tableView + cellForRowAtIndexPath:(NSIndexPath*)indexPath { + UITableViewCell* cell = [super tableView:tableView + cellForRowAtIndexPath:indexPath]; + ItemType itemType = static_cast<ItemType>( + [self.tableViewModel itemTypeForIndexPath:indexPath]); + + // Update the history search result count once available. + if (itemType == ItemTypeSuggestedActionSearchHistory) { + __weak TableViewTabsSearchSuggestedHistoryCell* weakCell = + base::mac::ObjCCastStrict<TableViewTabsSearchSuggestedHistoryCell>( + cell); + NSString* currentSearchText = self.searchText; + weakCell.searchTerm = currentSearchText; + [self.delegate suggestedActionsViewController:self + fetchHistoryResultsCountWithCompletion:^(size_t resultCount) { + if ([weakCell.searchTerm isEqualToString:currentSearchText]) { + [weakCell updateHistoryResultsCount:resultCount]; + } + }]; + } + return cell; +} + +#pragma mark - UITableViewDelegate + +- (void)tableView:(UITableView*)tableView + didSelectRowAtIndexPath:(NSIndexPath*)indexPath { + DCHECK_EQ(tableView, self.tableView); + [self.tableView deselectRowAtIndexPath:indexPath animated:YES]; + NSInteger itemTypeSelected = + [self.tableViewModel itemTypeForIndexPath:indexPath]; + switch (itemTypeSelected) { + case ItemTypeSuggestedActionSearchRecentTabs: + [self.delegate + didSelectSearchRecentTabsInSuggestedActionsViewController:self]; + break; + case ItemTypeSuggestedActionSearchWeb: + [self.delegate didSelectSearchWebInSuggestedActionsViewController:self]; + break; + case ItemTypeSuggestedActionSearchHistory: + [self.delegate + didSelectSearchHistoryInSuggestedActionsViewController:self]; + break; + } +} + +#pragma mark - Public + +- (void)setSearchText:(NSString*)searchText { + _searchText = searchText; + [self.tableView reloadData]; +} + +- (CGFloat)contentHeight { + return self.tableView.contentSize.height; +} + +@end
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_view_controller_unittest.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_view_controller_unittest.mm new file mode 100644 index 0000000..5cc1854 --- /dev/null +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_view_controller_unittest.mm
@@ -0,0 +1,98 @@ +// Copyright 2022 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 "ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_view_controller.h" + +#import "ios/chrome/browser/ui/table_view/cells/table_view_image_item.h" +#import "ios/chrome/browser/ui/table_view/chrome_table_view_controller_test.h" +#include "ios/chrome/grit/ios_strings.h" +#include "testing/gtest/include/gtest/gtest.h" +#import "testing/gtest_mac.h" +#include "ui/base/l10n/l10n_util_mac.h" + +#import "third_party/ocmock/OCMock/OCMock.h" +#include "third_party/ocmock/gtest_support.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +// Test fixture for testing SuggestedActionsViewController class. +class SuggestedActionsViewControllerTest + : public ChromeTableViewControllerTest { + protected: + SuggestedActionsViewControllerTest() + : delegate_(OCMProtocolMock( + @protocol(SuggestedActionsViewControllerDelegate))) {} + + ChromeTableViewController* InstantiateController() override { + return [[SuggestedActionsViewController alloc] initWithDelegate:delegate_]; + } + // Delegate mock conforming to SuggestedActionsViewControllerDelegate + // protocol. + id delegate_; +}; + +// Tests that the view controller has been initialized correctly. +TEST_F(SuggestedActionsViewControllerTest, Initialization) { + CreateController(); + CheckController(); + + ASSERT_EQ(1, NumberOfSections()); + ASSERT_EQ(3, NumberOfItemsInSection(0)); + + // This is a static table it should have 3 items in that order: (SearchWeb, + // SearchRecentTabs, SearchHistory). + TableViewImageItem* item = GetTableViewItem(0, 0); + EXPECT_NSEQ( + l10n_util::GetNSString(IDS_IOS_TABS_SEARCH_SUGGESTED_ACTION_SEARCH_WEB), + item.title); + item = GetTableViewItem(0, 1); + EXPECT_NSEQ(l10n_util::GetNSString( + IDS_IOS_TABS_SEARCH_SUGGESTED_ACTION_SEARCH_RECENT_TABS), + item.title); + item = GetTableViewItem(0, 2); + EXPECT_NSEQ( + l10n_util::GetNSString( + IDS_IOS_TABS_SEARCH_SUGGESTED_ACTION_SEARCH_HISTORY_UNKNOWN_RESULT_COUNT), + item.title); + CheckSectionHeaderWithId(IDS_IOS_TABS_SEARCH_SUGGESTED_ACTIONS, 0); +} + +// Tests that selecting the search web item should call the relevant delegate. +TEST_F(SuggestedActionsViewControllerTest, SelectSearchWebSuggestedAction) { + CreateController(); + CheckController(); + OCMExpect([delegate_ didSelectSearchWebInSuggestedActionsViewController: + (SuggestedActionsViewController*)controller()]); + [controller() tableView:controller().tableView + didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]; + EXPECT_OCMOCK_VERIFY(delegate_); +} + +// Tests that selecting the search recent tabs item should call the relevant +// delegate. +TEST_F(SuggestedActionsViewControllerTest, + SelectSearchRecentTabsSuggestedAction) { + CreateController(); + CheckController(); + OCMExpect( + [delegate_ didSelectSearchRecentTabsInSuggestedActionsViewController: + (SuggestedActionsViewController*)controller()]); + [controller() tableView:controller().tableView + didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]]; + EXPECT_OCMOCK_VERIFY(delegate_); +} + +// Tests that selecting the search history item should call the relevant +// delegate. +TEST_F(SuggestedActionsViewControllerTest, SelectSearchHistorySuggestedAction) { + CreateController(); + CheckController(); + OCMExpect([delegate_ didSelectSearchHistoryInSuggestedActionsViewController: + (SuggestedActionsViewController*)controller()]); + [controller() tableView:controller().tableView + didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]]; + EXPECT_OCMOCK_VERIFY(delegate_); +}
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm index 256b3ae..347d529 100644 --- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm
@@ -11,6 +11,7 @@ #include "base/metrics/user_metrics_action.h" #include "base/strings/sys_string_conversions.h" #include "components/bookmarks/browser/bookmark_model.h" +#include "components/search_engines/template_url_service.h" #include "components/strings/grit/components_strings.h" #include "ios/chrome/browser/bookmarks/bookmark_model_factory.h" #include "ios/chrome/browser/browser_state/chrome_browser_state.h" @@ -20,6 +21,7 @@ #import "ios/chrome/browser/policy/policy_features.h" #import "ios/chrome/browser/policy/policy_util.h" #include "ios/chrome/browser/pref_names.h" +#include "ios/chrome/browser/search_engines/template_url_service_factory.h" #include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h" #import "ios/chrome/browser/ui/activity_services/activity_params.h" #import "ios/chrome/browser/ui/alert_coordinator/action_sheet_coordinator.h" @@ -945,22 +947,42 @@ } } -#pragma mark - RecentTabsPresentationDelegate +- (void)openSearchResultsPageForSearchText:(NSString*)searchText { + TemplateURLService* templateURLService = + ios::TemplateURLServiceFactory::GetForBrowserState( + self.regularBrowser->GetBrowserState()); -- (void)showHistoryFromRecentTabsFilteredBySearchTerms:(NSString*)searchTerms { + const TemplateURL* searchURLTemplate = + templateURLService->GetDefaultSearchProvider(); + + TemplateURLRef::SearchTermsArgs searchArgs( + base::SysNSStringToUTF16(searchText)); + + GURL searchURL(searchURLTemplate->url_ref().ReplaceSearchTerms( + searchArgs, templateURLService->search_terms_data())); + [self openLinkWithURL:searchURL]; +} + +- (void)showHistoryFilteredBySearchText:(NSString*)searchText { // A history coordinator from main_controller won't work properly from the // tab grid. Using a local coordinator works better and we need to set // |loadStrategy| to YES to ALWAYS_NEW_FOREGROUND_TAB. self.historyCoordinator = [[HistoryCoordinator alloc] initWithBaseViewController:self.baseViewController browser:self.regularBrowser]; - self.historyCoordinator.searchTerms = searchTerms; + self.historyCoordinator.searchTerms = searchText; self.historyCoordinator.loadStrategy = UrlLoadStrategy::ALWAYS_NEW_FOREGROUND_TAB; self.historyCoordinator.presentationDelegate = self; [self.historyCoordinator start]; } +#pragma mark - RecentTabsPresentationDelegate + +- (void)showHistoryFromRecentTabsFilteredBySearchTerms:(NSString*)searchTerms { + [self showHistoryFilteredBySearchText:searchTerms]; +} + - (void)showActiveRegularTabFromRecentTabs { [self.delegate tabGrid:self shouldActivateBrowser:self.regularBrowser
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_mediator.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_mediator.mm index 21a42eb..04c88f5 100644 --- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_mediator.mm +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_mediator.mm
@@ -602,6 +602,17 @@ [self populateConsumerItems]; } +- (void)fetchSearchHistoryResultsCountForText:(NSString*)searchText + completion:(void (^)(size_t))completion { + TabsSearchService* search_service = + TabsSearchServiceFactory::GetForBrowserState(self.browserState); + const std::u16string& searchTerm = base::SysNSStringToUTF16(searchText); + search_service->SearchHistory(searchTerm, + base::BindOnce(^(size_t resultCount) { + completion(resultCount); + })); +} + #pragma mark GridCommands helpers - (void)insertNewItemAtIndex:(NSUInteger)index withURL:(const GURL&)newTabURL {
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.h b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.h index ea5ff9ac..3e2b843b 100644 --- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.h +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.h
@@ -74,6 +74,13 @@ // back. - (void)showFullscreen:(BOOL)fullscreen; +// Asks the delegate to open history modal with results filtered by +// |searchText|. +- (void)showHistoryFilteredBySearchText:(NSString*)searchText; + +// Asks the delegate to open a new tab page with a web search for |searchText|. +- (void)openSearchResultsPageForSearchText:(NSString*)searchText; + @end // View controller representing a tab switcher. The tab switcher has an
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm index 07ea671..1d38a8d 100644 --- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm
@@ -27,6 +27,7 @@ #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_drag_drop_handler.h" #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_image_data_source.h" #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.h" +#import "ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_delegate.h" #import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.h" #import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_constants.h" #import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_empty_state_view.h" @@ -123,6 +124,7 @@ @interface TabGridViewController () <DisabledTabViewControllerDelegate, GridViewControllerDelegate, + SuggestedActionsDelegate, LayoutSwitcher, UIScrollViewAccessibilityDelegate, UISearchBarDelegate, @@ -1123,6 +1125,7 @@ kTabGridIncognitoTabsEmptyStateIdentifier; viewController.theme = GridThemeDark; viewController.delegate = self; + viewController.suggestedActionsDelegate = self; viewController.dragDropHandler = self.incognitoTabsDragDropHandler; NSArray* constraints = @[ [viewController.view.topAnchor @@ -1154,7 +1157,7 @@ viewController.theme = GridThemeLight; viewController.delegate = self; viewController.dragDropHandler = self.regularTabsDragDropHandler; - + viewController.suggestedActionsDelegate = self; UIViewController* leadingSideViewController = self.incognitoTabsViewController ? self.incognitoTabsViewController @@ -1855,10 +1858,17 @@ - (void)searchBar:(UISearchBar*)searchBar textDidChange:(NSString*)searchText { [self updateScrimVisibilityForText:searchText]; - - [self.incognitoTabsDelegate searchItemsWithText:searchText]; - [self.regularTabsDelegate searchItemsWithText:searchText]; - self.remoteTabsViewController.searchTerms = searchText; + switch (self.currentPage) { + case TabGridPageIncognitoTabs: + [self.incognitoTabsDelegate searchItemsWithText:searchText]; + break; + case TabGridPageRegularTabs: + case TabGridPageRemoteTabs: + self.regularTabsViewController.searchText = searchText; + [self.regularTabsDelegate searchItemsWithText:searchText]; + self.remoteTabsViewController.searchTerms = searchText; + break; + } } - (void)updateScrimVisibilityForText:(NSString*)searchText { @@ -1871,6 +1881,34 @@ } } +#pragma mark - SuggestedActionsDelegate + +- (void)fetchSearchHistoryResultsCountForText:(NSString*)searchText + completion:(void (^)(size_t))completion { + if (self.currentPage == TabGridPageIncognitoTabs) { + // History retrival shouldn't be done from incognito tabs page. + completion(0); + return; + } + [self.regularTabsDelegate fetchSearchHistoryResultsCountForText:searchText + completion:completion]; +} + +- (void)searchHistoryForText:(NSString*)searchText { + DCHECK(self.tabGridMode == TabGridModeSearch); + [self.delegate showHistoryFilteredBySearchText:searchText]; +} + +- (void)searchWebForText:(NSString*)searchText { + DCHECK(self.tabGridMode == TabGridModeSearch); + [self.delegate openSearchResultsPageForSearchText:searchText]; +} + +- (void)searchRecentTabsForText:(NSString*)searchText { + DCHECK(self.tabGridMode == TabGridModeSearch); + [self setCurrentPageAndPageControl:TabGridPageRemoteTabs animated:YES]; +} + #pragma mark - ThumbStripSupporting - (BOOL)isThumbStripEnabled {
diff --git a/ios/chrome/test/BUILD.gn b/ios/chrome/test/BUILD.gn index 8c82994..ab3f3623 100644 --- a/ios/chrome/test/BUILD.gn +++ b/ios/chrome/test/BUILD.gn
@@ -351,6 +351,7 @@ "//ios/chrome/browser/ui/start_surface:unit_tests", "//ios/chrome/browser/ui/tab_switcher/tab_grid:unit_tests", "//ios/chrome/browser/ui/tab_switcher/tab_grid/grid:unit_tests", + "//ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions:unit_tests", "//ios/chrome/browser/ui/table_view:unit_tests", "//ios/chrome/browser/ui/table_view/cells:unit_tests", "//ios/chrome/browser/ui/tabs:unit_tests",
diff --git a/media/video/h265_parser.cc b/media/video/h265_parser.cc index 87b4a54..36465f76 100644 --- a/media/video/h265_parser.cc +++ b/media/video/h265_parser.cc
@@ -493,6 +493,8 @@ READ_BOOL_OR_RETURN(&sps->scaling_list_enabled_flag); if (sps->scaling_list_enabled_flag) { READ_BOOL_OR_RETURN(&sps->sps_scaling_list_data_present_flag); + } + if (sps->sps_scaling_list_data_present_flag) { res = ParseScalingListData(&sps->scaling_list_data); if (res != kOk) return res;
diff --git a/mojo/public/cpp/test_support/test_utils.h b/mojo/public/cpp/test_support/test_utils.h index d2a6225..6247c52 100644 --- a/mojo/public/cpp/test_support/test_utils.h +++ b/mojo/public/cpp/test_support/test_utils.h
@@ -40,14 +40,16 @@ // structure using the struct traits. This allows malformed data to be put in // the StructPtr<MojomType>, in order to verify the behaviour of deserialization // back to the C++ structure type. -template < - typename MojomType, - typename UserStructType, - typename MojomStructPtr, - std::enable_if_t<std::is_same<mojo::StructPtr<MojomType>, - std::remove_const_t<MojomStructPtr>>::value && - !std::is_enum<UserStructType>::value, - int> = 0> +template <typename MojomType, + typename UserStructType, + typename MojomStructPtr, + std::enable_if_t< + (std::is_same<mojo::InlinedStructPtr<MojomType>, + std::remove_const_t<MojomStructPtr>>::value || + std::is_same<mojo::StructPtr<MojomType>, + std::remove_const_t<MojomStructPtr>>::value) && + !std::is_enum<UserStructType>::value, + int> = 0> bool SerializeAndDeserialize(MojomStructPtr& input, UserStructType& output) { mojo::Message message = MojomType::SerializeAsMessage(&input);
diff --git a/net/dns/dns_config_service.cc b/net/dns/dns_config_service.cc index 1aafa73..69aa66c 100644 --- a/net/dns/dns_config_service.cc +++ b/net/dns/dns_config_service.cc
@@ -137,17 +137,15 @@ std::make_unique<DnsHostsFileParser>(hosts_file_path_)); } -bool DnsConfigService::HostsReader::OnWorkFinished( +void DnsConfigService::HostsReader::OnWorkFinished( std::unique_ptr<SerialWorker::WorkItem> serial_worker_work_item) { DCHECK(serial_worker_work_item); WorkItem* work_item = static_cast<WorkItem*>(serial_worker_work_item.get()); if (work_item->hosts_.has_value()) { service_->OnHostsRead(std::move(work_item->hosts_).value()); - return true; } else { LOG(WARNING) << "Failed to read DnsHosts."; - return false; } }
diff --git a/net/dns/dns_config_service.h b/net/dns/dns_config_service.h index 6afc7c0..7f116694 100644 --- a/net/dns/dns_config_service.h +++ b/net/dns/dns_config_service.h
@@ -156,7 +156,7 @@ // SerialWorker: std::unique_ptr<SerialWorker::WorkItem> CreateWorkItem() override; - bool OnWorkFinished( + void OnWorkFinished( std::unique_ptr<SerialWorker::WorkItem> work_item) final; private:
diff --git a/net/dns/dns_config_service_android.cc b/net/dns/dns_config_service_android.cc index f81856e5..2110fc8 100644 --- a/net/dns/dns_config_service_android.cc +++ b/net/dns/dns_config_service_android.cc
@@ -102,7 +102,7 @@ return std::make_unique<WorkItem>(dns_server_getter_); } - bool OnWorkFinished(std::unique_ptr<SerialWorker::WorkItem> + void OnWorkFinished(std::unique_ptr<SerialWorker::WorkItem> serial_worker_work_item) override { DCHECK(serial_worker_work_item); DCHECK(!IsCancelled()); @@ -110,10 +110,8 @@ WorkItem* work_item = static_cast<WorkItem*>(serial_worker_work_item.get()); if (work_item->dns_config_.has_value()) { service_->OnConfigRead(std::move(work_item->dns_config_).value()); - return true; } else { LOG(WARNING) << "Failed to read DnsConfig."; - return false; } }
diff --git a/net/dns/dns_config_service_linux.cc b/net/dns/dns_config_service_linux.cc index b762a2fc..ffa7ad5 100644 --- a/net/dns/dns_config_service_linux.cc +++ b/net/dns/dns_config_service_linux.cc
@@ -414,7 +414,7 @@ return std::move(work_item_); } - bool OnWorkFinished(std::unique_ptr<SerialWorker::WorkItem> + void OnWorkFinished(std::unique_ptr<SerialWorker::WorkItem> serial_worker_work_item) override { DCHECK(serial_worker_work_item); DCHECK(!work_item_); @@ -423,10 +423,8 @@ work_item_.reset(static_cast<WorkItem*>(serial_worker_work_item.release())); if (work_item_->dns_config_.has_value()) { service_->OnConfigRead(std::move(work_item_->dns_config_).value()); - return true; } else { LOG(WARNING) << "Failed to read DnsConfig."; - return false; } }
diff --git a/net/dns/dns_config_service_posix.cc b/net/dns/dns_config_service_posix.cc index 3cce5f20..e59fec6 100644 --- a/net/dns/dns_config_service_posix.cc +++ b/net/dns/dns_config_service_posix.cc
@@ -182,7 +182,7 @@ return std::make_unique<WorkItem>(); } - bool OnWorkFinished(std::unique_ptr<SerialWorker::WorkItem> + void OnWorkFinished(std::unique_ptr<SerialWorker::WorkItem> serial_worker_work_item) override { DCHECK(serial_worker_work_item); DCHECK(!IsCancelled()); @@ -190,10 +190,8 @@ WorkItem* work_item = static_cast<WorkItem*>(serial_worker_work_item.get()); if (work_item->dns_config_.has_value()) { service_->OnConfigRead(std::move(work_item->dns_config_).value()); - return true; } else { LOG(WARNING) << "Failed to read DnsConfig."; - return false; } }
diff --git a/net/dns/dns_config_service_win.cc b/net/dns/dns_config_service_win.cc index a3cab6e..350b601 100644 --- a/net/dns/dns_config_service_win.cc +++ b/net/dns/dns_config_service_win.cc
@@ -28,6 +28,8 @@ #include "base/synchronization/lock.h" #include "base/task/single_thread_task_runner.h" #include "base/threading/scoped_blocking_call.h" +#include "base/threading/sequenced_task_runner_handle.h" +#include "base/time/time.h" #include "base/win/registry.h" #include "base/win/scoped_handle.h" #include "base/win/windows_types.h" @@ -44,6 +46,9 @@ namespace { +// Interval between retries to parse config. Used only until parsing succeeds. +const int kRetryIntervalSeconds = 5; + // Registry key paths. const wchar_t kTcpipPath[] = L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters"; @@ -486,8 +491,7 @@ // Reads config from registry and IpHelper. All work performed in ThreadPool. class DnsConfigServiceWin::ConfigReader : public SerialWorker { public: - explicit ConfigReader(DnsConfigServiceWin& service) - : SerialWorker(/*max_number_of_retries=*/3), service_(&service) {} + explicit ConfigReader(DnsConfigServiceWin& service) : service_(&service) {} ~ConfigReader() override {} // SerialWorker:: @@ -495,7 +499,7 @@ return std::make_unique<WorkItem>(); } - bool OnWorkFinished(std::unique_ptr<SerialWorker::WorkItem> + void OnWorkFinished(std::unique_ptr<SerialWorker::WorkItem> serial_worker_work_item) override { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(serial_worker_work_item); @@ -504,10 +508,12 @@ WorkItem* work_item = static_cast<WorkItem*>(serial_worker_work_item.get()); if (work_item->dns_config_.has_value()) { service_->OnConfigRead(std::move(work_item->dns_config_).value()); - return true; } else { LOG(WARNING) << "Failed to read DnsConfig."; - return false; + // Try again in a while in case DnsConfigWatcher missed the signal. + base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, base::BindOnce(&ConfigReader::WorkNow, AsWeakPtr()), + base::Seconds(kRetryIntervalSeconds)); } }
diff --git a/net/dns/dns_response_result_extractor.cc b/net/dns/dns_response_result_extractor.cc index 123c7777..ac3048f3 100644 --- a/net/dns/dns_response_result_extractor.cc +++ b/net/dns/dns_response_result_extractor.cc
@@ -51,19 +51,18 @@ void SaveMetricsForAdditionalHttpsRecord(const RecordParsed& record, bool is_unsolicited) { const HttpsRecordRdata* rdata = record.rdata<HttpsRecordRdata>(); + DCHECK(rdata); // These values are persisted to logs. Entries should not be renumbered and // numeric values should never be reused. enum class UnsolicitedHttpsRecordStatus { - kMalformed = 0, + kMalformed = 0, // No longer recorded. kAlias = 1, kService = 2, kMaxValue = kService } status; - if (!rdata || rdata->IsMalformed()) { - status = UnsolicitedHttpsRecordStatus::kMalformed; - } else if (rdata->IsAlias()) { + if (rdata->IsAlias()) { status = UnsolicitedHttpsRecordStatus::kAlias; } else { status = UnsolicitedHttpsRecordStatus::kService; @@ -457,10 +456,7 @@ return extraction_error; } -// TODO(crbug.com/1203426): Remove `malformed_record_is_fatal` and make it -// always fatal once HTTPS queries are no longer done for pure experimental use. ExtractionError ExtractHttpsResults(const DnsResponse& response, - bool malformed_record_is_fatal, HostCache::Entry* out_results) { DCHECK(out_results); @@ -476,21 +472,19 @@ return extraction_error; } - // Record experimental result bools for each record. - std::vector<bool> condensed_results; + // Record record compatibility (draft-ietf-dnsop-svcb-https-08#section-8) for + // each record. + std::vector<bool> record_compatibility; for (const auto& record : records) { - const HttpsRecordRdata& rdata = *record->rdata<HttpsRecordRdata>(); - if (rdata.IsMalformed() && malformed_record_is_fatal) { - *out_results = HostCache::Entry(ERR_DNS_MALFORMED_RESPONSE, - HostCache::Entry::SOURCE_DNS); - return ExtractionError::kMalformedRecord; - } - condensed_results.push_back(!rdata.IsMalformed()); + const HttpsRecordRdata* rdata = record->rdata<HttpsRecordRdata>(); + DCHECK(rdata); + record_compatibility.push_back(rdata->IsAlias() || + rdata->AsServiceForm()->IsCompatible()); } // TODO(crbug.com/1225776): Output a non-experimental result representation. *out_results = HostCache::Entry(records.empty() ? ERR_NAME_NOT_RESOLVED : OK, - std::move(condensed_results), + std::move(record_compatibility), HostCache::Entry::SOURCE_DNS, response_ttl); DCHECK_EQ(extraction_error, ExtractionError::kOk); return extraction_error; @@ -530,11 +524,8 @@ case DnsQueryType::INTEGRITY: return ExtractIntegrityResults(*response_, out_results); case DnsQueryType::HTTPS: - return ExtractHttpsResults(*response_, /*malformed_record_is_fatal=*/true, - out_results); case DnsQueryType::HTTPS_EXPERIMENTAL: - return ExtractHttpsResults( - *response_, /*malformed_record_is_fatal=*/false, out_results); + return ExtractHttpsResults(*response_, out_results); } }
diff --git a/net/dns/dns_response_result_extractor_unittest.cc b/net/dns/dns_response_result_extractor_unittest.cc index ac0ffa7..39a7389 100644 --- a/net/dns/dns_response_result_extractor_unittest.cc +++ b/net/dns/dns_response_result_extractor_unittest.cc
@@ -658,8 +658,8 @@ DnsResponseResultExtractor::ExtractionError::kOk); EXPECT_THAT(results.error(), test::IsOk()); - EXPECT_THAT(results.experimental_results(), - testing::Optional(testing::ElementsAre(true))); + EXPECT_THAT(results.https_record_compatibility(), + testing::Pointee(testing::ElementsAre(true))); ASSERT_TRUE(results.has_ttl()); EXPECT_EQ(results.ttl(), kTtl); @@ -683,8 +683,8 @@ DnsResponseResultExtractor::ExtractionError::kOk); EXPECT_THAT(results.error(), test::IsError(ERR_NAME_NOT_RESOLVED)); - EXPECT_THAT(results.experimental_results(), - testing::Optional(testing::IsEmpty())); + EXPECT_THAT(results.https_record_compatibility(), + testing::Pointee(testing::IsEmpty())); ASSERT_TRUE(results.has_ttl()); EXPECT_EQ(results.ttl(), kTtl); @@ -706,15 +706,14 @@ DnsResponseResultExtractor::ExtractionError::kOk); EXPECT_THAT(results.error(), test::IsError(ERR_NAME_NOT_RESOLVED)); - EXPECT_THAT(results.experimental_results(), - testing::Optional(testing::IsEmpty())); + EXPECT_THAT(results.https_record_compatibility(), + testing::Pointee(testing::IsEmpty())); ASSERT_TRUE(results.has_ttl()); EXPECT_EQ(results.ttl(), kTtl); } -TEST(DnsResponseResultExtractorTest, - RecognizesMalformedExperimentalHttpsRecord) { +TEST(DnsResponseResultExtractorTest, RejectsMalformedExperimentalHttpsRecord) { DnsResponse response = BuildTestDnsResponse( "https.test", dns_protocol::kTypeHttps, {BuildTestDnsRecord("https.test", dns_protocol::kTypeHttps, @@ -724,13 +723,10 @@ HostCache::Entry results(ERR_FAILED, HostCache::Entry::SOURCE_UNKNOWN); EXPECT_EQ( extractor.ExtractDnsResults(DnsQueryType::HTTPS_EXPERIMENTAL, &results), - DnsResponseResultExtractor::ExtractionError::kOk); + DnsResponseResultExtractor::ExtractionError::kMalformedRecord); - // HTTPS_EXPERIMENTAL continues on finding malformed records to allow metrics - // to be recorded about them. - EXPECT_THAT(results.error(), test::IsOk()); - EXPECT_THAT(results.experimental_results(), - testing::Optional(testing::ElementsAre(false))); + EXPECT_THAT(results.error(), test::IsError(ERR_DNS_MALFORMED_RESPONSE)); + EXPECT_FALSE(results.has_ttl()); } TEST(DnsResponseResultExtractorTest, RejectsWrongNameExperimentalHttpsRecord) { @@ -761,8 +757,8 @@ DnsResponseResultExtractor::ExtractionError::kOk); EXPECT_THAT(results.error(), test::IsError(ERR_NAME_NOT_RESOLVED)); - EXPECT_THAT(results.experimental_results(), - testing::Optional(testing::IsEmpty())); + EXPECT_THAT(results.https_record_compatibility(), + testing::Pointee(testing::IsEmpty())); EXPECT_FALSE(results.has_ttl()); } @@ -787,8 +783,8 @@ DnsResponseResultExtractor::ExtractionError::kOk); EXPECT_THAT(results.error(), test::IsOk()); - EXPECT_THAT(results.experimental_results(), - testing::Optional(testing::ElementsAre(true))); + EXPECT_THAT(results.https_record_compatibility(), + testing::Pointee(testing::ElementsAre(true))); ASSERT_TRUE(results.has_ttl()); EXPECT_EQ(results.ttl(), kTtl); @@ -808,8 +804,60 @@ DnsResponseResultExtractor::ExtractionError::kOk); EXPECT_THAT(results.error(), test::IsOk()); - EXPECT_THAT(results.experimental_results(), - testing::Optional(testing::ElementsAre(true))); + EXPECT_THAT(results.https_record_compatibility(), + testing::Pointee(testing::ElementsAre(true))); + + ASSERT_TRUE(results.has_ttl()); + EXPECT_EQ(results.ttl(), kTtl); +} + +TEST(DnsResponseResultExtractorTest, ExtractsCompatibleHttpsServiceResponses) { + constexpr uint16_t kMadeUpParamKey = 65411; // From the private-use block. + constexpr auto kTtl = base::Hours(11); + + DnsResponse response = BuildTestDnsResponse( + "https.test", dns_protocol::kTypeHttps, + /*answers=*/ + {BuildTestHttpsServiceRecord( + "https.test", /*priority=*/2, /*service_name=*/".", + /*params=*/{{kMadeUpParamKey, "foo"}}, kTtl)}); + DnsResponseResultExtractor extractor(&response); + + HostCache::Entry results(ERR_FAILED, HostCache::Entry::SOURCE_UNKNOWN); + EXPECT_EQ(extractor.ExtractDnsResults(DnsQueryType::HTTPS, &results), + DnsResponseResultExtractor::ExtractionError::kOk); + + EXPECT_THAT(results.error(), test::IsOk()); + EXPECT_THAT(results.https_record_compatibility(), + testing::Pointee(testing::ElementsAre(true))); + + ASSERT_TRUE(results.has_ttl()); + EXPECT_EQ(results.ttl(), kTtl); +} + +TEST(DnsResponseResultExtractorTest, + ExtractsIncompatibleHttpsServiceResponses) { + constexpr uint16_t kMadeUpParamKey = 65411; // From the private-use block. + constexpr auto kTtl = base::Hours(40); + + DnsResponse response = BuildTestDnsResponse( + "https.test", dns_protocol::kTypeHttps, + /*answers=*/ + {BuildTestHttpsServiceRecord( + "https.test", /*priority=*/2, /*service_name=*/".", + /*params=*/ + {BuildTestHttpsServiceMandatoryParam({kMadeUpParamKey}), + {kMadeUpParamKey, "foo"}}, + kTtl)}); + DnsResponseResultExtractor extractor(&response); + + HostCache::Entry results(ERR_FAILED, HostCache::Entry::SOURCE_UNKNOWN); + EXPECT_EQ(extractor.ExtractDnsResults(DnsQueryType::HTTPS, &results), + DnsResponseResultExtractor::ExtractionError::kOk); + + EXPECT_THAT(results.error(), test::IsOk()); + EXPECT_THAT(results.https_record_compatibility(), + testing::Pointee(testing::ElementsAre(false))); ASSERT_TRUE(results.has_ttl()); EXPECT_EQ(results.ttl(), kTtl); @@ -831,8 +879,8 @@ DnsResponseResultExtractor::ExtractionError::kOk); EXPECT_THAT(results.error(), test::IsError(ERR_NAME_NOT_RESOLVED)); - EXPECT_THAT(results.experimental_results(), - testing::Optional(testing::IsEmpty())); + EXPECT_THAT(results.https_record_compatibility(), + testing::Pointee(testing::IsEmpty())); ASSERT_TRUE(results.has_ttl()); EXPECT_EQ(results.ttl(), kTtl); @@ -853,8 +901,8 @@ DnsResponseResultExtractor::ExtractionError::kOk); EXPECT_THAT(results.error(), test::IsError(ERR_NAME_NOT_RESOLVED)); - EXPECT_THAT(results.experimental_results(), - testing::Optional(testing::IsEmpty())); + EXPECT_THAT(results.https_record_compatibility(), + testing::Pointee(testing::IsEmpty())); ASSERT_TRUE(results.has_ttl()); EXPECT_EQ(results.ttl(), kTtl); @@ -900,8 +948,8 @@ DnsResponseResultExtractor::ExtractionError::kOk); EXPECT_THAT(results.error(), test::IsError(ERR_NAME_NOT_RESOLVED)); - EXPECT_THAT(results.experimental_results(), - testing::Optional(testing::IsEmpty())); + EXPECT_THAT(results.https_record_compatibility(), + testing::Pointee(testing::IsEmpty())); } TEST(DnsResponseResultExtractorTest, IgnoresAdditionalHttpsRecords) { @@ -923,8 +971,8 @@ DnsResponseResultExtractor::ExtractionError::kOk); EXPECT_THAT(results.error(), test::IsOk()); - EXPECT_THAT(results.experimental_results(), - testing::Optional(testing::ElementsAre(true))); + EXPECT_THAT(results.https_record_compatibility(), + testing::Pointee(testing::ElementsAre(true))); ASSERT_TRUE(results.has_ttl()); EXPECT_EQ(results.ttl(), kTtl);
diff --git a/net/dns/dns_test_util.cc b/net/dns/dns_test_util.cc index a014ded..04e3897a 100644 --- a/net/dns/dns_test_util.cc +++ b/net/dns/dns_test_util.cc
@@ -159,6 +159,21 @@ std::move(rdata), ttl); } +std::pair<uint16_t, std::string> BuildTestHttpsServiceMandatoryParam( + std::vector<uint16_t> param_key_list) { + base::ranges::sort(param_key_list); + + std::string value; + for (uint16_t param_key : param_key_list) { + char num_buffer[2]; + base::WriteBigEndian(num_buffer, param_key); + value.append(num_buffer, 2); + } + + return std::make_pair(dns_protocol::kHttpsServiceParamKeyMandatory, + std::move(value)); +} + DnsResourceRecord BuildTestHttpsServiceRecord( std::string name, uint16_t priority,
diff --git a/net/dns/dns_test_util.h b/net/dns/dns_test_util.h index 5a37f5d..da12c26 100644 --- a/net/dns/dns_test_util.h +++ b/net/dns/dns_test_util.h
@@ -218,6 +218,9 @@ base::StringPiece alias_name, base::TimeDelta ttl = base::Days(1)); +std::pair<uint16_t, std::string> BuildTestHttpsServiceMandatoryParam( + std::vector<uint16_t> param_key_list); + // `params` is a mapping from service param keys to a string containing the // encoded bytes of a service param value (without the value length prefix which // this method will automatically add).
diff --git a/net/dns/host_cache.cc b/net/dns/host_cache.cc index 58f4e91..f32fc09 100644 --- a/net/dns/host_cache.cc +++ b/net/dns/host_cache.cc
@@ -328,7 +328,8 @@ front.MergeAddressesFrom(back); MergeLists(&front.text_records_, back.text_records()); MergeLists(&front.hostnames_, back.hostnames()); - MergeLists(&front.experimental_results_, back.experimental_results()); + MergeLists(&front.https_record_compatibility_, + back.https_record_compatibility_); // The DNS aliases include the canonical name(s), if any, each as the // first entry in the field, which is an optional vector. If |front| has @@ -397,7 +398,7 @@ legacy_addresses_(entry.legacy_addresses()), text_records_(entry.text_records()), hostnames_(entry.hostnames()), - experimental_results_(entry.experimental_results()), + https_record_compatibility_(entry.https_record_compatibility_), source_(entry.source()), pinning_(entry.pinning()), ttl_(entry.ttl()), @@ -414,7 +415,7 @@ const absl::optional<AddressList>& legacy_addresses, absl::optional<std::vector<std::string>>&& text_records, absl::optional<std::vector<HostPortPair>>&& hostnames, - absl::optional<std::vector<bool>>&& experimental_results, + absl::optional<std::vector<bool>>&& https_record_compatibility, Source source, base::TimeTicks expires, int network_changes) @@ -425,13 +426,13 @@ legacy_addresses_(legacy_addresses), text_records_(std::move(text_records)), hostnames_(std::move(hostnames)), - experimental_results_(std::move(experimental_results)), + https_record_compatibility_(std::move(https_record_compatibility)), source_(source), expires_(expires), network_changes_(network_changes) {} void HostCache::Entry::PrepareForCacheInsertion() { - experimental_results_.reset(); + https_record_compatibility_.reset(); } bool HostCache::Entry::IsStale(base::TimeTicks now, int network_changes) const {
diff --git a/net/dns/host_cache.h b/net/dns/host_cache.h index e76573a..d84150f 100644 --- a/net/dns/host_cache.h +++ b/net/dns/host_cache.h
@@ -166,11 +166,11 @@ bool ContentsEqual(const Entry& other) const { return std::tie(error_, ip_endpoints_, endpoint_metadatas_, aliases_, legacy_addresses_, text_records_, hostnames_, - experimental_results_) == + https_record_compatibility_) == std::tie(other.error_, other.ip_endpoints_, other.endpoint_metadatas_, other.aliases_, other.legacy_addresses_, other.text_records_, - other.hostnames_, other.experimental_results_); + other.hostnames_, other.https_record_compatibility_); } int error() const { return error_; } @@ -213,12 +213,12 @@ void set_hostnames(absl::optional<std::vector<HostPortPair>> hostnames) { hostnames_ = std::move(hostnames); } - const absl::optional<std::vector<bool>>& experimental_results() const { - return experimental_results_; + const std::vector<bool>* https_record_compatibility() const { + return base::OptionalOrNullptr(https_record_compatibility_); } - void set_experimental_results( - absl::optional<std::vector<bool>> experimental_results) { - experimental_results_ = std::move(experimental_results); + void set_https_record_compatibility( + absl::optional<std::vector<bool>> https_record_compatibility) { + https_record_compatibility_ = std::move(https_record_compatibility); } absl::optional<bool> pinning() const { return pinning_; } void set_pinning(absl::optional<bool> pinning) { pinning_ = pinning; } @@ -270,7 +270,7 @@ const absl::optional<AddressList>& legacy_addresses, absl::optional<std::vector<std::string>>&& text_results, absl::optional<std::vector<HostPortPair>>&& hostnames, - absl::optional<std::vector<bool>>&& experimental_results, + absl::optional<std::vector<bool>>&& https_record_compatibility, Source source, base::TimeTicks expires, int network_changes); @@ -294,8 +294,8 @@ void SetResult(std::vector<HostPortPair> hostnames) { hostnames_ = std::move(hostnames); } - void SetResult(std::vector<bool> experimental_results) { - experimental_results_ = std::move(experimental_results); + void SetResult(std::vector<bool> https_record_compatibility) { + https_record_compatibility_ = std::move(https_record_compatibility); } int total_hits() const { return total_hits_; } @@ -336,7 +336,17 @@ absl::optional<AddressList> legacy_addresses_; absl::optional<std::vector<std::string>> text_records_; absl::optional<std::vector<HostPortPair>> hostnames_; - absl::optional<std::vector<bool>> experimental_results_; + + // Bool of whether each HTTPS record received is compatible + // (draft-ietf-dnsop-svcb-https-08#section-8), considering alias records to + // always be compatible. + // + // This field may be reused for experimental query types to record + // successfully received records of that experimental type. + // + // For either usage, cleared before inserting in cache. + absl::optional<std::vector<bool>> https_record_compatibility_; + // Where results were obtained (e.g. DNS lookup, hosts file, etc). Source source_ = SOURCE_UNKNOWN; // If true, this entry cannot be evicted from the cache until after the next
diff --git a/net/dns/host_resolver.cc b/net/dns/host_resolver.cc index b705042..f77e8f0 100644 --- a/net/dns/host_resolver.cc +++ b/net/dns/host_resolver.cc
@@ -91,7 +91,7 @@ } // namespace -const absl::optional<std::vector<bool>>& +const std::vector<bool>* HostResolver::ResolveHostRequest::GetExperimentalResultsForTesting() const { IMMEDIATE_CRASH(); }
diff --git a/net/dns/host_resolver.h b/net/dns/host_resolver.h index 1dd0b1f..a898fc7 100644 --- a/net/dns/host_resolver.h +++ b/net/dns/host_resolver.h
@@ -131,7 +131,7 @@ // Result of an experimental query. Meaning depends on the specific query // type, but each boolean value generally refers to a valid or invalid // record of the experimental type. - NET_EXPORT virtual const absl::optional<std::vector<bool>>& + NET_EXPORT virtual const std::vector<bool>* GetExperimentalResultsForTesting() const; // Error info for the request.
diff --git a/net/dns/host_resolver_manager.cc b/net/dns/host_resolver_manager.cc index 2b1792b..c4ebfac 100644 --- a/net/dns/host_resolver_manager.cc +++ b/net/dns/host_resolver_manager.cc
@@ -28,6 +28,7 @@ #include "base/containers/linked_list.h" #include "base/debug/debugger.h" #include "base/feature_list.h" +#include "base/functional/identity.h" #include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/memory/raw_ptr.h" @@ -761,12 +762,9 @@ return base::OptionalOrNullptr(fixed_up_dns_alias_results_); } - const absl::optional<std::vector<bool>>& GetExperimentalResultsForTesting() - const override { + const std::vector<bool>* GetExperimentalResultsForTesting() const override { DCHECK(complete_); - static const base::NoDestructor<absl::optional<std::vector<bool>>> - nullopt_result; - return results_ ? results_.value().experimental_results() : *nullopt_result; + return results_ ? results_.value().https_record_compatibility() : nullptr; } net::ResolveErrorInfo GetResolveErrorInfo() const override { @@ -1617,20 +1615,21 @@ if (httpssvc_metrics_) { if (dns_query_type == DnsQueryType::INTEGRITY) { - const absl::optional<std::vector<bool>>& condensed = - results.experimental_results(); - CHECK(condensed.has_value()); + const std::vector<bool>* experimental_results = + results.https_record_compatibility(); + CHECK(experimental_results); // INTEGRITY queries can time out the normal way (here), or when the // experimental query timer runs out (OnExperimentalQueryTimeout). httpssvc_metrics_->SaveForIntegrity(doh_provider_id, rcode_for_httpssvc, - *condensed, elapsed_time); + *experimental_results, + elapsed_time); } else if (dns_query_type == DnsQueryType::HTTPS || dns_query_type == DnsQueryType::HTTPS_EXPERIMENTAL) { - const absl::optional<std::vector<bool>>& condensed = - results.experimental_results(); - CHECK(condensed.has_value()); + const std::vector<bool>* record_compatibility = + results.https_record_compatibility(); + CHECK(record_compatibility); httpssvc_metrics_->SaveForHttps(doh_provider_id, rcode_for_httpssvc, - *condensed, elapsed_time); + *record_compatibility, elapsed_time); } else { httpssvc_metrics_->SaveForAddressQuery(doh_provider_id, elapsed_time, rcode_for_httpssvc); @@ -1921,7 +1920,9 @@ kMaxValue = kUpgradeDisabled } upgrade_result; - if (results.error() != OK) { + if (!results.https_record_compatibility() || + base::ranges::none_of(*results.https_record_compatibility(), + base::identity())) { upgrade_result = UpgradeResult::kNoHttpsRecord; } else if (GetScheme(host_) == url::kHttpsScheme || GetScheme(host_) == url::kWssScheme) {
diff --git a/net/dns/host_resolver_manager_unittest.cc b/net/dns/host_resolver_manager_unittest.cc index 707db263..a381af8 100644 --- a/net/dns/host_resolver_manager_unittest.cc +++ b/net/dns/host_resolver_manager_unittest.cc
@@ -5561,7 +5561,7 @@ EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true))); + testing::Pointee(testing::ElementsAre(true))); } TEST_F(HostResolverManagerDnsTest, SeparateJobsBySecureDnsMode) { @@ -9886,7 +9886,7 @@ // TODO(crbug.com/1225776): Check non-experimental HTTPS output once // available. EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true))); + testing::Pointee(testing::ElementsAre(true))); } TEST_F(HostResolverManagerDnsTest, HttpsQueryForNonStandardPort) { @@ -9921,7 +9921,7 @@ // TODO(crbug.com/1225776): Check non-experimental HTTPS output once // available. EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true))); + testing::Pointee(testing::ElementsAre(true))); } TEST_F(HostResolverManagerDnsTest, HttpsQueryForHttpUpgrade) { @@ -9953,7 +9953,7 @@ // TODO(crbug.com/1225776): Check non-experimental HTTPS output once // available. EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true))); + testing::Pointee(testing::ElementsAre(true))); } // Test that HTTPS requests for an http host with port 443 will result in a @@ -9991,7 +9991,7 @@ // TODO(crbug.com/1225776): Check non-experimental HTTPS output once // available. EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true))); + testing::Pointee(testing::ElementsAre(true))); } TEST_F(HostResolverManagerDnsTest, @@ -10027,7 +10027,7 @@ // TODO(crbug.com/1225776): Check non-experimental HTTPS output once // available. EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true))); + testing::Pointee(testing::ElementsAre(true))); } TEST_F(HostResolverManagerDnsTest, ExperimentalHttpsQuery) { @@ -10056,7 +10056,7 @@ EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true))); + testing::Pointee(testing::ElementsAre(true))); } TEST_F(HostResolverManagerDnsTest, ExperimentalHttpsQueryRejectsIpLiteral) { @@ -10149,7 +10149,7 @@ EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true))); + testing::Pointee(testing::ElementsAre(true))); } TEST_F(HostResolverManagerDnsTest, @@ -10218,7 +10218,7 @@ EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true))); + testing::Pointee(testing::ElementsAre(true))); } TEST_F(HostResolverManagerDnsTest, ExperimentalHttpsQuery_MultipleResults) { @@ -10248,7 +10248,7 @@ EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true, true))); + testing::Pointee(testing::ElementsAre(true, true))); } TEST_F(HostResolverManagerDnsTest, ExperimentalHttpsQuery_InvalidConfig) { @@ -10492,7 +10492,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); // Additional records aren't currently used in results. EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true))); + testing::Pointee(testing::ElementsAre(true))); } TEST_F(HostResolverManagerDnsTest, ExperimentalHttpsQuery_WrongType) { @@ -10559,7 +10559,7 @@ EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true))); + testing::Pointee(testing::ElementsAre(true))); } // Same as ExperimentalHttpsInsecureQueryDisallowedWhenAdditionalTypesDisallowed @@ -10631,7 +10631,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true))); + testing::Pointee(testing::ElementsAre(true))); } TEST_F(HostResolverManagerDnsTest, HttpsInAddressQueryWithNonstandardPort) { @@ -10674,7 +10674,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true))); + testing::Pointee(testing::ElementsAre(true))); } TEST_F(HostResolverManagerDnsTest, HttpsInAddressQueryWithoutAddresses) { @@ -10753,7 +10753,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::IsEmpty())); + testing::Pointee(testing::IsEmpty())); } // For a response where DnsTransaction can at least do its basic parsing and @@ -10799,7 +10799,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::IsEmpty())); + testing::Pointee(testing::IsEmpty())); } TEST_F(HostResolverManagerDnsTest, @@ -10843,7 +10843,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::IsEmpty())); + testing::Pointee(testing::IsEmpty())); } TEST_F(HostResolverManagerDnsTest, @@ -10929,7 +10929,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::IsEmpty())); + testing::Pointee(testing::IsEmpty())); } TEST_F(HostResolverManagerDnsTest, TimeoutHttpsInAddressRequestIsFatal) { @@ -11116,7 +11116,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::IsEmpty())); + testing::Pointee(testing::IsEmpty())); } TEST_F(HostResolverManagerDnsTest, HttpsInAddressQueryForWssScheme) { @@ -11156,7 +11156,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true))); + testing::Pointee(testing::ElementsAre(true))); } TEST_F(HostResolverManagerDnsTest, NoHttpsInAddressQueryWithoutScheme) { @@ -11282,7 +11282,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true))); + testing::Pointee(testing::ElementsAre(true))); } TEST_F(HostResolverManagerDnsTest, @@ -11329,6 +11329,101 @@ EXPECT_FALSE(response.request()->GetExperimentalResultsForTesting()); } +TEST_F(HostResolverManagerDnsTest, + HttpsInAddressQueryForHttpSchemeWhenUpgradeEnabledWithAliasRecord) { + const char kName[] = "name.test"; + + base::test::ScopedFeatureList features; + features.InitAndEnableFeatureWithParameters( + features::kUseDnsHttpsSvcb, {{"UseDnsHttpsSvcbHttpUpgrade", "true"}}); + + MockDnsClientRuleList rules; + std::vector<DnsResourceRecord> records = { + BuildTestHttpsAliasRecord(kName, "alias.test")}; + rules.emplace_back(kName, dns_protocol::kTypeHttps, /*secure=*/true, + MockDnsClientRule::Result(BuildTestDnsResponse( + kName, dns_protocol::kTypeHttps, records)), + /*delay=*/false); + rules.emplace_back( + kName, dns_protocol::kTypeA, /*secure=*/true, + MockDnsClientRule::Result(MockDnsClientRule::ResultType::kOk), + /*delay=*/false); + rules.emplace_back( + kName, dns_protocol::kTypeAAAA, /*secure=*/true, + MockDnsClientRule::Result(MockDnsClientRule::ResultType::kOk), + /*delay=*/false); + + CreateResolver(); + UseMockDnsClient(CreateValidDnsConfig(), std::move(rules)); + DnsConfigOverrides overrides; + overrides.secure_dns_mode = SecureDnsMode::kSecure; + resolver_->SetDnsConfigOverrides(overrides); + + ResolveHostResponseHelper response(resolver_->CreateRequest( + url::SchemeHostPort(url::kHttpScheme, kName, 80), NetworkIsolationKey(), + NetLogWithSource(), absl::nullopt, resolve_context_.get(), + resolve_context_->host_cache())); + + EXPECT_THAT(response.result_error(), IsError(ERR_DNS_NAME_HTTPS_ONLY)); + EXPECT_FALSE(response.request()->GetAddressResults()); + EXPECT_FALSE(response.request()->GetEndpointResults()); + EXPECT_FALSE(response.request()->GetTextResults()); + EXPECT_FALSE(response.request()->GetHostnameResults()); + EXPECT_FALSE(response.request()->GetExperimentalResultsForTesting()); +} + +TEST_F( + HostResolverManagerDnsTest, + HttpsInAddressQueryForHttpSchemeWhenUpgradeEnabledWithIncompatibleServiceRecord) { + const char kName[] = "name.test"; + const uint16_t kMadeUpParam = 65300; // From the private-use block. + + base::test::ScopedFeatureList features; + features.InitAndEnableFeatureWithParameters( + features::kUseDnsHttpsSvcb, {{"UseDnsHttpsSvcbHttpUpgrade", "true"}}); + + MockDnsClientRuleList rules; + std::vector<DnsResourceRecord> records = {BuildTestHttpsServiceRecord( + kName, /*priority=*/1, /*service_name=*/".", + /*params=*/ + {BuildTestHttpsServiceMandatoryParam({kMadeUpParam}), + {kMadeUpParam, "foo"}})}; + rules.emplace_back(kName, dns_protocol::kTypeHttps, /*secure=*/true, + MockDnsClientRule::Result(BuildTestDnsResponse( + kName, dns_protocol::kTypeHttps, records)), + /*delay=*/false); + rules.emplace_back( + kName, dns_protocol::kTypeA, /*secure=*/true, + MockDnsClientRule::Result(MockDnsClientRule::ResultType::kOk), + /*delay=*/false); + rules.emplace_back( + kName, dns_protocol::kTypeAAAA, /*secure=*/true, + MockDnsClientRule::Result(MockDnsClientRule::ResultType::kOk), + /*delay=*/false); + + CreateResolver(); + UseMockDnsClient(CreateValidDnsConfig(), std::move(rules)); + DnsConfigOverrides overrides; + overrides.secure_dns_mode = SecureDnsMode::kSecure; + resolver_->SetDnsConfigOverrides(overrides); + + ResolveHostResponseHelper response(resolver_->CreateRequest( + url::SchemeHostPort(url::kHttpScheme, kName, 80), NetworkIsolationKey(), + NetLogWithSource(), absl::nullopt, resolve_context_.get(), + resolve_context_->host_cache())); + + // Expect incompatible HTTPS record to have no effect on results (except + // `GetExperimentalResultsForTesting()` which returns the record + // compatibility). + EXPECT_THAT(response.result_error(), IsOk()); + EXPECT_TRUE(response.request()->GetAddressResults()); + EXPECT_TRUE(response.request()->GetEndpointResults()); + EXPECT_FALSE(response.request()->GetTextResults()); + EXPECT_FALSE(response.request()->GetHostnameResults()); + EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), + testing::Pointee(testing::ElementsAre(false))); +} + // Even if no addresses are received for a request, finding an HTTPS record // should still force an HTTP->HTTPS upgrade. TEST_F(HostResolverManagerDnsTest, @@ -11412,7 +11507,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true))); + testing::Pointee(testing::ElementsAre(true))); } TEST_F(HostResolverManagerDnsTest, FailedHttpsInInsecureAddressRequestIgnored) { @@ -11449,7 +11544,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::IsEmpty())); + testing::Pointee(testing::IsEmpty())); } TEST_F(HostResolverManagerDnsTest, @@ -11487,7 +11582,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::IsEmpty())); + testing::Pointee(testing::IsEmpty())); } TEST_F(HostResolverManagerDnsTest, @@ -11530,7 +11625,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::IsEmpty())); + testing::Pointee(testing::IsEmpty())); } TEST_F(HostResolverManagerDnsTest, @@ -11570,7 +11665,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::IsEmpty())); + testing::Pointee(testing::IsEmpty())); } TEST_F(HostResolverManagerDnsTest, @@ -11666,7 +11761,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::IsEmpty())); + testing::Pointee(testing::IsEmpty())); } TEST_F(HostResolverManagerDnsTest, HttpsInAddressQueryWithAbsoluteTimeout) { @@ -11973,7 +12068,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true))); + testing::Pointee(testing::ElementsAre(true))); } // Test that HTTPS timeouts are always respected for insecure requests. @@ -12070,11 +12165,55 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true))); + testing::Pointee(testing::ElementsAre(true))); } TEST_F(HostResolverManagerDnsTest, - ExperimentalHttpsInAddressQuery_MultipleResults) { + ExperimentalHttpsInAddressQueryWithMultipleResults) { + const char kName[] = "combined.test"; + + base::test::ScopedFeatureList features; + features.InitAndEnableFeatureWithParameters( + features::kDnsHttpssvc, {{"DnsHttpssvcUseHttpssvc", "true"}, + {"DnsHttpssvcExperimentDomains", kName}}); + + MockDnsClientRuleList rules; + std::vector<DnsResourceRecord> records = { + BuildTestHttpsAliasRecord(kName, "alias.test"), + BuildTestHttpsAliasRecord(kName, "another-alias.test")}; + rules.emplace_back(kName, dns_protocol::kTypeHttps, true /* secure */, + MockDnsClientRule::Result(BuildTestDnsResponse( + kName, dns_protocol::kTypeHttps, records)), + false /* delay */); + rules.emplace_back( + kName, dns_protocol::kTypeA, true /* secure */, + MockDnsClientRule::Result(MockDnsClientRule::ResultType::kOk), + false /* delay */); + rules.emplace_back( + kName, dns_protocol::kTypeAAAA, true /* secure */, + MockDnsClientRule::Result(MockDnsClientRule::ResultType::kOk), + false /* delay */); + + CreateResolver(); + UseMockDnsClient(CreateValidDnsConfig(), std::move(rules)); + DnsConfigOverrides overrides; + overrides.secure_dns_mode = SecureDnsMode::kSecure; + resolver_->SetDnsConfigOverrides(overrides); + + ResolveHostResponseHelper response(resolver_->CreateRequest( + HostPortPair(kName, 108), NetworkIsolationKey(), NetLogWithSource(), + absl::nullopt, resolve_context_.get(), resolve_context_->host_cache())); + EXPECT_THAT(response.result_error(), IsOk()); + EXPECT_TRUE(response.request()->GetAddressResults()); + EXPECT_TRUE(response.request()->GetEndpointResults()); + EXPECT_FALSE(response.request()->GetTextResults()); + EXPECT_FALSE(response.request()->GetHostnameResults()); + EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), + testing::Pointee(testing::UnorderedElementsAre(true, true))); +} + +TEST_F(HostResolverManagerDnsTest, + ExperimentalHttpsInAddressQueryWithMalformedResult) { const char kName[] = "combined.test"; base::test::ScopedFeatureList features; @@ -12115,9 +12254,10 @@ EXPECT_TRUE(response.request()->GetEndpointResults()); EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); - EXPECT_THAT( - response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::UnorderedElementsAre(true, true, false))); + + // HTTPS records cannot be read if any are malformed. + EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), + testing::Pointee(testing::IsEmpty())); } TEST_F(HostResolverManagerDnsTest, @@ -12158,7 +12298,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::IsEmpty())); + testing::Pointee(testing::IsEmpty())); } TEST_F(HostResolverManagerDnsTest, ExperimentalHttpsInAddressQuery_HttpsOnly) { @@ -12283,7 +12423,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::IsEmpty())); + testing::Pointee(testing::IsEmpty())); } TEST_F(HostResolverManagerDnsTest, @@ -12325,7 +12465,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::IsEmpty())); + testing::Pointee(testing::IsEmpty())); } TEST_F(HostResolverManagerDnsTest, @@ -12369,7 +12509,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(false))); + testing::Pointee(testing::IsEmpty())); } TEST_F(HostResolverManagerDnsTest, ExperimentalHttpsInAddressQuery_NoData) { @@ -12458,7 +12598,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true))); + testing::Pointee(testing::ElementsAre(true))); } TEST_F(HostResolverManagerDnsTest, @@ -12510,7 +12650,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true))); + testing::Pointee(testing::ElementsAre(true))); } TEST_F(HostResolverManagerDnsTest, @@ -12597,7 +12737,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true))); + testing::Pointee(testing::ElementsAre(true))); } TEST_F( @@ -12698,7 +12838,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::ElementsAre(true))); + testing::Pointee(testing::ElementsAre(true))); } TEST_F(HostResolverManagerDnsTest, @@ -12856,7 +12996,7 @@ EXPECT_FALSE(response.request()->GetTextResults()); EXPECT_FALSE(response.request()->GetHostnameResults()); EXPECT_THAT(response.request()->GetExperimentalResultsForTesting(), - testing::Optional(testing::IsEmpty())); + testing::Pointee(testing::IsEmpty())); } TEST_F(HostResolverManagerDnsTest, MultipleExperimentalQueries_Timeout) { @@ -13059,13 +13199,11 @@ DoIntegrityQuery(true /* use_secure */); EXPECT_THAT(response->result_error(), IsOk()); - absl::optional<std::vector<bool>> results = - response->request()->GetExperimentalResultsForTesting(); - EXPECT_TRUE(response->request()->GetAddressResults()); EXPECT_TRUE(response->request()->GetEndpointResults()); EXPECT_FALSE(response->request()->GetTextResults()); - EXPECT_THAT(results, Optional(UnorderedElementsAre(true))); + EXPECT_THAT(response->request()->GetExperimentalResultsForTesting(), + testing::Pointee(UnorderedElementsAre(true))); } TEST_F(HostResolverManagerDnsTestIntegrity, IntegrityQueryMangled) { @@ -13077,14 +13215,11 @@ DoIntegrityQuery(true /* use_secure */); EXPECT_THAT(response->result_error(), IsOk()); - absl::optional<std::vector<bool>> results = - response->request()->GetExperimentalResultsForTesting(); - EXPECT_TRUE(response->request()->GetAddressResults()); EXPECT_TRUE(response->request()->GetEndpointResults()); EXPECT_FALSE(response->request()->GetTextResults()); EXPECT_THAT(response->request()->GetExperimentalResultsForTesting(), - Optional(UnorderedElementsAre(false))); + testing::Pointee(UnorderedElementsAre(false))); } TEST_F(HostResolverManagerDnsTestIntegrity, IntegrityQueryOnlyOverSecure) { @@ -13098,10 +13233,7 @@ DoIntegrityQuery(false /* use_secure */); EXPECT_THAT(response->result_error(), IsOk()); - absl::optional<std::vector<bool>> results = - response->request()->GetExperimentalResultsForTesting(); - - EXPECT_FALSE(results); + EXPECT_FALSE(response->request()->GetExperimentalResultsForTesting()); } // Ensure that the address results are preserved, even when the INTEGRITY query @@ -13151,7 +13283,7 @@ // Check the |kDnsHttpssvcExtraTimeMs| and |kDnsHttpssvcExtraTimePercent| // feature params in relation to this test's FastForward steps. EXPECT_THAT(response->request()->GetExperimentalResultsForTesting(), - Optional(UnorderedElementsAre(true))); + testing::Pointee(UnorderedElementsAre(true))); } // Ensure that a successful INTEGRITY query cannot mask the appropriate @@ -13247,7 +13379,7 @@ ASSERT_THAT(response->result_error(), IsOk()); EXPECT_THAT(response->request()->GetExperimentalResultsForTesting(), - Optional(UnorderedElementsAre(true))); + testing::Pointee(UnorderedElementsAre(true))); ASSERT_TRUE(response->request()->GetAddressResults()); EXPECT_THAT(response->request()->GetAddressResults()->endpoints(), testing::UnorderedElementsAre(CreateExpected("127.0.0.1", 108), @@ -13303,7 +13435,7 @@ ExpectEndpointResult(testing::UnorderedElementsAre( CreateExpected("::1", 108), CreateExpected("127.0.0.1", 108)))))); EXPECT_THAT(response->request()->GetExperimentalResultsForTesting(), - Optional(IsEmpty())); + testing::Pointee(IsEmpty())); } // Ensure that the address results are preserved, even when the INTEGRITY query @@ -13342,7 +13474,7 @@ ASSERT_THAT(response->result_error(), IsOk()); EXPECT_THAT(response->request()->GetExperimentalResultsForTesting(), - Optional(IsEmpty())); + testing::Pointee(IsEmpty())); ASSERT_TRUE(response->request()->GetAddressResults()); EXPECT_THAT(response->request()->GetAddressResults()->endpoints(), testing::UnorderedElementsAre(CreateExpected("127.0.0.1", 108), @@ -13394,7 +13526,7 @@ ExpectEndpointResult(testing::UnorderedElementsAre( CreateExpected("::1", 108), CreateExpected("127.0.0.1", 108)))))); EXPECT_THAT(response->request()->GetExperimentalResultsForTesting(), - Optional(UnorderedElementsAre(false))); + testing::Pointee(UnorderedElementsAre(false))); } TEST_F(HostResolverManagerDnsTestIntegrity, @@ -13428,7 +13560,7 @@ ASSERT_THAT(response->result_error(), IsOk()); EXPECT_THAT(response->request()->GetExperimentalResultsForTesting(), - Optional(UnorderedElementsAre(false))); + testing::Pointee(UnorderedElementsAre(false))); ASSERT_TRUE(response->request()->GetAddressResults()); EXPECT_THAT(response->request()->GetAddressResults()->endpoints(), testing::UnorderedElementsAre(CreateExpected("127.0.0.1", 108),
diff --git a/net/dns/https_record_rdata.cc b/net/dns/https_record_rdata.cc index e16df6e4..f9512b0 100644 --- a/net/dns/https_record_rdata.cc +++ b/net/dns/https_record_rdata.cc
@@ -127,23 +127,16 @@ std::unique_ptr<HttpsRecordRdata> HttpsRecordRdata::Parse( base::StringPiece data) { if (!HasValidSize(data, kType)) - return std::make_unique<MalformedHttpsRecordRdata>(); + return nullptr; auto reader = base::BigEndianReader::FromStringPiece(data); uint16_t priority; CHECK(reader.ReadU16(&priority)); - std::unique_ptr<HttpsRecordRdata> parsed; if (priority == 0) { - parsed = AliasFormHttpsRecordRdata::Parse(data); - } else { - parsed = ServiceFormHttpsRecordRdata::Parse(data); + return AliasFormHttpsRecordRdata::Parse(data); } - - if (!parsed) - return std::make_unique<MalformedHttpsRecordRdata>(); - - return parsed; + return ServiceFormHttpsRecordRdata::Parse(data); } HttpsRecordRdata::~HttpsRecordRdata() = default; @@ -164,7 +157,6 @@ AliasFormHttpsRecordRdata* HttpsRecordRdata::AsAliasForm() { CHECK(IsAlias()); - CHECK(!IsMalformed()); return static_cast<AliasFormHttpsRecordRdata*>(this); } @@ -174,7 +166,6 @@ ServiceFormHttpsRecordRdata* HttpsRecordRdata::AsServiceForm() { CHECK(!IsAlias()); - CHECK(!IsMalformed()); return static_cast<ServiceFormHttpsRecordRdata*>(this); } @@ -182,25 +173,6 @@ return const_cast<HttpsRecordRdata*>(this)->AsServiceForm(); } -bool HttpsRecordRdata::IsMalformed() const { - return false; -} - -MalformedHttpsRecordRdata::MalformedHttpsRecordRdata() = default; - -bool MalformedHttpsRecordRdata::IsEqual(const HttpsRecordRdata* other) const { - DCHECK(other); - return other->IsMalformed(); -} - -bool MalformedHttpsRecordRdata::IsAlias() const { - return false; -} - -bool MalformedHttpsRecordRdata::IsMalformed() const { - return true; -} - AliasFormHttpsRecordRdata::AliasFormHttpsRecordRdata(std::string alias_name) : alias_name_(std::move(alias_name)) {} @@ -294,7 +266,7 @@ bool ServiceFormHttpsRecordRdata::IsEqual(const HttpsRecordRdata* other) const { DCHECK(other); - if (other->IsAlias() || other->IsMalformed()) + if (other->IsAlias()) return false; const ServiceFormHttpsRecordRdata* service = other->AsServiceForm();
diff --git a/net/dns/https_record_rdata.h b/net/dns/https_record_rdata.h index dbef3dd9..39c53271 100644 --- a/net/dns/https_record_rdata.h +++ b/net/dns/https_record_rdata.h
@@ -30,6 +30,7 @@ public: static const uint16_t kType = dns_protocol::kTypeHttps; + // Returns `nullptr` on malformed input. static std::unique_ptr<HttpsRecordRdata> Parse(base::StringPiece data); HttpsRecordRdata(const HttpsRecordRdata& rdata) = delete; @@ -47,25 +48,10 @@ ServiceFormHttpsRecordRdata* AsServiceForm(); const ServiceFormHttpsRecordRdata* AsServiceForm() const; - // For experimental query metrics. - // TODO(crbug.com/1138620): Remove as HTTPS use becomes non-experimental. - virtual bool IsMalformed() const; - protected: HttpsRecordRdata() = default; }; -// For experimental query metrics. -// TODO(crbug.com/1138620): Remove as HTTPS use becomes non-experimental. -class NET_EXPORT_PRIVATE MalformedHttpsRecordRdata : public HttpsRecordRdata { - public: - MalformedHttpsRecordRdata(); - - bool IsEqual(const HttpsRecordRdata* other) const override; - bool IsAlias() const override; - bool IsMalformed() const override; -}; - class NET_EXPORT_PRIVATE AliasFormHttpsRecordRdata : public HttpsRecordRdata { public: explicit AliasFormHttpsRecordRdata(std::string alias_name);
diff --git a/net/dns/https_record_rdata_fuzzer.cc b/net/dns/https_record_rdata_fuzzer.cc index 7e0b2b1..a24a102f 100644 --- a/net/dns/https_record_rdata_fuzzer.cc +++ b/net/dns/https_record_rdata_fuzzer.cc
@@ -47,10 +47,9 @@ CHECK_EQ(parsed->Type(), dns_protocol::kTypeHttps); if (parsed->IsAlias()) { - CHECK(!parsed->IsMalformed()); AliasFormHttpsRecordRdata* alias = parsed->AsAliasForm(); alias->alias_name(); - } else if (!parsed->IsMalformed()) { + } else { ServiceFormHttpsRecordRdata* service = parsed->AsServiceForm(); CHECK_GT(service->priority(), 0); service->service_name();
diff --git a/net/dns/https_record_rdata_unittest.cc b/net/dns/https_record_rdata_unittest.cc index ada6b0a..fc481e8 100644 --- a/net/dns/https_record_rdata_unittest.cc +++ b/net/dns/https_record_rdata_unittest.cc
@@ -27,7 +27,6 @@ std::unique_ptr<HttpsRecordRdata> rdata = HttpsRecordRdata::Parse(base::StringPiece(kRdata, sizeof(kRdata) - 1)); ASSERT_TRUE(rdata); - EXPECT_FALSE(rdata->IsMalformed()); AliasFormHttpsRecordRdata expected("chromium.org"); EXPECT_TRUE(rdata->IsEqual(&expected)); @@ -107,7 +106,6 @@ std::unique_ptr<HttpsRecordRdata> rdata = HttpsRecordRdata::Parse(base::StringPiece(kRdata, sizeof(kRdata) - 1)); ASSERT_TRUE(rdata); - EXPECT_FALSE(rdata->IsMalformed()); IPAddress expected_ipv6; ASSERT_TRUE(expected_ipv6.AssignFromIPLiteral("2001:4860:4860::8888")); @@ -151,9 +149,7 @@ std::unique_ptr<HttpsRecordRdata> rdata = HttpsRecordRdata::Parse(base::StringPiece(kRdata, sizeof(kRdata) - 1)); - ASSERT_TRUE(rdata); - - EXPECT_TRUE(rdata->IsMalformed()); + EXPECT_FALSE(rdata); } TEST(HttpsRecordRdataTest, AliasIsEqualRejectsWrongType) { @@ -163,11 +159,9 @@ {} /* alpn_ids */, true /* default_alpn */, absl::nullopt /* port */, {} /* ipv4_hint */, "" /* ech_config */, {} /* ipv6_hint */, {} /* unparsed_params */); - MalformedHttpsRecordRdata malformed; EXPECT_TRUE(alias.IsEqual(&alias)); EXPECT_FALSE(alias.IsEqual(&service)); - EXPECT_FALSE(alias.IsEqual(&malformed)); } TEST(HttpsRecordRdataTest, ServiceIsEqualRejectsWrongType) { @@ -177,25 +171,9 @@ {} /* alpn_ids */, true /* default_alpn */, absl::nullopt /* port */, {} /* ipv4_hint */, "" /* ech_config */, {} /* ipv6_hint */, {} /* unparsed_params */); - MalformedHttpsRecordRdata malformed; EXPECT_FALSE(service.IsEqual(&alias)); EXPECT_TRUE(service.IsEqual(&service)); - EXPECT_FALSE(service.IsEqual(&malformed)); -} - -TEST(HttpsRecordRdataTest, MalformedIsEqualRejectsWrongType) { - AliasFormHttpsRecordRdata alias("alias.name.test"); - ServiceFormHttpsRecordRdata service( - 1u /* priority */, "service.name.test", {} /* mandatory_keys */, - {} /* alpn_ids */, true /* default_alpn */, absl::nullopt /* port */, - {} /* ipv4_hint */, "" /* ech_config */, {} /* ipv6_hint */, - {} /* unparsed_params */); - MalformedHttpsRecordRdata malformed; - - EXPECT_FALSE(malformed.IsEqual(&alias)); - EXPECT_FALSE(malformed.IsEqual(&service)); - EXPECT_TRUE(malformed.IsEqual(&malformed)); } } // namespace
diff --git a/net/dns/serial_worker.cc b/net/dns/serial_worker.cc index 635a1a2..980f6851 100644 --- a/net/dns/serial_worker.cc +++ b/net/dns/serial_worker.cc
@@ -16,26 +16,10 @@ #include "base/task/thread_pool.h" #include "base/threading/sequenced_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h" -#include "base/timer/timer.h" -#include "net/base/backoff_entry.h" namespace net { namespace { -// Default retry configuration. Only in effect if |max_number_of_retries| is -// greater than 0. -constexpr BackoffEntry::Policy kDefaultBackoffPolicy = { - 0, // Number of initial errors to ignore without backoff. - 5000, // Initial delay for backoff in ms: 5 seconds. - 2, // Factor to multiply for exponential backoff. - 0, // Fuzzing percentage. - -1, // No maximum delay. - -1, // Don't discard entry. - false // Don't use initial delay unless the last was an error. -}; -} // namespace - -namespace { std::unique_ptr<SerialWorker::WorkItem> DoWork( std::unique_ptr<SerialWorker::WorkItem> work_item) { DCHECK(work_item); @@ -48,28 +32,15 @@ std::move(closure).Run(); } -SerialWorker::SerialWorker(int max_number_of_retries, - const net::BackoffEntry::Policy* backoff_policy) - : state_(State::kIdle), - max_number_of_retries_(max_number_of_retries), - backoff_entry_(backoff_policy ? backoff_policy : &kDefaultBackoffPolicy) { -} +SerialWorker::SerialWorker() : state_(State::kIdle) {} SerialWorker::~SerialWorker() = default; void SerialWorker::WorkNow() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - // Not a retry; reset failure count and cancel the pending retry (if any). - backoff_entry_.Reset(); - retry_timer_.Stop(); - WorkNowInternal(); -} - -void SerialWorker::WorkNowInternal() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); switch (state_) { case State::kIdle: - // We are posting weak pointer to OnWorkJobFinished to avoid a leak when + // We are posting weak pointer to OnWorkJobFinished to avoid leak when // PostTaskAndReply fails to post task back to the original // task runner. In this case the callback is not destroyed, and the // weak reference allows SerialWorker instance to be deleted. @@ -124,16 +95,7 @@ return; case State::kWorking: state_ = State::kIdle; - if (OnWorkFinished(std::move(work_item)) || - backoff_entry_.failure_count() >= max_number_of_retries_) { - backoff_entry_.Reset(); - } else { - backoff_entry_.InformOfRequest(/*succeeded=*/false); - - // Try again after a delay. - retry_timer_.Start(FROM_HERE, backoff_entry_.GetTimeUntilRelease(), - this, &SerialWorker::WorkNowInternal); - } + OnWorkFinished(std::move(work_item)); return; case State::kPending: RerunWork(std::move(work_item)); @@ -157,16 +119,6 @@ base::BindOnce(&SerialWorker::OnDoWorkFinished, AsWeakPtr())); } -const BackoffEntry& SerialWorker::GetBackoffEntryForTesting() const { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - return backoff_entry_; -} - -const base::OneShotTimer& SerialWorker::GetRetryTimerForTesting() const { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - return retry_timer_; -} - base::WeakPtr<SerialWorker> SerialWorker::AsWeakPtr() { return weak_factory_.GetWeakPtr(); }
diff --git a/net/dns/serial_worker.h b/net/dns/serial_worker.h index b7076be..57fad932 100644 --- a/net/dns/serial_worker.h +++ b/net/dns/serial_worker.h
@@ -12,8 +12,6 @@ #include "base/memory/weak_ptr.h" #include "base/sequence_checker.h" #include "base/task/task_traits.h" -#include "base/timer/timer.h" -#include "net/base/backoff_entry.h" #include "net/base/net_export.h" namespace net { @@ -28,10 +26,6 @@ // made to `OnWorkFinished()` the same `WorkItem` will be passed back to the // `ThreadPool`, and `DoWork()` will be called once more. // -// If |OnWorkFinished| returns a failure and |max_number_of_retries| -// is non-zero, retries will be scheduled according to the |backoff_policy|. -// A default backoff policy is used if one is not provided. -// // This behavior is designed for updating a result after some trigger, for // example reading a file once FilePathWatcher indicates it changed. // @@ -62,9 +56,7 @@ virtual void FollowupWork(base::OnceClosure closure); }; - explicit SerialWorker( - int max_number_of_retries = 0, - const net::BackoffEntry::Policy* backoff_policy = nullptr); + SerialWorker(); SerialWorker(const SerialWorker&) = delete; SerialWorker& operator=(const SerialWorker&) = delete; @@ -78,10 +70,6 @@ bool IsCancelled() const { return state_ == State::kCancelled; } - // Allows tests to inspect the current backoff/retry state. - const BackoffEntry& GetBackoffEntryForTesting() const; - const base::OneShotTimer& GetRetryTimerForTesting() const; - protected: // protected to allow sub-classing, but prevent deleting virtual ~SerialWorker(); @@ -90,8 +78,7 @@ virtual std::unique_ptr<WorkItem> CreateWorkItem() = 0; // Executed on origin thread after `WorkItem` completes. - // Must return true on success. - virtual bool OnWorkFinished(std::unique_ptr<WorkItem> work_item) = 0; + virtual void OnWorkFinished(std::unique_ptr<WorkItem> work_item) = 0; base::WeakPtr<SerialWorker> AsWeakPtr(); @@ -107,8 +94,6 @@ kPending, // |WorkNow| while WORKING, must re-do work }; - void WorkNowInternal(); - // Called on the origin thread after `WorkItem::DoWork()` completes. void OnDoWorkFinished(std::unique_ptr<WorkItem> work_item); @@ -119,11 +104,6 @@ State state_; - // Max retries and backoff entry to control timing. - const int max_number_of_retries_; - BackoffEntry backoff_entry_; - base::OneShotTimer retry_timer_; - base::WeakPtrFactory<SerialWorker> weak_factory_{this}; };
diff --git a/net/dns/serial_worker_unittest.cc b/net/dns/serial_worker_unittest.cc index cdb6bb9..ea9d4365 100644 --- a/net/dns/serial_worker_unittest.cc +++ b/net/dns/serial_worker_unittest.cc
@@ -9,7 +9,6 @@ #include "base/bind.h" #include "base/callback.h" -#include "base/check.h" #include "base/location.h" #include "base/memory/raw_ptr.h" #include "base/run_loop.h" @@ -17,35 +16,14 @@ #include "base/synchronization/waitable_event.h" #include "base/task/current_thread.h" #include "base/task/single_thread_task_runner.h" -#include "base/test/simple_test_tick_clock.h" #include "base/threading/thread_restrictions.h" #include "base/threading/thread_task_runner_handle.h" -#include "base/time/time.h" -#include "base/timer/timer.h" -#include "net/base/backoff_entry.h" #include "net/test/test_with_task_environment.h" #include "testing/gtest/include/gtest/gtest.h" namespace net { namespace { -constexpr base::TimeDelta kBackoffInitialDelay = base::Milliseconds(100); -constexpr int kBackoffMultiplyFactor = 2; -constexpr int kMaxRetries = 3; - -static const BackoffEntry::Policy kTestBackoffPolicy = { - 0, // Number of initial errors to ignore without backoff. - static_cast<int>( - kBackoffInitialDelay - .InMilliseconds()), // Initial delay for backoff in ms. - kBackoffMultiplyFactor, // Factor to multiply for exponential backoff. - 0, // Fuzzing percentage. - static_cast<int>( - base::Seconds(1).InMilliseconds()), // Maximum time to delay requests - // in ms: 1 second. - -1, // Don't discard entry. - false // Don't use initial delay unless the last was an error. -}; class SerialWorkerTest : public TestWithTaskEnvironment { public: @@ -70,20 +48,17 @@ raw_ptr<SerialWorkerTest> test_; }; - explicit TestSerialWorker(SerialWorkerTest* t) - : SerialWorker(/*max_number_of_retries=*/kMaxRetries, - &kTestBackoffPolicy), - test_(t) {} + explicit TestSerialWorker(SerialWorkerTest* t) : test_(t) {} ~TestSerialWorker() override = default; std::unique_ptr<SerialWorker::WorkItem> CreateWorkItem() override { return std::make_unique<TestWorkItem>(test_); } - bool OnWorkFinished( + void OnWorkFinished( std::unique_ptr<SerialWorker::WorkItem> work_item) override { - CHECK(test_); - return test_->OnWorkFinished(std::move(work_item)); + ASSERT_TRUE(test_); + test_->OnWorkFinished(); } private: @@ -101,7 +76,6 @@ EXPECT_FALSE(work_running_) << "`DoWork()` is not called serially!"; work_running_ = true; } - num_work_calls_observed_++; BreakNow("OnWork"); { base::ScopedAllowBaseSyncPrimitivesForTesting @@ -128,12 +102,11 @@ CompleteFollowup(); } - bool OnWorkFinished(std::unique_ptr<SerialWorker::WorkItem> work_item) { + void OnWorkFinished() { EXPECT_TRUE(task_runner_->BelongsToCurrentThread()); EXPECT_EQ(output_value_, input_value_); ++work_finished_calls_; BreakNow("OnWorkFinished"); - return on_work_finished_should_report_success_; } protected: @@ -163,8 +136,8 @@ } SerialWorkerTest() - : TestWithTaskEnvironment( - base::test::TaskEnvironment::TimeSource::MOCK_TIME), + : input_value_(0), + output_value_(-1), work_allowed_(base::WaitableEvent::ResetPolicy::AUTOMATIC, base::WaitableEvent::InitialState::NOT_SIGNALED), work_called_(base::WaitableEvent::ResetPolicy::AUTOMATIC, @@ -200,12 +173,9 @@ } // Input value read on WorkerPool. - int input_value_ = 0; + int input_value_; // Output value written on WorkerPool. - int output_value_ = -1; - // The number of times we saw an OnWork call. - int num_work_calls_observed_ = 0; - bool on_work_finished_should_report_success_ = true; + int output_value_; // read is called on WorkerPool so we need to synchronize with it. base::WaitableEvent work_allowed_; @@ -428,120 +398,6 @@ EXPECT_TRUE(base::CurrentThread::Get()->IsIdleForTesting()); } -TEST_F(SerialWorkerTest, RetryAndThenSucceed) { - ASSERT_EQ(0, worker_->GetBackoffEntryForTesting().failure_count()); - - // Induce a failure. - on_work_finished_should_report_success_ = false; - ++input_value_; - worker_->WorkNow(); - RunUntilBreak("OnWork"); - UnblockWork(); - RunUntilBreak("OnFollowup"); - RunUntilBreak("OnWorkFinished"); - - // Confirm it failed and that a retry was scheduled. - ASSERT_EQ(1, worker_->GetBackoffEntryForTesting().failure_count()); - EXPECT_EQ(kBackoffInitialDelay, - worker_->GetBackoffEntryForTesting().GetTimeUntilRelease()); - - // Make the subsequent attempt succeed. - on_work_finished_should_report_success_ = true; - - RunUntilBreak("OnWork"); - UnblockWork(); - RunUntilBreak("OnFollowup"); - RunUntilBreak("OnWorkFinished"); - ASSERT_EQ(0, worker_->GetBackoffEntryForTesting().failure_count()); - - EXPECT_EQ(2, num_work_calls_observed_); - - // No more tasks should remain. - EXPECT_TRUE(base::CurrentThread::Get()->IsIdleForTesting()); -} - -TEST_F(SerialWorkerTest, ExternalWorkRequestResetsRetryState) { - ASSERT_EQ(0, worker_->GetBackoffEntryForTesting().failure_count()); - - // Induce a failure. - on_work_finished_should_report_success_ = false; - ++input_value_; - worker_->WorkNow(); - RunUntilBreak("OnWork"); - UnblockWork(); - RunUntilBreak("OnFollowup"); - RunUntilBreak("OnWorkFinished"); - - // Confirm it failed and that a retry was scheduled. - ASSERT_EQ(1, worker_->GetBackoffEntryForTesting().failure_count()); - EXPECT_TRUE(worker_->GetRetryTimerForTesting().IsRunning()); - EXPECT_EQ(kBackoffInitialDelay, - worker_->GetBackoffEntryForTesting().GetTimeUntilRelease()); - on_work_finished_should_report_success_ = true; - - // The retry state should be reset before we see OnWorkFinished. - worker_->WorkNow(); - ASSERT_EQ(0, worker_->GetBackoffEntryForTesting().failure_count()); - EXPECT_FALSE(worker_->GetRetryTimerForTesting().IsRunning()); - EXPECT_EQ(base::TimeDelta(), - worker_->GetBackoffEntryForTesting().GetTimeUntilRelease()); - RunUntilBreak("OnWork"); - UnblockWork(); - RunUntilBreak("OnFollowup"); - RunUntilBreak("OnWorkFinished"); - - // No more tasks should remain. - EXPECT_TRUE(base::CurrentThread::Get()->IsIdleForTesting()); -} - -TEST_F(SerialWorkerTest, MultipleFailureExponentialBackoff) { - ASSERT_EQ(0, worker_->GetBackoffEntryForTesting().failure_count()); - - // Induce a failure. - on_work_finished_should_report_success_ = false; - ++input_value_; - worker_->WorkNow(); - RunUntilBreak("OnWork"); - UnblockWork(); - RunUntilBreak("OnFollowup"); - RunUntilBreak("OnWorkFinished"); - - for (int retry_attempt_count = 1; retry_attempt_count <= kMaxRetries; - retry_attempt_count++) { - // Confirm it failed and that a retry was scheduled. - ASSERT_EQ(retry_attempt_count, - worker_->GetBackoffEntryForTesting().failure_count()); - EXPECT_TRUE(worker_->GetRetryTimerForTesting().IsRunning()); - base::TimeDelta expected_backoff_delay; - if (retry_attempt_count == 1) { - expected_backoff_delay = kBackoffInitialDelay; - } else { - expected_backoff_delay = kBackoffInitialDelay * kBackoffMultiplyFactor * - (retry_attempt_count - 1); - } - EXPECT_EQ(expected_backoff_delay, - worker_->GetBackoffEntryForTesting().GetTimeUntilRelease()) - << "retry_attempt_count=" << retry_attempt_count; - - // |on_work_finished_should_report_success_| is still false, so the retry - // will fail too - RunUntilBreak("OnWork"); - UnblockWork(); - RunUntilBreak("OnFollowup"); - RunUntilBreak("OnWorkFinished"); - } - - // The last retry attempt resets the retry state. - ASSERT_EQ(0, worker_->GetBackoffEntryForTesting().failure_count()); - EXPECT_FALSE(worker_->GetRetryTimerForTesting().IsRunning()); - EXPECT_EQ(base::TimeDelta(), - worker_->GetBackoffEntryForTesting().GetTimeUntilRelease()); - on_work_finished_should_report_success_ = true; - - // No more tasks should remain. - EXPECT_TRUE(base::CurrentThread::Get()->IsIdleForTesting()); -} - } // namespace } // namespace net
diff --git a/services/device/geolocation/android/junit/src/org/chromium/device/geolocation/LocationProviderTest.java b/services/device/geolocation/android/junit/src/org/chromium/device/geolocation/LocationProviderTest.java index 53e718e..93b5536 100644 --- a/services/device/geolocation/android/junit/src/org/chromium/device/geolocation/LocationProviderTest.java +++ b/services/device/geolocation/android/junit/src/org/chromium/device/geolocation/LocationProviderTest.java
@@ -9,7 +9,6 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.doAnswer; -import android.content.Context; import android.location.LocationManager; import android.os.Build; @@ -26,9 +25,9 @@ import org.mockito.stubbing.Answer; import org.robolectric.ParameterizedRobolectricTestRunner; import org.robolectric.ParameterizedRobolectricTestRunner.Parameters; +import org.robolectric.RuntimeEnvironment; import org.robolectric.Shadows; import org.robolectric.annotation.Config; -import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowLocationManager; import org.robolectric.shadows.ShadowLog; // remove me ? @@ -58,16 +57,14 @@ {LocationProviderType.ANDROID}, {LocationProviderType.GMS_CORE}}); } - @Mock - private Context mContext; - // Member variables for LocationProviderType.GMS_CORE case. @Mock private GoogleApiClient mGoogleApiClient; private boolean mGoogleApiClientIsConnected; // Member variables for LocationProviderType.ANDROID case. - private LocationManager mLocationManager; + private LocationManager mLocationManager = + RuntimeEnvironment.application.getSystemService(LocationManager.class); private ShadowLocationManager mShadowLocationManager; private LocationProviderAdapter mLocationProviderAdapter; @@ -82,8 +79,6 @@ public void setUp() { ShadowLog.stream = System.out; MockitoAnnotations.initMocks(this); - - mContext = Mockito.mock(Context.class); } /** @@ -143,7 +138,6 @@ // Robolectric has a ShadowLocationManager class that mocks the behaviour of the real // class very closely. Use it here. - mLocationManager = Shadow.newInstanceOf(LocationManager.class); mShadowLocationManager = Shadows.shadowOf(mLocationManager); locationProviderAndroid.setLocationManagerForTesting(mLocationManager); LocationProviderFactory.setLocationProviderImpl(locationProviderAndroid);
diff --git a/sql/sqlite_features_unittest.cc b/sql/sqlite_features_unittest.cc index 15611b824..6c225021 100644 --- a/sql/sqlite_features_unittest.cc +++ b/sql/sqlite_features_unittest.cc
@@ -382,6 +382,23 @@ ASSERT_TRUE(db_.Execute("DELETE FROM rows WHERE id=6")); EXPECT_FALSE(select.Step()); + + // Check that the DELETEs were applied as expected. + + static const char kSelectAllSql[] = "SELECT id,t FROM rows"; + sql::Statement select_all( + db_.GetCachedStatement(SQL_FROM_HERE, kSelectAllSql)); + std::vector<int> remaining_ids; + std::vector<std::string> remaining_texts; + while (select_all.Step()) { + remaining_ids.push_back(select_all.ColumnInt(0)); + remaining_texts.push_back(select_all.ColumnString(1)); + } + + std::vector<int> expected_remaining_ids = {3, 5}; + EXPECT_EQ(expected_remaining_ids, remaining_ids); + std::vector<std::string> expected_remaining_texts = {"three", "five"}; + EXPECT_EQ(expected_remaining_texts, remaining_texts); } // The "No Isolation Between Operations On The Same Database Connection" section @@ -417,6 +434,93 @@ ASSERT_TRUE(db_.Execute("DELETE FROM rows WHERE id=6")); EXPECT_FALSE(select.Step()); + + // Check that the DELETEs were applied as expected. + + static const char kSelectAllSql[] = "SELECT id,t FROM rows"; + sql::Statement select_all( + db_.GetCachedStatement(SQL_FROM_HERE, kSelectAllSql)); + std::vector<int> remaining_ids; + std::vector<std::string> remaining_texts; + while (select_all.Step()) { + remaining_ids.push_back(select_all.ColumnInt(0)); + remaining_texts.push_back(select_all.ColumnString(1)); + } + + std::vector<int> expected_remaining_ids = {3, 5}; + EXPECT_EQ(expected_remaining_ids, remaining_ids); + std::vector<std::string> expected_remaining_texts = {"three", "five"}; + EXPECT_EQ(expected_remaining_texts, remaining_texts); +} + +// The "No Isolation Between Operations On The Same Database Connection" section +// in https://sqlite.org/isolation.html states that it's safe to DELETE a row +// while a SELECT statement executes, but the DELETEd row may or may not show up +// in the SELECT results. (See the test above for a case where the DELETEd row +// is guaranteed to now show up in the SELECT results.) +// +// This seems to imply that DELETEing from a table that is not read by the +// concurrent SELECT statement is safe and well-defined, as the DELETEd row(s) +// cannot possibly show up in the SELECT results. +// +// Chrome features are allowed to rely on the implication above, because it +// comes in very handy for DELETEing data across multiple tables. This test +// ensures that our assumption remains valid. +TEST_F(SQLiteFeaturesTest, DeleteWhileSelectingFromDifferentTable) { + ASSERT_TRUE(db_.Execute("CREATE TABLE main(id INTEGER PRIMARY KEY, t TEXT)")); + ASSERT_TRUE(db_.Execute("INSERT INTO main VALUES(2, 'two')")); + ASSERT_TRUE(db_.Execute("INSERT INTO main VALUES(3, 'three')")); + ASSERT_TRUE(db_.Execute("INSERT INTO main VALUES(4, 'four')")); + ASSERT_TRUE(db_.Execute("INSERT INTO main VALUES(5, 'five')")); + ASSERT_TRUE(db_.Execute("INSERT INTO main VALUES(6, 'six')")); + + ASSERT_TRUE( + db_.Execute("CREATE TABLE other(id INTEGER PRIMARY KEY, t TEXT)")); + ASSERT_TRUE(db_.Execute("INSERT INTO other VALUES(1, 'one')")); + ASSERT_TRUE(db_.Execute("INSERT INTO other VALUES(2, 'two')")); + ASSERT_TRUE(db_.Execute("INSERT INTO other VALUES(3, 'three')")); + ASSERT_TRUE(db_.Execute("INSERT INTO other VALUES(4, 'four')")); + ASSERT_TRUE(db_.Execute("INSERT INTO other VALUES(5, 'five')")); + ASSERT_TRUE(db_.Execute("INSERT INTO other VALUES(6, 'six')")); + ASSERT_TRUE(db_.Execute("INSERT INTO other VALUES(7, 'seven')")); + + static const char kSelectEvenSql[] = "SELECT id,t FROM main WHERE id%2=0"; + sql::Statement select(db_.GetCachedStatement(SQL_FROM_HERE, kSelectEvenSql)); + + ASSERT_TRUE(select.Step()); + ASSERT_EQ(select.ColumnInt(0), 2); + ASSERT_EQ(select.ColumnString(1), "two"); + EXPECT_TRUE(db_.Execute("DELETE FROM other WHERE id=2")); + + ASSERT_TRUE(select.Step()); + ASSERT_EQ(select.ColumnInt(0), 4); + ASSERT_EQ(select.ColumnString(1), "four"); + + ASSERT_TRUE(select.Step()); + ASSERT_EQ(select.ColumnInt(0), 6); + ASSERT_EQ(select.ColumnString(1), "six"); + ASSERT_TRUE(db_.Execute("DELETE FROM other WHERE id=4")); + ASSERT_TRUE(db_.Execute("DELETE FROM other WHERE id=5")); + ASSERT_TRUE(db_.Execute("DELETE FROM other WHERE id=6")); + + EXPECT_FALSE(select.Step()); + + // Check that the DELETEs were applied as expected. + + static const char kSelectAllSql[] = "SELECT id,t FROM other"; + sql::Statement select_all( + db_.GetCachedStatement(SQL_FROM_HERE, kSelectAllSql)); + std::vector<int> remaining_ids; + std::vector<std::string> remaining_texts; + while (select_all.Step()) { + remaining_ids.push_back(select_all.ColumnInt(0)); + remaining_texts.push_back(select_all.ColumnString(1)); + } + + std::vector<int> expected_remaining_ids = {1, 3, 7}; + EXPECT_EQ(expected_remaining_ids, remaining_ids); + std::vector<std::string> expected_remaining_texts = {"one", "three", "seven"}; + EXPECT_EQ(expected_remaining_texts, remaining_texts); } // The "No Isolation Between Operations On The Same Database Connection" section @@ -446,7 +550,7 @@ ASSERT_TRUE(select_src.Step()); ASSERT_EQ(select_src.ColumnInt(0), 2); ASSERT_EQ(select_src.ColumnString(1), "two"); - EXPECT_TRUE(db_.Execute("INSERT INTO dst VALUES(2, 'two');")); + EXPECT_TRUE(db_.Execute("INSERT INTO dst VALUES(2, 'two')")); ASSERT_TRUE(db_.Execute("INSERT INTO dst VALUES(3, 'three')")); ASSERT_TRUE(select_src.Step()); @@ -472,11 +576,11 @@ dst_texts.push_back(select_dst.ColumnString(1)); } - std::vector<int> golden_dst_ids = {2, 3, 4, 5, 6}; - EXPECT_EQ(golden_dst_ids, dst_ids); - std::vector<std::string> golden_dst_texts = {"two", "three", "four", "five", - "six"}; - EXPECT_EQ(golden_dst_texts, dst_texts); + std::vector<int> expected_dst_ids = {2, 3, 4, 5, 6}; + EXPECT_EQ(expected_dst_ids, dst_ids); + std::vector<std::string> expected_dst_texts = {"two", "three", "four", "five", + "six"}; + EXPECT_EQ(expected_dst_texts, dst_texts); } #if BUILDFLAG(IS_APPLE)
diff --git a/sql/statement_unittest.cc b/sql/statement_unittest.cc index 8601dea..89e67fe 100644 --- a/sql/statement_unittest.cc +++ b/sql/statement_unittest.cc
@@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <limits> #include <string> #include "base/bind.h" @@ -18,9 +19,9 @@ namespace sql { namespace { -class SQLStatementTest : public testing::Test { +class StatementTest : public testing::Test { public: - ~SQLStatementTest() override = default; + ~StatementTest() override = default; void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); @@ -33,102 +34,198 @@ Database db_; }; -TEST_F(SQLStatementTest, Assign) { - Statement s; - EXPECT_FALSE(s.is_valid()); +TEST_F(StatementTest, Assign) { + Statement create; + EXPECT_FALSE(create.is_valid()); - s.Assign(db_.GetUniqueStatement("CREATE TABLE foo (a, b)")); - EXPECT_TRUE(s.is_valid()); + create.Assign(db_.GetUniqueStatement( + "CREATE TABLE rows(a INTEGER PRIMARY KEY NOT NULL, b INTEGER NOT NULL)")); + EXPECT_TRUE(create.is_valid()); } -TEST_F(SQLStatementTest, Run) { - ASSERT_TRUE(db_.Execute("CREATE TABLE foo (a, b)")); - ASSERT_TRUE(db_.Execute("INSERT INTO foo (a, b) VALUES (3, 12)")); +TEST_F(StatementTest, Run) { + ASSERT_TRUE(db_.Execute( + "CREATE TABLE rows(a INTEGER PRIMARY KEY NOT NULL, b INTEGER NOT NULL)")); + ASSERT_TRUE(db_.Execute("INSERT INTO rows(a, b) VALUES(3, 12)")); - Statement s(db_.GetUniqueStatement("SELECT b FROM foo WHERE a=?")); - EXPECT_FALSE(s.Succeeded()); + Statement select(db_.GetUniqueStatement("SELECT b FROM rows WHERE a=?")); + EXPECT_FALSE(select.Succeeded()); // Stepping it won't work since we haven't bound the value. - EXPECT_FALSE(s.Step()); + EXPECT_FALSE(select.Step()); // Run should fail since this produces output, and we should use Step(). This // gets a bit wonky since sqlite says this is OK so succeeded is set. - s.Reset(true); - s.BindInt(0, 3); - EXPECT_FALSE(s.Run()); + select.Reset(/*clear_bound_vars=*/true); + select.BindInt64(0, 3); + EXPECT_FALSE(select.Run()); EXPECT_EQ(SQLITE_ROW, db_.GetErrorCode()); - EXPECT_TRUE(s.Succeeded()); + EXPECT_TRUE(select.Succeeded()); // Resetting it should put it back to the previous state (not runnable). - s.Reset(true); - EXPECT_FALSE(s.Succeeded()); + select.Reset(/*clear_bound_vars=*/true); + EXPECT_FALSE(select.Succeeded()); // Binding and stepping should produce one row. - s.BindInt(0, 3); - EXPECT_TRUE(s.Step()); - EXPECT_TRUE(s.Succeeded()); - EXPECT_EQ(12, s.ColumnInt(0)); - EXPECT_FALSE(s.Step()); - EXPECT_TRUE(s.Succeeded()); + select.BindInt64(0, 3); + EXPECT_TRUE(select.Step()); + EXPECT_TRUE(select.Succeeded()); + EXPECT_EQ(12, select.ColumnInt64(0)); + EXPECT_FALSE(select.Step()); + EXPECT_TRUE(select.Succeeded()); } // Error callback called for error running a statement. -TEST_F(SQLStatementTest, ErrorCallback) { - ASSERT_TRUE(db_.Execute("CREATE TABLE foo (a INTEGER PRIMARY KEY, b)")); +TEST_F(StatementTest, ErrorCallback) { + ASSERT_TRUE(db_.Execute( + "CREATE TABLE rows(a INTEGER PRIMARY KEY NOT NULL, b INTEGER NOT NULL)")); int error = SQLITE_OK; ScopedErrorCallback sec(&db_, base::BindRepeating(&CaptureErrorCallback, &error)); - // Insert in the foo table the primary key. It is an error to insert - // something other than an number. This error causes the error callback - // handler to be called with SQLITE_MISMATCH as error code. - Statement s(db_.GetUniqueStatement("INSERT INTO foo (a) VALUES (?)")); - EXPECT_TRUE(s.is_valid()); - s.BindCString(0, "bad bad"); - EXPECT_FALSE(s.Run()); + // `rows` is a table with ROWID. https://www.sqlite.org/rowidtable.html + // Since `a` is declared as INTEGER PRIMARY KEY, it is an alias for SQLITE's + // rowid. This means `a` can only take on integer values. Attempting to insert + // anything else causes the error callback handler to be called with + // SQLITE_MISMATCH as error code. + Statement insert(db_.GetUniqueStatement("INSERT INTO rows(a) VALUES(?)")); + EXPECT_TRUE(insert.is_valid()); + insert.BindString(0, "not an integer, not suitable as primary key value"); + EXPECT_FALSE(insert.Run()); EXPECT_EQ(SQLITE_MISMATCH, error); } // Error expecter works for error running a statement. -TEST_F(SQLStatementTest, ScopedIgnoreError) { - ASSERT_TRUE(db_.Execute("CREATE TABLE foo (a INTEGER PRIMARY KEY, b)")); +TEST_F(StatementTest, ScopedIgnoreError) { + ASSERT_TRUE(db_.Execute( + "CREATE TABLE rows(a INTEGER PRIMARY KEY NOT NULL, b INTEGER NOT NULL)")); - Statement s(db_.GetUniqueStatement("INSERT INTO foo (a) VALUES (?)")); - EXPECT_TRUE(s.is_valid()); + Statement insert(db_.GetUniqueStatement("INSERT INTO rows(a) VALUES(?)")); + EXPECT_TRUE(insert.is_valid()); + insert.BindString(0, "not an integer, not suitable as primary key value"); { sql::test::ScopedErrorExpecter expecter; expecter.ExpectError(SQLITE_MISMATCH); - s.BindCString(0, "bad bad"); - ASSERT_FALSE(s.Run()); - ASSERT_TRUE(expecter.SawExpectedErrors()); + EXPECT_FALSE(insert.Run()); + EXPECT_TRUE(expecter.SawExpectedErrors()); } } -TEST_F(SQLStatementTest, Reset) { - ASSERT_TRUE(db_.Execute("CREATE TABLE foo (a, b)")); - ASSERT_TRUE(db_.Execute("INSERT INTO foo (a, b) VALUES (3, 12)")); - ASSERT_TRUE(db_.Execute("INSERT INTO foo (a, b) VALUES (4, 13)")); +TEST_F(StatementTest, Reset) { + ASSERT_TRUE(db_.Execute( + "CREATE TABLE rows(a INTEGER PRIMARY KEY NOT NULL, b INTEGER NOT NULL)")); + ASSERT_TRUE(db_.Execute("INSERT INTO rows(a, b) VALUES(3, 12)")); + ASSERT_TRUE(db_.Execute("INSERT INTO rows(a, b) VALUES(4, 13)")); - Statement s(db_.GetUniqueStatement("SELECT b FROM foo WHERE a = ? ")); - s.BindInt(0, 3); - ASSERT_TRUE(s.Step()); - EXPECT_EQ(12, s.ColumnInt(0)); - ASSERT_FALSE(s.Step()); + Statement insert(db_.GetUniqueStatement("SELECT b FROM rows WHERE a=?")); + insert.BindInt64(0, 3); + ASSERT_TRUE(insert.Step()); + EXPECT_EQ(12, insert.ColumnInt64(0)); + ASSERT_FALSE(insert.Step()); - s.Reset(false); + insert.Reset(/*clear_bound_vars=*/false); // Verify that we can get all rows again. - ASSERT_TRUE(s.Step()); - EXPECT_EQ(12, s.ColumnInt(0)); - EXPECT_FALSE(s.Step()); + ASSERT_TRUE(insert.Step()); + EXPECT_EQ(12, insert.ColumnInt64(0)); + EXPECT_FALSE(insert.Step()); - s.Reset(true); - ASSERT_FALSE(s.Step()); + insert.Reset(/*clear_bound_vars=*/true); + ASSERT_FALSE(insert.Step()); } -TEST_F(SQLStatementTest, BindBlob) { - ASSERT_TRUE(db_.Execute("CREATE TABLE blobs (b BLOB NOT NULL)")); +TEST_F(StatementTest, BindInt64) { + // `id` makes SQLite's rowid mechanism explicit. We rely on it to retrieve + // the rows in the same order that they were inserted. + ASSERT_TRUE(db_.Execute( + "CREATE TABLE ints(id INTEGER PRIMARY KEY, i INTEGER NOT NULL)")); + + const std::vector<int64_t> values = { + // Small positive values. + 0, + 1, + 2, + 10, + 101, + 1002, + + // Small negative values. + -1, + -2, + -3, + -10, + -101, + -1002, + + // Large values. + std::numeric_limits<int64_t>::max(), + std::numeric_limits<int64_t>::min(), + }; + + Statement insert(db_.GetUniqueStatement("INSERT INTO ints(i) VALUES(?)")); + for (int64_t value : values) { + insert.BindInt64(0, value); + ASSERT_TRUE(insert.Run()); + insert.Reset(/*clear_bound_vars=*/true); + } + + Statement select(db_.GetUniqueStatement("SELECT i FROM ints ORDER BY id")); + for (int64_t value : values) { + ASSERT_TRUE(select.Step()); + int64_t column_value = select.ColumnInt64(0); + EXPECT_EQ(value, column_value); + } +} + +// Chrome features rely on being able to use uint64_t with ColumnInt64(). +// This is supported, because (starting in C++20) casting between signed and +// unsigned integers is well-defined in both directions. This test ensures that +// the casting works as expected. +TEST_F(StatementTest, BindInt64_FromUint64t) { + // `id` makes SQLite's rowid mechanism explicit. We rely on it to retrieve + // the rows in the same order that they were inserted. + static constexpr char kSql[] = + "CREATE TABLE ints(id INTEGER PRIMARY KEY NOT NULL, i INTEGER NOT NULL)"; + ASSERT_TRUE(db_.Execute(kSql)); + + const std::vector<uint64_t> values = { + // Small positive values. + 0, + 1, + 2, + 10, + 101, + 1002, + + // Large values. + std::numeric_limits<int64_t>::max() - 1, + std::numeric_limits<int64_t>::max(), + std::numeric_limits<uint64_t>::max() - 1, + std::numeric_limits<uint64_t>::max(), + }; + + Statement insert(db_.GetUniqueStatement("INSERT INTO ints(i) VALUES(?)")); + for (uint64_t value : values) { + insert.BindInt64(0, static_cast<int64_t>(value)); + ASSERT_TRUE(insert.Run()); + insert.Reset(/*clear_bound_vars=*/true); + } + + Statement select(db_.GetUniqueStatement("SELECT i FROM ints ORDER BY id")); + for (uint64_t value : values) { + ASSERT_TRUE(select.Step()); + int64_t column_value = select.ColumnInt64(0); + uint64_t cast_column_value = static_cast<uint64_t>(column_value); + EXPECT_EQ(value, cast_column_value) << " column_value: " << column_value; + } +} + +TEST_F(StatementTest, BindBlob) { + // `id` makes SQLite's rowid mechanism explicit. We rely on it to retrieve + // the rows in the same order that they were inserted. + ASSERT_TRUE(db_.Execute( + "CREATE TABLE blobs(id INTEGER PRIMARY KEY NOT NULL, b BLOB NOT NULL)")); const std::vector<std::vector<uint8_t>> values = { {}, @@ -136,14 +233,14 @@ {0x41, 0x42, 0x43, 0x44}, }; - Statement insert(db_.GetUniqueStatement("INSERT INTO blobs VALUES(?)")); + Statement insert(db_.GetUniqueStatement("INSERT INTO blobs(b) VALUES(?)")); for (const std::vector<uint8_t>& value : values) { insert.BindBlob(0, value); ASSERT_TRUE(insert.Run()); - insert.Reset(/* clear_bound_vars= */ true); + insert.Reset(/*clear_bound_vars=*/true); } - Statement select(db_.GetUniqueStatement("SELECT b FROM blobs")); + Statement select(db_.GetUniqueStatement("SELECT b FROM blobs ORDER BY id")); for (const std::vector<uint8_t>& value : values) { ASSERT_TRUE(select.Step()); std::vector<uint8_t> column_value; @@ -153,8 +250,11 @@ EXPECT_FALSE(select.Step()); } -TEST_F(SQLStatementTest, BindString) { - ASSERT_TRUE(db_.Execute("CREATE TABLE strings (s TEXT NOT NULL)")); +TEST_F(StatementTest, BindString) { + // `id` makes SQLite's rowid mechanism explicit. We rely on it to retrieve + // the rows in the same order that they were inserted. + ASSERT_TRUE(db_.Execute( + "CREATE TABLE texts(id INTEGER PRIMARY KEY NOT NULL, t TEXT NOT NULL)")); const std::vector<std::string> values = { "", @@ -167,14 +267,14 @@ std::string("\x00Test", 5), }; - Statement insert(db_.GetUniqueStatement("INSERT INTO strings VALUES(?)")); + Statement insert(db_.GetUniqueStatement("INSERT INTO texts(t) VALUES(?)")); for (const std::string& value : values) { insert.BindString(0, value); ASSERT_TRUE(insert.Run()); - insert.Reset(/* clear_bound_vars= */ true); + insert.Reset(/*clear_bound_vars=*/true); } - Statement select(db_.GetUniqueStatement("SELECT s FROM strings")); + Statement select(db_.GetUniqueStatement("SELECT t FROM texts ORDER BY id")); for (const std::string& value : values) { ASSERT_TRUE(select.Step()); EXPECT_EQ(value, select.ColumnString(0)); @@ -182,14 +282,17 @@ EXPECT_FALSE(select.Step()); } -TEST_F(SQLStatementTest, BindString_NullData) { - ASSERT_TRUE(db_.Execute("CREATE TABLE strings (s TEXT NOT NULL)")); +TEST_F(StatementTest, BindString_NullData) { + // `id` makes SQLite's rowid mechanism explicit. We rely on it to retrieve + // the rows in the same order that they were inserted. + ASSERT_TRUE(db_.Execute( + "CREATE TABLE texts(id INTEGER PRIMARY KEY NOT NULL, t TEXT NOT NULL)")); - Statement insert(db_.GetUniqueStatement("INSERT INTO strings VALUES(?)")); + Statement insert(db_.GetUniqueStatement("INSERT INTO texts(t) VALUES(?)")); insert.BindString(0, base::StringPiece(nullptr, 0)); ASSERT_TRUE(insert.Run()); - Statement select(db_.GetUniqueStatement("SELECT s FROM strings")); + Statement select(db_.GetUniqueStatement("SELECT t FROM texts ORDER BY id")); ASSERT_TRUE(select.Step()); EXPECT_EQ(std::string(), select.ColumnString(0));
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json index 52f6f05..c312da06 100644 --- a/testing/buildbot/chromium.android.fyi.json +++ b/testing/buildbot/chromium.android.fyi.json
@@ -5350,6 +5350,609 @@ } ] }, + "android-weblayer-11-x86-rel-tests": { + "gtest_tests": [ + { + "args": [ + "--gs-results-bucket=chromium-result-details", + "--recover-devices", + "--avd-config=../../tools/android/avd/proto/generic_android30.textpb" + ], + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", + "weblayer_instrumentation_test_apk_ToT_Tests" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "name": "weblayer_instrumentation_test_apk_ToT_Tests", + "resultdb": { + "enable": true, + "has_native_resultdb_integration": true + }, + "swarming": { + "can_use_on_swarming_builders": true, + "cipd_packages": [ + { + "cipd_package": "infra/tools/luci/logdog/butler/${platform}", + "location": "bin", + "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c" + } + ], + "dimension_sets": [ + { + "cpu": "x86-64", + "device_os": null, + "device_type": null, + "machine_type": "n1-standard-4|e2-standard-4", + "os": "Ubuntu-16.04|Ubuntu-18.04", + "pool": "chromium.tests.avd" + } + ], + "named_caches": [ + { + "name": "avd_generic_android30", + "path": ".android" + }, + { + "name": "system_images_android_30_google_apis_x86", + "path": ".emulator_sdk" + } + ], + "optional_dimensions": { + "60": [ + { + "caches": "avd_generic_android30" + } + ] + }, + "output_links": [ + { + "link": [ + "https://luci-logdog.appspot.com/v/?s", + "=android%2Fswarming%2Flogcats%2F", + "${TASK_ID}%2F%2B%2Funified_logcats" + ], + "name": "shard #${SHARD_INDEX} logcats" + } + ], + "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" + }, + "test": "weblayer_instrumentation_test_apk", + "test_id_prefix": "ninja://weblayer/browser/android/javatests:weblayer_instrumentation_test_apk/" + }, + { + "args": [ + "--additional-apk=apks/WebLayerShellSystemWebView.apk", + "--webview-apk-path=apks/SystemWebView.apk", + "--test-runner-outdir", + ".", + "--client-outdir", + "../../weblayer_instrumentation_test_M100/out/Release", + "--implementation-outdir", + ".", + "--test-expectations", + "../../weblayer/browser/android/javatests/skew/expectations.txt", + "--client-version=100", + "--gs-results-bucket=chromium-result-details", + "--recover-devices", + "--avd-config=../../tools/android/avd/proto/generic_android30.textpb" + ], + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", + "weblayer_skew_tests_with_client_from_100" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "name": "weblayer_skew_tests_with_client_from_100", + "resultdb": { + "enable": true, + "has_native_resultdb_integration": true + }, + "swarming": { + "can_use_on_swarming_builders": true, + "cipd_packages": [ + { + "cipd_package": "chromium/testing/weblayer-x86", + "location": "weblayer_instrumentation_test_M100", + "revision": "version:100.0.4896.8" + }, + { + "cipd_package": "infra/tools/luci/logdog/butler/${platform}", + "location": "bin", + "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c" + } + ], + "dimension_sets": [ + { + "cpu": "x86-64", + "device_os": null, + "device_type": null, + "machine_type": "n1-standard-4|e2-standard-4", + "os": "Ubuntu-16.04|Ubuntu-18.04", + "pool": "chromium.tests.avd" + } + ], + "named_caches": [ + { + "name": "avd_generic_android30", + "path": ".android" + }, + { + "name": "system_images_android_30_google_apis_x86", + "path": ".emulator_sdk" + } + ], + "optional_dimensions": { + "60": [ + { + "caches": "avd_generic_android30" + } + ] + }, + "output_links": [ + { + "link": [ + "https://luci-logdog.appspot.com/v/?s", + "=android%2Fswarming%2Flogcats%2F", + "${TASK_ID}%2F%2B%2Funified_logcats" + ], + "name": "shard #${SHARD_INDEX} logcats" + } + ], + "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", + "shards": 2 + }, + "test": "weblayer_skew_tests", + "test_id_prefix": "ninja://weblayer/browser/android/javatests:weblayer_skew_tests/" + }, + { + "args": [ + "--additional-apk=apks/WebLayerShellSystemWebView.apk", + "--webview-apk-path=apks/SystemWebView.apk", + "--test-runner-outdir", + ".", + "--client-outdir", + "../../weblayer_instrumentation_test_M95/out/Release", + "--implementation-outdir", + ".", + "--test-expectations", + "../../weblayer/browser/android/javatests/skew/expectations.txt", + "--client-version=95", + "--gs-results-bucket=chromium-result-details", + "--recover-devices", + "--avd-config=../../tools/android/avd/proto/generic_android30.textpb" + ], + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", + "weblayer_skew_tests_with_client_from_95" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "name": "weblayer_skew_tests_with_client_from_95", + "resultdb": { + "enable": true, + "has_native_resultdb_integration": true + }, + "swarming": { + "can_use_on_swarming_builders": true, + "cipd_packages": [ + { + "cipd_package": "chromium/testing/weblayer-x86", + "location": "weblayer_instrumentation_test_M95", + "revision": "version:95.0.4638.78" + }, + { + "cipd_package": "infra/tools/luci/logdog/butler/${platform}", + "location": "bin", + "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c" + } + ], + "dimension_sets": [ + { + "cpu": "x86-64", + "device_os": null, + "device_type": null, + "machine_type": "n1-standard-4|e2-standard-4", + "os": "Ubuntu-16.04|Ubuntu-18.04", + "pool": "chromium.tests.avd" + } + ], + "named_caches": [ + { + "name": "avd_generic_android30", + "path": ".android" + }, + { + "name": "system_images_android_30_google_apis_x86", + "path": ".emulator_sdk" + } + ], + "optional_dimensions": { + "60": [ + { + "caches": "avd_generic_android30" + } + ] + }, + "output_links": [ + { + "link": [ + "https://luci-logdog.appspot.com/v/?s", + "=android%2Fswarming%2Flogcats%2F", + "${TASK_ID}%2F%2B%2Funified_logcats" + ], + "name": "shard #${SHARD_INDEX} logcats" + } + ], + "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", + "shards": 2 + }, + "test": "weblayer_skew_tests", + "test_id_prefix": "ninja://weblayer/browser/android/javatests:weblayer_skew_tests/" + }, + { + "args": [ + "--additional-apk=apks/WebLayerShellSystemWebView.apk", + "--webview-apk-path=apks/SystemWebView.apk", + "--test-runner-outdir", + ".", + "--client-outdir", + "../../weblayer_instrumentation_test_M99/out/Release", + "--implementation-outdir", + ".", + "--test-expectations", + "../../weblayer/browser/android/javatests/skew/expectations.txt", + "--client-version=99", + "--gs-results-bucket=chromium-result-details", + "--recover-devices", + "--avd-config=../../tools/android/avd/proto/generic_android30.textpb" + ], + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", + "weblayer_skew_tests_with_client_from_99" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "name": "weblayer_skew_tests_with_client_from_99", + "resultdb": { + "enable": true, + "has_native_resultdb_integration": true + }, + "swarming": { + "can_use_on_swarming_builders": true, + "cipd_packages": [ + { + "cipd_package": "chromium/testing/weblayer-x86", + "location": "weblayer_instrumentation_test_M99", + "revision": "version:99.0.4844.44" + }, + { + "cipd_package": "infra/tools/luci/logdog/butler/${platform}", + "location": "bin", + "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c" + } + ], + "dimension_sets": [ + { + "cpu": "x86-64", + "device_os": null, + "device_type": null, + "machine_type": "n1-standard-4|e2-standard-4", + "os": "Ubuntu-16.04|Ubuntu-18.04", + "pool": "chromium.tests.avd" + } + ], + "named_caches": [ + { + "name": "avd_generic_android30", + "path": ".android" + }, + { + "name": "system_images_android_30_google_apis_x86", + "path": ".emulator_sdk" + } + ], + "optional_dimensions": { + "60": [ + { + "caches": "avd_generic_android30" + } + ] + }, + "output_links": [ + { + "link": [ + "https://luci-logdog.appspot.com/v/?s", + "=android%2Fswarming%2Flogcats%2F", + "${TASK_ID}%2F%2B%2Funified_logcats" + ], + "name": "shard #${SHARD_INDEX} logcats" + } + ], + "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", + "shards": 2 + }, + "test": "weblayer_skew_tests", + "test_id_prefix": "ninja://weblayer/browser/android/javatests:weblayer_skew_tests/" + }, + { + "args": [ + "--additional-apk=apks/WebLayerShellSystemWebView.apk", + "--webview-apk-path=apks/AOSP_SystemWebView.apk", + "--test-runner-outdir", + ".", + "--client-outdir", + ".", + "--implementation-outdir", + "../../weblayer_instrumentation_test_M100/out/Release", + "--test-expectations", + "../../weblayer/browser/android/javatests/skew/expectations.txt", + "--impl-version=100", + "--gs-results-bucket=chromium-result-details", + "--recover-devices", + "--avd-config=../../tools/android/avd/proto/generic_android30.textpb" + ], + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", + "weblayer_skew_tests_with_impl_from_100" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "name": "weblayer_skew_tests_with_impl_from_100", + "resultdb": { + "enable": true, + "has_native_resultdb_integration": true + }, + "swarming": { + "can_use_on_swarming_builders": true, + "cipd_packages": [ + { + "cipd_package": "chromium/testing/weblayer-x86", + "location": "weblayer_instrumentation_test_M100", + "revision": "version:100.0.4896.8" + }, + { + "cipd_package": "infra/tools/luci/logdog/butler/${platform}", + "location": "bin", + "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c" + } + ], + "dimension_sets": [ + { + "cpu": "x86-64", + "device_os": null, + "device_type": null, + "machine_type": "n1-standard-4|e2-standard-4", + "os": "Ubuntu-16.04|Ubuntu-18.04", + "pool": "chromium.tests.avd" + } + ], + "named_caches": [ + { + "name": "avd_generic_android30", + "path": ".android" + }, + { + "name": "system_images_android_30_google_apis_x86", + "path": ".emulator_sdk" + } + ], + "optional_dimensions": { + "60": [ + { + "caches": "avd_generic_android30" + } + ] + }, + "output_links": [ + { + "link": [ + "https://luci-logdog.appspot.com/v/?s", + "=android%2Fswarming%2Flogcats%2F", + "${TASK_ID}%2F%2B%2Funified_logcats" + ], + "name": "shard #${SHARD_INDEX} logcats" + } + ], + "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", + "shards": 2 + }, + "test": "weblayer_skew_tests", + "test_id_prefix": "ninja://weblayer/browser/android/javatests:weblayer_skew_tests/" + }, + { + "args": [ + "--additional-apk=apks/WebLayerShellSystemWebView.apk", + "--webview-apk-path=apks/AOSP_SystemWebView.apk", + "--test-runner-outdir", + ".", + "--client-outdir", + ".", + "--implementation-outdir", + "../../weblayer_instrumentation_test_M95/out/Release", + "--test-expectations", + "../../weblayer/browser/android/javatests/skew/expectations.txt", + "--impl-version=95", + "--gs-results-bucket=chromium-result-details", + "--recover-devices", + "--avd-config=../../tools/android/avd/proto/generic_android30.textpb" + ], + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", + "weblayer_skew_tests_with_impl_from_95" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "name": "weblayer_skew_tests_with_impl_from_95", + "resultdb": { + "enable": true, + "has_native_resultdb_integration": true + }, + "swarming": { + "can_use_on_swarming_builders": true, + "cipd_packages": [ + { + "cipd_package": "chromium/testing/weblayer-x86", + "location": "weblayer_instrumentation_test_M95", + "revision": "version:95.0.4638.78" + }, + { + "cipd_package": "infra/tools/luci/logdog/butler/${platform}", + "location": "bin", + "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c" + } + ], + "dimension_sets": [ + { + "cpu": "x86-64", + "device_os": null, + "device_type": null, + "machine_type": "n1-standard-4|e2-standard-4", + "os": "Ubuntu-16.04|Ubuntu-18.04", + "pool": "chromium.tests.avd" + } + ], + "named_caches": [ + { + "name": "avd_generic_android30", + "path": ".android" + }, + { + "name": "system_images_android_30_google_apis_x86", + "path": ".emulator_sdk" + } + ], + "optional_dimensions": { + "60": [ + { + "caches": "avd_generic_android30" + } + ] + }, + "output_links": [ + { + "link": [ + "https://luci-logdog.appspot.com/v/?s", + "=android%2Fswarming%2Flogcats%2F", + "${TASK_ID}%2F%2B%2Funified_logcats" + ], + "name": "shard #${SHARD_INDEX} logcats" + } + ], + "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", + "shards": 2 + }, + "test": "weblayer_skew_tests", + "test_id_prefix": "ninja://weblayer/browser/android/javatests:weblayer_skew_tests/" + }, + { + "args": [ + "--additional-apk=apks/WebLayerShellSystemWebView.apk", + "--webview-apk-path=apks/AOSP_SystemWebView.apk", + "--test-runner-outdir", + ".", + "--client-outdir", + ".", + "--implementation-outdir", + "../../weblayer_instrumentation_test_M99/out/Release", + "--test-expectations", + "../../weblayer/browser/android/javatests/skew/expectations.txt", + "--impl-version=99", + "--gs-results-bucket=chromium-result-details", + "--recover-devices", + "--avd-config=../../tools/android/avd/proto/generic_android30.textpb" + ], + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", + "weblayer_skew_tests_with_impl_from_99" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "name": "weblayer_skew_tests_with_impl_from_99", + "resultdb": { + "enable": true, + "has_native_resultdb_integration": true + }, + "swarming": { + "can_use_on_swarming_builders": true, + "cipd_packages": [ + { + "cipd_package": "chromium/testing/weblayer-x86", + "location": "weblayer_instrumentation_test_M99", + "revision": "version:99.0.4844.44" + }, + { + "cipd_package": "infra/tools/luci/logdog/butler/${platform}", + "location": "bin", + "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c" + } + ], + "dimension_sets": [ + { + "cpu": "x86-64", + "device_os": null, + "device_type": null, + "machine_type": "n1-standard-4|e2-standard-4", + "os": "Ubuntu-16.04|Ubuntu-18.04", + "pool": "chromium.tests.avd" + } + ], + "named_caches": [ + { + "name": "avd_generic_android30", + "path": ".android" + }, + { + "name": "system_images_android_30_google_apis_x86", + "path": ".emulator_sdk" + } + ], + "optional_dimensions": { + "60": [ + { + "caches": "avd_generic_android30" + } + ] + }, + "output_links": [ + { + "link": [ + "https://luci-logdog.appspot.com/v/?s", + "=android%2Fswarming%2Flogcats%2F", + "${TASK_ID}%2F%2B%2Funified_logcats" + ], + "name": "shard #${SHARD_INDEX} logcats" + } + ], + "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", + "shards": 2 + }, + "test": "weblayer_skew_tests", + "test_id_prefix": "ninja://weblayer/browser/android/javatests:weblayer_skew_tests/" + } + ] + }, "android-weblayer-pie-x86-wpt-fyi-rel": { "isolated_scripts": [ {
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json index 75fb49a..8ce3bdc 100644 --- a/testing/buildbot/chromium.android.json +++ b/testing/buildbot/chromium.android.json
@@ -42782,7 +42782,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M99", - "revision": "version:99.0.4844.42" + "revision": "version:99.0.4844.44" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -43046,7 +43046,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M99", - "revision": "version:99.0.4844.42" + "revision": "version:99.0.4844.44" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -43314,7 +43314,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M99", - "revision": "version:99.0.4844.42" + "revision": "version:99.0.4844.44" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -43578,7 +43578,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M99", - "revision": "version:99.0.4844.42" + "revision": "version:99.0.4844.44" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -43917,7 +43917,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M99", - "revision": "version:99.0.4844.42" + "revision": "version:99.0.4844.44" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -44181,7 +44181,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M99", - "revision": "version:99.0.4844.42" + "revision": "version:99.0.4844.44" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -44520,7 +44520,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M99", - "revision": "version:99.0.4844.42" + "revision": "version:99.0.4844.44" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -44784,7 +44784,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M99", - "revision": "version:99.0.4844.42" + "revision": "version:99.0.4844.44" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl index a0b0e7a..f7b87011 100644 --- a/testing/buildbot/variants.pyl +++ b/testing/buildbot/variants.pyl
@@ -411,7 +411,7 @@ { 'cipd_package': 'chromium/testing/weblayer-x86', 'location': 'weblayer_instrumentation_test_M99', - 'revision': 'version:99.0.4844.42', + 'revision': 'version:99.0.4844.44', } ], }, @@ -483,7 +483,7 @@ { 'cipd_package': 'chromium/testing/weblayer-x86', 'location': 'weblayer_instrumentation_test_M99', - 'revision': 'version:99.0.4844.42', + 'revision': 'version:99.0.4844.44', } ], }, @@ -555,7 +555,7 @@ { 'cipd_package': 'chromium/testing/weblayer-x86', 'location': 'weblayer_instrumentation_test_M99', - 'revision': 'version:99.0.4844.42', + 'revision': 'version:99.0.4844.44', } ], },
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl index 9e06206..0471ad9 100644 --- a/testing/buildbot/waterfalls.pyl +++ b/testing/buildbot/waterfalls.pyl
@@ -1195,6 +1195,19 @@ 'use_swarming': True, 'os_type': 'android', }, + 'android-weblayer-11-x86-rel-tests': { + 'mixins': [ + 'has_native_resultdb_integration', + '11-x86-emulator', + 'emulator-4-cores', + 'linux-xenial-or-bionic', + 'x86-64', + ], + 'os_type': 'android', + 'test_suites': { + 'gtest_tests': 'android_weblayer_x86_10_gtests', + } + }, 'android-weblayer-pie-x86-wpt-fyi-rel': { 'mixins': [ 'has_native_resultdb_integration',
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json index 56cd170..c543556 100644 --- a/testing/variations/fieldtrial_testing_config.json +++ b/testing/variations/fieldtrial_testing_config.json
@@ -3506,6 +3506,21 @@ ] } ], + "IOSTabGridSearch": [ + { + "platforms": [ + "ios" + ], + "experiments": [ + { + "name": "Enabled", + "enable_features": [ + "IOSTabGridSearch" + ] + } + ] + } + ], "IOSUseUserDefaultsForExitedCleanlyBeacon": [ { "platforms": [ @@ -5433,21 +5448,6 @@ ] } ], - "RemoveExcessNTPs": [ - { - "platforms": [ - "ios" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "RemoveExcessNTPs" - ] - } - ] - } - ], "ReportCertificateErrors": [ { "platforms": [ @@ -5862,17 +5862,30 @@ ] } ], - "SingleNTP": [ + "SingleNTPRemoveExcessNTPs": [ { "platforms": [ "ios" ], "experiments": [ { - "name": "Enabled", + "name": "Enabled_SingleNTP_RemoveExtraNTP", + "enable_features": [ + "RemoveExcessNTPs", + "SingleNTP" + ] + }, + { + "name": "Enabled_SingleNTP", "enable_features": [ "SingleNTP" ] + }, + { + "name": "Enabled_RemoveExtraNTP", + "enable_features": [ + "RemoveExcessNTPs" + ] } ] }
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc index 50976de..a6b8e6b 100644 --- a/third_party/blink/common/features.cc +++ b/third_party/blink/common/features.cc
@@ -1269,5 +1269,8 @@ const base::Feature kWindowPlacement{"WindowPlacement", base::FEATURE_DISABLED_BY_DEFAULT}; +// TODO(crbug.com/1277431): This flag should be eventually disabled. +const base::Feature kEventPath{"EventPath", base::FEATURE_ENABLED_BY_DEFAULT}; + } // namespace features } // namespace blink
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h index 10219e3..15bae36 100644 --- a/third_party/blink/public/common/features.h +++ b/third_party/blink/public/common/features.h
@@ -654,6 +654,9 @@ // Enables the WindowPlacement RuntimeEnabledFeature. BLINK_COMMON_EXPORT extern const base::Feature kWindowPlacement; +// Gates the non-standard API Event.path to help its deprecation and removal. +BLINK_COMMON_EXPORT extern const base::Feature kEventPath; + } // namespace features } // namespace blink
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_code_cache.cc b/third_party/blink/renderer/bindings/core/v8/v8_code_cache.cc index cc59853..73a20531 100644 --- a/third_party/blink/renderer/bindings/core/v8/v8_code_cache.cc +++ b/third_party/blink/renderer/bindings/core/v8/v8_code_cache.cc
@@ -244,7 +244,7 @@ static_cast<int>(100.0 * length / source_text_length); DEFINE_THREAD_SAFE_STATIC_LOCAL( CustomCountHistogram, code_cache_size_histogram, - ("V8.CodeCacheSizeRatio", 0, 10000, 50)); + ("V8.CodeCacheSizeRatio", 1, 10000, 50)); code_cache_size_histogram.Count(cache_size_ratio); } cache_handler->ClearCachedMetadata(
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_metrics.cc b/third_party/blink/renderer/bindings/core/v8/v8_metrics.cc index 1eb6da9..ab8d7ca 100644 --- a/third_party/blink/renderer/bindings/core/v8/v8_metrics.cc +++ b/third_party/blink/renderer/bindings/core/v8/v8_metrics.cc
@@ -258,7 +258,7 @@ DEFINE_THREAD_SAFE_STATIC_LOCAL( CustomCountHistogram, collection_rate_histogram, - ("V8.GC.Cycle.CollectionRate.Full.Cpp", 0, 100, 20)); + ("V8.GC.Cycle.CollectionRate.Full.Cpp", 1, 100, 20)); collection_rate_histogram.Count(base::saturated_cast<base::Histogram::Sample>( 100 * event.collection_rate_cpp_in_percent)); } @@ -354,7 +354,7 @@ DEFINE_THREAD_SAFE_STATIC_LOCAL( CustomCountHistogram, collection_rate_histogram, - ("V8.GC.Cycle.CollectionRate.Young", 0, 100, 20)); + ("V8.GC.Cycle.CollectionRate.Young", 1, 100, 20)); collection_rate_histogram.Count(base::saturated_cast<base::Histogram::Sample>( 100 * event.collection_rate_in_percent)); }
diff --git a/third_party/blink/renderer/core/css/css_selector.cc b/third_party/blink/renderer/core/css/css_selector.cc index 3389244e..e114c2b 100644 --- a/third_party/blink/renderer/core/css/css_selector.cc +++ b/third_party/blink/renderer/core/css/css_selector.cc
@@ -355,6 +355,7 @@ case kPseudoVideoPersistentAncestor: case kPseudoXrOverlay: case kPseudoModal: + case kPseudoSelectorFragmentAnchor: case kPseudoHas: case kPseudoRelativeLeftmost: return kPseudoIdNone; @@ -385,6 +386,8 @@ {"-internal-multi-select-focus", CSSSelector::kPseudoMultiSelectFocus}, {"-internal-popup-open", CSSSelector::kPseudoPopupOpen}, {"-internal-relative-leftmost", CSSSelector::kPseudoRelativeLeftmost}, + {"-internal-selector-fragment-anchor", + CSSSelector::kPseudoSelectorFragmentAnchor}, {"-internal-shadow-host-has-appearance", CSSSelector::kPseudoHostHasAppearance}, {"-internal-spatial-navigation-focus", @@ -765,6 +768,7 @@ case kPseudoRequired: case kPseudoRoot: case kPseudoScope: + case kPseudoSelectorFragmentAnchor: case kPseudoSingleButton: case kPseudoStart: case kPseudoState:
diff --git a/third_party/blink/renderer/core/css/css_selector.h b/third_party/blink/renderer/core/css/css_selector.h index aa65ec4..2240cb4d2 100644 --- a/third_party/blink/renderer/core/css/css_selector.h +++ b/third_party/blink/renderer/core/css/css_selector.h
@@ -219,6 +219,7 @@ kPseudoAfter, kPseudoMarker, kPseudoModal, + kPseudoSelectorFragmentAnchor, kPseudoBackdrop, kPseudoLang, kPseudoNot,
diff --git a/third_party/blink/renderer/core/css/element_rule_collector.cc b/third_party/blink/renderer/core/css/element_rule_collector.cc index 84491bd7..03abb3d 100644 --- a/third_party/blink/renderer/core/css/element_rule_collector.cc +++ b/third_party/blink/renderer/core/css/element_rule_collector.cc
@@ -51,6 +51,7 @@ #include "third_party/blink/renderer/core/dom/node_computed_style.h" #include "third_party/blink/renderer/core/dom/shadow_root.h" #include "third_party/blink/renderer/core/html/html_document.h" +#include "third_party/blink/renderer/core/page/scrolling/fragment_anchor.h" #include "third_party/blink/renderer/core/style/computed_style.h" namespace blink { @@ -480,6 +481,11 @@ CollectMatchingRulesForList(match_request.rule_set->FocusPseudoClassRules(), match_request, checker); } + if (SelectorChecker::MatchesSelectorFragmentAnchorPseudoClass(element)) { + CollectMatchingRulesForList( + match_request.rule_set->SelectorFragmentAnchorRules(), match_request, + checker); + } if (SelectorChecker::MatchesFocusVisiblePseudoClass(element)) { CollectMatchingRulesForList( match_request.rule_set->FocusVisiblePseudoClassRules(), match_request,
diff --git a/third_party/blink/renderer/core/css/has_argument_match_context.cc b/third_party/blink/renderer/core/css/has_argument_match_context.cc index 4bd9532..74fdb6d 100644 --- a/third_party/blink/renderer/core/css/has_argument_match_context.cc +++ b/third_party/blink/renderer/core/css/has_argument_match_context.cc
@@ -6,34 +6,9 @@ #include "third_party/blink/renderer/core/dom/element_traversal.h" -namespace { // anonymous namespace for file-local method and constant +namespace blink { -using blink::CSSSelector; -using blink::Element; -using blink::To; -using blink::Traversal; - -const int kInfiniteDepth = -1; -const int kInfiniteAdjacentDistance = -1; - -inline Element* LastDescendantOf(const Element& element, - int& depth, - const int& depth_limit) { - // If the current depth is at the depth limit, return null. - if (depth == depth_limit) - return nullptr; - - // Return the rightmost bottom element of the element without exceeding the - // depth limit. - Element* last_descendant = nullptr; - for (Element* descendant = Traversal<Element>::LastChild(element); descendant; - descendant = Traversal<Element>::LastChild(*descendant)) { - last_descendant = descendant; - if (++depth == depth_limit) - break; - } - return last_descendant; -} +namespace { inline const CSSSelector* GetCurrentRelationAndNextCompound( const CSSSelector* compound_selector, @@ -50,13 +25,10 @@ } // namespace -namespace blink { - -HasArgumentMatchContext::HasArgumentMatchContext(const CSSSelector* selector) - : leftmost_relation_(CSSSelector::kSubSelector), - adjacent_traversal_distance_(0), - descendant_traversal_depth_(0) { +HasArgumentMatchContext::HasArgumentMatchContext(const CSSSelector* selector) { CSSSelector::RelationType relation = CSSSelector::kSubSelector; + depth_limit_ = 0; + adjacent_distance_limit_ = 0; // The explicit ':scope' in ':has' argument selector is not considered // for getting the depth and adjacent distance. // TODO(blee@igalia.com) Need to clarify the :scope dependency in relative @@ -71,17 +43,17 @@ leftmost_relation_ = relation; [[fallthrough]]; case CSSSelector::kDescendant: - descendant_traversal_depth_ = kInfiniteDepth; - adjacent_traversal_distance_ = 0; + depth_limit_ = kInfiniteDepth; + adjacent_distance_limit_ = 0; break; case CSSSelector::kRelativeChild: leftmost_relation_ = relation; [[fallthrough]]; case CSSSelector::kChild: - if (descendant_traversal_depth_ != kInfiniteDepth) { - descendant_traversal_depth_++; - adjacent_traversal_distance_ = 0; + if (DepthFixed()) { + depth_limit_++; + adjacent_distance_limit_ = 0; } break; @@ -89,102 +61,94 @@ leftmost_relation_ = relation; [[fallthrough]]; case CSSSelector::kDirectAdjacent: - if (adjacent_traversal_distance_ != kInfiniteAdjacentDistance) - adjacent_traversal_distance_++; + if (AdjacentDistanceFixed()) + adjacent_distance_limit_++; break; case CSSSelector::kRelativeIndirectAdjacent: leftmost_relation_ = relation; [[fallthrough]]; case CSSSelector::kIndirectAdjacent: - adjacent_traversal_distance_ = kInfiniteAdjacentDistance; + adjacent_distance_limit_ = kInfiniteAdjacentDistance; break; - case CSSSelector::kUAShadow: - case CSSSelector::kShadowSlot: - case CSSSelector::kShadowPart: - // TODO(blee@igalia.com) Need to check how to handle the shadow tree - // (e.g. ':has(::slotted(img))', ':has(component::part(my-part))') - return; default: NOTREACHED(); - break; + return; } } } -CSSSelector::RelationType HasArgumentMatchContext::GetLeftMostRelation() const { - return leftmost_relation_; -} - -bool HasArgumentMatchContext::GetDepthFixed() const { - return descendant_traversal_depth_ != kInfiniteDepth; -} - -bool HasArgumentMatchContext::GetAdjacentDistanceFixed() const { - return adjacent_traversal_distance_ != kInfiniteAdjacentDistance; -} - HasArgumentSubtreeIterator::HasArgumentSubtreeIterator( - Element& scope_element, + Element& has_scope_element, HasArgumentMatchContext& context) - : scope_element_(&scope_element), - adjacent_distance_fixed_(context.GetAdjacentDistanceFixed()), - adjacent_distance_limit_(adjacent_distance_fixed_ - ? context.adjacent_traversal_distance_ - : std::numeric_limits<int>::max()), - depth_limit_(context.GetDepthFixed() ? context.descendant_traversal_depth_ - : std::numeric_limits<int>::max()), - depth_(0) { - if (!adjacent_distance_fixed_) { + : has_scope_element_(&has_scope_element), context_(context) { + if (!context_.AdjacentDistanceFixed()) { // Set the traversal_end_ as the next sibling of the :has scope element, // and move to the last sibling of the :has scope element, and move again // to the last descendant of the last sibling. - traversal_end_ = Traversal<Element>::NextSibling(*scope_element_); + traversal_end_ = ElementTraversal::NextSibling(*has_scope_element_); if (!traversal_end_) { current_ = nullptr; return; } Element* last_sibling = - Traversal<Element>::LastChild(*scope_element_->parentNode()); - current_ = LastDescendantOf(*last_sibling, depth_, depth_limit_); + Traversal<Element>::LastChild(*has_scope_element_->parentNode()); + current_ = LastWithin(last_sibling); if (!current_) current_ = last_sibling; - } else if (adjacent_distance_limit_ == 0) { - DCHECK_GT(depth_limit_, 0); + } else if (context_.AdjacentDistanceLimit() == 0) { + DCHECK_GT(context_.DepthLimit(), 0); // Set the traversal_end_ as the first child of the :has scope element, // and move to the last descendant of the :has scope element without // exceeding the depth limit. - traversal_end_ = Traversal<Element>::FirstChild(*scope_element_); + traversal_end_ = ElementTraversal::FirstChild(*has_scope_element_); if (!traversal_end_) { current_ = nullptr; return; } - current_ = LastDescendantOf(*scope_element_, depth_, depth_limit_); + current_ = LastWithin(has_scope_element_); DCHECK(current_); } else { // Set the traversal_end_ as the element at the adjacent distance of the // :has scope element, and move to the last descendant of the element // without exceeding the depth limit. - int distance; - for (distance = 1, - traversal_end_ = Traversal<Element>::NextSibling(*scope_element_); - distance < adjacent_distance_limit_ && traversal_end_; distance++, - traversal_end_ = Traversal<Element>::NextSibling(*traversal_end_)) { + int distance = 1; + for (traversal_end_ = ElementTraversal::NextSibling(*has_scope_element_); + distance < context_.AdjacentDistanceLimit() && traversal_end_; + distance++, + traversal_end_ = ElementTraversal::NextSibling(*traversal_end_)) { } if (!traversal_end_) { current_ = nullptr; return; } - if ((current_ = LastDescendantOf(*traversal_end_, depth_, depth_limit_))) + if ((current_ = LastWithin(traversal_end_))) return; current_ = traversal_end_; } } +Element* HasArgumentSubtreeIterator::LastWithin(Element* element) { + // If the current depth is at the depth limit, return null. + if (depth_ == context_.DepthLimit()) + return nullptr; + + // Return the last element of the pre-order traversal starting from the passed + // in element without exceeding the depth limit. + Element* last_descendant = nullptr; + for (Element* descendant = ElementTraversal::LastChild(*element); descendant; + descendant = ElementTraversal::LastChild(*descendant)) { + last_descendant = descendant; + if (++depth_ == context_.DepthLimit()) + break; + } + return last_descendant; +} + void HasArgumentSubtreeIterator::operator++() { DCHECK(current_); - DCHECK_NE(current_, scope_element_); + DCHECK_NE(current_, has_scope_element_); if (current_ == traversal_end_) { current_ = nullptr; return; @@ -192,7 +156,7 @@ // Move to the previous element in DOM tree order within the depth limit. if (Element* next = Traversal<Element>::PreviousSibling(*current_)) { - Element* last_descendant = LastDescendantOf(*next, depth_, depth_limit_); + Element* last_descendant = LastWithin(next); current_ = last_descendant ? last_descendant : next; } else { DCHECK_GT(depth_, 0);
diff --git a/third_party/blink/renderer/core/css/has_argument_match_context.h b/third_party/blink/renderer/core/css/has_argument_match_context.h index 1d84cdc..0d7d2cf 100644 --- a/third_party/blink/renderer/core/css/has_argument_match_context.h +++ b/third_party/blink/renderer/core/css/has_argument_match_context.h
@@ -10,36 +10,45 @@ namespace blink { -class HasArgumentSubtreeIterator; - class HasArgumentMatchContext { STACK_ALLOCATED(); public: explicit HasArgumentMatchContext(const CSSSelector* selector); - CSSSelector::RelationType GetLeftMostRelation() const; - bool GetDepthFixed() const; - bool GetAdjacentDistanceFixed() const; + + inline bool AdjacentDistanceFixed() const { + return adjacent_distance_limit_ != kInfiniteAdjacentDistance; + } + inline int AdjacentDistanceLimit() const { return adjacent_distance_limit_; } + inline bool DepthFixed() const { return depth_limit_ != kInfiniteDepth; } + inline int DepthLimit() const { return depth_limit_; } + + inline CSSSelector::RelationType LeftmostRelation() const { + return leftmost_relation_; + } private: + const static int kInfiniteDepth = std::numeric_limits<int>::max(); + const static int kInfiniteAdjacentDistance = std::numeric_limits<int>::max(); + // Indicate the :has argument relative type and subtree traversal scope. - // If 'adjacent_traversal_distance_' is greater than 0, then it means that - // it is enough to traverse the adjacent subtree at that distance. - // If it is -1, it means that all the adjacent subtree need to be traversed. - // If 'descendant_traversal_depth_' is greater than 0, then it means that - // it is enough to traverse elements at the certain depth. If it is -1, - // it means that all of the descendant subtree need to be traversed. + // If 'adjacent_distance_limit' is integer max, it means that all the + // adjacent subtrees need to be traversed. otherwise, it means that it is + // enough to traverse the adjacent subtree at that distance. + // If 'descendant_traversal_depth_' is integer max, it means that all of the + // descendant subtree need to be traversed. Otherwise, it means that it is + // enough to traverse elements at the certain depth. // - // Case 1: (kDescendant, 0, -1) + // Case 1: (kDescendant, 0, max) // - Argument selector conditions // - Starts with descendant combinator. - // - E.g. ':has(.a)', ':has(:scope .a)', ':has(.a ~ .b > .c)' + // - E.g. ':has(.a)', ':has(.a)', ':has(.a ~ .b > .c)' // - Traverse all descendants of the :has scope element. - // Case 2: (kChild, 0, -1) + // Case 2: (kChild, 0, max) // - Argument selector conditions // - Starts with child combinator. // - At least one descendant combinator. - // - E.g. ':has(:scope > .a .b)', ':has(:scope > .a ~ .b .c)' + // - E.g. ':has(> .a .b)', ':has(> .a ~ .b .c)' // - Traverse all descendants of the :has scope element. // Case 3: (kChild, 0, n) // - Argument selector conditions @@ -47,49 +56,49 @@ // - n number of child combinator. (n > 0) // - No descendant combinator. // - E.g. - // - ':has(:scope > .a)' : (kChild, 0, 1) - // - ':has(:scope > .a ~ .b > .c)' : (kChild, 0, 2) + // - ':has(> .a)' : (kChild, 0, 1) + // - ':has(> .a ~ .b > .c)' : (kChild, 0, 2) // - Traverse the depth n descendants of the :has scope element. - // Case 4: (kIndirectAdjacent, -1, -1) + // Case 4: (kIndirectAdjacent, max, max) // - Argument selector conditions // - Starts with subsequent-sibling combinator. // - At least one descendant combinator. - // - E.g. ':has(:scope ~ .a .b)', ':has(:scope ~ .a + .b > .c ~ .d .e)' + // - E.g. ':has(~ .a .b)', ':has(~ .a + .b > .c ~ .d .e)' // - Traverse all the subsequent sibling subtrees of the :has scope element. // (all subsequent siblings and it's descendants) - // Case 5: (kIndirectAdjacent, -1, 0) + // Case 5: (kIndirectAdjacent, max, 0) // - Argument selector conditions // - Starts with subsequent-sibling combinator. // - No descendant/child combinator. - // - E.g. ':has(:scope ~ .a)', ':has(:scope ~ .a + .b ~ .c)' + // - E.g. ':has(~ .a)', ':has(~ .a + .b ~ .c)' // - Traverse all subsequent siblings of the :has scope element. - // Case 6: (kIndirectAdjacent, -1, n) + // Case 6: (kIndirectAdjacent, max, n) // - Argument selector conditions // - Starts with subsequent-sibling combinator. // - n number of child combinator. (n > 0) // - No descendant combinator. // - E.g. - // - ':has(:scope ~ .a > .b)' : (kIndirectAdjacent, -1, 1) - // - ':has(:scope ~ .a + .b > .c ~ .d > .e)' : (kIndirectAdjacent, -1, 2) + // - ':has(~ .a > .b)' : (kIndirectAdjacent, max, 1) + // - ':has(~ .a + .b > .c ~ .d > .e)' : (kIndirectAdjacent, max, 2) // - Traverse depth n elements of all subsequent sibling subtree of the // :has scope element. - // Case 7: (kDirectAdjacent, -1, -1) + // Case 7: (kDirectAdjacent, max, max) // - Argument selector conditions // - Starts with next-sibling combinator. // - At least one subsequent-sibling combinator to the left of every // descendant or child combinator. // - At least 1 descendant combinator. - // - E.g. ':has(:scope + .a ~ .b .c)', ':has(:scope + .a ~ .b > .c + .e .f)' + // - E.g. ':has(+ .a ~ .b .c)', ':has(+ .a ~ .b > .c + .e .f)' // - Traverse all the subsequent sibling subtrees of the :has scope element. // (all subsequent siblings and it's descendants) - // Case 8: (kDirectAdjacent, -1, 0) + // Case 8: (kDirectAdjacent, max, 0) // - Argument selector conditions // - Starts with next-sibling combinator. // - At least one subsequent-sibling combinator. // - No descendant/child combinator. - // - E.g. ':has(:scope + .a ~ .b)', ':has(:scope + .a + .b ~ .c)' + // - E.g. ':has(+ .a ~ .b)', ':has(+ .a + .b ~ .c)' // - Traverse all subsequent siblings of the :has scope element. - // Case 9: (kDirectAdjacent, -1, n) + // Case 9: (kDirectAdjacent, max, n) // - Argument selector conditions // - Starts with next-sibling combinator. // - At least one subsequent-sibling combinator to the left of every @@ -97,11 +106,11 @@ // - n number of child combinator. (n > 0) // - No descendant combinator. // - E.g. - // - ':has(:scope + .a ~ .b > .c)' : (kDirectAdjacent, -1, 1) - // - ':has(:scope + .a ~ .b > .c + .e >.f)' : (kDirectAdjacent, -1, 2) + // - ':has(+ .a ~ .b > .c)' : (kDirectAdjacent, max, 1) + // - ':has(+ .a ~ .b > .c + .e >.f)' : (kDirectAdjacent, max, 2) // - Traverse depth n elements of all subsequent sibling subtree of the // :has scope element. - // Case 10: (kDirectAdjacent, n, -1) + // Case 10: (kDirectAdjacent, n, max) // - Argument selector conditions // - Starts with next-sibling combinator. // - n number of next-sibling combinator to the left of the leftmost @@ -110,9 +119,9 @@ // (or descendant) combinator. // - At least 1 descendant combinator. // - E.g. - // - ':has(:scope + .a .b)' : (kDirectAdjacent, 1, -1) - // - ':has(:scope + .a > .b + .c .d)' : (kDirectAdjacent, 1, -1) - // - ':has(:scope + .a + .b > .c .d)' : (kDirectAdjacent, 2, -1) + // - ':has(+ .a .b)' : (kDirectAdjacent, 1, max) + // - ':has(+ .a > .b + .c .d)' : (kDirectAdjacent, 1, max) + // - ':has(+ .a + .b > .c .d)' : (kDirectAdjacent, 2, max) // - Traverse the distance n sibling subtree of the :has scope element. // (sibling element at distance n, and it's descendants). // Case 11: (kDirectAdjacent, n, 0) @@ -121,8 +130,8 @@ // - n number of next-sibling combinator. (n > 0) // - No child/descendant/subsequent-sibling combinator. // - E.g. - // - ':has(:scope + .a)' : (kDirectAdjacent, 1, 0) - // - ':has(:scope + .a + .b + .c)' : (kDirectAdjacent, 3, 0) + // - ':has(+ .a)' : (kDirectAdjacent, 1, 0) + // - ':has(+ .a + .b + .c)' : (kDirectAdjacent, 3, 0) // - Traverse the distance n sibling element of the :has scope element. // Case 12: (kDirectAdjacent, n, m) // - Argument selector conditions @@ -134,16 +143,14 @@ // - n number of child combinator. (n > 0) // - No descendant combinator. // - E.g. - // - ':has(:scope + .a > .b)' : (kDirectAdjacent, 1, 1) - // - ':has(:scope + .a + .b > .c ~ .d > .e)' : (kDirectAdjacent, 2, 2) + // - ':has(+ .a > .b)' : (kDirectAdjacent, 1, 1) + // - ':has(+ .a + .b > .c ~ .d > .e)' : (kDirectAdjacent, 2, 2) // - Traverse the depth m elements of the distance n sibling subtree of // the :has scope element. (elements at depth m of the descendant subtree // of the sibling element at distance n) CSSSelector::RelationType leftmost_relation_; - int adjacent_traversal_distance_; - int descendant_traversal_depth_; - - friend class HasArgumentSubtreeIterator; + int adjacent_distance_limit_; + int depth_limit_; }; // Subtree traversal iterator class for ':has' argument matching. To @@ -179,35 +186,33 @@ // // We can limit the tree traversal range when we count the leftmost // combinators of a ':has' argument selector. For example, when we have -// 'div:has(:scope > .a > .b)', instead of traversing all the descendants +// 'div:has(> .a > .b)', instead of traversing all the descendants // of div element, we can limit the traversal only for the elements at -// depth 2 of the div element. When we have 'div:has(:scope + .a > .b)', +// depth 2 of the div element. When we have 'div:has(+ .a > .b)', // we can limit the traversal only for the child elements of the direct // adjacent sibling of the div element. To implement this, we need a // way to limit the traversal depth and a way to check whether the // iterator is currently at the fixed depth or not. -// -// TODO(blee@igalia.com) Need to check how to handle the shadow tree -// cases (e.g. ':has(::slotted(img))', ':has(component::part(my-part))') class HasArgumentSubtreeIterator { STACK_ALLOCATED(); public: HasArgumentSubtreeIterator(Element&, HasArgumentMatchContext&); void operator++(); - Element* Get() const { return current_; } - bool IsEnd() const { return !current_; } - bool IsAtFixedDepth() const { return depth_ == depth_limit_; } - int IsAtSiblingOfHasScope() const { return depth_ == 0; } + Element* CurrentElement() const { return current_; } + bool AtEnd() const { return !current_; } + bool AtFixedDepth() const { return depth_ == context_.DepthLimit(); } + bool AtSiblingOfHasScope() const { return depth_ == 0; } + int Depth() const { return depth_; } private: - Element* const scope_element_; - const bool adjacent_distance_fixed_; - const int adjacent_distance_limit_; - const int depth_limit_; - int depth_; - Element* current_; - Element* traversal_end_; + inline Element* LastWithin(Element*); + + Element* const has_scope_element_; + const HasArgumentMatchContext& context_; + int depth_{0}; + Element* current_{nullptr}; + Element* traversal_end_{nullptr}; }; } // namespace blink
diff --git a/third_party/blink/renderer/core/css/rule_feature_set.cc b/third_party/blink/renderer/core/css/rule_feature_set.cc index 29ef4bb..27bf208b 100644 --- a/third_party/blink/renderer/core/css/rule_feature_set.cc +++ b/third_party/blink/renderer/core/css/rule_feature_set.cc
@@ -126,6 +126,7 @@ case CSSSelector::kPseudoAfter: case CSSSelector::kPseudoMarker: case CSSSelector::kPseudoModal: + case CSSSelector::kPseudoSelectorFragmentAnchor: case CSSSelector::kPseudoBackdrop: case CSSSelector::kPseudoLang: case CSSSelector::kPseudoDir: @@ -635,6 +636,7 @@ case CSSSelector::kPseudoHasDatalist: case CSSSelector::kPseudoMultiSelectFocus: case CSSSelector::kPseudoModal: + case CSSSelector::kPseudoSelectorFragmentAnchor: return &EnsurePseudoInvalidationSet(selector.GetPseudoType(), type, position); case CSSSelector::kPseudoFirstOfType:
diff --git a/third_party/blink/renderer/core/css/rule_set.cc b/third_party/blink/renderer/core/css/rule_set.cc index 517edeaa..43d84752 100644 --- a/third_party/blink/renderer/core/css/rule_set.cc +++ b/third_party/blink/renderer/core/css/rule_set.cc
@@ -181,6 +181,7 @@ case CSSSelector::kPseudoHostContext: case CSSSelector::kPseudoSpatialNavigationInterest: case CSSSelector::kPseudoSlotted: + case CSSSelector::kPseudoSelectorFragmentAnchor: pseudo_type = selector->GetPseudoType(); break; case CSSSelector::kPseudoWebKitCustomElement: @@ -288,6 +289,9 @@ case CSSSelector::kPseudoFocus: focus_pseudo_class_rules_.push_back(rule_data); return true; + case CSSSelector::kPseudoSelectorFragmentAnchor: + selector_fragment_anchor_rules_.push_back(rule_data); + return true; case CSSSelector::kPseudoFocusVisible: focus_visible_pseudo_class_rules_.push_back(rule_data); return true; @@ -593,6 +597,7 @@ link_pseudo_class_rules_.ShrinkToFit(); cue_pseudo_rules_.ShrinkToFit(); focus_pseudo_class_rules_.ShrinkToFit(); + selector_fragment_anchor_rules_.ShrinkToFit(); focus_visible_pseudo_class_rules_.ShrinkToFit(); spatial_navigation_interest_class_rules_.ShrinkToFit(); universal_rules_.ShrinkToFit(); @@ -645,6 +650,7 @@ DCHECK(IsRuleListSorted(link_pseudo_class_rules_)); DCHECK(IsRuleListSorted(cue_pseudo_rules_)); DCHECK(IsRuleListSorted(focus_pseudo_class_rules_)); + DCHECK(IsRuleListSorted(selector_fragment_anchor_rules_)); DCHECK(IsRuleListSorted(focus_visible_pseudo_class_rules_)); DCHECK(IsRuleListSorted(spatial_navigation_interest_class_rules_)); DCHECK(IsRuleListSorted(universal_rules_)); @@ -728,6 +734,7 @@ visitor->Trace(link_pseudo_class_rules_); visitor->Trace(cue_pseudo_rules_); visitor->Trace(focus_pseudo_class_rules_); + visitor->Trace(selector_fragment_anchor_rules_); visitor->Trace(focus_visible_pseudo_class_rules_); visitor->Trace(spatial_navigation_interest_class_rules_); visitor->Trace(universal_rules_);
diff --git a/third_party/blink/renderer/core/css/rule_set.h b/third_party/blink/renderer/core/css/rule_set.h index 65e973b3..95e9f09 100644 --- a/third_party/blink/renderer/core/css/rule_set.h +++ b/third_party/blink/renderer/core/css/rule_set.h
@@ -301,6 +301,11 @@ DCHECK(!pending_rules_); return &visited_dependent_rules_; } + const HeapVector<Member<const RuleData>>* SelectorFragmentAnchorRules() + const { + DCHECK(!pending_rules_); + return &selector_fragment_anchor_rules_; + } const HeapVector<Member<StyleRulePage>>& PageRules() const { DCHECK(!pending_rules_); return page_rules_; @@ -471,6 +476,7 @@ HeapVector<Member<const RuleData>> part_pseudo_rules_; HeapVector<Member<const RuleData>> slotted_pseudo_element_rules_; HeapVector<Member<const RuleData>> visited_dependent_rules_; + HeapVector<Member<const RuleData>> selector_fragment_anchor_rules_; RuleFeatureSet features_; HeapVector<Member<StyleRulePage>> page_rules_; HeapVector<Member<StyleRuleFontFace>> font_face_rules_;
diff --git a/third_party/blink/renderer/core/css/selector_checker.cc b/third_party/blink/renderer/core/css/selector_checker.cc index e770bc29..b3d3d04d 100644 --- a/third_party/blink/renderer/core/css/selector_checker.cc +++ b/third_party/blink/renderer/core/css/selector_checker.cc
@@ -66,6 +66,7 @@ #include "third_party/blink/renderer/core/html_names.h" #include "third_party/blink/renderer/core/page/focus_controller.h" #include "third_party/blink/renderer/core/page/page.h" +#include "third_party/blink/renderer/core/page/scrolling/fragment_anchor.h" #include "third_party/blink/renderer/core/page/spatial_navigation.h" #include "third_party/blink/renderer/core/page/spatial_navigation_controller.h" #include "third_party/blink/renderer/core/probe/core_probes.h" @@ -694,7 +695,7 @@ sub_context.selector = selector; HasArgumentMatchContext has_argument_match_context(selector); - bool depth_fixed = has_argument_match_context.GetDepthFixed(); + bool depth_fixed = has_argument_match_context.DepthFixed(); // To prevent incorrect 'NotChecked' status while matching ':has' pseudo // class, change the argument matching context scope when the ':has' @@ -735,7 +736,7 @@ if (!depth_fixed) { sub_context.relative_leftmost_element = &element->GetTreeScope().RootNode(); - } else if (has_argument_match_context.GetAdjacentDistanceFixed()) { + } else if (has_argument_match_context.AdjacentDistanceFixed()) { if (ContainerNode* parent_node = element->parentNode()) { sub_context.relative_leftmost_element = Traversal<Element>::FirstChild(*parent_node); @@ -749,10 +750,10 @@ bool selector_matched = false; for (HasArgumentSubtreeIterator iterator(*element, has_argument_match_context); - !iterator.IsEnd(); ++iterator) { - if (depth_fixed && !iterator.IsAtFixedDepth()) + !iterator.AtEnd(); ++iterator) { + if (depth_fixed && !iterator.AtFixedDepth()) continue; - sub_context.element = iterator.Get(); + sub_context.element = iterator.CurrentElement(); HeapVector<Member<Element>> has_argument_leftmost_compound_matches; MatchResult sub_result; sub_result.has_argument_leftmost_compound_matches = @@ -760,9 +761,9 @@ MatchSelector(sub_context, sub_result); - switch (has_argument_match_context.GetLeftMostRelation()) { + switch (has_argument_match_context.LeftmostRelation()) { case CSSSelector::kRelativeDescendant: - map.insert(iterator.Get(), false); // Mark as checked + map.insert(iterator.CurrentElement(), false); // Mark as checked if (!has_argument_leftmost_compound_matches.IsEmpty()) { sub_context.element = has_argument_leftmost_compound_matches.front(); @@ -784,8 +785,8 @@ } break; case CSSSelector::kRelativeDirectAdjacent: - if (!depth_fixed && !iterator.IsAtSiblingOfHasScope()) - map.insert(iterator.Get(), false); // Mark as checked + if (!depth_fixed && !iterator.AtSiblingOfHasScope()) + map.insert(iterator.CurrentElement(), false); // Mark as checked for (auto leftmost : has_argument_leftmost_compound_matches) { if (Element* sibling = Traversal<Element>::PreviousSibling(*leftmost)) { @@ -797,7 +798,7 @@ break; case CSSSelector::kRelativeIndirectAdjacent: if (!depth_fixed) - map.insert(iterator.Get(), false); // Mark as checked + map.insert(iterator.CurrentElement(), false); // Mark as checked for (auto leftmost : has_argument_leftmost_compound_matches) { for (Element* sibling = Traversal<Element>::PreviousSibling(*leftmost); @@ -838,13 +839,13 @@ // TODO(blee@igalia.com) Need to traverse to siblings and siblings of // ancestors to support sibling combinator and complex selector in // :has() argument. - for (Element* parent = iterator.Get(); parent && parent != element; - parent = parent->parentElement()) { + for (Element* parent = iterator.CurrentElement(); + parent && parent != element; parent = parent->parentElement()) { parent->SetAncestorsAffectedByHas(); } return true; } else { - iterator.Get()->SetAncestorsAffectedByHas(); + iterator.CurrentElement()->SetAncestorsAffectedByHas(); } } } @@ -993,6 +994,8 @@ return false; return selector.MatchNth(NthIndexCache::NthLastOfTypeIndex(element)); } + case CSSSelector::kPseudoSelectorFragmentAnchor: + return MatchesSelectorFragmentAnchorPseudoClass(element); case CSSSelector::kPseudoTarget: probe::ForcePseudoState(&element, CSSSelector::kPseudoTarget, &force_pseudo_state); @@ -1574,6 +1577,16 @@ } } +bool SelectorChecker::MatchesSelectorFragmentAnchorPseudoClass( + const Element& element) { + return element == element.GetDocument().CssTarget() && + element.GetDocument().View()->GetFragmentAnchor() && + element.GetDocument() + .View() + ->GetFragmentAnchor() + ->IsSelectorFragmentAnchor(); +} + bool SelectorChecker::MatchesFocusPseudoClass(const Element& element) { bool force_pseudo_state = false; probe::ForcePseudoState(const_cast<Element*>(&element),
diff --git a/third_party/blink/renderer/core/css/selector_checker.h b/third_party/blink/renderer/core/css/selector_checker.h index a42ffa2..2c2398a 100644 --- a/third_party/blink/renderer/core/css/selector_checker.h +++ b/third_party/blink/renderer/core/css/selector_checker.h
@@ -203,6 +203,7 @@ static bool MatchesFocusPseudoClass(const Element&); static bool MatchesFocusVisiblePseudoClass(const Element&); static bool MatchesSpatialNavigationInterestPseudoClass(const Element&); + static bool MatchesSelectorFragmentAnchorPseudoClass(const Element&); private: // Does the work of checking whether the simple selector and element pointed
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc index 0d96b78..371f7dd0 100644 --- a/third_party/blink/renderer/core/dom/document.cc +++ b/third_party/blink/renderer/core/dom/document.cc
@@ -5028,10 +5028,24 @@ return nullptr; } +void Document::SetSelectorFragmentAnchorCSSTarget(Element* new_target) { + SetCSSTarget(new_target); + if (css_target_) { + css_target_is_selector_fragment_ = true; + css_target_->PseudoStateChanged(CSSSelector::kPseudoSelectorFragmentAnchor); + } +} + void Document::SetCSSTarget(Element* new_target) { - if (css_target_) + if (css_target_) { css_target_->PseudoStateChanged(CSSSelector::kPseudoTarget); + if (css_target_is_selector_fragment_) { + css_target_->PseudoStateChanged( + CSSSelector::kPseudoSelectorFragmentAnchor); + } + } css_target_ = new_target; + css_target_is_selector_fragment_ = false; if (css_target_) css_target_->PseudoStateChanged(CSSSelector::kPseudoTarget); }
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h index 033762e..cab07be 100644 --- a/third_party/blink/renderer/core/dom/document.h +++ b/third_party/blink/renderer/core/dom/document.h
@@ -978,6 +978,7 @@ // Updates for :target (CSS3 selector). void SetCSSTarget(Element*); Element* CssTarget() const { return css_target_; } + void SetSelectorFragmentAnchorCSSTarget(Element*); void ScheduleLayoutTreeUpdateIfNeeded(); bool HasPendingForcedStyleRecalc() const; @@ -2158,6 +2159,7 @@ bool should_update_selection_after_layout_ = false; Member<Element> css_target_; + bool css_target_is_selector_fragment_ = false; bool was_discarded_;
diff --git a/third_party/blink/renderer/core/dom/events/event.idl b/third_party/blink/renderer/core/dom/events/event.idl index 4d92c2a..a31175b5 100644 --- a/third_party/blink/renderer/core/dom/events/event.idl +++ b/third_party/blink/renderer/core/dom/events/event.idl
@@ -56,5 +56,6 @@ [MeasureAs=EventCancelBubble, CallWith=ScriptState] attribute boolean cancelBubble; // Non-standard APIs - [MeasureAs=EventPath, CallWith=ScriptState] readonly attribute object path; + [MeasureAs=EventPath, CallWith=ScriptState, RuntimeEnabled=EventPath] + readonly attribute object path; };
diff --git a/third_party/blink/renderer/core/fragment_directive/css_selector_fragment_anchor.cc b/third_party/blink/renderer/core/fragment_directive/css_selector_fragment_anchor.cc index c24279f..3bdfbfb4 100644 --- a/third_party/blink/renderer/core/fragment_directive/css_selector_fragment_anchor.cc +++ b/third_party/blink/renderer/core/fragment_directive/css_selector_fragment_anchor.cc
@@ -35,21 +35,29 @@ Element* anchor_node = nullptr; for (CssSelectorDirective* directive : css_selector_directives) { - if (!directive->value().IsEmpty()) + if (!directive->value().IsEmpty() && !directive->IsConsumed()) { anchor_node = doc.RootNode().QuerySelector(directive->value()); - // TODO(crbug.com/1265721): this will ignore directives after the first - // successful match, for now we are just scrolling the element into view, - // later when we add highlighting, it's good considering highlighting all - // matching elements. - if (anchor_node) - break; + + // TODO(crbug.com/1265721): this will ignore directives after the first + // successful match, for now we are just scrolling the element into view, + // later when we add highlighting, it's good considering highlighting all + // matching elements. + if (anchor_node) + break; + } } - doc.SetCSSTarget(anchor_node); + doc.SetSelectorFragmentAnchorCSSTarget(anchor_node); if (!anchor_node) return nullptr; + // On the same page navigation i.e. <a href="#element>Go to element</a> + // we don't want to create a CssSelectorFragmentAnchor again, + // we want to create an ElementFragmentAnchor instead, so consume all of them + for (CssSelectorDirective* directive : css_selector_directives) + directive->SetConsumed(true); + return MakeGarbageCollected<CssSelectorFragmentAnchor>(*anchor_node, frame, should_scroll); } @@ -62,18 +70,6 @@ bool CssSelectorFragmentAnchor::InvokeSelector() { DCHECK(anchor_node_); - - // if user has not scrolled yet, do the necessary work to scroll anchor_node_ - // into view, otherwise we are done scrolling. - if (!user_scrolled_ && should_scroll_ && - frame_->GetDocument()->IsLoadCompleted()) { - ScrollIntoViewOptions* options = ScrollIntoViewOptions::Create(); - options->setBlock("center"); - options->setInlinePosition("nearest"); - ScrollElementIntoViewWithOptions(anchor_node_, options); - should_scroll_ = false; - } - return true; }
diff --git a/third_party/blink/renderer/core/fragment_directive/css_selector_fragment_anchor.h b/third_party/blink/renderer/core/fragment_directive/css_selector_fragment_anchor.h index d86e33d..580bbcd 100644 --- a/third_party/blink/renderer/core/fragment_directive/css_selector_fragment_anchor.h +++ b/third_party/blink/renderer/core/fragment_directive/css_selector_fragment_anchor.h
@@ -44,6 +44,8 @@ void Trace(Visitor*) const override; + bool IsSelectorFragmentAnchor() override { return true; } + private: Member<Element> anchor_node_; };
diff --git a/third_party/blink/renderer/core/fragment_directive/css_selector_fragment_anchor_test.cc b/third_party/blink/renderer/core/fragment_directive/css_selector_fragment_anchor_test.cc index d0fc3283..62f99b3 100644 --- a/third_party/blink/renderer/core/fragment_directive/css_selector_fragment_anchor_test.cc +++ b/third_party/blink/renderer/core/fragment_directive/css_selector_fragment_anchor_test.cc
@@ -7,7 +7,9 @@ #include "third_party/blink/public/common/features.h" #include "third_party/blink/public/web/web_script_source.h" #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" +#include "third_party/blink/renderer/core/css/css_property_names.h" #include "third_party/blink/renderer/core/css/css_style_declaration.h" +#include "third_party/blink/renderer/core/dom/node_computed_style.h" #include "third_party/blink/renderer/core/frame/local_frame_view.h" #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" #include "third_party/blink/renderer/core/html/html_anchor_element.h" @@ -33,6 +35,10 @@ void SetUp() override { SimTest::SetUp(); + base::test::ScopedFeatureList feature_list; + feature_list.InitAndEnableFeature( + blink::features::kCssSelectorFragmentAnchor); + // Focus handlers aren't run unless the page is focused. GetDocument().GetPage()->GetFocusController().SetFocused(true); @@ -64,14 +70,40 @@ bool IsVisibleInViewport(Element& element) { return ViewportRect().Contains(BoundingRectInFrame(element)); } + + bool IsSelectorFragmentAnchorCreated() { + return GetDocument().View()->GetFragmentAnchor() && + GetDocument() + .View() + ->GetFragmentAnchor() + ->IsSelectorFragmentAnchor(); + } + + const CSSValue* GetComputedValue(const CSSPropertyID& property_id, + const Element& element) { + return CSSProperty::Get(property_id) + .CSSValueFromComputedStyle(element.ComputedStyleRef(), + nullptr /* layout_object */, + false /* allow_visited_style */); + } + + bool IsElementOutlined(const Element& element) { + const CSSValue* value = + GetComputedValue(CSSPropertyID::kOutlineWidth, element); + return "0px" != value->CssText(); + } + + const String CircleSVG() { + return R"SVG( + <svg id="svg" width="200" height="200" xmlns="http://www.w3.org/2000/svg"> + <circle class="path" cx="100" cy="100" r="100" fill="red"/> + </svg> + )SVG"; + } }; -// Make sure we find the element and scroll it to the middle of viewport. +// Make sure we find the element and set it as the CSS target. TEST_F(CssSelectorFragmentAnchorTest, BasicTest) { - base::test::ScopedFeatureList feature_list_; - feature_list_.InitAndEnableFeature( - blink::features::kCssSelectorFragmentAnchor); - SimRequest main_request( "https://example.com/" "test.html#:~:selector(type=CssSelector,value=img[src$=\"image.svg\"])", @@ -85,58 +117,23 @@ // main frame widget size is 800x600 main_request.Complete(R"HTML( <!DOCTYPE html> - <body style="margin:0px;"> - <div style="height:600px;">some text</div> - <img id="image" src="image.svg" style="vertical-align:top;"> - <div style="height:600px;">some other text</div> - </body> + <img id="image" src="image.svg"> )HTML"); - image_request.Complete(R"SVG( - <svg id="svg" width="200" height="200" xmlns="http://www.w3.org/2000/svg"> - <circle class="path" cx="100" cy="100" r="100" fill="red"/> - </svg> - )SVG"); + image_request.Complete(CircleSVG()); test::RunPendingTasks(); Compositor().BeginFrame(); - // +-------------------------------+ <-- 0px - // | some text (height:600px) - // | - // | - // | - // | <-- 400px (scroll offset) | visible - // | | height - // | circle (height:200px) | - // | | - // | some other text (height:600px) | - // | | - // | - // | - // | - // | - // +-------------------------------+ - EXPECT_EQ(ScrollOffset(0, 400), LayoutViewport()->GetScrollOffset()) - << "<img> was not EXACTLY scrolled into the MIDDLE of the viewport " - << "vertically, viewport's scroll offset: " - << LayoutViewport()->GetScrollOffset().ToString(); - Element& img = *GetDocument().getElementById("image"); - EXPECT_TRUE(IsVisibleInViewport(img)) - << "<img> Element wasn't scrolled into view, viewport's scroll offset: " - << LayoutViewport()->GetScrollOffset().ToString(); EXPECT_EQ(img, *GetDocument().CssTarget()); + EXPECT_EQ(true, IsSelectorFragmentAnchorCreated()); } -// When more than one CssSelector Fragments are present, scroll the first one -// into view -TEST_F(CssSelectorFragmentAnchorTest, TwoCssSelectorFragmentsScrollToFirst) { - base::test::ScopedFeatureList feature_list_; - feature_list_.InitAndEnableFeature( - blink::features::kCssSelectorFragmentAnchor); - +// When more than one CssSelector Fragments are present, set the first one as +// the CSS target (which will be outlined accordingly) +TEST_F(CssSelectorFragmentAnchorTest, TwoCssSelectorFragmentsOutlineFirst) { SimRequest main_request( "https://example.com/test.html" "#:~:selector(type=CssSelector,value=img[src$=\"second.svg\"])" @@ -154,24 +151,12 @@ main_request.Complete(R"HTML( <!DOCTYPE html> - <p style="height:1000px;">some text</p> <img id="first" src="first.svg"> - <p style="height:1000px;">some other text</p> <img id="second" src="second.svg"> - <p style="height:1000px;">yet some more text</p> )HTML"); - first_img_request.Complete(R"SVG( - <svg id="svg" width="50" height="50" xmlns="http://www.w3.org/2000/svg"> - <circle class="path" cx="25" cy="25" r="25" fill="red"/> - </svg> - )SVG"); - - second_img_request.Complete(R"SVG( - <svg id="svg" width="50" height="50" xmlns="http://www.w3.org/2000/svg"> - <circle class="path" cx="25" cy="25" r="25" fill="red"/> - </svg> - )SVG"); + first_img_request.Complete(CircleSVG()); + second_img_request.Complete(CircleSVG()); test::RunPendingTasks(); Compositor().BeginFrame(); @@ -179,19 +164,12 @@ Element& second = *GetDocument().getElementById("second"); EXPECT_EQ(second, *GetDocument().CssTarget()); - EXPECT_TRUE(IsVisibleInViewport(second)) - << "second <img> Element wasn't scrolled into view, viewport's scroll " - "offset: " - << LayoutViewport()->GetScrollOffset().ToString(); + EXPECT_EQ(true, IsSelectorFragmentAnchorCreated()); } // If the first CssSelector Fragment is not found, look for the second one -// and scroll it into view +// and set that as the CSS target TEST_F(CssSelectorFragmentAnchorTest, TwoCssSelectorFragmentsFirstNotFound) { - base::test::ScopedFeatureList feature_list_; - feature_list_.InitAndEnableFeature( - blink::features::kCssSelectorFragmentAnchor); - SimRequest main_request( "https://example.com/test.html" "#:~:selector(type=CssSelector,value=img[src$=\"penguin.svg\"])" @@ -206,16 +184,10 @@ main_request.Complete(R"HTML( <!DOCTYPE html> - <p style="height:1000px;">some text</p> <img id="first" src="first.svg"> - <p style="height:1000px;">some other text</p> )HTML"); - image_request.Complete(R"SVG( - <svg id="svg" width="50" height="50" xmlns="http://www.w3.org/2000/svg"> - <circle class="path" cx="25" cy="25" r="25" fill="red"/> - </svg> - )SVG"); + image_request.Complete(CircleSVG()); test::RunPendingTasks(); Compositor().BeginFrame(); @@ -223,19 +195,13 @@ Element& first = *GetDocument().getElementById("first"); EXPECT_EQ(first, *GetDocument().CssTarget()); - EXPECT_TRUE(IsVisibleInViewport(first)) - << "<img> Element wasn't scrolled into view, viewport's scroll offset: " - << LayoutViewport()->GetScrollOffset().ToString(); + EXPECT_EQ(true, IsSelectorFragmentAnchorCreated()); } // If both CssSelectorFragment and ElementFragment present, // prioritize CssSelectorFragment TEST_F(CssSelectorFragmentAnchorTest, PrioritizeCssSelectorFragmentOverElementFragment) { - base::test::ScopedFeatureList feature_list_; - feature_list_.InitAndEnableFeature( - blink::features::kCssSelectorFragmentAnchor); - SimRequest main_request( "https://example.com/test.html#element" ":~:selector(type=CssSelector,value=img[src$=\"first.svg\"])", @@ -248,17 +214,11 @@ main_request.Complete(R"HTML( <!DOCTYPE html> - <p style="height:1000px;">some text</p> + <p id="element">the element!</p> <img id="first" src="first.svg"> - <p style="height:1000px;">some other text</p> - <p id="element" style="height:1000px;">the element!</p> )HTML"); - image_request.Complete(R"SVG( - <svg id="svg" width="50" height="50" xmlns="http://www.w3.org/2000/svg"> - <circle class="path" cx="25" cy="25" r="25" fill="red"/> - </svg> - )SVG"); + image_request.Complete(CircleSVG()); test::RunPendingTasks(); Compositor().BeginFrame(); @@ -266,19 +226,13 @@ Element& first = *GetDocument().getElementById("first"); EXPECT_EQ(first, *GetDocument().CssTarget()); - EXPECT_TRUE(IsVisibleInViewport(first)) - << "<img> Element wasn't scrolled into view, viewport's scroll offset: " - << LayoutViewport()->GetScrollOffset().ToString(); + EXPECT_EQ(true, IsSelectorFragmentAnchorCreated()); } // TODO(crbug/1253707): Enable after fixing! -// Don't scroll into view if attribute selector is not allowed according to spec +// Don't do anything if attribute selector is not allowed according to spec // https://github.com/WICG/scroll-to-text-fragment/blob/main/EXTENSIONS.md#proposed-solution TEST_F(CssSelectorFragmentAnchorTest, DISABLED_CheckCssSelectorRestrictions) { - base::test::ScopedFeatureList feature_list_; - feature_list_.InitAndEnableFeature( - blink::features::kCssSelectorFragmentAnchor); - SimRequest main_request( "https://example.com/test.html" "#:~:selector(type=CssSelector,value=div[id$=\"first\"])", @@ -290,31 +244,19 @@ main_request.Complete(R"HTML( <!DOCTYPE html> - <p style="height:1000px;">some text</p> - <div id="first" style="height:50px;">some other text</p> - <p style="height:1000px;">another paragraph</p> + <div id="first">some other text</p> )HTML"); test::RunPendingTasks(); Compositor().BeginFrame(); - EXPECT_EQ(ScrollOffset(0, 0), LayoutViewport()->GetScrollOffset()) - << "No scroll should happen, viewport's scroll offset: " - << LayoutViewport()->GetScrollOffset().ToString(); EXPECT_EQ(nullptr, *GetDocument().CssTarget()); - - EXPECT_FALSE(GetDocument().View()->GetFragmentAnchor()); - - EXPECT_EQ(GetDocument().Url(), "https://example.com/test.html"); + EXPECT_EQ(nullptr, GetDocument().View()->GetFragmentAnchor()); + EXPECT_EQ("https://example.com/test.html", GetDocument().Url()); } // Make sure fragment is not dismissed after user clicks TEST_F(CssSelectorFragmentAnchorTest, FragmentStaysAfterUserClicks) { - base::test::ScopedFeatureList feature_list_; - feature_list_.InitWithFeatures( - /*enabled_features=*/{blink::features::kCssSelectorFragmentAnchor}, - /*disabled_features=*/{}); - SimRequest main_request( "https://example.com/" "test.html#:~:selector(type=CssSelector,value=img[src$=\"image.svg\"])", @@ -328,18 +270,10 @@ // main frame widget size is 800x600 main_request.Complete(R"HTML( <!DOCTYPE html> - <body style="margin:0px;"> - <div style="height:600px;">some text</div> - <img id="image" src="image.svg" style="vertical-align:top;"> - <div style="height:600px;">some other text</div> - </body> + <img id="image" src="image.svg"> )HTML"); - image_request.Complete(R"SVG( - <svg id="svg" width="200" height="200" xmlns="http://www.w3.org/2000/svg"> - <circle class="path" cx="100" cy="100" r="100" fill="red"/> - </svg> - )SVG"); + image_request.Complete(CircleSVG()); test::RunPendingTasks(); Compositor().BeginFrame(); @@ -352,10 +286,8 @@ ->Url(); Element& img = *GetDocument().getElementById("image"); - EXPECT_TRUE(IsVisibleInViewport(img)) - << "<img> Element wasn't scrolled into view, viewport's scroll offset: " - << LayoutViewport()->GetScrollOffset().ToString(); EXPECT_EQ(img, *GetDocument().CssTarget()); + EXPECT_EQ(true, IsSelectorFragmentAnchorCreated()); SimulateClick(100, 100); @@ -371,13 +303,9 @@ EXPECT_EQ(expected_url, url); } -// Although parsed correctly, the element is not found, hence no scroll happens -// and scroll offset should be zero +// Although parsed correctly, the element is not found, hence no CSS target +// should be set TEST_F(CssSelectorFragmentAnchorTest, ParsedCorrectlyButElementNotFound) { - base::test::ScopedFeatureList feature_list_; - feature_list_.InitAndEnableFeature( - blink::features::kCssSelectorFragmentAnchor); - SimRequest main_request( "https://example.com/test.html" "#:~:selector(type=CssSelector,value=img[src$=\"lorem.svg\"])", @@ -389,27 +317,18 @@ main_request.Complete(R"HTML( <!DOCTYPE html> - <p style="height:1000px;">some text</p> - <p style="height:1000px;">another paragraph</p> + <p>some text</p> )HTML"); test::RunPendingTasks(); Compositor().BeginFrame(); - EXPECT_EQ(ScrollOffset(0, 0), LayoutViewport()->GetScrollOffset()) - << "No scroll should happen, viewport's scroll offset: " - << LayoutViewport()->GetScrollOffset().ToString(); EXPECT_EQ(nullptr, *GetDocument().CssTarget()); - - EXPECT_FALSE(GetDocument().View()->GetFragmentAnchor()); + EXPECT_EQ(nullptr, GetDocument().View()->GetFragmentAnchor()); } // value= part should be encoded/decoded TEST_F(CssSelectorFragmentAnchorTest, ValuePartHasCommaAndIsEncoded) { - base::test::ScopedFeatureList feature_list_; - feature_list_.InitAndEnableFeature( - blink::features::kCssSelectorFragmentAnchor); - SimRequest main_request( "https://example.com/test.html" // "#:~:selector(value=img[src$="cat,dog"],type=CssSelector)", @@ -423,16 +342,10 @@ main_request.Complete(R"HTML( <!DOCTYPE html> - <p style="height:1000px;">some text</p> <img id="first" src="cat,dog"> - <p style="height:1000px;">some other text</p> )HTML"); - img_request.Complete(R"SVG( - <svg id="svg" width="50" height="50" xmlns="http://www.w3.org/2000/svg"> - <circle class="path" cx="25" cy="25" r="25" fill="red"/> - </svg> - )SVG"); + img_request.Complete(CircleSVG()); test::RunPendingTasks(); Compositor().BeginFrame(); @@ -440,19 +353,12 @@ Element& first = *GetDocument().getElementById("first"); EXPECT_EQ(first, *GetDocument().CssTarget()); - EXPECT_TRUE(IsVisibleInViewport(first)) - << "second <img> Element wasn't scrolled into view, viewport's scroll " - "offset: " - << LayoutViewport()->GetScrollOffset().ToString(); + EXPECT_EQ(true, IsSelectorFragmentAnchorCreated()); } // What if value= part is not encoded, and it contains a comma, -// Should not crash, and should not scroll to anywhere and no fragment anchor +// Should not crash and no CSS target should be set TEST_F(CssSelectorFragmentAnchorTest, ValuePartHasCommaButIsNotEncoded) { - base::test::ScopedFeatureList feature_list_; - feature_list_.InitAndEnableFeature( - blink::features::kCssSelectorFragmentAnchor); - SimRequest main_request( "https://example.com/test.html" "#:~:selector(value=img[src$=\"cat,dog\"],type=CssSelector)", @@ -465,25 +371,105 @@ main_request.Complete(R"HTML( <!DOCTYPE html> - <p style="height:1000px;">some text</p> <img id="first" src="cat,dog"> - <p style="height:1000px;">some other text</p> )HTML"); - img_request.Complete(R"SVG( - <svg id="svg" width="50" height="50" xmlns="http://www.w3.org/2000/svg"> - <circle class="path" cx="25" cy="25" r="25" fill="red"/> - </svg> - )SVG"); + img_request.Complete(CircleSVG()); test::RunPendingTasks(); Compositor().BeginFrame(); - EXPECT_FALSE(GetDocument().View()->GetFragmentAnchor()); EXPECT_EQ(nullptr, *GetDocument().CssTarget()); - EXPECT_EQ(ScrollOffset(0, 0), LayoutViewport()->GetScrollOffset()) - << "No scroll should happen, viewport's scroll offset: " - << LayoutViewport()->GetScrollOffset().ToString(); + EXPECT_EQ(nullptr, GetDocument().View()->GetFragmentAnchor()); +} + +TEST_F(CssSelectorFragmentAnchorTest, + TargetElementIsNotHighlightedWithElementFragment) { + SimRequest main_request("https://example.com/test.html#image", "text/html"); + SimRequest image_request("https://example.com/image.svg", "image/svg+xml"); + + LoadURL("https://example.com/test.html#image"); + + // main frame widget size is 800x600 + main_request.Complete(R"HTML( + <!DOCTYPE html> + <img id="image" src="image.svg"> + )HTML"); + + image_request.Complete(CircleSVG()); + + test::RunPendingTasks(); + Compositor().BeginFrame(); + + Element& img = *GetDocument().getElementById("image"); + + EXPECT_FALSE(IsElementOutlined(img)); + EXPECT_EQ(img, *GetDocument().CssTarget()); +} + +TEST_F(CssSelectorFragmentAnchorTest, + TargetElementIsNotHighlightedWithTextFragment) { + SimRequest main_request("https://example.com/test.html#:~:text=some other", + "text/html"); + + LoadURL("https://example.com/test.html#:~:text=some other"); + + // main frame widget size is 800x600 + main_request.Complete(R"HTML( + <!DOCTYPE html> + <div id="element">some other text</div> + )HTML"); + + test::RunPendingTasks(); + + // Render two frames to handle the async step added by the beforematch event. + Compositor().BeginFrame(); + Compositor().BeginFrame(); + + Element& element = *GetDocument().getElementById("element"); + + EXPECT_FALSE(IsElementOutlined(element)); + EXPECT_EQ(element, *GetDocument().CssTarget()); +} + +// Simulate an anchor link navigation and check that the style is removed. +TEST_F(CssSelectorFragmentAnchorTest, SelectorFragmentTargetOutline) { + SimRequest main_request( + "https://example.com/test.html" + "#:~:selector(type=CssSelector,value=img[src=\"image.svg\"])", + "text/html"); + SimRequest image_request("https://example.com/image.svg", "image/svg+xml"); + + LoadURL( + "https://example.com/test.html" + "#:~:selector(type=CssSelector,value=img[src=\"image.svg\"])"); + + // main frame widget size is 800x600 + main_request.Complete(R"HTML( + <!DOCTYPE html> + <a id="element" href="#paragraph">Go to paragraph</a> + <img id="image" src="image.svg"> + <p id="paragraph"></p> + )HTML"); + + image_request.Complete(CircleSVG()); + + test::RunPendingTasks(); + Compositor().BeginFrame(); + + Element& paragraph = *GetDocument().getElementById("paragraph"); + Element& img = *GetDocument().getElementById("image"); + + EXPECT_TRUE(IsElementOutlined(img)); + EXPECT_EQ(img, *GetDocument().CssTarget()); + EXPECT_EQ(true, IsSelectorFragmentAnchorCreated()); + + auto* anchor = To<HTMLAnchorElement>(GetDocument().getElementById("element")); + anchor->click(); + + EXPECT_FALSE(IsElementOutlined(img)); + EXPECT_EQ(paragraph, *GetDocument().CssTarget()); + EXPECT_EQ("https://example.com/test.html#paragraph", GetDocument().Url()); } } // namespace blink
diff --git a/third_party/blink/renderer/core/frame/directive.h b/third_party/blink/renderer/core/frame/directive.h index bf45d74..7f593fe4 100644 --- a/third_party/blink/renderer/core/frame/directive.h +++ b/third_party/blink/renderer/core/frame/directive.h
@@ -25,6 +25,8 @@ ~Directive() override; Type GetType() const; + bool IsConsumed() { return consumed_; } + void SetConsumed(bool consumed) { consumed_ = consumed; } void Trace(Visitor*) const override; // Web-exposed Directive interface. @@ -37,6 +39,7 @@ private: Type type_; + bool consumed_ = false; }; } // namespace blink
diff --git a/third_party/blink/renderer/core/html/blocking_attribute.cc b/third_party/blink/renderer/core/html/blocking_attribute.cc new file mode 100644 index 0000000..e4e910b --- /dev/null +++ b/third_party/blink/renderer/core/html/blocking_attribute.cc
@@ -0,0 +1,28 @@ +// Copyright 2022 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 "third_party/blink/renderer/core/html/blocking_attribute.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" +#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" +#include "third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h" + +namespace blink { + +// static +HashSet<AtomicString>& BlockingAttribute::SupportedTokens() { + DEFINE_STATIC_LOCAL(HashSet<AtomicString>, tokens, + ({ + "render", + })); + + return tokens; +} + +bool BlockingAttribute::ValidateTokenValue(const AtomicString& token_value, + ExceptionState&) const { + DCHECK(RuntimeEnabledFeatures::BlockingAttributeEnabled()); + return SupportedTokens().Contains(token_value); +} + +} // namespace blink
diff --git a/third_party/blink/renderer/core/html/blocking_attribute.h b/third_party/blink/renderer/core/html/blocking_attribute.h new file mode 100644 index 0000000..de29232e --- /dev/null +++ b/third_party/blink/renderer/core/html/blocking_attribute.h
@@ -0,0 +1,32 @@ +// Copyright 2022 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 THIRD_PARTY_BLINK_RENDERER_CORE_HTML_BLOCKING_ATTRIBUTE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_BLOCKING_ATTRIBUTE_H_ + +#include "third_party/blink/renderer/core/dom/dom_token_list.h" +#include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/platform/wtf/hash_set.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" + +namespace blink { + +// https://html.spec.whatwg.org/#blocking-attribute +// TODO(crbug.com/1271296): Add use counter. +class BlockingAttribute final : public DOMTokenList { + public: + explicit BlockingAttribute(Element* element) + : DOMTokenList(*element, html_names::kBlockingAttr) {} + + bool IsRenderBlocking() const { return contains("render"); } + + private: + static HashSet<AtomicString>& SupportedTokens(); + + bool ValidateTokenValue(const AtomicString&, ExceptionState&) const override; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_BLOCKING_ATTRIBUTE_H_
diff --git a/third_party/blink/renderer/core/html/build.gni b/third_party/blink/renderer/core/html/build.gni index 6375e21..8e3341e 100644 --- a/third_party/blink/renderer/core/html/build.gni +++ b/third_party/blink/renderer/core/html/build.gni
@@ -8,6 +8,8 @@ "anchor_element_metrics_sender.cc", "anchor_element_metrics_sender.h", "battery_savings.h", + "blocking_attribute.cc", + "blocking_attribute.h", "canvas/canvas_async_blob_creator.cc", "canvas/canvas_async_blob_creator.h", "canvas/canvas_context_creation_attributes_core.cc",
diff --git a/third_party/blink/renderer/core/html/html_attribute_names.json5 b/third_party/blink/renderer/core/html/html_attribute_names.json5 index 5c4d515..a35a7126 100644 --- a/third_party/blink/renderer/core/html/html_attribute_names.json5 +++ b/third_party/blink/renderer/core/html/html_attribute_names.json5
@@ -40,6 +40,7 @@ "background", "behavior", "bgcolor", + "blocking", "border", "bordercolor", "capture",
diff --git a/third_party/blink/renderer/core/html/html_link_element.cc b/third_party/blink/renderer/core/html/html_link_element.cc index e139ee1..a358331a 100644 --- a/third_party/blink/renderer/core/html/html_link_element.cc +++ b/third_party/blink/renderer/core/html/html_link_element.cc
@@ -82,6 +82,7 @@ referrer_policy_(network::mojom::ReferrerPolicy::kDefault), sizes_(MakeGarbageCollected<DOMTokenList>(*this, html_names::kSizesAttr)), rel_list_(MakeGarbageCollected<RelList>(this)), + blocking_attribute_(MakeGarbageCollected<BlockingAttribute>(this)), resources_( MakeGarbageCollected<DOMTokenList>(*this, html_names::kResourcesAttr)), @@ -109,6 +110,10 @@ } rel_list_->DidUpdateAttributeValue(params.old_value, value); Process(); + } else if (name == html_names::kBlockingAttr && + RuntimeEnabledFeatures::BlockingAttributeEnabled()) { + blocking_attribute_->DidUpdateAttributeValue(params.old_value, value); + Process(); } else if (name == html_names::kHrefAttr) { // Log href attribute before logging resource fetching in process(). LogUpdateAttributeIfIsolatedWorldAndInDocument("link", params); @@ -440,6 +445,7 @@ visitor->Trace(sizes_); visitor->Trace(link_loader_); visitor->Trace(rel_list_); + visitor->Trace(blocking_attribute_); visitor->Trace(resources_); visitor->Trace(scopes_); HTMLElement::Trace(visitor);
diff --git a/third_party/blink/renderer/core/html/html_link_element.h b/third_party/blink/renderer/core/html/html_link_element.h index fa7c0cb..3fe0b46 100644 --- a/third_party/blink/renderer/core/html/html_link_element.h +++ b/third_party/blink/renderer/core/html/html_link_element.h
@@ -31,6 +31,7 @@ #include "third_party/blink/renderer/core/dom/create_element_flags.h" #include "third_party/blink/renderer/core/dom/dom_token_list.h" #include "third_party/blink/renderer/core/dom/increment_load_event_delay_count.h" +#include "third_party/blink/renderer/core/html/blocking_attribute.h" #include "third_party/blink/renderer/core/html/html_element.h" #include "third_party/blink/renderer/core/html/link_rel_attribute.h" #include "third_party/blink/renderer/core/html/link_resource.h" @@ -68,6 +69,7 @@ DOMTokenList& relList() const { return static_cast<DOMTokenList&>(*rel_list_); } + BlockingAttribute& blocking() const { return *blocking_attribute_; } const AtomicString& GetType() const; @@ -169,6 +171,7 @@ Vector<gfx::Size> icon_sizes_; Member<RelList> rel_list_; LinkRelAttribute rel_attribute_; + Member<BlockingAttribute> blocking_attribute_; Member<DOMTokenList> resources_; HashSet<KURL> valid_resource_urls_; Member<DOMTokenList> scopes_;
diff --git a/third_party/blink/renderer/core/html/html_link_element.idl b/third_party/blink/renderer/core/html/html_link_element.idl index 5856df7..27f1d68 100644 --- a/third_party/blink/renderer/core/html/html_link_element.idl +++ b/third_party/blink/renderer/core/html/html_link_element.idl
@@ -60,4 +60,7 @@ // crbug.com/1082020 [RuntimeEnabled=SubresourceWebBundles, PutForwards=value, SecureContext] readonly attribute DOMTokenList resources; [RuntimeEnabled=SubresourceWebBundles, PutForwards=value, SecureContext] readonly attribute DOMTokenList scopes; + + // https://html.spec.whatwg.org/multipage/semantics.html#dom-link-blocking + [RuntimeEnabled=BlockingAttribute, SameObject, PutForwards=value] readonly attribute DOMTokenList blocking; };
diff --git a/third_party/blink/renderer/core/html/html_script_element.cc b/third_party/blink/renderer/core/html/html_script_element.cc index 1503831a..6101c16 100644 --- a/third_party/blink/renderer/core/html/html_script_element.cc +++ b/third_party/blink/renderer/core/html/html_script_element.cc
@@ -47,6 +47,7 @@ const CreateElementFlags flags) : HTMLElement(html_names::kScriptTag, document), children_changed_by_api_(false), + blocking_attribute_(MakeGarbageCollected<BlockingAttribute>(this)), loader_(InitializeScriptLoader(flags)) {} const AttrNameToTrustedType& HTMLScriptElement::GetCheckedAttributeTypes() @@ -103,6 +104,10 @@ // Hints is count usage upon parsing. Processing the value happens when the // element loads. UseCounter::Count(GetDocument(), WebFeature::kPriorityHints); + } else if (params.name == html_names::kBlockingAttr && + RuntimeEnabledFeatures::BlockingAttributeEnabled()) { + blocking_attribute_->DidUpdateAttributeValue(params.old_value, + params.new_value); } else { HTMLElement::ParseAttribute(params); } @@ -346,6 +351,7 @@ } void HTMLScriptElement::Trace(Visitor* visitor) const { + visitor->Trace(blocking_attribute_); visitor->Trace(loader_); HTMLElement::Trace(visitor); ScriptElementBase::Trace(visitor);
diff --git a/third_party/blink/renderer/core/html/html_script_element.h b/third_party/blink/renderer/core/html/html_script_element.h index 61b7768..0ef07f56 100644 --- a/third_party/blink/renderer/core/html/html_script_element.h +++ b/third_party/blink/renderer/core/html/html_script_element.h
@@ -27,6 +27,7 @@ #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/dom/create_element_flags.h" +#include "third_party/blink/renderer/core/html/blocking_attribute.h" #include "third_party/blink/renderer/core/html/html_element.h" #include "third_party/blink/renderer/core/script/script_element_base.h" #include "third_party/blink/renderer/core/script/script_loader.h" @@ -63,6 +64,8 @@ void setAsync(bool); bool async() const; + BlockingAttribute& blocking() const { return *blocking_attribute_; } + ScriptLoader* Loader() const final { return loader_.Get(); } bool IsScriptElement() const override { return true; } @@ -126,6 +129,7 @@ ParkableString script_text_internal_slot_; bool children_changed_by_api_; + Member<BlockingAttribute> blocking_attribute_; Member<ScriptLoader> loader_; };
diff --git a/third_party/blink/renderer/core/html/html_script_element.idl b/third_party/blink/renderer/core/html/html_script_element.idl index 84be3e5..f9168d7 100644 --- a/third_party/blink/renderer/core/html/html_script_element.idl +++ b/third_party/blink/renderer/core/html/html_script_element.idl
@@ -46,4 +46,7 @@ // https://html.spec.whatwg.org/multipage/scripting.html#dom-script-supports [RuntimeEnabled=ScriptElementSupports, CallWith=ScriptState, Measure] static boolean supports(DOMString type); + + // https://html.spec.whatwg.org/multipage/scripting.html#dom-script-blocking + [RuntimeEnabled=BlockingAttribute, SameObject, PutForwards=value] readonly attribute DOMTokenList blocking; };
diff --git a/third_party/blink/renderer/core/html/html_style_element.cc b/third_party/blink/renderer/core/html/html_style_element.cc index 031497f..e22402bf 100644 --- a/third_party/blink/renderer/core/html/html_style_element.cc +++ b/third_party/blink/renderer/core/html/html_style_element.cc
@@ -37,7 +37,8 @@ HTMLStyleElement::HTMLStyleElement(Document& document, const CreateElementFlags flags) : HTMLElement(html_names::kStyleTag, document), - StyleElement(&document, flags.IsCreatedByParser()) {} + StyleElement(&document, flags.IsCreatedByParser()), + blocking_attribute_(MakeGarbageCollected<BlockingAttribute>(this)) {} HTMLStyleElement::~HTMLStyleElement() = default; @@ -53,6 +54,10 @@ } else if (params.name == html_names::kTypeAttr) { HTMLElement::ParseAttribute(params); StyleElement::ChildrenChanged(*this); + } else if (params.name == html_names::kBlockingAttr && + RuntimeEnabledFeatures::BlockingAttributeEnabled()) { + blocking_attribute_->DidUpdateAttributeValue(params.old_value, + params.new_value); } else { HTMLElement::ParseAttribute(params); } @@ -148,6 +153,7 @@ } void HTMLStyleElement::Trace(Visitor* visitor) const { + visitor->Trace(blocking_attribute_); StyleElement::Trace(visitor); HTMLElement::Trace(visitor); }
diff --git a/third_party/blink/renderer/core/html/html_style_element.h b/third_party/blink/renderer/core/html/html_style_element.h index 6786be81..f0642d5 100644 --- a/third_party/blink/renderer/core/html/html_style_element.h +++ b/third_party/blink/renderer/core/html/html_style_element.h
@@ -26,6 +26,7 @@ #include <memory> #include "third_party/blink/renderer/core/css/style_element.h" #include "third_party/blink/renderer/core/dom/increment_load_event_delay_count.h" +#include "third_party/blink/renderer/core/html/blocking_attribute.h" #include "third_party/blink/renderer/core/html/html_element.h" namespace blink { @@ -43,6 +44,8 @@ bool disabled() const; void setDisabled(bool); + BlockingAttribute& blocking() const { return *blocking_attribute_; } + void Trace(Visitor*) const override; private: @@ -70,6 +73,8 @@ const AtomicString& media() const override; const AtomicString& type() const override; + + Member<BlockingAttribute> blocking_attribute_; }; } // namespace blink
diff --git a/third_party/blink/renderer/core/html/html_style_element.idl b/third_party/blink/renderer/core/html/html_style_element.idl index 5370a36..fa3236d 100644 --- a/third_party/blink/renderer/core/html/html_style_element.idl +++ b/third_party/blink/renderer/core/html/html_style_element.idl
@@ -32,4 +32,7 @@ // HTMLStyleElement includes LinkStyle // https://drafts.csswg.org/cssom/#the-linkstyle-interface readonly attribute StyleSheet? sheet; + + // https://html.spec.whatwg.org/multipage/semantics.html#dom-style-blocking + [RuntimeEnabled=BlockingAttribute, SameObject, PutForwards=value] readonly attribute DOMTokenList blocking; };
diff --git a/third_party/blink/renderer/core/html/resources/html.css b/third_party/blink/renderer/core/html/resources/html.css index 784eb158..dcce448 100644 --- a/third_party/blink/renderer/core/html/resources/html.css +++ b/third_party/blink/renderer/core/html/resources/html.css
@@ -1159,6 +1159,10 @@ /* states */ +:-internal-selector-fragment-anchor { + outline: Highlight 3px solid; +} + :-internal-spatial-navigation-interest { outline: auto 1px -webkit-focus-ring-color !important; box-shadow: none !important
diff --git a/third_party/blink/renderer/core/inspector/inspector_trace_events.cc b/third_party/blink/renderer/core/inspector/inspector_trace_events.cc index 7e0e03fd..196d758 100644 --- a/third_party/blink/renderer/core/inspector/inspector_trace_events.cc +++ b/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
@@ -385,6 +385,7 @@ DEFINE_STRING_MAPPING(PseudoVideoPersistentAncestor) DEFINE_STRING_MAPPING(PseudoXrOverlay) DEFINE_STRING_MAPPING(PseudoTargetText) + DEFINE_STRING_MAPPING(PseudoSelectorFragmentAnchor) DEFINE_STRING_MAPPING(PseudoModal) DEFINE_STRING_MAPPING(PseudoHighlight) DEFINE_STRING_MAPPING(PseudoSpellingError)
diff --git a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_geometry.h b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_geometry.h index 12022d6..6b64212 100644 --- a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_geometry.h +++ b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_geometry.h
@@ -71,35 +71,23 @@ } Vector<wtf_size_t> last_indefinite_indices; + Vector<LayoutUnit> major_baselines; + Vector<LayoutUnit> minor_baselines; Vector<SetOffsetData> sets; + LayoutUnit gutter_size; }; // Contains all the geometry information relevant to the final grid. This // should have all the grid information needed to size and position items. struct NGGridGeometry { - NGGridGeometry(SetGeometry&& column_geometry, SetGeometry&& row_geometry) - : column_geometry(column_geometry), - row_geometry(row_geometry), - major_inline_baselines(column_geometry.sets.size(), LayoutUnit::Min()), - minor_inline_baselines(column_geometry.sets.size(), LayoutUnit::Min()), - major_block_baselines(row_geometry.sets.size(), LayoutUnit::Min()), - minor_block_baselines(row_geometry.sets.size(), LayoutUnit::Min()) {} - NGGridGeometry() = default; - const SetGeometry& Geometry( - const GridTrackSizingDirection track_direction) const { - return (track_direction == kForColumns) ? column_geometry : row_geometry; - } + NGGridGeometry(SetGeometry&& column_geometry, SetGeometry&& row_geometry) + : column_geometry(column_geometry), row_geometry(row_geometry) {} SetGeometry column_geometry; SetGeometry row_geometry; - - Vector<LayoutUnit> major_inline_baselines; - Vector<LayoutUnit> minor_inline_baselines; - Vector<LayoutUnit> major_block_baselines; - Vector<LayoutUnit> minor_block_baselines; }; } // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_item.h b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_item.h index e8e5c22..5e35c991 100644 --- a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_item.h +++ b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_item.h
@@ -7,7 +7,6 @@ #include "third_party/blink/renderer/core/layout/ng/grid/ng_grid_track_collection.h" #include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" -#include "third_party/blink/renderer/core/style/grid_positions_resolver.h" #include "third_party/blink/renderer/platform/wtf/vector.h" namespace blink {
diff --git a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc index 286490e7..b429ffc8 100644 --- a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc +++ b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc
@@ -542,50 +542,35 @@ InitializeTrackSizes(grid_properties, &row_track_collection)); bool needs_additional_pass = false; - if (grid_properties.HasBaseline(kForColumns)) { - CalculateAlignmentBaselines(kForColumns, sizing_constraint, - &grid_geometry, &grid_items, - &needs_additional_pass); - } - - grid_geometry.column_geometry = - ComputeUsedTrackSizes(grid_geometry, grid_properties, sizing_constraint, - &column_track_collection, &grid_items); + grid_geometry.column_geometry = ComputeUsedTrackSizes( + grid_properties, sizing_constraint, &column_track_collection, + &grid_geometry, &grid_items, &needs_additional_pass); if (needs_additional_pass || HasBlockSizeDependentGridItem(grid_items)) { // If we need to calculate the row geometry, we have a dependency on our // block constraints. depends_on_block_constraints = true; - if (grid_properties.HasBaseline(kForRows)) { - CalculateAlignmentBaselines(kForRows, sizing_constraint, &grid_geometry, - &grid_items, &needs_additional_pass); - } - absl::optional<SetGeometry> initial_row_geometry; if (!needs_additional_pass) initial_row_geometry = grid_geometry.row_geometry; grid_geometry.row_geometry = ComputeUsedTrackSizes( - grid_geometry, grid_properties, sizing_constraint, - &row_track_collection, &grid_items); + grid_properties, sizing_constraint, &row_track_collection, + &grid_geometry, &grid_items, &needs_additional_pass); - if (initial_row_geometry) { - DCHECK(!needs_additional_pass); + if (!needs_additional_pass) { + DCHECK(initial_row_geometry); needs_additional_pass = MayChangeOrthogonalItemContributions( grid_items, *initial_row_geometry, grid_geometry.row_geometry); } if (needs_additional_pass) { - if (grid_properties.HasBaseline(kForColumns)) { - CalculateAlignmentBaselines(kForColumns, sizing_constraint, - &grid_geometry, &grid_items); - } grid_geometry.column_geometry = InitializeTrackSizes(grid_properties, &column_track_collection); grid_geometry.column_geometry = ComputeUsedTrackSizes( - grid_geometry, grid_properties, sizing_constraint, - &column_track_collection, &grid_items); + grid_properties, sizing_constraint, &column_track_collection, + &grid_geometry, &grid_items); } } return ComputeSetSpanSize( @@ -740,13 +725,13 @@ if (track_direction == kForColumns) { const wtf_size_t set_index = grid_item.column_set_indices.begin; return (grid_item.column_baseline_type == BaselineType::kMajor) - ? grid_geometry.major_inline_baselines[set_index] - : grid_geometry.minor_inline_baselines[set_index]; + ? grid_geometry.column_geometry.major_baselines[set_index] + : grid_geometry.column_geometry.minor_baselines[set_index]; } else { const wtf_size_t set_index = grid_item.row_set_indices.begin; return (grid_item.row_baseline_type == BaselineType::kMajor) - ? grid_geometry.major_block_baselines[set_index] - : grid_geometry.minor_block_baselines[set_index]; + ? grid_geometry.row_geometry.major_baselines[set_index] + : grid_geometry.row_geometry.minor_baselines[set_index]; } } @@ -795,37 +780,20 @@ InitializeTrackSizes(grid_properties, column_track_collection), InitializeTrackSizes(grid_properties, row_track_collection)); - // Store column baselines, as these contributions can influence column - // sizing. bool needs_additional_pass = false; - if (grid_properties.HasBaseline(kForColumns)) { - CalculateAlignmentBaselines(kForColumns, SizingConstraint::kLayout, - &grid_geometry, grid_items, - &needs_additional_pass); - } - - // Resolve inline size. grid_geometry.column_geometry = ComputeUsedTrackSizes( - grid_geometry, grid_properties, SizingConstraint::kLayout, - column_track_collection, grid_items); - - if (grid_properties.HasBaseline(kForRows)) { - CalculateAlignmentBaselines(kForRows, SizingConstraint::kLayout, - &grid_geometry, grid_items, - &needs_additional_pass); - } + grid_properties, SizingConstraint::kLayout, column_track_collection, + &grid_geometry, grid_items, &needs_additional_pass); absl::optional<SetGeometry> initial_row_geometry; if (!needs_additional_pass && HasBlockSizeDependentGridItem(*grid_items)) initial_row_geometry = grid_geometry.row_geometry; - // Resolve block size. grid_geometry.row_geometry = ComputeUsedTrackSizes( - grid_geometry, grid_properties, SizingConstraint::kLayout, - row_track_collection, grid_items); + grid_properties, SizingConstraint::kLayout, row_track_collection, + &grid_geometry, grid_items, &needs_additional_pass); - if (initial_row_geometry) { - DCHECK(!needs_additional_pass); + if (!needs_additional_pass && initial_row_geometry) { needs_additional_pass = MayChangeOrthogonalItemContributions( *grid_items, *initial_row_geometry, grid_geometry.row_geometry); } @@ -833,36 +801,17 @@ // If we had an orthogonal item which may have depended on the resolved row // tracks, re-run the track sizing algorithm for both dimensions. if (needs_additional_pass) { - if (grid_properties.HasBaseline(kForColumns)) { - CalculateAlignmentBaselines(kForColumns, SizingConstraint::kLayout, - &grid_geometry, grid_items); - } - grid_geometry.column_geometry = InitializeTrackSizes(grid_properties, column_track_collection); grid_geometry.column_geometry = ComputeUsedTrackSizes( - grid_geometry, grid_properties, SizingConstraint::kLayout, - column_track_collection, grid_items); - - if (grid_properties.HasBaseline(kForRows)) { - CalculateAlignmentBaselines(kForRows, SizingConstraint::kLayout, - &grid_geometry, grid_items); - } + grid_properties, SizingConstraint::kLayout, column_track_collection, + &grid_geometry, grid_items); grid_geometry.row_geometry = InitializeTrackSizes(grid_properties, row_track_collection); grid_geometry.row_geometry = ComputeUsedTrackSizes( - grid_geometry, grid_properties, SizingConstraint::kLayout, - row_track_collection, grid_items); - } - - if (grid_properties.HasBaseline(kForColumns)) { - CalculateAlignmentBaselines(kForColumns, SizingConstraint::kLayout, - &grid_geometry, grid_items); - } - if (grid_properties.HasBaseline(kForRows)) { - CalculateAlignmentBaselines(kForRows, SizingConstraint::kLayout, - &grid_geometry, grid_items); + grid_properties, SizingConstraint::kLayout, row_track_collection, + &grid_geometry, grid_items); } }; @@ -896,18 +845,13 @@ ConstraintSpace(), container_style, BorderPadding(), *intrinsic_block_size, container_builder_.InlineSize()); + DCHECK_NE(block_size, kIndefiniteSize); + grid_available_size_.block_size = grid_min_available_size_.block_size = grid_max_available_size_.block_size = (block_size - BorderScrollbarPadding().BlockSum()) .ClampNegativeToZero(); - // Re-compute the row geometry now that we have the resolved available - // block-size. "align-content: space-evenly" etc, require the resolved size. - if (container_style.AlignContent() != - ComputedStyleInitialValues::InitialAlignContent()) { - grid_geometry.row_geometry = ComputeSetGeometry(*row_track_collection); - } - // If we have any rows, gaps which will resolve differently if we have a // definite |grid_available_size_| re-compute the grid using the // |block_size| calculated above. @@ -933,15 +877,28 @@ } if (should_recompute_grid) { - DCHECK_NE(grid_available_size_.block_size, kIndefiniteSize); - *row_track_collection = NGGridLayoutAlgorithmTrackCollection( row_block_track_collection, /* is_available_size_indefinite */ false); CacheGridTrackSpanProperties(*row_track_collection, grid_items, &grid_properties); ComputeGrid(); + } else if (container_style.AlignContent() != + ComputedStyleInitialValues::InitialAlignContent()) { + // Re-compute the row geometry now that we resolved the available block + // size. "align-content: space-evenly", etc, require the resolved size. + grid_geometry.row_geometry = ComputeSetGeometry(*row_track_collection); } } + + // Calculate final alignment baselines for grid item layout. + if (grid_properties.HasBaseline(kForColumns)) { + CalculateAlignmentBaselines(kForColumns, SizingConstraint::kLayout, + &grid_geometry, grid_items); + } + if (grid_properties.HasBaseline(kForRows)) { + CalculateAlignmentBaselines(kForRows, SizingConstraint::kLayout, + &grid_geometry, grid_items); + } return grid_geometry; } @@ -984,9 +941,9 @@ InitializeTrackSizes(grid_properties, &row_track_collection)); // Resolve the rows. - grid_geometry.row_geometry = ComputeUsedTrackSizes( - grid_geometry, grid_properties, SizingConstraint::kLayout, - &row_track_collection, &grid_items); + grid_geometry.row_geometry = + ComputeUsedTrackSizes(grid_properties, SizingConstraint::kLayout, + &row_track_collection, &grid_geometry, &grid_items); return grid_geometry.row_geometry.sets.back().offset - grid_geometry.row_geometry.FinalGutterSize() + @@ -1449,16 +1406,13 @@ bool* needs_additional_pass) const { DCHECK(grid_geometry && grid_items); - // Reset existing baselines from geometry so they are clean with each call to - // this method. Use 'WTF::Vector::Fill()' over 'WTF::Vector::clear()', as - // 'clear' will reset the capacity to zero and require re-allocations. - if (track_direction == kForColumns) { - grid_geometry->major_inline_baselines.Fill(LayoutUnit::Min()); - grid_geometry->minor_inline_baselines.Fill(LayoutUnit::Min()); - } else { - grid_geometry->major_block_baselines.Fill(LayoutUnit::Min()); - grid_geometry->minor_block_baselines.Fill(LayoutUnit::Min()); - } + const bool is_for_columns = track_direction == kForColumns; + auto& set_geometry = is_for_columns ? grid_geometry->column_geometry + : grid_geometry->row_geometry; + + const wtf_size_t set_count = set_geometry.sets.size(); + set_geometry.major_baselines.Fill(LayoutUnit::Min(), set_count); + set_geometry.minor_baselines.Fill(LayoutUnit::Min(), set_count); auto UpdateBaseline = [&](const GridItemData& grid_item, LayoutUnit candidate_baseline) { @@ -1467,19 +1421,17 @@ // alignment context along that axis", so we only need to look at the first // index for baseline/first-baseline support. // https://www.w3.org/TR/css-align-3/#baseline-sharing-group - LayoutUnit* track_baseline; - if (track_direction == kForColumns) { - const wtf_size_t set_index = grid_item.column_set_indices.begin; - track_baseline = (grid_item.column_baseline_type == BaselineType::kMajor) - ? &grid_geometry->major_inline_baselines[set_index] - : &grid_geometry->minor_inline_baselines[set_index]; - } else { - const wtf_size_t set_index = grid_item.row_set_indices.begin; - track_baseline = (grid_item.row_baseline_type == BaselineType::kMajor) - ? &grid_geometry->major_block_baselines[set_index] - : &grid_geometry->minor_block_baselines[set_index]; - } - *track_baseline = std::max(*track_baseline, candidate_baseline); + const wtf_size_t set_index = is_for_columns + ? grid_item.column_set_indices.begin + : grid_item.row_set_indices.begin; + const bool is_major_baseline = + BaselineType::kMajor == (is_for_columns ? grid_item.column_baseline_type + : grid_item.row_baseline_type); + + LayoutUnit& track_baseline = is_major_baseline + ? set_geometry.major_baselines[set_index] + : set_geometry.minor_baselines[set_index]; + track_baseline = std::max(track_baseline, candidate_baseline); }; for (auto& grid_item : grid_items->item_data) { @@ -1630,16 +1582,26 @@ // https://drafts.csswg.org/css-grid-2/#algo-track-sizing SetGeometry NGGridLayoutAlgorithm::ComputeUsedTrackSizes( - const NGGridGeometry& grid_geometry, const NGGridProperties& grid_properties, const SizingConstraint sizing_constraint, NGGridLayoutAlgorithmTrackCollection* track_collection, - GridItems* grid_items) const { - DCHECK(track_collection && grid_items); + NGGridGeometry* grid_geometry, + GridItems* grid_items, + bool* needs_additional_pass) const { + DCHECK(track_collection && grid_geometry && grid_items); + + const auto track_direction = track_collection->Direction(); + + // Cache baselines, as these contributions can influence track sizing. + if (grid_properties.HasBaseline(track_direction)) { + CalculateAlignmentBaselines(track_direction, sizing_constraint, + grid_geometry, grid_items, + needs_additional_pass); + } // 2. Resolve intrinsic track sizing functions to absolute lengths. - if (grid_properties.HasIntrinsicTrack(track_collection->Direction())) { - ResolveIntrinsicTrackSizes(grid_geometry, sizing_constraint, + if (grid_properties.HasIntrinsicTrack(track_direction)) { + ResolveIntrinsicTrackSizes(*grid_geometry, sizing_constraint, track_collection, grid_items); } @@ -1654,8 +1616,8 @@ // 4. This step sizes flexible tracks using the largest value it can assign to // an 'fr' without exceeding the available space. - if (grid_properties.HasFlexibleTrack(track_collection->Direction())) { - ExpandFlexibleTracks(grid_geometry, sizing_constraint, track_collection, + if (grid_properties.HasFlexibleTrack(track_direction)) { + ExpandFlexibleTracks(*grid_geometry, sizing_constraint, track_collection, grid_items); } @@ -2456,8 +2418,9 @@ return; const auto track_direction = track_collection->Direction(); - const LayoutUnit gutter_size = - grid_geometry.Geometry(track_direction).gutter_size; + const LayoutUnit gutter_size = (track_direction == kForColumns) + ? grid_geometry.column_geometry.gutter_size + : grid_geometry.row_geometry.gutter_size; // https://drafts.csswg.org/css-grid-2/#algo-find-fr-size GridSetVector flexible_sets;
diff --git a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.h b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.h index 3562a393..8cc88a0f 100644 --- a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.h +++ b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.h
@@ -116,11 +116,12 @@ // Calculates from the min and max track sizing functions the used track size. SetGeometry ComputeUsedTrackSizes( - const NGGridGeometry& grid_geometry, const NGGridProperties& grid_properties, const SizingConstraint sizing_constraint, NGGridLayoutAlgorithmTrackCollection* track_collection, - GridItems* grid_items) const; + NGGridGeometry* grid_geometry, + GridItems* grid_items, + bool* needs_additional_pass = nullptr) const; // These methods implement the steps of the algorithm for intrinsic track size // resolution defined in https://drafts.csswg.org/css-grid-2/#algo-content.
diff --git a/third_party/blink/renderer/core/page/scrolling/fragment_anchor.h b/third_party/blink/renderer/core/page/scrolling/fragment_anchor.h index 6923114..5e22e2b 100644 --- a/third_party/blink/renderer/core/page/scrolling/fragment_anchor.h +++ b/third_party/blink/renderer/core/page/scrolling/fragment_anchor.h
@@ -63,6 +63,8 @@ virtual bool IsTextFragmentAnchor() { return false; } + virtual bool IsSelectorFragmentAnchor() { return false; } + virtual void ScrollElementIntoViewWithOptions(Element* element_to_scroll, ScrollIntoViewOptions* options);
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc index cd8d5f1..42ba5c32 100644 --- a/third_party/blink/renderer/modules/accessibility/ax_object.cc +++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -6014,6 +6014,15 @@ // ----- Conditional: contribute to ancestor only, unless focusable ------- // Some objects can contribute their contents to ancestor names, but // only have their own name if they are focusable + case ax::mojom::blink::Role::kGenericContainer: + // The <body> and <html> element can pass information up to the the root + // for a portal name. + if (IsA<HTMLBodyElement>(GetNode()) || + GetNode() == GetDocument()->documentElement()) { + return recursive && GetDocument()->GetPage() && + GetDocument()->GetPage()->InsidePortal(); + } + [[fallthrough]]; case ax::mojom::blink::Role::kAbbr: case ax::mojom::blink::Role::kCanvas: case ax::mojom::blink::Role::kCaption: @@ -6029,7 +6038,6 @@ case ax::mojom::blink::Role::kFigcaption: case ax::mojom::blink::Role::kFooter: case ax::mojom::blink::Role::kFooterAsNonLandmark: - case ax::mojom::blink::Role::kGenericContainer: case ax::mojom::blink::Role::kHeaderAsNonLandmark: case ax::mojom::blink::Role::kInlineTextBox: case ax::mojom::blink::Role::kLabelText:
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc index bcf1148..597518a 100644 --- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc +++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
@@ -2407,13 +2407,23 @@ Node* node = current->GetNode(); DCHECK(node) << "Refresh() is currently only supported for objects " "with a backing node."; + bool is_ax_layout_object = current->GetLayoutObject(); + bool will_be_ax_layout_object = + node->GetLayoutObject() && + IsLayoutObjectRelevantForAccessibility(*node->GetLayoutObject()) && + !IsDisplayLocked(node->GetLayoutObject()); + if (is_ax_layout_object == will_be_ax_layout_object) + return static_cast<AXObject*>(nullptr); // No change in the AXObject. + AXID retained_axid = current->AXObjectID(); // Remove from relevant maps, but not from relation cache, as the relations // between AXIDs will still be the same. node_object_mapping_.erase(node); - if (current->GetLayoutObject()) { + if (is_ax_layout_object) { layout_object_mapping_.erase(current->GetLayoutObject()); - } else if (node->GetLayoutObject()) { + } else { + DCHECK(will_be_ax_layout_object); + DCHECK(node->GetLayoutObject()); DCHECK(!layout_object_mapping_.Contains(node->GetLayoutObject())) << node << " " << node->GetLayoutObject(); } @@ -2938,7 +2948,7 @@ if (ax_object->RoleValue() == ax_object->DetermineAccessibilityRole()) return; - Invalidate(element->GetDocument(), ax_object->AXObjectID()); + HandleRoleChangeWithCleanLayout(element); } // Be as safe as possible about changes that could alter the accessibility role,
diff --git a/third_party/blink/renderer/modules/accessibility/ax_relation_cache.cc b/third_party/blink/renderer/modules/accessibility/ax_relation_cache.cc index d9eaf3d..6bf3c969 100644 --- a/third_party/blink/renderer/modules/accessibility/ax_relation_cache.cc +++ b/third_party/blink/renderer/modules/accessibility/ax_relation_cache.cc
@@ -543,7 +543,11 @@ // TODO(crbug.com/1109265): It's very likely this loop should only walk the // unignored AXObject chain, but doing so breaks a number of tests related to // name or description computation / invalidation. - for (Node* current_node = node; current_node; + int count = 0; + constexpr int kMaxAncestorsForNameChangeCheck = 8; + for (Node* current_node = node; + ++count < kMaxAncestorsForNameChangeCheck && current_node && + !IsA<HTMLBodyElement>(current_node); current_node = current_node->parentNode()) { // Reverse relations via aria-labelledby, aria-describedby, aria-owns. HeapVector<Member<AXObject>> related_sources; @@ -560,12 +564,15 @@ if (obj && obj->AccessibilityIsIncludedInTree() && obj->SupportsNameFromContents(/*recursive=*/false)) { object_cache_->MarkAXObjectDirtyWithCleanLayout(obj); + break; // Unlikely/unusual to need multiple name/description changes. } } // Forward relation via <label for="[id]">. - if (IsA<HTMLLabelElement>(*current_node)) + if (IsA<HTMLLabelElement>(*current_node)) { LabelChanged(current_node); + break; // Unlikely/unusual to need multiple name/description changes. + } } }
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.cc index fd37b9f..270bda7 100644 --- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.cc +++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.cc
@@ -83,6 +83,8 @@ is_transform_invertible_(true), has_clip_(false), has_complex_clip_(false), + letter_spacing_is_set_(false), + word_spacing_is_set_(false), fill_style_dirty_(true), stroke_style_dirty_(true), line_dash_dirty_(false), @@ -148,6 +150,8 @@ is_transform_invertible_(other.is_transform_invertible_), has_clip_(other.has_clip_), has_complex_clip_(other.has_complex_clip_), + letter_spacing_is_set_(other.letter_spacing_is_set_), + word_spacing_is_set_(other.word_spacing_is_set_), fill_style_dirty_(other.fill_style_dirty_), stroke_style_dirty_(other.stroke_style_dirty_), line_dash_dirty_(other.line_dash_dirty_), @@ -315,15 +319,23 @@ 1.0f /*Deliberately ignore zoom on the canvas element*/); conversion_data.SetFontSizes(font_size); - // Convert word spacing to pixel length and set it in font_description. - float word_spacing_in_pixel = - conversion_data.ZoomedComputedPixels(word_spacing_, word_spacing_unit_); - font_description.SetWordSpacing(word_spacing_in_pixel); + // If wordSpacing is set in CanvasRenderingContext2D, then update the + // information in fontDescription. + if (word_spacing_is_set_) { + // Convert word spacing to pixel length and set it in font_description. + float word_spacing_in_pixel = + conversion_data.ZoomedComputedPixels(word_spacing_, word_spacing_unit_); + font_description.SetWordSpacing(word_spacing_in_pixel); + } - // Convert letter spacing to pixel length and set it in font_description. - float letter_spacing_in_pixel = conversion_data.ZoomedComputedPixels( - letter_spacing_, letter_spacing_unit_); - font_description.SetLetterSpacing(letter_spacing_in_pixel); + // If wordSpacing is set in CanvasRenderingContext2D, then update the + // information in fontDescription. + if (letter_spacing_is_set_) { + // Convert letter spacing to pixel length and set it in font_description. + float letter_spacing_in_pixel = conversion_data.ZoomedComputedPixels( + letter_spacing_, letter_spacing_unit_); + font_description.SetLetterSpacing(letter_spacing_in_pixel); + } font_ = Font(font_description, selector); realized_font_ = true; @@ -775,6 +787,8 @@ void CanvasRenderingContext2DState::SetLetterSpacing( const String& letter_spacing) { DCHECK(realized_font_); + if (!letter_spacing_is_set_) + letter_spacing_is_set_ = true; if (parsed_letter_spacing_ == letter_spacing) return; float num_spacing; @@ -796,6 +810,8 @@ void CanvasRenderingContext2DState::SetWordSpacing(const String& word_spacing) { DCHECK(realized_font_); + if (!word_spacing_is_set_) + word_spacing_is_set_ = true; if (parsed_word_spacing_ == word_spacing) return; float num_spacing;
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.h b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.h index 03f0c54..97c6da2d 100644 --- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.h +++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.h
@@ -322,6 +322,8 @@ bool is_transform_invertible_ : 1; bool has_clip_ : 1; bool has_complex_clip_ : 1; + bool letter_spacing_is_set_ : 1; + bool word_spacing_is_set_ : 1; mutable bool fill_style_dirty_ : 1; mutable bool stroke_style_dirty_ : 1; mutable bool line_dash_dirty_ : 1;
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 index 6451d06..c34546f3 100644 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -315,6 +315,11 @@ name: "BlinkRuntimeCallStats", }, { + // "blocking" attribute on scripts and link resources + name: "BlockingAttribute", + status: "experimental", + }, + { name: "BlockingFocusWithoutUserActivation", status: "experimental", }, @@ -987,6 +992,12 @@ status: "stable", }, { + // Non-standard API Event.path. Should be replaced by Event.composedPath. + // TODO(1277431): This flag should be eventually disabled. + name: "EventPath", + status: "stable", + }, + { name: "ExceptionMetaDataForDevTools", status: "stable", },
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations index ecf27c2..b9dd08a 100644 --- a/third_party/blink/web_tests/TestExpectations +++ b/third_party/blink/web_tests/TestExpectations
@@ -2820,21 +2820,13 @@ crbug.com/1299212 fast/forms/color-scheme/select/select-appearance-after-closing-popup.html [ Pass Failure Timeout Skip ] crbug.com/1299212 fast/forms/color-scheme/select/select-popup-appearance-basic.html [ Pass Failure Timeout Skip ] crbug.com/1299212 fast/forms/select/menulist-popup-type-ahead-style-change.html [ Pass Failure Timeout ] -crbug.com/1299212 virtual/scroll-unification/fast/forms/suggestion-picker/date-suggestion-picker-appearance-zoom125.html [ Pass Failure Timeout ] -crbug.com/1299212 virtual/scroll-unification/fast/forms/suggestion-picker/datetimelocal-suggestion-picker-appearance-locale-hebrew.html [ Pass Failure Timeout ] -crbug.com/1299212 virtual/scroll-unification/fast/forms/suggestion-picker/datetimelocal-suggestion-picker-appearance.html [ Pass Failure Timeout ] -crbug.com/1299212 virtual/scroll-unification/fast/forms/suggestion-picker/month-suggestion-picker-appearance-rtl.html [ Pass Failure Timeout ] -crbug.com/1299212 virtual/scroll-unification/fast/forms/suggestion-picker/date-suggestion-picker-appearance-with-scroll-bar.html [ Pass Failure Timeout ] -crbug.com/1299212 virtual/scroll-unification/fast/forms/suggestion-picker/month-suggestion-picker-appearance-with-scroll-bar.html [ Pass Failure Timeout ] -crbug.com/1299212 virtual/scroll-unification/fast/forms/suggestion-picker/date-suggestion-picker-appearance-rtl.html [ Pass Failure Timeout ] -crbug.com/1299212 virtual/scroll-unification/fast/forms/suggestion-picker/time-suggestion-picker-appearance-locale-hebrew.html [ Pass Failure Timeout ] -crbug.com/1299212 virtual/scroll-unification/fast/forms/suggestion-picker/time-suggestion-picker-appearance.html [ Pass Failure Timeout ] -crbug.com/1299212 virtual/scroll-unification/fast/forms/suggestion-picker/week-suggestion-picker-appearance-rtl.html [ Pass Failure Timeout ] -crbug.com/1299212 virtual/scroll-unification/fast/forms/suggestion-picker/week-suggestion-picker-appearance.html [ Pass Failure Timeout ] +# This might be just flaky on Windows: +crbug.com/1299212 virtual/scroll-unification/fast/forms/suggestion-picker/* [ Pass Failure Timeout ] # At least scrollbars, if not window sizes, are flaky on Win7: crbug.com/1299212 [ Win7 ] fast/forms/select-popup/popup-menu-appearance-tall.html [ Pass Failure Timeout ] crbug.com/1299212 [ Win7 ] fast/forms/suggestion-picker/* [ Pass Failure Timeout ] crbug.com/1299212 [ Win7 ] virtual/scroll-unification/fast/forms/suggestion-picker/time-suggestion-picker-appearance-rtl.html [ Pass Failure Timeout ] +crbug.com/1299212 [ Linux ] fast/forms/select/menulist-popup-mutation-crash.html [ Failure Timeout Pass ] # isInputPending requires threaded compositing and layerized iframes crbug.com/910421 external/wpt/is-input-pending/* [ Skip ] @@ -7508,7 +7500,6 @@ crbug.com/1038139 [ Mac ] virtual/gpu-rasterization/images/2-comp.html [ Failure Pass ] # Need implementation of blocking=render attribute -crbug.com/1271296 virtual/no-forced-frame-updates/external/wpt/html/dom/render-blocking/blocking-idl-attr.tentative.html [ Skip ] crbug.com/1271296 virtual/no-forced-frame-updates/external/wpt/html/dom/render-blocking/parser-inserted-preload-link.tentative.html [ Skip ] crbug.com/1271296 virtual/no-forced-frame-updates/external/wpt/html/dom/render-blocking/render-blocked-apis-by-preload-link.tentative.html [ Skip ] crbug.com/1271296 virtual/no-forced-frame-updates/external/wpt/html/dom/render-blocking/script-inserted-preload-link.tentative.html [ Skip ] @@ -7597,4 +7588,6 @@ crbug.com/1299858 [ Win ] http/tests/fetch/window/thorough/cors-base-https-other-https.html [ Skip Timeout Pass ] crbug.com/1299858 [ Win ] http/tests/fetch/workers/thorough/cookie-nocors-base-https-other-https.html [ Skip Timeout Pass ] crbug.com/1299903 [ Win7 ] virtual/prerender/external/wpt/speculation-rules/prerender/restriction-window-move.html [ Failure Pass ] - +crbug.com/1299946 [ Mac ] external/wpt/css/css-sizing/min-content-negative-margin-crash.html [ Timeout Pass ] +crbug.com/1299948 [ Mac ] external/wpt/css/css-tables/crashtests/textarea-intrinsic-size-crash.html [ Timeout Pass ] +crbug.com/1299972 [ Linux ] screen_orientation/screenorientation-unsupported-no-crash.html [ Failure Timeout Pass ]
diff --git a/third_party/blink/web_tests/WPTOverrideExpectations b/third_party/blink/web_tests/WPTOverrideExpectations index 5759cf2..79fad3cd 100644 --- a/third_party/blink/web_tests/WPTOverrideExpectations +++ b/third_party/blink/web_tests/WPTOverrideExpectations
@@ -46,7 +46,7 @@ external/wpt/input-events/input-events-get-target-ranges-deleting-in-list-items.tentative.html?Delete,ol [ Pass ] # wpt_use_checked_in_metadata external/wpt/input-events/input-events-get-target-ranges-deleting-in-list-items.tentative.html?Delete,ul [ Pass ] # wpt_use_checked_in_metadata external/wpt/input-events/input-events-get-target-ranges-during-and-after-dispatch.tentative.html [ Pass ] # wpt_use_checked_in_metadata -external/wpt/input-events/input-events-get-target-ranges-forwarddelete.tentative.html [ Pass ] # wpt_use_checked_in_metadata +external/wpt/input-events/input-events-get-target-ranges-forwarddelete.tentative.html [ Skip ] external/wpt/input-events/input-events-get-target-ranges-joining-dl-element-and-another-list.tentative.html?Backspace [ Pass ] # wpt_use_checked_in_metadata external/wpt/input-events/input-events-get-target-ranges-joining-dl-element-and-another-list.tentative.html?Delete [ Pass ] # wpt_use_checked_in_metadata external/wpt/input-events/input-events-get-target-ranges-joining-dl-elements.tentative.html?Backspace [ Pass ] # wpt_use_checked_in_metadata
diff --git a/third_party/blink/web_tests/css3/calc/number-parsing-expected.txt b/third_party/blink/web_tests/css3/calc/number-parsing-expected.txt index c9e5a29..8b13030 100644 --- a/third_party/blink/web_tests/css3/calc/number-parsing-expected.txt +++ b/third_party/blink/web_tests/css3/calc/number-parsing-expected.txt
@@ -1,4 +1,20 @@ This is a testharness.js-based test. -FAIL Ensure using calc() for CSS numbers does not crash or produce incorrect values. assert_equals: -webkit-box-ordinal-group: calc(6 + 4) expected "10" but got "calc(10)" +Harness Error. harness_status.status = 1 , harness_status.message = 1 duplicate test name: "Ensure using calc() for CSS numbers with + on transition-timing-function does not crash or produce incorrect values." +PASS Ensure using calc() for CSS numbers with + on -webkit-box-ordinal-group does not crash or produce incorrect values. +PASS Ensure using calc() for CSS number 0 on -webkit-box-ordinal-group does not crash or produce incorrect values. +PASS Ensure using calc() for CSS numbers with - on -webkit-column-span does not crash or produce incorrect values. +PASS Ensure using calc() for CSS numbers with + on -webkit-column-span does not crash or produce incorrect values. +PASS Ensure using calc() for CSS lengths with - on -webkit-column-width does not crash or produce incorrect values. +PASS Ensure using calc() for CSS numbers with + on transition-timing-function does not crash or produce incorrect values. +PASS Ensure using calc() for CSS numbers with / on transition-timing-function does not crash or produce incorrect values. +PASS Ensure using calc() for CSS numbers with + on transition-timing-function does not crash or produce incorrect values. +PASS Ensure using calc() for CSS numbers with + on grid-row-start does not crash or produce incorrect values. +PASS Ensure using calc() for CSS numbers with / on grid-row-start does not crash or produce incorrect values. +PASS Ensure using calc() for CSS numbers with + on font-weight does not crash or produce incorrect values. +PASS Ensure using calc() for CSS numbers with + on flex does not crash or produce incorrect values. +PASS Ensure using calc() for CSS numbers with / on saturate() does not crash or produce incorrect values. +FAIL Ensure using calc() for CSS numbers with / on invert() resulting bigger than 1 does not crash or produce incorrect values. assert_equals: -webkit-filter: invert(calc(4 / 2)) expected "invert(calc(1))" but got "invert(1)" +PASS Ensure using calc() for CSS numbers with / on invert() resulting smaller than 1 does not crash or produce incorrect values. +PASS Ensure using calc() for CSS numbers with / on brightness() does not crash or produce incorrect values. Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/css3/calc/number-parsing.html b/third_party/blink/web_tests/css3/calc/number-parsing.html index 2ef944e..b63b8a9 100644 --- a/third_party/blink/web_tests/css3/calc/number-parsing.html +++ b/third_party/blink/web_tests/css3/calc/number-parsing.html
@@ -9,21 +9,51 @@ } test(function() { - assertParsedValue('-webkit-box-ordinal-group', 'calc(6 + 4)', '10'); - assertParsedValue('-webkit-box-ordinal-group', 'calc(0)', '0'); + assertParsedValue('-webkit-box-ordinal-group', 'calc(6 + 4)', 'calc(10)'); +}, 'Ensure using calc() for CSS numbers with + on -webkit-box-ordinal-group does not crash or produce incorrect values.'); +test(function() { + assertParsedValue('-webkit-box-ordinal-group', 'calc(0)', 'calc(0)'); +}, 'Ensure using calc() for CSS number 0 on -webkit-box-ordinal-group does not crash or produce incorrect values.'); +test(function() { assertParsedValue('-webkit-column-span', 'calc(2 - 1)', ''); +}, 'Ensure using calc() for CSS numbers with - on -webkit-column-span does not crash or produce incorrect values.'); +test(function() { assertParsedValue('-webkit-column-span', 'calc(1 + 2)', ''); +}, 'Ensure using calc() for CSS numbers with + on -webkit-column-span does not crash or produce incorrect values.'); +test(function() { assertParsedValue('-webkit-column-width', 'calc(1em - 1px)', 'calc(1em - 1px)'); +}, 'Ensure using calc() for CSS lengths with - on -webkit-column-width does not crash or produce incorrect values.'); +test(function() { assertParsedValue('transition-timing-function', 'cubic-bezier(calc(1 + 2), 0, 1, 1)', ''); +}, 'Ensure using calc() for CSS numbers with + on transition-timing-function does not crash or produce incorrect values.'); +test(function() { assertParsedValue('transition-timing-function', 'cubic-bezier(calc(1 / 2), calc(1 - 1), calc(2 - 1), calc(2 * 3))', 'cubic-bezier(0.5, 0, 1, 6)'); +}, 'Ensure using calc() for CSS numbers with / on transition-timing-function does not crash or produce incorrect values.'); +test(function() { assertParsedValue('transition-timing-function', 'steps(calc(1 + 2), start)', 'steps(3, start)'); +}, 'Ensure using calc() for CSS numbers with + on transition-timing-function does not crash or produce incorrect values.'); +test(function() { assertParsedValue('grid-row-start', 'calc(1 + 2) test', '3 test'); - assertParsedValue('grid-row-start', 'calc(1 / 2) test', ''); - assertParsedValue('font-weight', 'calc(100 + 200)', '300'); +}, 'Ensure using calc() for CSS numbers with + on grid-row-start does not crash or produce incorrect values.'); +test(function() { + assertParsedValue('grid-row-start', 'calc(1 / 2) test', '1 test'); +}, 'Ensure using calc() for CSS numbers with / on grid-row-start does not crash or produce incorrect values.'); +test(function() { + assertParsedValue('font-weight', 'calc(100 + 200)', 'calc(300)'); +}, 'Ensure using calc() for CSS numbers with + on font-weight does not crash or produce incorrect values.'); +test(function() { assertParsedValue('flex', 'calc(1 + 2) calc(3 + 4)', '3 7 0%'); - assertParsedValue('-webkit-filter', 'saturate(calc(4 / 2))', 'saturate(2)'); - assertParsedValue('-webkit-filter', 'invert(calc(4 / 2))', 'invert(1)'); - assertParsedValue('-webkit-filter', 'invert(calc(2 / 4))', 'invert(0.5)'); - assertParsedValue('-webkit-filter', 'brightness(calc(4 / 2))', 'brightness(2)'); -}, 'Ensure using calc() for CSS numbers does not crash or produce incorrect values.'); +}, 'Ensure using calc() for CSS numbers with + on flex does not crash or produce incorrect values.'); +test(function() { + assertParsedValue('-webkit-filter', 'saturate(calc(4 / 2))', 'saturate(calc(2))'); +}, 'Ensure using calc() for CSS numbers with / on saturate() does not crash or produce incorrect values.'); +test(function() { + assertParsedValue('-webkit-filter', 'invert(calc(4 / 2))', 'invert(calc(1))'); +}, 'Ensure using calc() for CSS numbers with / on invert() resulting bigger than 1 does not crash or produce incorrect values.'); +test(function() { + assertParsedValue('-webkit-filter', 'invert(calc(2 / 4))', 'invert(calc(0.5))'); +}, 'Ensure using calc() for CSS numbers with / on invert() resulting smaller than 1 does not crash or produce incorrect values.'); +test(function() { + assertParsedValue('-webkit-filter', 'brightness(calc(4 / 2))', 'brightness(calc(2))'); +}, 'Ensure using calc() for CSS numbers with / on brightness() does not crash or produce incorrect values.'); </script>
diff --git a/third_party/blink/web_tests/external/wpt/input-events/input-events-get-target-ranges-forwarddelete.tentative.html.ini b/third_party/blink/web_tests/external/wpt/input-events/input-events-get-target-ranges-forwarddelete.tentative.html.ini deleted file mode 100644 index aa9f10bf..0000000 --- a/third_party/blink/web_tests/external/wpt/input-events/input-events-get-target-ranges-forwarddelete.tentative.html.ini +++ /dev/null
@@ -1,530 +0,0 @@ -[input-events-get-target-ranges-forwarddelete.tentative.html] - expected: OK - - [Delete at "<p>ab[]c</p>"] - expected: PASS - - [Delete at "<p>a[]bc</p>"] - expected: PASS - - [Delete at "<p>[]abc</p>"] - expected: PASS - - [Delete at "<p>abc[]</p>"] - expected: PASS - - [Delete at "<p>abc[]<br></p>"] - expected: PASS - - [Delete at "<p><img>[]<br></p>"] - expected: PASS - - [Delete at "<p>a[]<span>b</span>c</p>"] - expected: FAIL - - [Delete at "<p>a<span>[]b</span>c</p>"] - expected: FAIL - - [Delete at "<p>ab[]c </p>"] - expected: FAIL - - [Delete at "<p>abc[]</p><p>def</p>"] - expected: FAIL - - [Delete at "<p>abc[] </p><p> def</p>"] - expected: FAIL - - [Delete at "<p>abc [] </p><p> def</p>"] - expected: FAIL - - [Delete at "<p>abc [] </p><p> def</p>"] - expected: FAIL - - [Delete at "<p>abc []</p><p> def</p>"] - expected: FAIL - - [Delete at "<p>abc[]</p><p><b>def</b></p>"] - expected: FAIL - - [Delete at "<p><b>abc[]</b></p><p><b>def</b></p>"] - expected: FAIL - - [Delete at "<p><i>abc[]</i></p><p><b>def</b></p>"] - expected: FAIL - - [Delete at "<pre>abc []</pre><p> def</p>"] - expected: FAIL - - [Delete at "<pre>abc []</pre><p> def </p>"] - expected: FAIL - - [Delete at "<p>abc[] </p><pre> def</pre>"] - expected: FAIL - - [Delete at "<p style="white-space:pre-line">abc[]\ndef</p>"] - expected: PASS - - [Delete at "<p style="white-space:pre-line">abc[]\n def</p>"] - expected: FAIL - - [Delete at "<p style="white-space:pre-line">abc[] \ndef</p>"] - expected: FAIL - - [Delete at "<p style="white-space:pre-line">abc[] \n def</p>"] - expected: FAIL - - [Delete at "<p style="white-space:pre-line">abc[] \n \n def</p>"] - expected: FAIL - - [Delete at "<p>abc[]<br></p><p>def</p>"] - expected: FAIL - - [Delete at "<p><img>{}<br></p><p>def</p>"] - expected: FAIL - - [Delete at "<p>abc<br>{}<br></p><p>def</p>"] - expected: FAIL - - [Delete at "<p>abc[]<br>def</p>"] - expected: PASS - - [Delete at "<p>abc []<br>def</p>"] - expected: PASS - - [Delete at "<p>abc[]<br> def</p>"] - expected: FAIL - - [Delete at "<p>abc[]<img>def</p>"] - expected: PASS - - [Delete at "<p>abc []<img>def</p>"] - expected: PASS - - [Delete at "<p>abc[]<img> def</p>"] - expected: PASS - - [Delete at "<p>abc[]<img><img>def</p>"] - expected: PASS - - [Delete at "<p>abc<img>{}<img>def</p>"] - expected: PASS - - [Delete at "<div>abc[]<hr>def</div>"] - expected: FAIL - - [Delete at "<div>abc[] <hr>def</div>"] - expected: FAIL - - [Delete at "<div>abc[]<hr> def</div>"] - expected: FAIL - - [Delete at "<div>abc[]<br><hr>def</div>"] - expected: FAIL - - [Delete at "<div><img>{}<br><hr>def</div>"] - expected: FAIL - - [Delete at "<div>abc[]<p>def<br>ghi</p></div>"] - expected: FAIL - - [Delete at "<div>abc[]<br><p>def<br>ghi</p></div>"] - expected: FAIL - - [Delete at "<div><img>{}<br><p>def<br>ghi</p></div>"] - expected: FAIL - - [Delete at "<div>abc[] <p> def<br>ghi</p></div>"] - expected: FAIL - - [Delete at "<div>abc[]<p><b>def</b></p></div>"] - expected: FAIL - - [Delete at "<div><b>abc[]</b><p><b>def</b></p></div>"] - expected: FAIL - - [Delete at "<div><i>abc[]</i><p><b>def</b></p></div>"] - expected: FAIL - - [Delete at "<div><p>abc[]</p>def</div>"] - expected: PASS - - [Delete at "<div><p>abc[]<br></p>def</div>"] - expected: PASS - - [Delete at "<div><p><img>{}<br></p>def</div>"] - expected: PASS - - [Delete at "<div><p>abc[] </p> def</div>"] - expected: FAIL - - [Delete at "<div><p><b>abc[]</b></p>def</div>"] - expected: PASS - - [Delete at "<div><p><b>abc[]</b></p><b>def</b></div>"] - expected: FAIL - - [Delete at "<div><p><b>abc[]</b></p><i>def</i></div>"] - expected: FAIL - - [Delete at "<div>abc[]<ul><li>def</li></ul>ghi</div>"] - expected: FAIL - - [Delete at "<div>abc[] <ul><li> def </li></ul> ghi</div>"] - expected: FAIL - - [Delete at "<div>abc []<ul><li> def </li></ul> ghi</div>"] - expected: FAIL - - [Delete at "<div>abc<ul><li>def[]</li></ul>ghi</div>"] - expected: PASS - - [Delete at "<div>abc <ul><li> def[] </li></ul> ghi</div>"] - expected: FAIL - - [Delete at "<div>abc <ul><li> def []</li></ul> ghi</div>"] - expected: FAIL - - [Delete at "<div>abc[]<ul><li>def</li><li>ghi</li></ul>jkl</div>"] - expected: FAIL - - [Delete at "<div>abc<ul><li>def[]</li><li>ghi</li></ul>jkl</div>"] - expected: FAIL - - [Delete at "<div>abc<ul><li>def</li><li>ghi[]</li></ul>jkl</div>"] - expected: PASS - - [Delete at "<p>{}<br></p><p>abc</p>"] - expected: FAIL - - [Delete at "<p><span>{}</span><br></p><p>abc</p>"] - expected: FAIL - - [Delete at "<p>{}<br></p><p> abc</p>"] - expected: FAIL - - [Delete at "<p>{}<br></p><p><span contenteditable="false">abc</span>def</p>"] - expected: PASS - - [Delete at "<p>{}<br></p><p contenteditable="false">abc</p><p>def</p>"] - expected: FAIL - - [Delete at "<p>abc[]<span contenteditable="false">def</span>ghi</p>"] - expected: FAIL - - [Delete at "<p>{}<br></p><table><tr><td>cell</td></tr></table>"] - expected: FAIL - - [Delete at "<table><tr><td>{}<br></td><td>cell2</td></tr></table>"] - expected: FAIL - - [Delete at "<table><caption>{}<br></caption><tr><td>cell</td></tr></table>"] - expected: FAIL - - [Delete at "<table><tr>{<td>cell1</td>}<td>cell2</td></tr></table>"] - expected: FAIL - - [Delete at "<table><tr><td>cell1</td>{<td>cell2</td>}</tr></table>"] - expected: FAIL - - [Delete at "<table><tr>{<td>cell</td>}</tr></table>"] - expected: FAIL - - [Delete at "<table><tr>{<td>cell1</td>}<td>cell2</td></tr><tr><td>cell3</td>{<td>cell4</td>}</tr></table>"] - expected: FAIL - - [Delete at "<table><tr>{<td>cell1</td>}<td>cell2</td></tr><tr>{<td>cell3</td>}<td>cell4</td></tr></table>"] - expected: FAIL - - [Delete at "<table><tr>{<td>cell1</td>}{<td>cell2</td>}</tr><tr><td>cell3</td><td>cell4</td></tr></table>"] - expected: FAIL - - [Delete at "<table><tr><td>cell1</td><td>cell2</td></tr><tr>{<td>cell3</td>}{<td>cell4</td>}</tr></table>"] - expected: FAIL - - [Delete at "<table><tr>{<td>cell1</td>}{<td>cell2</td>}</tr><tr>{<td>cell3</td>}{<td>cell4</td>}</tr></table>"] - expected: FAIL - - [Delete at "<table><tr>{<td>cell1</td>}<td>c[ell]2</td></tr><tr>{<td>cell3</td>}<td>cell4</td></tr></table>"] - expected: FAIL - - [Delete at "<p>hello[]שלום</p>"] - expected: PASS - - [Shift + Delete at "<p>abc []def ghi</p>"] - expected: PASS - - [Control + Delete at "<p>abc []def ghi</p>"] - expected: PASS - - [Alt + Delete at "<p>abc []def ghi</p>"] - expected: PASS - - [Meta + Delete at "<p>abc []def ghi</p>"] - expected: PASS - - [Shift + Delete at "<p>abc []def </p>"] - expected: PASS - - [Control + Delete at "<p>abc []def </p>"] - expected: FAIL - - [Alt + Delete at "<p>abc []def </p>"] - expected: PASS - - [Meta + Delete at "<p>abc []def</p>"] - expected: PASS - - [Delete at "<p>ab[]c</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p>a[]bc</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p>[]abc</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p>abc[]</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p>abc[]<br></p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p><img>[]<br></p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p>a[]<span>b</span>c</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p>a<span>[]b</span>c</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p>ab[]c </p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p>abc[]</p><p>def</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p>abc[] </p><p> def</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p>abc [] </p><p> def</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p>abc [] </p><p> def</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p>abc []</p><p> def</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p>abc[]</p><p><b>def</b></p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p><b>abc[]</b></p><p><b>def</b></p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p><i>abc[]</i></p><p><b>def</b></p>" - comparing innerHTML] - expected: PASS - - [Delete at "<pre>abc []</pre><p> def</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<pre>abc []</pre><p> def </p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p>abc[] </p><pre> def</pre>" - comparing innerHTML] - expected: PASS - - [Delete at "<p style="white-space:pre-line">abc[]\ndef</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p style="white-space:pre-line">abc[]\n def</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p style="white-space:pre-line">abc[] \ndef</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p style="white-space:pre-line">abc[] \n def</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p style="white-space:pre-line">abc[] \n \n def</p>" - comparing innerHTML] - expected: FAIL - - [Delete at "<p>abc[]<br></p><p>def</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p><img>{}<br></p><p>def</p>" - comparing innerHTML] - expected: FAIL - - [Delete at "<p>abc<br>{}<br></p><p>def</p>" - comparing innerHTML] - expected: FAIL - - [Delete at "<p>abc[]<br>def</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p>abc []<br>def</p>" - comparing innerHTML] - expected: FAIL - - [Delete at "<p>abc[]<br> def</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p>abc[]<img>def</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p>abc []<img>def</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p>abc[]<img> def</p>" - comparing innerHTML] - expected: FAIL - - [Delete at "<p>abc[]<img><img>def</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p>abc<img>{}<img>def</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<div>abc[]<hr>def</div>" - comparing innerHTML] - expected: PASS - - [Delete at "<div>abc[] <hr>def</div>" - comparing innerHTML] - expected: PASS - - [Delete at "<div>abc[]<hr> def</div>" - comparing innerHTML] - expected: FAIL - - [Delete at "<div>abc[]<br><hr>def</div>" - comparing innerHTML] - expected: PASS - - [Delete at "<div><img>{}<br><hr>def</div>" - comparing innerHTML] - expected: PASS - - [Delete at "<div>abc[]<p>def<br>ghi</p></div>" - comparing innerHTML] - expected: PASS - - [Delete at "<div>abc[]<br><p>def<br>ghi</p></div>" - comparing innerHTML] - expected: PASS - - [Delete at "<div><img>{}<br><p>def<br>ghi</p></div>" - comparing innerHTML] - expected: PASS - - [Delete at "<div>abc[] <p> def<br>ghi</p></div>" - comparing innerHTML] - expected: PASS - - [Delete at "<div>abc[]<p><b>def</b></p></div>" - comparing innerHTML] - expected: FAIL - - [Delete at "<div><b>abc[]</b><p><b>def</b></p></div>" - comparing innerHTML] - expected: FAIL - - [Delete at "<div><i>abc[]</i><p><b>def</b></p></div>" - comparing innerHTML] - expected: FAIL - - [Delete at "<div><p>abc[]</p>def</div>" - comparing innerHTML] - expected: FAIL - - [Delete at "<div><p>abc[]<br></p>def</div>" - comparing innerHTML] - expected: FAIL - - [Delete at "<div><p><img>{}<br></p>def</div>" - comparing innerHTML] - expected: FAIL - - [Delete at "<div><p>abc[] </p> def</div>" - comparing innerHTML] - expected: FAIL - - [Delete at "<div><p><b>abc[]</b></p>def</div>" - comparing innerHTML] - expected: FAIL - - [Delete at "<div><p><b>abc[]</b></p><b>def</b></div>" - comparing innerHTML] - expected: FAIL - - [Delete at "<div><p><b>abc[]</b></p><i>def</i></div>" - comparing innerHTML] - expected: FAIL - - [Delete at "<div>abc[]<ul><li>def</li></ul>ghi</div>" - comparing innerHTML] - expected: FAIL - - [Delete at "<div>abc[] <ul><li> def </li></ul> ghi</div>" - comparing innerHTML] - expected: FAIL - - [Delete at "<div>abc []<ul><li> def </li></ul> ghi</div>" - comparing innerHTML] - expected: FAIL - - [Delete at "<div>abc<ul><li>def[]</li></ul>ghi</div>" - comparing innerHTML] - expected: FAIL - - [Delete at "<div>abc <ul><li> def[] </li></ul> ghi</div>" - comparing innerHTML] - expected: FAIL - - [Delete at "<div>abc <ul><li> def []</li></ul> ghi</div>" - comparing innerHTML] - expected: FAIL - - [Delete at "<div>abc[]<ul><li>def</li><li>ghi</li></ul>jkl</div>" - comparing innerHTML] - expected: FAIL - - [Delete at "<div>abc<ul><li>def[]</li><li>ghi</li></ul>jkl</div>" - comparing innerHTML] - expected: FAIL - - [Delete at "<div>abc<ul><li>def</li><li>ghi[]</li></ul>jkl</div>" - comparing innerHTML] - expected: FAIL - - [Delete at "<p>{}<br></p><p>abc</p>" - comparing innerHTML] - expected: FAIL - - [Delete at "<p><span>{}</span><br></p><p>abc</p>" - comparing innerHTML] - expected: FAIL - - [Delete at "<p>{}<br></p><p> abc</p>" - comparing innerHTML] - expected: FAIL - - [Delete at "<p>{}<br></p><p><span contenteditable="false">abc</span>def</p>" - comparing innerHTML] - expected: FAIL - - [Delete at "<p>{}<br></p><p contenteditable="false">abc</p><p>def</p>" - comparing innerHTML] - expected: FAIL - - [Delete at "<p>abc[]<span contenteditable="false">def</span>ghi</p>" - comparing innerHTML] - expected: PASS - - [Delete at "<p>{}<br></p><table><tr><td>cell</td></tr></table>" - comparing innerHTML] - expected: PASS - - [Delete at "<table><tr><td>{}<br></td><td>cell2</td></tr></table>" - comparing innerHTML] - expected: PASS - - [Delete at "<table><caption>{}<br></caption><tr><td>cell</td></tr></table>" - comparing innerHTML] - expected: FAIL - - [Delete at "<table><tr>{<td>cell1</td>}<td>cell2</td></tr></table>" - comparing innerHTML] - expected: PASS - - [Delete at "<table><tr><td>cell1</td>{<td>cell2</td>}</tr></table>" - comparing innerHTML] - expected: FAIL - - [Delete at "<table><tr>{<td>cell</td>}</tr></table>" - comparing innerHTML] - expected: PASS - - [Delete at "<p>hello[]שלום</p>" - comparing innerHTML] - expected: PASS - - [Shift + Delete at "<p>abc []def ghi</p>" - comparing innerHTML] - expected: PASS - - [Control + Delete at "<p>abc []def ghi</p>" - comparing innerHTML] - expected: PASS - - [Alt + Delete at "<p>abc []def ghi</p>" - comparing innerHTML] - expected: PASS - - [Meta + Delete at "<p>abc []def ghi</p>" - comparing innerHTML] - expected: PASS - - [Shift + Delete at "<p>abc []def </p>" - comparing innerHTML] - expected: FAIL - - [Control + Delete at "<p>abc []def </p>" - comparing innerHTML] - expected: PASS - - [Alt + Delete at "<p>abc []def </p>" - comparing innerHTML] - expected: FAIL - - [Meta + Delete at "<p>abc []def</p>" - comparing innerHTML] - expected: FAIL
diff --git a/third_party/blink/web_tests/webexposed/element-instance-property-listing-expected.txt b/third_party/blink/web_tests/webexposed/element-instance-property-listing-expected.txt index 015ea58..24de3bf 100644 --- a/third_party/blink/web_tests/webexposed/element-instance-property-listing-expected.txt +++ b/third_party/blink/web_tests/webexposed/element-instance-property-listing-expected.txt
@@ -782,6 +782,7 @@ property value html element link property as + property blocking property charset property crossOrigin property disabled @@ -936,6 +937,7 @@ html element samp html element script property async + property blocking property charset property crossOrigin property defer @@ -1007,6 +1009,7 @@ html element strike html element strong html element style + property blocking property disabled property media property sheet
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt index a36f758c..2a41e77 100644 --- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt +++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -3846,6 +3846,7 @@ interface HTMLLinkElement : HTMLElement attribute @@toStringTag getter as + getter blocking getter charset getter crossOrigin getter disabled @@ -3868,6 +3869,7 @@ getter type method constructor setter as + setter blocking setter charset setter crossOrigin setter disabled @@ -4199,6 +4201,7 @@ static method supports attribute @@toStringTag getter async + getter blocking getter charset getter crossOrigin getter defer @@ -4213,6 +4216,7 @@ getter type method constructor setter async + setter blocking setter charset setter crossOrigin setter defer @@ -4314,11 +4318,13 @@ method constructor interface HTMLStyleElement : HTMLElement attribute @@toStringTag + getter blocking getter disabled getter media getter sheet getter type method constructor + setter blocking setter disabled setter media setter type
diff --git a/third_party/blink/web_tests/wpt_internal/fenced_frame/consume-user-activation.https.html b/third_party/blink/web_tests/wpt_internal/fenced_frame/consume-user-activation.https.html index e4ad20d..d78d08e 100644 --- a/third_party/blink/web_tests/wpt_internal/fenced_frame/consume-user-activation.https.html +++ b/third_party/blink/web_tests/wpt_internal/fenced_frame/consume-user-activation.https.html
@@ -88,7 +88,11 @@ // Check that B's transient user activation was consumed. assert_activations(true, true, "A"); - await B.execute(assert_activations, [true, false, "B"]); + // TODO(crbug.com/1291210): B should be inactive after consuming the + // transient user activation, but due to a preexisting bug it isn't. + // Replace with this once fixed: + // await B.execute(assert_activations, [true, false, "B"]); + await B.execute(assert_activations, [true, true, "B"]); }, 'user-activation'); </script>
diff --git a/third_party/blink/web_tests/wpt_internal/fenced_frame/restrict-size.https.html b/third_party/blink/web_tests/wpt_internal/fenced_frame/restrict-size.https.html deleted file mode 100644 index 5668407d..0000000 --- a/third_party/blink/web_tests/wpt_internal/fenced_frame/restrict-size.https.html +++ /dev/null
@@ -1,15 +0,0 @@ -<!DOCTYPE html> -<title>Test fencedframe size restrictions in opaque ads mode.</title> -<script src="/resources/testharness.js"></script> -<script src="/resources/testharnessreport.js"></script> -<script src="/common/utils.js"></script> -<script src="/common/dispatcher/dispatcher.js"></script> -<script src="resources/utils.js"></script> - -<body> -<script> -promise_test(async () => { - const frame = attachFencedFrameContext(); -}, 'restrict fencedframe size'); -</script> -</body>
diff --git a/third_party/blink/web_tests/wpt_internal/fragment-directive/scroll-to-selector-fragment-target.html b/third_party/blink/web_tests/wpt_internal/fragment-directive/scroll-to-selector-fragment-target.html deleted file mode 100644 index 62d06b0..0000000 --- a/third_party/blink/web_tests/wpt_internal/fragment-directive/scroll-to-selector-fragment-target.html +++ /dev/null
@@ -1,50 +0,0 @@ -<!doctype html> -<title>Navigating to a selector fragment anchor</title> -<script src="stash.js"></script> -<script> -function isInView(element) { - let rect = element.getBoundingClientRect(); - return rect.top >= 0 && rect.top <= window.innerHeight - && rect.left >= 0 && rect.left <= window.innerWidth; -} - -function checkScroll() { - let position = 'unknown'; - if (window.scrollY === 0) - position = 'top'; - else if (isInView(document.getElementById('some-text'))) - position = 'some-text'; - else if (isInView(document.getElementById('cat'))) - position = 'cat'; - else if (isInView(document.getElementById('some-other-text'))) - position = 'some-other-text'; - else if (isInView(document.getElementById('input-button'))) - position = 'input-button'; - else if (isInView(document.getElementById('more-text-goes-here'))) - position = 'more-text-goes-here'; - - let target = document.querySelector(":target"); - - let results = { - scrollPosition: position, - href: window.location.href, - target: target ? target.id : 'undefined' - }; - - let key = (new URL(document.location)).searchParams.get("key"); - stashResultsThenClose(key, results); -} - -window.onload = function() { - window.requestAnimationFrame(checkScroll) -} -</script> -<body> - <div id='some-text' style='height:600px;'>some text</div> - <svg id='cat' width='200' height='200' xmlns='http://www.w3.org/2000/svg'> - <circle class='path' cx='100' cy='100' r='100' fill='red'/> - </svg> - <div id='some-other-text' style='height:600px;'>some other text</div> - <input id='input-button' type="button" value="Add to favorites"> - <div id='more-text-goes-here' style='height:600px;'>more text goes here</div> -</body>
diff --git a/third_party/blink/web_tests/wpt_internal/fragment-directive/scroll-to-selector-fragment.html b/third_party/blink/web_tests/wpt_internal/fragment-directive/scroll-to-selector-fragment.html deleted file mode 100644 index 2ffda17..0000000 --- a/third_party/blink/web_tests/wpt_internal/fragment-directive/scroll-to-selector-fragment.html +++ /dev/null
@@ -1,119 +0,0 @@ -<!doctype html> -<title>Navigating to a selector fragment directive</title> -<meta charset=utf-8> -<link rel="help" href="https://github.com/WICG/scroll-to-text-fragment/blob/main/EXTENSIONS.md#proposed-solution"> -<meta name="timeout" content="long"> -<script src="/resources/testharness.js"></script> -<script src="/resources/testharnessreport.js"></script> -<script src="/resources/testdriver.js"></script> -<script src="/resources/testdriver-vendor.js"></script> -<script src="/common/utils.js"></script> -<script src="stash.js"></script> -<!-- - This test suite performs scroll to selector navigations to - scroll-to-selector-fragment-target.html and then checks the results, which are - communicated back from the target page via the WPT Stash server (see stash.py). ---> -<script> -let test_cases = [ - { - description: 'Basic selector test', - fragment: '#:~:selector(type=CssSelector,value=svg[id=cat])', - expect_position: 'cat' - }, - { - description: 'value= comes before type=', - fragment: '#:~:selector(value=svg[id=cat],type=CssSelector)', - expect_position: 'cat' - }, - { - description: 'Invalid selector directive 1', - fragment: '#:~:selector(ignorethisvalue=type=)', - expect_position: 'top' - }, - { - description: 'Invalid selector directive 2', - fragment: '#:~:selector()', - expect_position: 'top' - }, - { - description: 'Invalid selector directive 3', - fragment: '#:~:selector(type=,value=)', - expect_position: 'top' - }, - { - description: 'Invalid selector directive 4', - fragment: '#:~:selector(value=,type=)', - expect_position: 'top' - }, - { - description: 'Value is empty, technically a valid selector, though it wouldn\'t select antyhing', - fragment: '#:~:selector(value=,type=CssSelector)', - expect_position: 'top' - }, - { - description: 'There is a type= in css selector value', - fragment: '#:~:selector(value=input[type=button],type=CssSelector)', - expect_position: 'input-button' - }, - { - description: 'There is a type= in css selector value, value is URLEncoded', - // fragment: '#:~:selector(value=input[type=button],type=CssSelector)', - fragment: '#:~:selector(value=input%5Btype%3Dbutton%5D,type=CssSelector)', - expect_position: 'input-button' - }, - { - description: 'Should parse 2 selector directives', - fragment: '#:~:selector(type=CssSelector,value=svg[id=cat])&selector(type=CssSelector,value=input[type=button])', - expect_position: 'cat' - }, - { - description: 'Ignore other parts, only parse type= and value=', - fragment: '#:~:selector(type=CssSelector,lorem_part,value=svg[id=cat])', - expect_position: 'cat' - }, - { - description: 'Don\'t accept ambiguous directive, don\'t allow two type= parts', - fragment: '#:~:selector(type=CssSelector,type=SomethingElse,value=svg[id=cat])', - expect_position: 'top' - }, - { - description: 'Don\'t accept ambiguous directive, don\'t allow two value= parts', - fragment: '#:~:selector(type=CssSelector,value=input[type=button],value=svg[id=cat])', - expect_position: 'top' - }, - // { - // description: 'a comma in value= part should be encoded', - // fragment: '#:~:selector(value=svg[id%3Dcat%2Cdog],type=CssSelector)', - // expect_position: 'cat' - // }, - { - description: 'Should not allow multiple value= parts v1', - fragment: '#:~:selector(value=,type=CssSelector,value=img) ', - expect_position: 'top' - }, - { - description: 'Should not allow multiple value= parts v2', - fragment: '#:~:selector(value=img,value=,type=CssSelector) ', - expect_position: 'top' - } -]; - -for (const test_case of test_cases) { - promise_test(t => new Promise((resolve, reject) => { - let key = token(); - - test_driver.bless('Open a URL with a selector fragment directive', () => { - window.open(`scroll-to-selector-fragment-target.html?key=${key}${test_case.fragment}`, '_blank', 'noopener'); - }); - - fetchResults(key, resolve, reject); - }).then(data => { - // If the position is not 'top', the :target element should be the positioned element. - assert_true(data.scrollPosition == 'top' || data.target == data.scrollPosition); - assert_equals(data.href.indexOf(':~:'), -1, 'Expected fragment directive to be stripped from the URL.'); - assert_equals(data.scrollPosition, test_case.expect_position, - `Expected ${test_case.fragment} (${test_case.description}) to scroll to ${test_case.expect_position}.`); - }), `Test navigation with fragment: ${test_case.description}.`); -} -</script>
diff --git a/tools/android/audio_focus_grabber/java/src/org/chromium/tools/audio_focus_grabber/AudioFocusGrabberActivity.java b/tools/android/audio_focus_grabber/java/src/org/chromium/tools/audio_focus_grabber/AudioFocusGrabberActivity.java index d8e36969..170695f9 100644 --- a/tools/android/audio_focus_grabber/java/src/org/chromium/tools/audio_focus_grabber/AudioFocusGrabberActivity.java +++ b/tools/android/audio_focus_grabber/java/src/org/chromium/tools/audio_focus_grabber/AudioFocusGrabberActivity.java
@@ -22,24 +22,17 @@ public void onButtonClicked(View view) { Intent intent = new Intent(this, AudioFocusGrabberListenerService.class); - switch (view.getId()) { - case R.id.button_gain: - intent.setAction(AudioFocusGrabberListenerService.ACTION_GAIN); - break; - case R.id.button_transient_pause: - intent.setAction(AudioFocusGrabberListenerService.ACTION_TRANSIENT_PAUSE); - break; - case R.id.button_transient_duck: - intent.setAction(AudioFocusGrabberListenerService.ACTION_TRANSIENT_DUCK); - break; - case R.id.button_show_notification: - intent.setAction(AudioFocusGrabberListenerService.ACTION_SHOW_NOTIFICATION); - break; - case R.id.button_hide_notification: - intent.setAction(AudioFocusGrabberListenerService.ACTION_HIDE_NOTIFICATION); - break; - default: - break; + int viewId = view.getId(); + if (viewId == R.id.button_gain) { + intent.setAction(AudioFocusGrabberListenerService.ACTION_GAIN); + } else if (viewId == R.id.button_transient_pause) { + intent.setAction(AudioFocusGrabberListenerService.ACTION_TRANSIENT_PAUSE); + } else if (viewId == R.id.button_transient_duck) { + intent.setAction(AudioFocusGrabberListenerService.ACTION_TRANSIENT_DUCK); + } else if (viewId == R.id.button_show_notification) { + intent.setAction(AudioFocusGrabberListenerService.ACTION_SHOW_NOTIFICATION); + } else if (viewId == R.id.button_hide_notification) { + intent.setAction(AudioFocusGrabberListenerService.ACTION_HIDE_NOTIFICATION); } startService(intent); }
diff --git a/tools/captured_sites/control.py b/tools/captured_sites/control.py index 7dd4e25..128cbdc 100755 --- a/tools/captured_sites/control.py +++ b/tools/captured_sites/control.py
@@ -84,6 +84,7 @@ _NORMAL_BROWSER_AUTOFILL = 'cache_replayer=1' _RUN_BACKGROUND = 'testing/xvfb.py' _RUN_DISABLED_TESTS = '--gtest_also_run_disabled_tests' +_RUN_DEBUGGING_TESTS = '--gtest_break_on_failure' _AUTOFILL_TEST = '*/AutofillCapturedSitesInteractiveTest' _PASSWORD_MANAGER_TEST = '*/CapturedSitesPasswordManagerBrowserTest' @@ -170,6 +171,12 @@ dest='add_disabled', action='store_true', help='Also run disabled tests that match the filter.') + parser.add_argument('-f', + '--break_on_failure', + dest='add_break_on_failure', + action='store_true', + help=('Run tests in single-process mode and brings the ' + 'debugger on an assertion failure.')) parser.add_argument('-v', '--verbose', dest='verbose_logging', @@ -334,6 +341,9 @@ if options.add_disabled: command_args.append(_RUN_DISABLED_TESTS) + if options.add_break_on_failure: + command_args.append(_RUN_DEBUGGING_TESTS) + if options.retry_count > 0: command_args.append('--test-launcher-retry-limit=%d' % options.retry_count)
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index 375b424..647db074 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml
@@ -51420,6 +51420,7 @@ <int value="-2075807193" label="enable-webusb-on-any-origin"/> <int value="-2075725205" label="disable-new-zip-unpacker"/> <int value="-2074080173" label="NightLight:disabled"/> + <int value="-2072471036" label="OobeHidDetectionRevamp:disabled"/> <int value="-2071515296" label="allow-sync-xhr-in-page-dismissal"/> <int value="-2071202821" label="CopyLinkToText:enabled"/> <int value="-2068490665" label="PasswordChange:enabled"/> @@ -53721,6 +53722,7 @@ label="AutofillFixServerQueriesIfPasswordManagerIsEnabled:enabled"/> <int value="-503601144" label="UserDataSnapshot:disabled"/> <int value="-503430431" label="XRSandbox:enabled"/> + <int value="-502004335" label="OobeHidDetectionRevamp:enabled"/> <int value="-499723386" label="EnableFilesAppCopyImage:disabled"/> <int value="-499186481" label="OmniboxGroupSuggestionsBySearchVsUrl:disabled"/> @@ -54303,6 +54305,7 @@ <int value="-76445689" label="WasmCodeCache:enabled"/> <int value="-76348564" label="PointerLockOptions:enabled"/> <int value="-75418012" label="ContextualSuggestionsOptOut:disabled"/> + <int value="-74964571" label="EcheCustomWidget:enabled"/> <int value="-73868318" label="ScanAppSearchablePdf:disabled"/> <int value="-73282711" label="EnableAppListSearchAutocomplete:enabled"/> <int value="-72455054" label="WebVrAutopresent:disabled"/> @@ -56647,6 +56650,7 @@ <int value="1590300329" label="CCTModulePostMessage:enabled"/> <int value="1590837406" label="SnoopingProtection:disabled"/> <int value="1591653786" label="SpeculativePreconnect:enabled"/> + <int value="1591780364" label="EcheCustomWidget:disabled"/> <int value="1592391721" label="DesktopDetailedLanguageSettings:disabled"/> <int value="1593720927" label="GamepadVibration:disabled"/> <int value="1593883073" label="OfflineIndicatorV2:disabled"/>
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml index 68c8052..5d74954fe 100644 --- a/tools/metrics/histograms/metadata/ash/histograms.xml +++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -801,7 +801,7 @@ </summary> </histogram> -<histogram name="Ash.CaptureModeController.SaveToLocation.{TabletOrClamshell}" +<histogram name="Ash.CaptureModeController.SaveLocation.{TabletOrClamshell}" enum="CaptureModeSaveToLocation" expires_after="2022-09-09"> <owner>michelefan@chromium.org</owner> <owner>gzadina@google.com</owner> @@ -3421,7 +3421,7 @@ </histogram> <histogram name="Ash.TabDrag.PresentationTime" units="ms" - expires_after="2022-04-07"> + expires_after="2023-02-22"> <!-- Name completed by histogram_suffixes name="TabletOrClamshellMode" --> @@ -3437,7 +3437,7 @@ </histogram> <histogram name="Ash.TabDrag.PresentationTime.MaxLatency" units="ms" - expires_after="2022-04-07"> + expires_after="2023-02-22"> <!-- Name completed by histogram_suffixes name="TabletOrClamshellMode" -->
diff --git a/tools/metrics/histograms/metadata/browser/histograms.xml b/tools/metrics/histograms/metadata/browser/histograms.xml index 3c506236a..b932afc 100644 --- a/tools/metrics/histograms/metadata/browser/histograms.xml +++ b/tools/metrics/histograms/metadata/browser/histograms.xml
@@ -113,8 +113,8 @@ <histogram name="Browser.PaintPreview.Capture.CompressedOnDiskSize" units="KB" expires_after="2022-08-07"> <owner>ckitagawa@chromium.org</owner> - <owner>yashard@chromium.org</owner> <owner>fredmello@chromium.org</owner> + <owner>chrome-fdt@google.com</owner> <summary> Records the size of a compressed on-disk capture for the paint preview after a proto is written to disk if compressed. @@ -124,8 +124,8 @@ <histogram name="Browser.PaintPreview.Capture.NumberOfFramesCaptured" units="units" expires_after="2022-08-07"> <owner>ckitagawa@chromium.org</owner> - <owner>yashard@chromium.org</owner> <owner>fredmello@chromium.org</owner> + <owner>chrome-fdt@google.com</owner> <summary> Records the number of frames captured by a paint preview if it succeeded. </summary> @@ -134,8 +134,8 @@ <histogram name="Browser.PaintPreview.Capture.Success" enum="BooleanSuccess" expires_after="2022-08-07"> <owner>ckitagawa@chromium.org</owner> - <owner>yashard@chromium.org</owner> <owner>fredmello@chromium.org</owner> + <owner>chrome-fdt@google.com</owner> <summary> Records a boolean indicating whether a capture attempt was successful. </summary> @@ -144,8 +144,8 @@ <histogram name="Browser.PaintPreview.Capture.TotalCaptureDuration" units="ms" expires_after="2022-08-07"> <owner>ckitagawa@chromium.org</owner> - <owner>yashard@chromium.org</owner> <owner>fredmello@chromium.org</owner> + <owner>chrome-fdt@google.com</owner> <summary> Records the total time spent capturing a paint preview of a page. This includes capture and serialization of all frames and IPC time. @@ -153,20 +153,20 @@ </histogram> <histogram name="Browser.PaintPreview.Player.CompositorProcessStartedCorrectly" - units="BooleanSuccess" expires_after="2022-04-17"> + units="BooleanSuccess" expires_after="2022-08-07"> <owner>ckitagawa@chromium.org</owner> - <owner>yashard@chromium.org</owner> <owner>fredmello@chromium.org</owner> + <owner>chrome-fdt@google.com</owner> <summary> Records if the paint preview compositor process started correctly. </summary> </histogram> <histogram name="Browser.PaintPreview.Player.CompositorProcessStartupTime" - units="ms" expires_after="2022-07-11"> + units="ms" expires_after="2022-08-07"> <owner>ckitagawa@chromium.org</owner> - <owner>yashard@chromium.org</owner> <owner>fredmello@chromium.org</owner> + <owner>chrome-fdt@google.com</owner> <summary> Records the time it took to start the paint preview compositor process. </summary> @@ -175,16 +175,16 @@ <histogram name="Browser.PaintPreview.Player.LinkClicked" units="BooleanSuccess" expires_after="2022-08-14"> <owner>ckitagawa@chromium.org</owner> - <owner>yashard@chromium.org</owner> <owner>fredmello@chromium.org</owner> + <owner>chrome-fdt@google.com</owner> <summary>Records when a link is clicked in the paint preview player.</summary> </histogram> <histogram name="Browser.PaintPreview.Player.TimeToFirstBitmap" units="ms" - expires_after="2022-07-11"> + expires_after="2022-08-07"> <owner>ckitagawa@chromium.org</owner> - <owner>yashard@chromium.org</owner> <owner>fredmello@chromium.org</owner> + <owner>chrome-fdt@google.com</owner> <summary> Records the time taken to from the request to start the compositor process to the first bitmap being loaded. @@ -194,8 +194,8 @@ <histogram name="Browser.PaintPreview.TabbedPlayer.CompositorFailureReason" enum="TabbedPaintPreviewCompositorFailureReason" expires_after="2022-08-14"> <owner>ckitagawa@chromium.org</owner> - <owner>yashard@chromium.org</owner> <owner>fredmello@chromium.org</owner> + <owner>chrome-fdt@google.com</owner> <summary> Records the the reason for exiting the compositor process for the TabbedPaintPreviewPlayer. Recorded when the compositor returns an error or @@ -206,8 +206,8 @@ <histogram name="Browser.PaintPreview.TabbedPlayer.ExitCause" enum="TabbedPaintPreviewExitCause" expires_after="2022-08-07"> <owner>ckitagawa@chromium.org</owner> - <owner>yashard@chromium.org</owner> <owner>fredmello@chromium.org</owner> + <owner>chrome-fdt@google.com</owner> <summary> Records the the reason for exiting TabbedPaintPreviewPlayer. Recorded when paint preview is removed. @@ -215,10 +215,10 @@ </histogram> <histogram name="Browser.PaintPreview.TabbedPlayer.FirstPaintBeforeTabLoad" - units="Boolean" expires_after="2022-04-10"> + units="Boolean" expires_after="2022-08-07"> <owner>ckitagawa@chromium.org</owner> - <owner>yashard@chromium.org</owner> <owner>fredmello@chromium.org</owner> + <owner>chrome-fdt@google.com</owner> <summary> Records whether paint preview was drawn before the tab finished loading. Recorded when tab has finished loading. @@ -228,8 +228,8 @@ <histogram name="Browser.PaintPreview.TabbedPlayer.HadCapture" units="Boolean" expires_after="2022-08-21"> <owner>ckitagawa@chromium.org</owner> - <owner>yashard@chromium.org</owner> <owner>fredmello@chromium.org</owner> + <owner>chrome-fdt@google.com</owner> <summary> Records whether a paint preview exists. Recorded when attempting to show a preview on startup. This in conjuction with CompositorFailureReason gives @@ -240,8 +240,8 @@ <histogram name="Browser.PaintPreview.TabbedPlayer.SnackbarCount" units="units" expires_after="2022-08-14"> <owner>ckitagawa@chromium.org</owner> - <owner>yashard@chromium.org</owner> <owner>fredmello@chromium.org</owner> + <owner>chrome-fdt@google.com</owner> <summary> Records the number of times that the upgrade snackbar was shown in a session of TabbedPaintPreviewPlayer. Recorded when paint preview is removed. @@ -249,10 +249,10 @@ </histogram> <histogram name="Browser.PaintPreview.TabbedPlayer.TimeToFirstBitmap" - units="ms" expires_after="2022-07-11"> + units="ms" expires_after="2022-08-07"> <owner>ckitagawa@chromium.org</owner> - <owner>yashard@chromium.org</owner> <owner>fredmello@chromium.org</owner> + <owner>chrome-fdt@google.com</owner> <summary> Records the time taken to from actvity creation to the first bitmap being shown. Recorded on first paint preview paint event. @@ -261,10 +261,10 @@ <histogram name="Browser.PaintPreview.TabbedPlayer.UpTime{TabbedPaintPreviewPlayerUptime}" - units="ms" expires_after="2022-03-31"> + units="ms" expires_after="2022-08-07"> <owner>ckitagawa@chromium.org</owner> - <owner>yashard@chromium.org</owner> <owner>fredmello@chromium.org</owner> + <owner>chrome-fdt@google.com</owner> <summary> Records the length of the time that a session of TabbedPaintPreviewPlayer lasts. Recorded when paint preview is removed. @@ -288,9 +288,8 @@ </histogram> <histogram name="Browser.PaintPreview.TabService.DiskUsageAtStartup" units="KB" - expires_after="2022-03-06"> + expires_after="2022-08-07"> <owner>ckitagawa@chromium.org</owner> - <owner>yashard@chromium.org</owner> <owner>fredmello@chromium.org</owner> <summary> Records the total disk usage by the Paint Preview Tab Service at startup of
diff --git a/tools/metrics/histograms/metadata/cryptohome/histograms.xml b/tools/metrics/histograms/metadata/cryptohome/histograms.xml index 3cd0577..4fa6221 100644 --- a/tools/metrics/histograms/metadata/cryptohome/histograms.xml +++ b/tools/metrics/histograms/metadata/cryptohome/histograms.xml
@@ -510,7 +510,7 @@ </histogram> <histogram name="Cryptohome.TimeToGenerateEccAuthValue" units="ms" - expires_after="2022-04-04"> + expires_after="2022-10-04"> <owner>yich@google.com</owner> <owner>cros-hwsec+uma@chromium.org</owner> <summary> @@ -670,7 +670,7 @@ </histogram> <histogram name="Cryptohome.{Action}AuthBlockType" - enum="CryptohomeAuthBlockType" expires_after="2022-04-04"> + enum="CryptohomeAuthBlockType" expires_after="2022-10-04"> <owner>yich@google.com</owner> <owner>cros-hwsec+uma@chromium.org</owner> <summary>
diff --git a/tools/metrics/histograms/metadata/renderer/histograms.xml b/tools/metrics/histograms/metadata/renderer/histograms.xml index 3cf538d..bac7b776 100644 --- a/tools/metrics/histograms/metadata/renderer/histograms.xml +++ b/tools/metrics/histograms/metadata/renderer/histograms.xml
@@ -347,8 +347,8 @@ <histogram name="Renderer.PaintPreview.Capture.MainFrameBlinkCaptureDuration" units="ms" expires_after="2022-08-14"> <owner>ckitagawa@chromium.org</owner> - <owner>mahmoudi@chromium.org</owner> <owner>fredmello@chromium.org</owner> + <owner>chrome-fdt@google.com</owner> <summary> Records the number of milliseconds spent blocking Blink's main thread while capturing the main frame. @@ -356,20 +356,20 @@ </histogram> <histogram name="Renderer.PaintPreview.Capture.MainFrameSuccess" - enum="BooleanSuccess" expires_after="2022-03-31"> + enum="BooleanSuccess" expires_after="2022-08-07"> <owner>ckitagawa@chromium.org</owner> - <owner>mahmoudi@chromium.org</owner> <owner>fredmello@chromium.org</owner> + <owner>chrome-fdt@google.com</owner> <summary> Records a boolean indicating whether a capture for the main frame succeeded. </summary> </histogram> <histogram name="Renderer.PaintPreview.Capture.SubframeBlinkCaptureDuration" - units="ms" expires_after="2022-07-11"> + units="ms" expires_after="2022-08-07"> <owner>ckitagawa@chromium.org</owner> - <owner>mahmoudi@chromium.org</owner> <owner>fredmello@chromium.org</owner> + <owner>chrome-fdt@google.com</owner> <summary> Records the number of milliseconds spent blocking Blink's main thread while capturing a subframe. @@ -377,10 +377,10 @@ </histogram> <histogram name="Renderer.PaintPreview.Capture.SubframeSuccess" - enum="BooleanSuccess" expires_after="2022-04-24"> + enum="BooleanSuccess" expires_after="2022-08-07"> <owner>ckitagawa@chromium.org</owner> - <owner>mahmoudi@chromium.org</owner> <owner>fredmello@chromium.org</owner> + <owner>chrome-fdt@google.com</owner> <summary> Records a boolean indicating whether a capture for a subframe succeeded. </summary>
diff --git a/tools/perf/page_sets/wasmpspdfkit_pages.py b/tools/perf/page_sets/wasmpspdfkit_pages.py index 7ab3581..c623f24 100644 --- a/tools/perf/page_sets/wasmpspdfkit_pages.py +++ b/tools/perf/page_sets/wasmpspdfkit_pages.py
@@ -14,7 +14,7 @@ def ParseTestResults(self, action_runner): self.AddJavaScriptMeasurement( - 'Total', 'score', """ + 'Total', 'ms', """ parseInt(document.querySelector( '#root > div > div:nth-child(10) > div.Result >' + 'div.Result-score > div > div.Score-value'
diff --git a/ui/display/mac/screen_mac.mm b/ui/display/mac/screen_mac.mm index 1fb42e6..a51b1326 100644 --- a/ui/display/mac/screen_mac.mm +++ b/ui/display/mac/screen_mac.mm
@@ -102,13 +102,21 @@ bool enable_hdr = false; float hdr_max_lum_relative = 1.f; if (@available(macOS 10.15, *)) { + // It can be the case that `max_potential_edr_value` > 1, but + // `max_edr_value` == 1. This happens, e.g, when an HDR capable Macbook Pro + // display is set to maximum brightness. This can create the confusing + // appearance that the display is rapidly fluctuating between being HDR + // capable and HDR incapable. To avoid this confusion, set a minimum value + // to report when `max_potential_edr_value` > 1. + constexpr float kMinMaxEdrValueForHDR = 1.0625; + const float max_potential_edr_value = [screen maximumPotentialExtendedDynamicRangeColorComponentValue]; const float max_edr_value = [screen maximumExtendedDynamicRangeColorComponentValue]; - if (max_potential_edr_value > 1) { + if (max_potential_edr_value > 1.f) { enable_hdr = true; - hdr_max_lum_relative = max_edr_value; + hdr_max_lum_relative = std::max(kMinMaxEdrValueForHDR, max_edr_value); } }
diff --git a/ui/display/manager/display_change_observer.cc b/ui/display/manager/display_change_observer.cc index 418c55e..4bc1d7b 100644 --- a/ui/display/manager/display_change_observer.cc +++ b/ui/display/manager/display_change_observer.cc
@@ -130,7 +130,10 @@ gfx::BufferFormat::RGBA_1010102); // TODO(https://crbug.com/1286074): Populate maximum luminance based on - // `hdr_static_metadata`. + // `hdr_static_metadata`. For now, assume that the HDR maximum luminance + // is 1,000% of the SDR maximum luminance. + constexpr float kHDRMaxLuminanceRelative = 10.f; + display_color_spaces.SetHDRMaxLuminanceRelative(kHDRMaxLuminanceRelative); } return display_color_spaces; }
diff --git a/ui/display/win/screen_win.cc b/ui/display/win/screen_win.cc index 17b9fc60..1706dfbe 100644 --- a/ui/display/win/screen_win.cc +++ b/ui/display/win/screen_win.cc
@@ -223,6 +223,14 @@ auto color_spaces = CreateDisplayColorSpaces(gfx::ColorSpace::CreateSRGB(), sdr_white_level); + // TODO(https://crbug.com/1299293): Retrieve the correct HDR maximum luminance + // value from DXGI_OUTPUT_DESC1. For now, just assume that it is the maximum + // of 1,000 nits and 150% of the SDR white level. + constexpr float kHDRMaxLuminanceNits = 1000; + constexpr float kHDRMinMaxLuminanceRelative = 1.5f; + color_spaces.SetHDRMaxLuminanceRelative(std::max( + kHDRMinMaxLuminanceRelative, kHDRMaxLuminanceNits / sdr_white_level)); + // This will map to DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709. In that space, // the brightness of (1,1,1) is 80 nits. const auto scrgb_linear = gfx::ColorSpace::CreateSCRGBLinear(sdr_white_level);
diff --git a/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar.html b/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar.html index 2c45696..a96e790 100644 --- a/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar.html +++ b/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar.html
@@ -91,6 +91,7 @@ :host(:not([narrow])[disable-right-content-grow]) #centeredContent { justify-content: start; + padding-inline-start: 12px; } :host(:not([narrow])) #rightContent {