diff --git a/DEPS b/DEPS index a0c8bdd..91cb8072 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': '55ec34727676ec9a3bcd9aea00d58ae5a4e1d11c', + 'skia_revision': '2ac7682b5303c11485ccc48f8e6264b27f102850', # 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': 'ad2e42379a0b772da47db66e251d173ec1fb63a3', + 'v8_revision': 'd2ce11ad0810831476da44a8f0a317078076f61b', # 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': 'f2e7a2359c5daf22719ffcac3fafb43d735baed3', + 'angle_revision': '041c4c6d285c571af0b3bc63567429043b70146b', # 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': '205ddee16e5f6fe673e2fdb64510034ce4c9ad03', + 'swiftshader_revision': '3ed03de5e79de515b3dc9f43bdb4c766ad2c5a20', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling PDFium # and whatever else without interference from each other. @@ -328,7 +328,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling devtools-frontend # and whatever else without interference from each other. - 'devtools_frontend_revision': '90f7ed24b9e2502844d26311760cab27495cd6b6', + 'devtools_frontend_revision': '5d6735af024d429f24f64e2c0b0de1408cdd5cbc', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libprotobuf-mutator # and whatever else without interference from each other. @@ -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': 'f2556ab35c0eecdfd93c02f7c226a5c94316d143', + 'dawn_revision': '57b7db9c7425cb4de5c7533c200e96d56fe2011b', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. @@ -435,7 +435,7 @@ 'libcxx_revision': '79a2e924d96e2fc1e4b937c42efd08898fa472d7', # GN CIPD package version. - 'gn_version': 'git_revision:bd99dbf98cbdefe18a4128189665c5761263bcfb', + 'gn_version': 'git_revision:ff14fc1112e0a8dd2c3910fb89539741cb3d3f23', } # Only these hosts are allowed for dependencies in this DEPS file. @@ -728,7 +728,7 @@ }, 'src/ios/third_party/earl_grey2/src': { - 'url': Var('chromium_git') + '/external/github.com/google/EarlGrey.git' + '@' + 'aaf6cd0daad5e447754d89141fb75a2a0c4cee9a', + 'url': Var('chromium_git') + '/external/github.com/google/EarlGrey.git' + '@' + '300146f8abf67e41bc6af894e38c9ae88404212b', 'condition': 'checkout_ios', }, @@ -1351,7 +1351,7 @@ Var('chromium_git') + '/external/libaddressinput.git' + '@' + '3b8ee157a8f3536bbf5ad2448e9e3370463c1e40', 'src/third_party/libaom/source/libaom': - Var('aomedia_git') + '/aom.git' + '@' + 'ee1ed1ccf2b9ecedd6aee438eafc7cc61c23342d', + Var('aomedia_git') + '/aom.git' + '@' + '24fa287e152b319d8998e24c0f174f4043138bfd', 'src/third_party/libavif/src': Var('chromium_git') + '/external/github.com/AOMediaCodec/libavif.git' + '@' + Var('libavif_revision'), @@ -1415,7 +1415,7 @@ Var('chromium_git') + '/webm/libwebm.git' + '@' + 'e4fbea0c9751ae8aa86629b197a28d8276a2b0da', 'src/third_party/libyuv': - Var('chromium_git') + '/libyuv/libyuv.git' + '@' + '3aebf69d668177e7ee6dbbe0025e5c3dbb525ff2', + Var('chromium_git') + '/libyuv/libyuv.git' + '@' + 'f4d25308467cbd50c2706a46fa0ddcef939e715a', 'src/third_party/lighttpd': { 'url': Var('chromium_git') + '/chromium/deps/lighttpd.git' + '@' + Var('lighttpd_revision'), @@ -1741,10 +1741,10 @@ Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'cf04aebdf9b53bb2853f22a81465688daf879ec6', 'src/third_party/webgpu-cts/src': - Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '68fbc0f8f30c00408fb94807c197f055e5535795', + Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'cc77d560f4feb725c1172306dd0943b44838c65f', 'src/third_party/webrtc': - Var('webrtc_git') + '/src.git' + '@' + '3cdd653d6691c145ac1e102224c7cd48a56c2ca5', + Var('webrtc_git') + '/src.git' + '@' + '2a83a9c5cd9f15a997d6d5857bce9ad11c2f1682', 'src/third_party/libgifcodec': Var('skia_git') + '/libgifcodec' + '@'+ Var('libgifcodec_revision'), @@ -1814,7 +1814,7 @@ Var('chromium_git') + '/v8/v8.git' + '@' + Var('v8_revision'), 'src-internal': { - 'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@2ed604efd56355b3d6c8ce7e78fe07d59fbdc1be', + 'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@fdca61bde57b64c3eec8fcd10e5fd9ccbebce04d', 'condition': 'checkout_src_internal', },
diff --git a/ash/BUILD.gn b/ash/BUILD.gn index 9d0377f5..13b381d3 100644 --- a/ash/BUILD.gn +++ b/ash/BUILD.gn
@@ -1252,6 +1252,8 @@ "system/network/auto_connect_notifier.h", "system/network/cellular_setup_notifier.cc", "system/network/cellular_setup_notifier.h", + "system/network/fake_network_detailed_network_view.cc", + "system/network/fake_network_detailed_network_view.h", "system/network/fake_network_detailed_view_delegate.cc", "system/network/fake_network_detailed_view_delegate.h", "system/network/network_detailed_network_view.cc", @@ -1279,6 +1281,10 @@ "system/network/network_info_bubble.h", "system/network/network_list_view.cc", "system/network/network_list_view.h", + "system/network/network_list_view_controller.cc", + "system/network/network_list_view_controller.h", + "system/network/network_list_view_controller_impl.cc", + "system/network/network_list_view_controller_impl.h", "system/network/network_observer.h", "system/network/network_row_title_view.cc", "system/network/network_row_title_view.h", @@ -2114,6 +2120,7 @@ "//ash/services/recording", "//ash/services/recording/public/mojom", "//ash/system/machine_learning:user_settings_event_proto", + "//ash/webui/eche_app_ui/mojom:mojom", "//ash/webui/personalization_app/mojom", "//ash/webui/personalization_app/proto", "//base", @@ -2601,6 +2608,8 @@ "system/eche/eche_icon_loading_indicator_view_unittest.cc", "system/eche/eche_tray_unittest.cc", "system/firmware_update/firmware_update_notification_controller_unittest.cc", + "system/geolocation/geolocation_controller_test_util.cc", + "system/geolocation/geolocation_controller_test_util.h", "system/geolocation/geolocation_controller_unittest.cc", "system/gesture_education/gesture_education_notification_controller_unittest.cc", "system/holding_space/holding_space_animation_registry_unittest.cc", @@ -2640,6 +2649,7 @@ "system/network/network_feature_pod_controller_unittest.cc", "system/network/network_icon_unittest.cc", "system/network/network_info_bubble_unittest.cc", + "system/network/network_list_view_controller_unittest.cc", "system/network/sms_observer_unittest.cc", "system/network/vpn_list_unittest.cc", "system/network/wifi_toggle_notification_controller_unittest.cc",
diff --git a/ash/capture_mode/capture_mode_camera_controller.cc b/ash/capture_mode/capture_mode_camera_controller.cc index ae30f11..585cf34 100644 --- a/ash/capture_mode/capture_mode_camera_controller.cc +++ b/ash/capture_mode/capture_mode_camera_controller.cc
@@ -22,6 +22,7 @@ #include "media/capture/video/video_capture_device_descriptor.h" #include "ui/compositor/layer.h" #include "ui/compositor/scoped_layer_animation_settings.h" +#include "ui/display/screen.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/point_conversions.h" #include "ui/gfx/geometry/rounded_corners_f.h" @@ -112,16 +113,6 @@ return iter == list.end() ? nullptr : &(*iter); } -// Stacking the camera preview window on top of all children of its parent so -// that it can show up in the recording above everything else. -void StackingPreviewAtTop(views::Widget* preview_widget) { - DCHECK(preview_widget); - auto* preview_window = preview_widget->GetNativeWindow(); - auto* parent = preview_window->parent(); - DCHECK(parent); - parent->StackChildAtTop(preview_window); -} - std::unique_ptr<views::Widget> CreateCameraPreviewWidget( const gfx::Rect& bounds) { auto camera_preview_widget = std::make_unique<views::Widget>(); @@ -134,7 +125,6 @@ params.child = true; params.name = "CameraPreviewWidget"; camera_preview_widget->Init(std::move(params)); - StackingPreviewAtTop(camera_preview_widget.get()); return camera_preview_widget; } @@ -272,10 +262,10 @@ auto* parent = controller->GetCameraPreviewParentWindow(); DCHECK(parent); auto* native_window = camera_preview_widget_->GetNativeWindow(); - if (parent != native_window->parent()) { + + if (parent != native_window->parent()) views::Widget::ReparentNativeView(native_window, parent); - StackingPreviewAtTop(camera_preview_widget_.get()); - } + MaybeUpdatePreviewWidgetBounds(); } @@ -301,6 +291,10 @@ } const gfx::Rect target_bounds = GetPreviewWidgetBounds(); + const bool did_bounds_change = + target_bounds != GetCurrentBoundsMatchingConfineBoundsCoordinates(); + if (!did_bounds_change) + return; if (animate) { ui::Layer* layer = camera_preview_widget_->GetLayer(); @@ -313,13 +307,21 @@ } else { camera_preview_widget_->SetBounds(target_bounds); } + + auto* controller = CaptureModeController::Get(); + if (controller->IsActive()) + controller->capture_mode_session()->OnCameraPreviewBoundsChanged(); } void CaptureModeCameraController::StartDraggingPreview( const gfx::PointF& screen_location) { + is_drag_in_progress_ = true; previous_location_in_screen_ = screen_location; - is_drag_in_progress_ = true; + auto* controller = CaptureModeController::Get(); + if (controller->IsActive()) + controller->capture_mode_session()->OnCameraPreviewDragStarted(); + // Use cursor compositing instead of the platform cursor when dragging to // ensure the cursor is aligned with the camera preview. Shell::Get()->UpdateCursorCompositingEnabled(); @@ -364,9 +366,9 @@ // Disable cursor compositing at the end of the drag. Shell::Get()->UpdateCursorCompositingEnabled(); - // Make sure cursor is updated correctly after camera preview is snapped. - if (CaptureModeController::Get()->IsActive()) { - CaptureModeController::Get()->capture_mode_session()->UpdateCursor( + auto* controller = CaptureModeController::Get(); + if (controller->IsActive()) { + controller->capture_mode_session()->OnCameraPreviewDragEnded( gfx::ToRoundedPoint(screen_location), is_touch); } }
diff --git a/ash/capture_mode/capture_mode_camera_unittests.cc b/ash/capture_mode/capture_mode_camera_unittests.cc index 58e52cfe..49e321d 100644 --- a/ash/capture_mode/capture_mode_camera_unittests.cc +++ b/ash/capture_mode/capture_mode_camera_unittests.cc
@@ -33,9 +33,11 @@ #include "base/test/bind.h" #include "base/test/scoped_feature_list.h" #include "ui/base/l10n/l10n_util.h" +#include "ui/compositor/layer.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/size.h" +#include "ui/gfx/geometry/vector2d.h" #include "ui/gfx/image/image_unittest_util.h" #include "ui/gfx/paint_vector_icon.h" #include "ui/views/widget/widget.h" @@ -49,6 +51,8 @@ constexpr char kDefaultCameraDisplayName[] = "Default Cam"; constexpr char kDefaultCameraModelId[] = "0def:c000"; +constexpr float kOverlapOpacity = 0.1f; + TestCaptureModeDelegate* GetTestDelegate() { return static_cast<TestCaptureModeDelegate*>( CaptureModeController::Get()->delegate_for_testing()); @@ -298,6 +302,20 @@ EXPECT_EQ(resize_button->GetTooltipText(), expected_tooltip_text); } + // Select capture region by pressing and dragging the mouse. + void SelectCaptureRegion(const gfx::Rect& region, bool release_mouse = true) { + auto* controller = CaptureModeController::Get(); + ASSERT_TRUE(controller->IsActive()); + ASSERT_EQ(CaptureModeSource::kRegion, controller->source()); + auto* event_generator = GetEventGenerator(); + event_generator->set_current_screen_location(region.origin()); + event_generator->PressLeftButton(); + event_generator->MoveMouseTo(region.bottom_right()); + if (release_mouse) + event_generator->ReleaseLeftButton(); + EXPECT_EQ(region, controller->user_capture_region()); + } + private: base::test::ScopedFeatureList scoped_feature_list_; base::SystemMonitor system_monitor_; @@ -684,23 +702,23 @@ EXPECT_TRUE(camera_preview_widget); auto* preview_window = camera_preview_widget->GetNativeWindow(); - const auto* overlay_container = preview_window->GetRootWindow()->GetChildById( - kShellWindowId_OverlayContainer); + const auto* menu_container = preview_window->GetRootWindow()->GetChildById( + kShellWindowId_MenuContainer); auto* parent = preview_window->parent(); - // Parent of the preview should be the OverlayContainer when capture mode + // Parent of the preview should be the MenuContainer when capture mode // session is active with `kFullscreen` type. And the preview window should // be the top-most child of it. - EXPECT_EQ(parent, overlay_container); - EXPECT_EQ(overlay_container->children().back(), preview_window); + EXPECT_EQ(parent, menu_container); + EXPECT_EQ(menu_container->children().back(), preview_window); StartRecordingFromSource(CaptureModeSource::kFullscreen); - // Parent of the preview should be the OverlayContainer when video recording + // Parent of the preview should be the MenuContainer when video recording // in progress with `kFullscreen` type. And the preview window should be the // top-most child of it. preview_window = camera_preview_widget->GetNativeWindow(); parent = preview_window->parent(); - EXPECT_EQ(parent, overlay_container); - EXPECT_EQ(overlay_container->children().back(), preview_window); + EXPECT_EQ(parent, menu_container); + EXPECT_EQ(menu_container->children().back(), preview_window); } TEST_F(CaptureModeCameraTest, CameraPreviewWidgetStackingInRegion) { @@ -725,13 +743,13 @@ controller->SetUserCaptureRegion(gfx::Rect(10, 20, 80, 60), /*by_user=*/true); StartRecordingFromSource(CaptureModeSource::kRegion); - const auto* overlay_container = preview_window->GetRootWindow()->GetChildById( - kShellWindowId_OverlayContainer); - // Parent of the preview should be the OverlayContainer when video recording + const auto* menu_container = preview_window->GetRootWindow()->GetChildById( + kShellWindowId_MenuContainer); + // Parent of the preview should be the MenuContainer when video recording // in progress with `kRegion` type. And the preview window should be the // top-most child of it. - ASSERT_EQ(preview_window->parent(), overlay_container); - EXPECT_EQ(overlay_container->children().back(), preview_window); + ASSERT_EQ(preview_window->parent(), menu_container); + EXPECT_EQ(menu_container->children().back(), preview_window); } // Tests that camera preview widget is shown, hidden and parented correctly @@ -755,10 +773,10 @@ controller->SetUserCaptureRegion(capture_region, /*by_user=*/true); // After user capture region is set, parent of the preview should be the - // OverlayContainer. - const auto* overlay_container = preview_window->GetRootWindow()->GetChildById( - kShellWindowId_OverlayContainer); - ASSERT_EQ(preview_window->parent(), overlay_container); + // MenuContainer. + const auto* menu_container = preview_window->GetRootWindow()->GetChildById( + kShellWindowId_MenuContainer); + ASSERT_EQ(preview_window->parent(), menu_container); // Press the bottom right of selection region. Verify preview is hidden and // parent of the preview should be UnparentedContainer. @@ -777,11 +795,11 @@ EXPECT_EQ(preview_window->parent(), unparented_container); // Now release the drag to end selection region update. Verify preview is - // shown and parent of the preview should be OverlayContainer. + // shown and parent of the preview should be MenuContainer. event_generator->ReleaseLeftButton(); EXPECT_FALSE(capture_session->is_drag_in_progress()); EXPECT_TRUE(camera_preview_widget->IsVisible()); - EXPECT_EQ(preview_window->parent(), overlay_container); + EXPECT_EQ(preview_window->parent(), menu_container); // Press in the selection region to move it around. Since in the // use case, selection region is not updated, preview should not be hidden. @@ -789,18 +807,18 @@ event_generator->set_current_screen_location(current_position); event_generator->PressLeftButton(); EXPECT_TRUE(camera_preview_widget->IsVisible()); - EXPECT_EQ(preview_window->parent(), overlay_container); + EXPECT_EQ(preview_window->parent(), menu_container); // Move mouse to move selection region around. Verify preview is shown. event_generator->MoveMouseTo(current_position + delta); EXPECT_TRUE(camera_preview_widget->IsVisible()); - EXPECT_EQ(preview_window->parent(), overlay_container); + EXPECT_EQ(preview_window->parent(), menu_container); // Now release the move to end moving selection region. Verify preview is // shown. event_generator->ReleaseLeftButton(); EXPECT_TRUE(camera_preview_widget->IsVisible()); - EXPECT_EQ(preview_window->parent(), overlay_container); + EXPECT_EQ(preview_window->parent(), menu_container); } TEST_F(CaptureModeCameraTest, CameraPreviewWidgetStackingInWindow) { @@ -1193,6 +1211,206 @@ EXPECT_EQ(window(), capture_mode_session->GetSelectedWindow()); } +// Tests that capture label's opacity changes accordingly when it's overlapped +// or it's not overlapped with camera preview. Also tests that when located +// events is or is not on capture label, its opacity is updated accordingly. +TEST_F(CaptureModeCameraTest, + CaptureLabelOpacityChangeWhenOverlappingWithCameraPreview) { + auto* controller = + StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo); + auto* capture_session = controller->capture_mode_session(); + auto* camera_controller = GetCameraController(); + AddDefaultCamera(); + camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1)); + const auto* camera_preview_widget = + camera_controller->camera_preview_widget(); + const auto* capture_label_widget = capture_session->capture_label_widget(); + const ui::Layer* capture_label_layer = capture_label_widget->GetLayer(); + + // Set capture region big enough to make capture label not overlapping with + // camera preview. Verify capture label is fully opaque. + const gfx::Rect capture_region(100, 100, 700, 700); + SelectCaptureRegion(capture_region); + EXPECT_FALSE(capture_label_widget->GetWindowBoundsInScreen().Intersects( + camera_preview_widget->GetWindowBoundsInScreen())); + EXPECT_EQ(capture_label_layer->GetTargetOpacity(), 1.f); + + // Update capture region smaller to make capture label overlap with camera + // preview. Verify capture label is `kOverlapOpacity`. + const gfx::Vector2d delta(-500, -600); + auto* event_generator = GetEventGenerator(); + event_generator->set_current_screen_location(capture_region.bottom_right()); + event_generator->PressLeftButton(); + event_generator->MoveMouseTo(capture_region.bottom_right() + delta); + event_generator->ReleaseLeftButton(); + EXPECT_TRUE(capture_label_widget->GetWindowBoundsInScreen().Intersects( + camera_preview_widget->GetWindowBoundsInScreen())); + EXPECT_EQ(capture_label_layer->GetTargetOpacity(), kOverlapOpacity); + + // Move mouse on top of capture label, verify it's updated to fully opaque + // even it's still overlapped with camera preview. + const gfx::Rect capture_lable_bounds = + capture_label_widget->GetWindowBoundsInScreen(); + event_generator->MoveMouseTo(capture_lable_bounds.CenterPoint()); + EXPECT_TRUE(capture_lable_bounds.Intersects( + camera_preview_widget->GetWindowBoundsInScreen())); + EXPECT_EQ(capture_label_layer->GetTargetOpacity(), 1.0f); + + // Mouse mouse to the outside of capture label, verify it's updated to + // `kOverlapOpacity`. + const gfx::Vector2d delta1(50, 50); + event_generator->MoveMouseTo(capture_lable_bounds.bottom_right() + delta1); + EXPECT_EQ(capture_label_layer->GetTargetOpacity(), kOverlapOpacity); + + // Click on the outside of the capture region to reset it, verify capture + // label is updated to full opaque. + const gfx::Rect current_capture_region = controller->user_capture_region(); + event_generator->MoveMouseTo(current_capture_region.bottom_right() + delta1); + event_generator->ClickLeftButton(); + EXPECT_EQ(capture_label_layer->GetTargetOpacity(), 1.0f); +} + +TEST_F(CaptureModeCameraTest, + CaptureBarOpacityChangeWhenOverlappingWithCameraPreview) { + // Update display size and create a new window with customized size to make + // sure camera preview overlap with capture bar with capture source `kWindow`. + UpdateDisplay("1366x768"); + std::unique_ptr<aura::Window> window( + CreateTestWindow(gfx::Rect(0, 195, 903, 492))); + + auto* controller = + StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo); + auto* capture_session = controller->capture_mode_session(); + auto* camera_controller = GetCameraController(); + AddDefaultCamera(); + camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1)); + const auto* camera_preview_widget = + camera_controller->camera_preview_widget(); + const auto* capture_bar_widget = capture_session->capture_mode_bar_widget(); + const ui::Layer* capture_bar_layer = capture_bar_widget->GetLayer(); + + // Move mouse on top of `window` to set the selected window. Verify capture + // bar is `kOverlapOpacity`. + auto* event_generator = GetEventGenerator(); + event_generator->MoveMouseTo(window->GetBoundsInScreen().CenterPoint()); + EXPECT_EQ(capture_session->GetSelectedWindow(), window.get()); + EXPECT_TRUE(capture_bar_widget->GetWindowBoundsInScreen().Intersects( + camera_preview_widget->GetWindowBoundsInScreen())); + EXPECT_EQ(capture_bar_layer->GetTargetOpacity(), kOverlapOpacity); + + // Move mouse on top of capture bar. Verify capture bar is updated to fully + // opaque. + event_generator->MoveMouseTo( + capture_bar_widget->GetWindowBoundsInScreen().CenterPoint()); + EXPECT_TRUE(capture_bar_widget->GetWindowBoundsInScreen().Intersects( + camera_preview_widget->GetWindowBoundsInScreen())); + EXPECT_EQ(capture_bar_layer->GetTargetOpacity(), 1.0f); + + // Mouse mouse to the outside of capture bar, verify it's updated to + // `kOverlapOpacity`. + const gfx::Point capture_bar_origin = + capture_bar_widget->GetWindowBoundsInScreen().origin(); + event_generator->MoveMouseTo(capture_bar_origin.x() - 10, + capture_bar_origin.y() - 10); + EXPECT_EQ(capture_bar_layer->GetTargetOpacity(), kOverlapOpacity); +} + +TEST_F(CaptureModeCameraTest, CaptureBarOpacityChangeOnDisplayRotation) { + // Update display size and create a new window with customized size to make + // sure camera preview overlap with capture bar with capture source `kWindow`. + UpdateDisplay("1366x768"); + std::unique_ptr<aura::Window> window( + CreateTestWindow(gfx::Rect(0, 195, 903, 492))); + + auto* controller = + StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo); + auto* capture_session = controller->capture_mode_session(); + auto* camera_controller = GetCameraController(); + AddDefaultCamera(); + camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1)); + const auto* camera_preview_widget = + camera_controller->camera_preview_widget(); + const auto* capture_bar_widget = capture_session->capture_mode_bar_widget(); + const ui::Layer* capture_bar_layer = capture_bar_widget->GetLayer(); + + // Move mouse on top of `window` to set the selected window. Verify capture + // bar is `kOverlapOpacity`. + auto* event_generator = GetEventGenerator(); + event_generator->MoveMouseTo(window->GetBoundsInScreen().CenterPoint()); + EXPECT_EQ(capture_session->GetSelectedWindow(), window.get()); + EXPECT_TRUE(capture_bar_widget->GetWindowBoundsInScreen().Intersects( + camera_preview_widget->GetWindowBoundsInScreen())); + EXPECT_EQ(capture_bar_layer->GetTargetOpacity(), kOverlapOpacity); + + // Rotate the primary display by 90 degrees. Verify that capture bar no longer + // overlaps with camera preview and it's updated to fully opaque. + Shell::Get()->display_manager()->SetDisplayRotation( + WindowTreeHostManager::GetPrimaryDisplayId(), display::Display::ROTATE_90, + display::Display::RotationSource::USER); + EXPECT_FALSE(capture_bar_widget->GetWindowBoundsInScreen().Intersects( + camera_preview_widget->GetWindowBoundsInScreen())); + EXPECT_EQ(capture_bar_layer->GetTargetOpacity(), 1.0f); + + // Rotate the primary display by 180 degrees. Verify that capture bar is + // overlapped with camera preview and it's updated to `kOverlapOpacity`. + Shell::Get()->display_manager()->SetDisplayRotation( + WindowTreeHostManager::GetPrimaryDisplayId(), + display::Display::ROTATE_180, display::Display::RotationSource::USER); + EXPECT_TRUE(capture_bar_widget->GetWindowBoundsInScreen().Intersects( + camera_preview_widget->GetWindowBoundsInScreen())); + EXPECT_EQ(capture_bar_layer->GetTargetOpacity(), kOverlapOpacity); +} + +TEST_F(CaptureModeCameraTest, CaptureLabelOpacityChangeOnCaptureSourceChange) { + auto* controller = + StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo); + auto* capture_session = controller->capture_mode_session(); + auto* camera_controller = GetCameraController(); + AddDefaultCamera(); + camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1)); + auto* camera_preview_widget = camera_controller->camera_preview_widget(); + auto* capture_label_widget = capture_session->capture_label_widget(); + ui::Layer* capture_label_layer = capture_label_widget->GetLayer(); + + // Select capture region to make sure capture label is overlapped with + // camera preview. Verify capture label is `kOverlapOpacity`. + SelectCaptureRegion({100, 100, 200, 100}); + EXPECT_TRUE(capture_label_widget->GetWindowBoundsInScreen().Intersects( + camera_preview_widget->GetWindowBoundsInScreen())); + EXPECT_EQ(capture_label_layer->GetTargetOpacity(), kOverlapOpacity); + + // Change the capture source from `kRegion` to `kFullscreen`, verify capture + // label is updated to fully opaque. + controller->SetSource(CaptureModeSource::kFullscreen); + EXPECT_EQ(capture_label_layer->GetTargetOpacity(), 1.0f); +} + +TEST_F(CaptureModeCameraTest, + CaptureLabelOpacityChangeWhileVideoRecordingInProgress) { + auto* controller = + StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo); + auto* camera_controller = GetCameraController(); + AddDefaultCamera(); + camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1)); + auto* camera_preview_widget = camera_controller->camera_preview_widget(); + controller->SetUserCaptureRegion({100, 100, 200, 100}, /*by_user=*/true); + + StartVideoRecordingImmediately(); + EXPECT_FALSE(controller->IsActive()); + + // Start a new capture session, verify even capture label is overlapped with + // camera preview, it's still fully opaque since camera preview does not + // belong to the new capture session. + controller->Start(CaptureModeEntryType::kQuickSettings); + EXPECT_EQ(CaptureModeSource::kRegion, controller->source()); + auto* capture_session = controller->capture_mode_session(); + + const auto* capture_label_widget = capture_session->capture_label_widget(); + EXPECT_TRUE(capture_label_widget->GetWindowBoundsInScreen().Intersects( + camera_preview_widget->GetWindowBoundsInScreen())); + EXPECT_EQ(capture_label_widget->GetLayer()->GetTargetOpacity(), 1.0f); +} + class CaptureModeCameraPreviewTest : public CaptureModeCameraTest, public testing::WithParamInterface<CaptureModeSource> { @@ -1274,8 +1492,8 @@ // capture bounds. VerifyPreviewAlignment(GetCaptureBoundsInScreen()); - // Rotate the primary display by 90 degrees. Verify that the camera preview is - // still at the bottom right corner of capture bounds. + // Rotate the primary display by 90 degrees. Verify that the camera preview + // is still at the bottom right corner of capture bounds. Shell::Get()->display_manager()->SetDisplayRotation( WindowTreeHostManager::GetPrimaryDisplayId(), display::Display::ROTATE_90, display::Display::RotationSource::USER); @@ -1302,9 +1520,9 @@ } // Tests that when camera preview is being dragged, at the end of the drag, it -// should be snapped to the correct snap position. It tests two use cases, when -// capture session is active and when there's a video recording in progress -// including drag to snap by mouse and by touch. +// should be snapped to the correct snap position. It tests two use cases, +// when capture session is active and when there's a video recording in +// progress including drag to snap by mouse and by touch. TEST_P(CaptureModeCameraPreviewTest, CameraPreviewDragToSnap) { StartCaptureSessionWithParam(); auto* camera_controller = GetCameraController(); @@ -1321,8 +1539,8 @@ VerifyPreviewAlignment(GetCaptureBoundsInScreen()); // Drag and drop camera preview by mouse to the top right of the - // `capture_bounds_center_point`, verify that camera preview is snapped to the - // top right with correct position. + // `capture_bounds_center_point`, verify that camera preview is snapped to + // the top right with correct position. DragPreviewToPoint(preview_widget, {capture_bounds_center_point.x() + 20, capture_bounds_center_point.y() - 20}); EXPECT_EQ(CameraPreviewSnapPosition::kTopRight, @@ -1340,8 +1558,9 @@ camera_controller->camera_preview_snap_position()); VerifyPreviewAlignment(GetCaptureBoundsInScreen()); - // Start video recording, verify camera preview is snapped to the correct snap - // position at the end of drag when there's a video recording in progress. + // Start video recording, verify camera preview is snapped to the correct + // snap position at the end of drag when there's a video recording in + // progress. StartVideoRecordingImmediately(); EXPECT_FALSE(CaptureModeController::Get()->IsActive()); @@ -1354,9 +1573,9 @@ camera_controller->camera_preview_snap_position()); VerifyPreviewAlignment(GetCaptureBoundsInScreen()); - // Now drag and drop camera preview by touch to the bottom right of the center - // point, verify that camera preview is snapped to the bottom right with - // correct position. + // Now drag and drop camera preview by touch to the bottom right of the + // center point, verify that camera preview is snapped to the bottom right + // with correct position. DragPreviewToPoint(preview_widget, {capture_bounds_center_point.x() + 20, capture_bounds_center_point.y() + 20}, @@ -1366,6 +1585,78 @@ VerifyPreviewAlignment(GetCaptureBoundsInScreen()); } +// Tests the use case after pressing on the resize button on camera preview and +// releasing the press outside of camera preview, camera preview is still +// draggable. Regression test for https://crbug.com/1308885. +TEST_P(CaptureModeCameraPreviewTest, + CameraPreviewDragToSnapAfterPressOnResizeButton) { + StartCaptureSessionWithParam(); + auto* camera_controller = GetCameraController(); + AddDefaultCamera(); + camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1)); + auto* preview_widget = camera_controller->camera_preview_widget(); + auto* resize_button = GetPreviewResizeButton(); + const int camera_previw_width = + preview_widget->GetWindowBoundsInScreen().width(); + const gfx::Point capture_bounds_center_point = + GetCaptureBoundsInScreen().CenterPoint(); + const gfx::Point center_point_of_resize_button = + resize_button->GetBoundsInScreen().CenterPoint(); + + // By default the snap position of preview widget should be `kBottomRight`. + EXPECT_EQ(CameraPreviewSnapPosition::kBottomRight, + camera_controller->camera_preview_snap_position()); + + auto* event_generator = GetEventGenerator(); + event_generator->set_current_screen_location(center_point_of_resize_button); + event_generator->PressLeftButton(); + + const gfx::Vector2d delta(-camera_previw_width, -camera_previw_width); + // Now move mouse to the outside of the camera preview and then release. + event_generator->MoveMouseTo(center_point_of_resize_button + delta); + event_generator->ReleaseLeftButton(); + + // Now try to drag the camera preview to the top left, after camera preview is + // snapped, the current snap position should be `kTopLeft`. + DragPreviewToPoint(preview_widget, capture_bounds_center_point + delta); + EXPECT_EQ(CameraPreviewSnapPosition::kTopLeft, + camera_controller->camera_preview_snap_position()); +} + +TEST_P(CaptureModeCameraPreviewTest, CaptureUisVisibilityChangeOnDragAndDrop) { + StartCaptureSessionWithParam(); + auto* camera_controller = GetCameraController(); + auto* capture_session = CaptureModeController::Get()->capture_mode_session(); + AddDefaultCamera(); + camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1)); + auto* preview_widget = camera_controller->camera_preview_widget(); + const gfx::Point center_point_of_preview_widget = + preview_widget->GetWindowBoundsInScreen().CenterPoint(); + + const auto* capture_bar_widget = capture_session->capture_mode_bar_widget(); + const auto* capture_label_widget = capture_session->capture_label_widget(); + + // Press on top of the preview widget. Verify capture bar and capture label + // are hidden. + auto* event_generator = GetEventGenerator(); + event_generator->set_current_screen_location(center_point_of_preview_widget); + event_generator->PressLeftButton(); + EXPECT_FALSE(capture_bar_widget->IsVisible()); + EXPECT_FALSE(capture_label_widget->IsVisible()); + + // Now drag and move the preview widget. Verify capture bar and capture + // label are still hidden. + const gfx::Vector2d delta(-50, -60); + event_generator->MoveMouseTo(center_point_of_preview_widget + delta); + EXPECT_FALSE(capture_bar_widget->IsVisible()); + EXPECT_FALSE(capture_label_widget->IsVisible()); + + // Release the drag. Verify capture bar and capture label are shown again. + event_generator->ReleaseLeftButton(); + EXPECT_TRUE(capture_bar_widget->IsVisible()); + EXPECT_TRUE(capture_label_widget->IsVisible()); +} + TEST_P(CaptureModeCameraPreviewTest, CameraPreviewDragToSnapOnMultipleDisplay) { UpdateDisplay("800x700,801+0-800x700"); @@ -1383,8 +1674,8 @@ GetCaptureBoundsInScreen().CenterPoint(); // Drag and drop camera preview by mouse to the top right of the - // `capture_bounds_center_point`, verify that camera preview is snapped to the - // top right with correct position. + // `capture_bounds_center_point`, verify that camera preview is snapped to + // the top right with correct position. DragPreviewToPoint(preview_widget, {capture_bounds_center_point.x() + 20, capture_bounds_center_point.y() - 20}); EXPECT_EQ(CameraPreviewSnapPosition::kTopRight, @@ -1427,9 +1718,9 @@ EXPECT_EQ(preview_widget->GetWindowBoundsInScreen(), preview_bounds_in_screen_before_drag); - // Try to drag and drop camera preview by touch to the top left of the current - // capture bounds' center point, verity it's not moved. Also verify the snap - // position is not updated. + // Try to drag and drop camera preview by touch to the top left of the + // current capture bounds' center point, verity it's not moved. Also verify + // the snap position is not updated. DragPreviewToPoint(preview_widget, {capture_bounds_center_point.x() - 20, capture_bounds_center_point.y() - 20}, @@ -1477,9 +1768,9 @@ /*drop=*/false); EXPECT_EQ(cursor_manager->GetCursor(), ui::mojom::CursorType::kPointer); - // Continue dragging and then drop camera preview, make sure cursor's position - // is outside of camera preview after it's snapped. Verify cursor type is - // updated to the correct type of the current capture source. + // Continue dragging and then drop camera preview, make sure cursor's + // position is outside of camera preview after it's snapped. Verify cursor + // type is updated to the correct type of the current capture source. DragPreviewToPoint(preview_widget, {camera_preview_origin_point.x() - 20, camera_preview_origin_point.y() - 20}); EXPECT_EQ(cursor_manager->GetCursor(), GetCursorTypeOnCaptureSurface());
diff --git a/ash/capture_mode/capture_mode_session.cc b/ash/capture_mode/capture_mode_session.cc index 8ea5ef5..12653e89 100644 --- a/ash/capture_mode/capture_mode_session.cc +++ b/ash/capture_mode/capture_mode_session.cc
@@ -47,6 +47,7 @@ #include "ui/aura/env.h" #include "ui/aura/window.h" #include "ui/aura/window_delegate.h" +#include "ui/aura/window_observer.h" #include "ui/aura/window_tracker.h" #include "ui/base/cursor/cursor_factory.h" #include "ui/base/l10n/l10n_util.h" @@ -68,9 +69,11 @@ #include "ui/gfx/scoped_canvas.h" #include "ui/gfx/shadow_value.h" #include "ui/gfx/skia_paint_util.h" +#include "ui/views/animation/animation_builder.h" #include "ui/views/background.h" #include "ui/views/controls/button/label_button.h" #include "ui/views/controls/label.h" +#include "ui/views/widget/widget.h" #include "ui/wm/core/coordinate_conversion.h" namespace ash { @@ -166,17 +169,20 @@ // widget will scale up from 80% -> 100%. constexpr float kLabelScaleDownOnPhaseChange = 0.8; -// Animation parameters for capture bar overlapping the user capture region. -// The default animation duration for opacity changes to the capture bar. -constexpr base::TimeDelta kCaptureBarOpacityChangeDuration = +// Animation parameters for capture UI (capture bar, capture label) overlapping +// the user capture region or camera preview. The default animation duration for +// opacity changes to the capture UI. +constexpr base::TimeDelta kCaptureUIOpacityChangeDuration = base::Milliseconds(100); // The animation duration for showing the capture bar on mouse/touch release. constexpr base::TimeDelta kCaptureBarOnReleaseOpacityChangeDuration = base::Milliseconds(167); -// When the capture bar and user capture region overlap and the mouse is not -// hovering over the capture bar, drop the opacity to this value to make the -// region easier to see. -constexpr float kCaptureBarOverlapOpacity = 0.1; + +// When capture UI (capture bar, capture label) is overlapped with user +// capture region or camera preview, and the mouse is not hovering over the +// capture UI, drop the opacity to this value to make the region or camera +// preview easier to see. +constexpr float kCaptureUiOverlapOpacity = 0.1; // If the user is using keyboard only and they are on the selecting region // phase, they can create default region which is centered and sized to this @@ -380,6 +386,41 @@ return false; } +views::Widget* GetCameraPreviewWidget() { + auto* camera_controller = CaptureModeController::Get()->camera_controller(); + return camera_controller ? camera_controller->camera_preview_widget() + : nullptr; +} + +// Returns true if the given `event` is targeted on the camera preview. +// Otherwise, returns false. +bool IsEventTargetedOnCameraPreview(ui::LocatedEvent* event) { + auto* camera_preview_widget = GetCameraPreviewWidget(); + if (camera_preview_widget && camera_preview_widget->IsVisible()) { + auto* target = static_cast<aura::Window*>(event->target()); + if (camera_preview_widget->GetNativeWindow()->Contains(target)) + return true; + } + return false; +} + +// Returns true if the given `widget` intersects with the camera preview. +// Otherwise, returns false; +bool IsWidgetOverlappedWithCameraPreview(views::Widget* widget) { + // Return false immediately if there's a video recording in propress since + // the camera preview doesn't belong to the current capture session. + if (CaptureModeController::Get()->is_recording_in_progress()) + return false; + + auto* camera_preview_widget = GetCameraPreviewWidget(); + if (!camera_preview_widget) + return false; + + return camera_preview_widget->IsVisible() && + camera_preview_widget->GetWindowBoundsInScreen().Intersects( + widget->GetWindowBoundsInScreen()); +} + } // namespace // ----------------------------------------------------------------------------- @@ -526,6 +567,50 @@ }; // ----------------------------------------------------------------------------- +// CaptureModeSession::ParentContainerObserver: + +// The observer class to observer window added to or removed from the parent +// container `kShellWindowId_MenuContainer`. Capture UIs (capture bar, capture +// label, capture settings, camera preview) are all parented to the parent +// container, thus whenever there's a window added or removed, we need to call +// `RefreshStackingOrder` to ensure the stacking order is correct for them. +class CaptureModeSession::ParentContainerObserver + : public aura::WindowObserver { + public: + ParentContainerObserver(aura::Window* parent_container, + CaptureModeSession* capture_mode_session) + : parent_container_(parent_container), + capture_mode_session_(capture_mode_session) { + parent_container_->AddObserver(this); + } + + ParentContainerObserver(const CursorSetter&) = delete; + ParentContainerObserver& operator=(const CursorSetter&) = delete; + + ~ParentContainerObserver() override { + parent_container_->RemoveObserver(this); + } + + // aura::WindowObserver: + void OnWindowAdded(aura::Window* window) override { + capture_mode_session_->RefreshStackingOrder(); + capture_mode_session_->MaybeUpdateCaptureUisOpacity(); + } + + void OnWindowRemoved(aura::Window* window) override { + capture_mode_session_->RefreshStackingOrder(); + capture_mode_session_->MaybeUpdateCaptureUisOpacity(); + } + + private: + aura::Window* const parent_container_; + + // Pointer to current capture session. Not nullptr during this lifecycle. + // Capture session owns `this`. + CaptureModeSession* const capture_mode_session_; +}; + +// ----------------------------------------------------------------------------- // CaptureModeSession: CaptureModeSession::CaptureModeSession(CaptureModeController* controller, @@ -584,6 +669,8 @@ layer()->SetFillsBoundsOpaquely(false); layer()->set_delegate(this); auto* parent = GetParentContainer(current_root_); + parent_container_observer_ = + std::make_unique<ParentContainerObserver>(parent, this); parent->layer()->Add(layer()); layer()->SetBounds(parent->bounds()); @@ -607,7 +694,6 @@ focus_cycler_->AdvanceFocus(/*reverse=*/false); UpdateCaptureLabelWidget(CaptureLabelAnimation::kNone); - RefreshStackingOrder(parent); UpdateCursor(display::Screen::GetScreen()->GetCursorScreenPoint(), /*is_touch=*/false); @@ -869,20 +955,20 @@ aura::Window* CaptureModeSession::GetCameraPreviewParentWindow() const { auto* controller = CaptureModeController::Get(); DCHECK(!controller->is_recording_in_progress()); - auto* overlay_container = - current_root_->GetChildById(kShellWindowId_OverlayContainer); + auto* menu_container = + current_root_->GetChildById(kShellWindowId_MenuContainer); auto* unparented_container = current_root_->GetChildById(kShellWindowId_UnparentedContainer); switch (controller->source()) { case CaptureModeSource::kFullscreen: - return overlay_container; + return menu_container; case CaptureModeSource::kRegion: return controller_->user_capture_region().IsEmpty() || (is_drag_in_progress_ && fine_tune_position_ != FineTunePosition::kCenter) ? unparented_container - : overlay_container; + : menu_container; case CaptureModeSource::kWindow: aura::Window* selected_window = GetSelectedWindow(); return selected_window ? selected_window : unparented_container; @@ -914,8 +1000,14 @@ } void CaptureModeSession::OnPaintLayer(const ui::PaintContext& context) { - if (!is_all_uis_visible_) + // If the drag of camera preview is in progress, we will hide other capture + // UIs (capture bar, capture label), but we should still paint the layer to + // indicate the capture surface where user can drag camera preview on. + if (!is_all_uis_visible_ && + !(controller_->camera_controller() && + controller_->camera_controller()->is_drag_in_progress())) { return; + } ui::PaintRecorder recorder(context, layer()->size()); @@ -1092,6 +1184,7 @@ if (capture_label_widget_) UpdateCaptureLabelWidget(CaptureLabelAnimation::kNone); layer()->SchedulePaint(layer()->bounds()); + MaybeUpdateCaptureUisOpacity(); } void CaptureModeSession::OnFolderSelected(const base::FilePath& path) { @@ -1225,6 +1318,99 @@ capture_mode_bar_view_, capture_mode_settings_view_)); } +void CaptureModeSession::MaybeUpdateCaptureUisOpacity( + absl::optional<gfx::Point> cursor_screen_location) { + if (is_shutting_down_) + return; + + // TODO(conniekxu): Handle this for tablet mode which doesn't have a cursor + // screen point. + if (!cursor_screen_location) { + cursor_screen_location = + display::Screen::GetScreen()->GetCursorScreenPoint(); + } + + base::flat_map<views::Widget*, /*opacity=*/float> widget_opacity_map; + if (capture_mode_bar_widget_) + widget_opacity_map[capture_mode_bar_widget_.get()] = 1.f; + if (capture_label_widget_) + widget_opacity_map[capture_label_widget_.get()] = 1.f; + + const bool is_settings_visible = capture_mode_settings_widget_ && + capture_mode_settings_widget_->IsVisible(); + + for (auto& pair : widget_opacity_map) { + views::Widget* widget = pair.first; + float& opacity = pair.second; + DCHECK(widget->GetLayer()); + + if (widget->GetWindowBoundsInScreen().Contains(*cursor_screen_location)) { + continue; + } + + if (widget == capture_mode_bar_widget_.get()) { + // If capture setting is visible, capture bar should be fully opaque even + // if it's overlapped with camera preview. + if (is_settings_visible) + continue; + + // If drag for capture region is in progress, capture bar should be + // hidden. + if (is_drag_in_progress_) { + opacity = 0.f; + continue; + } + } + + if (IsWidgetOverlappedWithCameraPreview(widget)) + opacity = kCaptureUiOverlapOpacity; + } + + for (const auto& pair : widget_opacity_map) { + ui::Layer* layer = pair.first->GetLayer(); + const float& opacity = pair.second; + if (layer->GetTargetOpacity() == opacity) + continue; + + views::AnimationBuilder() + .SetPreemptionStrategy( + ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET) + .Once() + .SetDuration(kCaptureUIOpacityChangeDuration) + .SetOpacity(layer, opacity, gfx::Tween::FAST_OUT_SLOW_IN); + } +} + +void CaptureModeSession::OnCameraPreviewDragStarted() { + DCHECK(!controller_->is_recording_in_progress()); + + // If settings menu is shown at the beginning of drag, we should close it. + if (capture_mode_settings_widget_) + SetSettingsMenuShown(false); + + // Hide capture UIs while dragging camera preview. + HideAllUis(); +} + +void CaptureModeSession::OnCameraPreviewDragEnded( + const gfx::Point& screen_location, + bool is_touch) { + // If CaptureUIs (capture bar, capture label) are overlapped with camera + // preview and cursor is not on top of it, its opacity should be updated to + // `kCaptureUiOverlapOpacity` instead of fully opaque. + MaybeUpdateCaptureUisOpacity(screen_location); + + // Show capture UIs which are hidden in `OnCameraPreviewDragStarted`. + ShowAllUis(); + + // Make sure cursor is updated correctly after camera preview is snapped. + UpdateCursor(screen_location, is_touch); +} + +void CaptureModeSession::OnCameraPreviewBoundsChanged() { + MaybeUpdateCaptureUisOpacity(); +} + std::vector<views::Widget*> CaptureModeSession::GetAvailableWidgets() { std::vector<views::Widget*> result; DCHECK(capture_mode_bar_widget_); @@ -1248,6 +1434,7 @@ // without animation) when ShowAllUis() is called. widget->GetNativeWindow()->SetProperty(aura::client::kAnimationsDisabledKey, true); + // The layer's opacity could be less than 1.f if the widget was hidden // before we disabled the animations above. We need to reset the opacity // back to 1.f as we will hide the widget without animation. @@ -1328,15 +1515,35 @@ return window ? window->bounds() : gfx::Rect(); } -void CaptureModeSession::RefreshStackingOrder(aura::Window* parent_container) { +void CaptureModeSession::RefreshStackingOrder() { + if (is_shutting_down_) + return; + + auto* parent_container = GetParentContainer(current_root_); DCHECK(parent_container); - auto* capture_mode_bar_layer = capture_mode_bar_widget_->GetLayer(); auto* overlay_layer = layer(); auto* parent_container_layer = parent_container->layer(); - parent_container_layer->StackAtTop(overlay_layer); - parent_container_layer->StackAtTop(capture_label_widget_->GetLayer()); - parent_container_layer->StackAtTop(capture_mode_bar_layer); + + std::vector<views::Widget*> widget_in_order; + + auto* camera_preview_widget = GetCameraPreviewWidget(); + // We don't need to update the stacking order for camera preview if + // there's a video recording in progress, since the camera preview don't + // belong to the current capture session. + if (camera_preview_widget && !controller_->is_recording_in_progress()) + widget_in_order.emplace_back(camera_preview_widget); + if (capture_label_widget_) + widget_in_order.emplace_back(capture_label_widget_.get()); + if (capture_mode_bar_widget_) + widget_in_order.emplace_back(capture_mode_bar_widget_.get()); + if (capture_mode_settings_widget_) + widget_in_order.emplace_back(capture_mode_settings_widget_.get()); + + for (auto* widget : widget_in_order) { + if (widget->GetNativeWindow()->parent() == parent_container) + parent_container_layer->StackAtTop(widget->GetLayer()); + } } void CaptureModeSession::PaintCaptureRegion(gfx::Canvas* canvas) { @@ -1522,20 +1729,36 @@ RefreshBarWidgetBounds(); } + MaybeUpdateCaptureUisOpacity(screen_location); + if (IsDragAllowedOnCameraPreview(screen_location)) { + DCHECK(!controller_->is_recording_in_progress()); // Update cursor type when the event is on top of camera preview. UpdateCursor(screen_location, is_touch); + // Pass the event to camera preview to handle it if the event is on top of // camera preview and there's no video recording is in progress. return; } - const bool is_event_on_settings_menu = - IsEventInSettingsMenuBounds(screen_location); + // If the event is targeted on the camera preview, even it's not located + // on the camera preview, we should still pass the event to camera preview + // to handle it. For example, when pressing on the resize button inside camera + // preview, but release the press outside of camera preview, even the release + // event is not on the camera preview, we should still pass the event to it, + // otherwise camera preview will wait for the release event forever which will + // make the regular drag for camera preview not work. + if (!controller_->is_recording_in_progress() && + IsEventTargetedOnCameraPreview(event)) { + UpdateCursor(screen_location, is_touch); + return; + } const bool is_event_on_capture_bar = capture_mode_bar_widget_->GetWindowBoundsInScreen().Contains( screen_location); + const bool is_event_on_settings_menu = + IsEventInSettingsMenuBounds(screen_location); const bool is_event_on_capture_bar_or_menu = is_event_on_capture_bar || is_event_on_settings_menu; const bool is_event_on_settings_button = @@ -1671,8 +1894,10 @@ if (capture_mode_settings_widget_ && !is_event_on_capture_bar_or_menu) SetSettingsMenuShown(/*shown=*/false); + // TODO(crbug.com/1310310): Consider combining + // `UpdateCaptureBarWidgetOpacity` into `MaybeUpdateCaptureUisOpacity`. UpdateCaptureBarWidgetOpacity( - is_event_on_capture_bar_or_menu ? 1.f : kCaptureBarOverlapOpacity, + is_event_on_capture_bar_or_menu ? 1.f : kCaptureUiOverlapOpacity, /*on_release=*/false); } break; @@ -1841,10 +2066,19 @@ // Do a repaint to show the affordance circles. RepaintRegion(); - // Show the camera which may have been hidden in `OnLocatedEventPressed` - // regardless of whether we're selecting a region for the first time, or just - // dragging to fine tune it. - MaybeReparentCameraPreviewWidget(); + // Run `MaybeReparentCameraPreviewWidget` when user releases the drag at + // the exit of this function's scope to show the camera preview which may have + // been hidden in `OnLocatedEventPressed`. Please notice, we should call + // `MaybeReparentCameraPreviewWidget` no matter if the event is on the capture + // bar or not, since at the end of drag, the event may happen to be located on + // the capture bar, we should still show the camera preview at this usecase. + // The reason we want to run it at the exit of this function is if + // `is_selecting_region_` is true, we want to wait until the capture label is + // updated since capture label's opacity may need to be updated based on if + // it's overlapped with camera preview or not. + base::ScopedClosureRunner deferred_runner( + base::BindOnce(&CaptureModeSession::MaybeReparentCameraPreviewWidget, + weak_ptr_factory_.GetWeakPtr())); if (!is_selecting_region_) return; @@ -2084,6 +2318,8 @@ gfx::GetScaleTransform(gfx::Point(center_point.x() - bounds.x(), center_point.y() - bounds.y()), kLabelScaleUpOnCountdown)); + // TODO (crbug/1310310): Consider combining the following codes into + // `MaybeUpdateCaptureUisOpacity`. layer->SetOpacity(0.f); // Fade in. @@ -2252,6 +2488,9 @@ new_root->AddObserver(this); auto* new_parent = GetParentContainer(new_root); + parent_container_observer_ = + std::make_unique<ParentContainerObserver>(new_parent, this); + new_parent->layer()->Add(layer()); layer()->SetBounds(new_parent->bounds()); @@ -2314,7 +2553,7 @@ capture_bar_layer->GetAnimator()); capture_bar_settings.SetTransitionDuration( on_release ? kCaptureBarOnReleaseOpacityChangeDuration - : kCaptureBarOpacityChangeDuration); + : kCaptureUIOpacityChangeDuration); capture_bar_settings.SetTweenType(on_release ? gfx::Tween::FAST_OUT_SLOW_IN : gfx::Tween::LINEAR); capture_bar_settings.SetPreemptionStrategy( @@ -2340,7 +2579,7 @@ // TODO(richui): Update this for tablet mode. UpdateCaptureBarWidgetOpacity( region_intersects_capture_bar && !is_event_on_capture_bar_or_menu - ? kCaptureBarOverlapOpacity + ? kCaptureUiOverlapOpacity : 1.f, /*on_release=*/true);
diff --git a/ash/capture_mode/capture_mode_session.h b/ash/capture_mode/capture_mode_session.h index 699b61a61..d38edb5 100644 --- a/ash/capture_mode/capture_mode_session.h +++ b/ash/capture_mode/capture_mode_session.h
@@ -23,6 +23,7 @@ #include "ui/display/display_observer.h" #include "ui/events/event.h" #include "ui/events/event_handler.h" +#include "ui/gfx/geometry/point.h" #include "ui/views/controls/button/button.h" #include "ui/views/widget/unique_widget_ptr.h" #include "ui/views/widget/widget.h" @@ -195,12 +196,25 @@ // updated correspondingly. void MaybeUpdateSettingsBounds(); + // Called when opacity of capture UIs (capture bar, capture label) may need to + // be updated. For example, when camera preview is created, destroyed, + // reparented, display metrics change or located events enter / exit / move + // on capture UI. + void MaybeUpdateCaptureUisOpacity( + absl::optional<gfx::Point> cursor_screen_location = absl::nullopt); + + void OnCameraPreviewDragStarted(); + void OnCameraPreviewDragEnded(const gfx::Point& screen_location, + bool is_touch); + void OnCameraPreviewBoundsChanged(); + private: friend class CaptureModeSettingsTestApi; friend class CaptureModeSessionFocusCycler; friend class CaptureModeSessionTestApi; friend class CaptureModeTestApi; class CursorSetter; + class ParentContainerObserver; enum class CaptureLabelAnimation { // No animation on the capture label. @@ -250,8 +264,8 @@ // Ensures that the bar widget is on top of everything, and the overlay (which // is the |layer()| of this class that paints the capture region) is stacked - // right below the bar. - void RefreshStackingOrder(aura::Window* parent_container); + // below the bar. + void RefreshStackingOrder(); // Paints the current capture region depending on the current capture source. void PaintCaptureRegion(gfx::Canvas* canvas); @@ -443,6 +457,9 @@ // Observer to observe the current selected to-be-captured window. std::unique_ptr<CaptureWindowObserver> capture_window_observer_; + // Observer to observe the parent container `kShellWindowId_MenuContainer`. + std::unique_ptr<ParentContainerObserver> parent_container_observer_; + // Contains the window dimmers which dim all the root windows except // |current_root_|. base::flat_set<std::unique_ptr<WindowDimmer>> root_window_dimmers_;
diff --git a/ash/capture_mode/capture_window_observer.cc b/ash/capture_mode/capture_window_observer.cc index 9a05784..c0c153b 100644 --- a/ash/capture_mode/capture_window_observer.cc +++ b/ash/capture_mode/capture_window_observer.cc
@@ -75,6 +75,8 @@ auto* camera_controller = controller->camera_controller(); if (camera_controller && !controller->is_recording_in_progress()) camera_controller->MaybeReparentPreviewWidget(); + capture_mode_session_->MaybeUpdateCaptureUisOpacity( + display::Screen::GetScreen()->GetCursorScreenPoint()); } void CaptureWindowObserver::OnWindowBoundsChanged(
diff --git a/ash/capture_mode/video_recording_watcher.cc b/ash/capture_mode/video_recording_watcher.cc index 7809d593..aea351ef 100644 --- a/ash/capture_mode/video_recording_watcher.cc +++ b/ash/capture_mode/video_recording_watcher.cc
@@ -283,7 +283,7 @@ DCHECK(window_being_recorded_); return window_being_recorded_->IsRootWindow() ? window_being_recorded_->GetChildById( - kShellWindowId_OverlayContainer) + kShellWindowId_MenuContainer) : window_being_recorded_; }
diff --git a/ash/components/device_activity/device_activity_client.cc b/ash/components/device_activity/device_activity_client.cc index 94aa06f..177380b 100644 --- a/ash/components/device_activity/device_activity_client.cc +++ b/ash/components/device_activity/device_activity_client.cc
@@ -63,11 +63,11 @@ // Record the minute the device activity client transitions out of idle. const char kDeviceActiveClientTransitionOutOfIdleMinute[] = - "Ash.DeviceActiveClient.TransitionOutOfIdleMinute"; + "Ash.DeviceActiveClient.RecordedTransitionOutOfIdleMinute"; // Record the minute the device activity client transitions to check in. const char kDeviceActiveClientTransitionToCheckInMinute[] = - "Ash.DeviceActiveClient.TransitionToCheckInMinute"; + "Ash.DeviceActiveClient.RecordedTransitionToCheckInMinute"; // Generates the full histogram name for histogram variants based on state. std::string HistogramVariantName(const std::string& histogram_prefix, @@ -99,28 +99,26 @@ } // Return the minute of the current UTC time. -base::TimeDelta GetCurrentMinute() { +int GetCurrentMinute() { base::Time cur_time = base::Time::Now(); // Extract minute from exploded |cur_time| in UTC. base::Time::Exploded exploded_utc; cur_time.UTCExplode(&exploded_utc); - return base::Minutes(exploded_utc.minute); + return exploded_utc.minute; } void RecordTransitionOutOfIdleMinute() { - base::UmaHistogramCustomTimes(kDeviceActiveClientTransitionOutOfIdleMinute, - GetCurrentMinute(), base::Minutes(0), - base::Minutes(59), - 60 /* number of histogram buckets */); + base::UmaHistogramCustomCounts(kDeviceActiveClientTransitionOutOfIdleMinute, + GetCurrentMinute(), 0, 59, + 60 /* number of histogram buckets */); } void RecordTransitionToCheckInMinute() { - base::UmaHistogramCustomTimes(kDeviceActiveClientTransitionToCheckInMinute, - GetCurrentMinute(), base::Minutes(0), - base::Minutes(59), - 60 /* number of histogram buckets */); + base::UmaHistogramCustomCounts(kDeviceActiveClientTransitionToCheckInMinute, + GetCurrentMinute(), 0, 59, + 60 /* number of histogram buckets */); } // Histogram sliced by duration and state.
diff --git a/ash/components/phonehub/BUILD.gn b/ash/components/phonehub/BUILD.gn index 601dc8cf..9826010 100644 --- a/ash/components/phonehub/BUILD.gn +++ b/ash/components/phonehub/BUILD.gn
@@ -26,6 +26,8 @@ "camera_roll_thumbnail_decoder.h", "camera_roll_thumbnail_decoder_impl.cc", "camera_roll_thumbnail_decoder_impl.h", + "combined_access_setup_operation.cc", + "combined_access_setup_operation.h", "connection_scheduler.h", "connection_scheduler_impl.cc", "connection_scheduler_impl.h",
diff --git a/ash/components/phonehub/combined_access_setup_operation.cc b/ash/components/phonehub/combined_access_setup_operation.cc new file mode 100644 index 0000000..d781859 --- /dev/null +++ b/ash/components/phonehub/combined_access_setup_operation.cc
@@ -0,0 +1,82 @@ +// 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/components/phonehub/combined_access_setup_operation.h" + +#include <array> + +#include "base/check.h" +#include "base/containers/contains.h" + +namespace ash { +namespace phonehub { +namespace { + +// Status values which are considered "final" - i.e., once the status of an +// operation changes to one of these values, the operation has completed. These +// status values indicate either a success or a fatal error. +constexpr std::array<CombinedAccessSetupOperation::Status, 4> + kOperationFinishedStatus{ + CombinedAccessSetupOperation::Status::kTimedOutConnecting, + CombinedAccessSetupOperation::Status::kConnectionDisconnected, + CombinedAccessSetupOperation::Status::kCompletedSuccessfully, + CombinedAccessSetupOperation::Status::kProhibitedFromProvidingAccess, + }; + +} // namespace + +// static +bool CombinedAccessSetupOperation::IsFinalStatus(Status status) { + return base::Contains(kOperationFinishedStatus, status); +} + +CombinedAccessSetupOperation::CombinedAccessSetupOperation( + Delegate* delegate, + base::OnceClosure destructor_callback) + : delegate_(delegate), + destructor_callback_(std::move(destructor_callback)) { + DCHECK(delegate_); + DCHECK(destructor_callback_); +} + +CombinedAccessSetupOperation::~CombinedAccessSetupOperation() { + std::move(destructor_callback_).Run(); +} + +void CombinedAccessSetupOperation::NotifyCombinedStatusChanged( + Status new_status) { + current_status_ = new_status; + + delegate_->OnCombinedStatusChange(new_status); +} + +std::ostream& operator<<(std::ostream& stream, + CombinedAccessSetupOperation::Status status) { + switch (status) { + case CombinedAccessSetupOperation::Status::kConnecting: + stream << "[Connecting]"; + break; + case CombinedAccessSetupOperation::Status::kTimedOutConnecting: + stream << "[Timed out connecting]"; + break; + case CombinedAccessSetupOperation::Status::kConnectionDisconnected: + stream << "[Connection disconnected]"; + break; + case CombinedAccessSetupOperation::Status:: + kSentMessageToPhoneAndWaitingForResponse: + stream << "[Sent message to phone; waiting for response]"; + break; + case CombinedAccessSetupOperation::Status::kCompletedSuccessfully: + stream << "[Completed successfully]"; + break; + case CombinedAccessSetupOperation::Status::kProhibitedFromProvidingAccess: + stream << "[Prohibited from providing access]"; + break; + } + + return stream; +} + +} // namespace phonehub +} // namespace ash
diff --git a/ash/components/phonehub/combined_access_setup_operation.h b/ash/components/phonehub/combined_access_setup_operation.h new file mode 100644 index 0000000..5171ddb5 --- /dev/null +++ b/ash/components/phonehub/combined_access_setup_operation.h
@@ -0,0 +1,105 @@ +// 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_COMPONENTS_PHONEHUB_COMBINED_ACCESS_SETUP_OPERATION_H_ +#define ASH_COMPONENTS_PHONEHUB_COMBINED_ACCESS_SETUP_OPERATION_H_ + +#include <ostream> + +#include "base/callback.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace ash { +namespace phonehub { + +// Implements the combined access setup flow. This flow involves: +// (1) Creating a connection to the phone if one does not already exist. +// (2) Sending a message to the phone which asks it to begin the setup flow; +// upon receipt of the message, the phone displays a UI which asks the +// user to enable permissions for the Phone Hub features that need setup. +// (3) Waiting for the user to complete the flow; once the flow is complete, the +// phone sends a message back to this device which indicates that permission +// has been granted. +// +// If an instance of this class exists, the flow continues until the status +// changes to a "final" status (i.e., a success or a fatal error). To cancel the +// ongoing setup operation, simply delete the instance of this class. +class CombinedAccessSetupOperation { + public: + // Note: Numerical values should not be changed because they must stay in + // sync with multidevice_permissions_access_setup_dialog.js, with the + // exception of NOT_STARTED, which has a value of 0. Also, these values are + // persisted to logs. Entries should not be renumbered and numeric values + // should never be reused. If entries are added, kMaxValue should be updated. + enum class Status { + // Connecting to the phone in order to set up feature access. + kConnecting = 1, + + // No connection was able to be made to the phone within the expected time + // period. + kTimedOutConnecting = 2, + + // A connection to the phone was successful, but it unexpectedly became + // disconnected before the setup flow could complete. + kConnectionDisconnected = 3, + + // A connection to the phone has succeeded, and a message has been sent to + // the phone to start the feature access opt-in flow. However, the user + // has not yet completed the flow phone-side. + kSentMessageToPhoneAndWaitingForResponse = 4, + + // The user has completed the phone-side opt-in flow. + kCompletedSuccessfully = 5, + + // The user's phone is prohibited from granting feature access (e.g., + // the user could be using a Work Profile). + kProhibitedFromProvidingAccess = 6, + + kMaxValue = kProhibitedFromProvidingAccess + }; + + // Returns true if the provided status is the final one for this operation, + // indicating either success or failure. + static bool IsFinalStatus(Status status); + + class Delegate { + public: + virtual ~Delegate() = default; + + // Called when status of the setup flow has changed. + virtual void OnCombinedStatusChange(Status new_status) = 0; + }; + + CombinedAccessSetupOperation(const CombinedAccessSetupOperation&) = delete; + CombinedAccessSetupOperation& operator=(const CombinedAccessSetupOperation&) = + delete; + virtual ~CombinedAccessSetupOperation(); + + private: + friend class MultideviceFeatureAccessManager; + + CombinedAccessSetupOperation(Delegate* delegate, + base::OnceClosure destructor_callback); + + void NotifyCombinedStatusChanged(Status new_status); + + absl::optional<Status> current_status_; + Delegate* const delegate_; + base::OnceClosure destructor_callback_; +}; + +std::ostream& operator<<(std::ostream& stream, + CombinedAccessSetupOperation::Status status); + +} // namespace phonehub +} // namespace ash + +// TODO(https://crbug.com/1164001): remove after the migration is finished. +namespace chromeos { +namespace phonehub { +using ::ash::phonehub::CombinedAccessSetupOperation; +} +} // namespace chromeos + +#endif // ASH_COMPONENTS_PHONEHUB_COMBINED_ACCESS_SETUP_OPERATION_H_
diff --git a/ash/components/phonehub/fake_message_sender.cc b/ash/components/phonehub/fake_message_sender.cc index b1db0560..b4d9879 100644 --- a/ash/components/phonehub/fake_message_sender.cc +++ b/ash/components/phonehub/fake_message_sender.cc
@@ -61,6 +61,11 @@ initiate_camera_roll_item_transfer_requests_.push_back(request); } +void FakeMessageSender::SendFeatureSetupRequest(bool camera_roll, + bool notifications) { + feature_setup_requests_.push_back(std::make_pair(camera_roll, notifications)); +} + size_t FakeMessageSender::GetCrosStateCallCount() const { return cros_states_.size(); } @@ -98,6 +103,10 @@ return initiate_camera_roll_item_transfer_requests_.size(); } +size_t FakeMessageSender::GetFeatureSetupRequestCallCount() const { + return feature_setup_requests_.size(); +} + std::pair<bool, bool> FakeMessageSender::GetRecentCrosState() const { return cros_states_.back(); } @@ -138,5 +147,9 @@ return initiate_camera_roll_item_transfer_requests_.back(); } +std::pair<bool, bool> FakeMessageSender::GetRecentFeatureSetupRequest() const { + return feature_setup_requests_.back(); +} + } // namespace phonehub } // namespace ash
diff --git a/ash/components/phonehub/fake_message_sender.h b/ash/components/phonehub/fake_message_sender.h index 05c5ded..3756f1c 100644 --- a/ash/components/phonehub/fake_message_sender.h +++ b/ash/components/phonehub/fake_message_sender.h
@@ -38,6 +38,7 @@ const proto::FetchCameraRollItemDataRequest& request) override; void SendInitiateCameraRollItemTransferRequest( const proto::InitiateCameraRollItemTransferRequest& request) override; + void SendFeatureSetupRequest(bool camera_roll, bool notifications) override; std::pair<bool, bool> GetRecentCrosState() const; bool GetRecentUpdateNotificationModeRequest() const; @@ -52,6 +53,7 @@ GetRecentFetchCameraRollItemDataRequest() const; const proto::InitiateCameraRollItemTransferRequest& GetRecentInitiateCameraRollItemTransferRequest() const; + std::pair<bool, bool> GetRecentFeatureSetupRequest() const; size_t GetCrosStateCallCount() const; @@ -75,6 +77,8 @@ size_t GetInitiateCameraRollItemTransferRequestCallCount() const; + size_t GetFeatureSetupRequestCallCount() const; + private: std::vector<std::pair</*is_notifications_setting_enabled*/ bool, /*is_camera_roll_setting_enabled*/ bool>> @@ -92,6 +96,7 @@ std::vector<proto::InitiateCameraRollItemTransferRequest> initiate_camera_roll_item_transfer_requests_; size_t show_notification_access_setup_count_ = 0; + std::vector<std::pair<bool, bool>> feature_setup_requests_; }; } // namespace phonehub
diff --git a/ash/components/phonehub/fake_multidevice_feature_access_manager.cc b/ash/components/phonehub/fake_multidevice_feature_access_manager.cc index e0627d5..44bd89c 100644 --- a/ash/components/phonehub/fake_multidevice_feature_access_manager.cc +++ b/ash/components/phonehub/fake_multidevice_feature_access_manager.cc
@@ -119,5 +119,24 @@ new_status); } +void FakeMultideviceFeatureAccessManager::SetCombinedSetupOperationStatus( + CombinedAccessSetupOperation::Status new_status) { + if (new_status == + CombinedAccessSetupOperation::Status::kCompletedSuccessfully) { + SetCameraRollAccessStatusInternal(AccessStatus::kAccessGranted); + } + MultideviceFeatureAccessManager::SetCombinedSetupOperationStatus(new_status); +} + +void FakeMultideviceFeatureAccessManager:: + SetFeatureSetupRequestSupportedInternal(bool supported) { + is_feature_setup_request_supported_ = supported; +} + +bool FakeMultideviceFeatureAccessManager::GetFeatureSetupRequestSupported() + const { + return is_feature_setup_request_supported_; +} + } // namespace phonehub } // namespace ash
diff --git a/ash/components/phonehub/fake_multidevice_feature_access_manager.h b/ash/components/phonehub/fake_multidevice_feature_access_manager.h index 1262d70..daa823db 100644 --- a/ash/components/phonehub/fake_multidevice_feature_access_manager.h +++ b/ash/components/phonehub/fake_multidevice_feature_access_manager.h
@@ -31,7 +31,8 @@ AccessProhibitedReason reason = AccessProhibitedReason::kWorkProfile); ~FakeMultideviceFeatureAccessManager() override; - using MultideviceFeatureAccessManager::IsSetupOperationInProgress; + using MultideviceFeatureAccessManager::IsCombinedSetupOperationInProgress; + using MultideviceFeatureAccessManager::IsNotificationSetupOperationInProgress; void SetNotificationAccessStatusInternal( AccessStatus notification_access_status, @@ -48,6 +49,9 @@ void SetCameraRollAccessStatusInternal( AccessStatus camera_roll_access_status) override; AccessStatus GetCameraRollAccessStatus() const override; + void SetCombinedSetupOperationStatus( + CombinedAccessSetupOperation::Status new_status); + AccessStatus GetAppsAccessStatus() const override; bool IsAccessRequestAllowed(Feature feature) override; @@ -55,6 +59,9 @@ void SetAppsAccessStatusInternal(AccessStatus apps_access_status); void SetFeatureReadyForAccess(Feature feature); + void SetFeatureSetupRequestSupportedInternal(bool supported) override; + bool GetFeatureSetupRequestSupported() const override; + private: AccessStatus notification_access_status_; AccessStatus camera_roll_access_status_; @@ -62,6 +69,7 @@ AccessProhibitedReason access_prohibited_reason_; bool has_notification_setup_ui_been_dismissed_ = false; std::vector<Feature> ready_for_access_features_; + bool is_feature_setup_request_supported_ = false; }; } // namespace phonehub
diff --git a/ash/components/phonehub/message_sender.h b/ash/components/phonehub/message_sender.h index f7b4053..f0fcf91 100644 --- a/ash/components/phonehub/message_sender.h +++ b/ash/components/phonehub/message_sender.h
@@ -46,6 +46,10 @@ // Requests that the phone should show the notification access set up. virtual void SendShowNotificationAccessSetupRequest() = 0; + // Requests that the phone should show the feature access set up. + virtual void SendFeatureSetupRequest(bool camera_roll, + bool notifications) = 0; + // Requests that the phone enables or disables ringing. virtual void SendRingDeviceRequest(bool device_ringing_enabled) = 0;
diff --git a/ash/components/phonehub/message_sender_impl.cc b/ash/components/phonehub/message_sender_impl.cc index 4e97975..ca28503 100644 --- a/ash/components/phonehub/message_sender_impl.cc +++ b/ash/components/phonehub/message_sender_impl.cc
@@ -110,6 +110,15 @@ &request); } +void MessageSenderImpl::SendFeatureSetupRequest(bool camera_roll, + bool notifications) { + proto::FeatureSetupRequest request; + request.set_camera_roll_setup_requested(camera_roll); + request.set_notification_setup_requested(notifications); + + SendMessage(proto::MessageType::FEATURE_SETUP_REQUEST, &request); +} + void MessageSenderImpl::SendRingDeviceRequest(bool device_ringing_enabled) { proto::FindMyDeviceRingStatus ringing_enabled = device_ringing_enabled ? proto::FindMyDeviceRingStatus::RINGING
diff --git a/ash/components/phonehub/message_sender_impl.h b/ash/components/phonehub/message_sender_impl.h index 248d985..a9998ff 100644 --- a/ash/components/phonehub/message_sender_impl.h +++ b/ash/components/phonehub/message_sender_impl.h
@@ -33,6 +33,7 @@ int64_t notification_id, const std::u16string& reply_text) override; void SendShowNotificationAccessSetupRequest() override; + void SendFeatureSetupRequest(bool camera_roll, bool notifications) override; void SendRingDeviceRequest(bool device_ringing_enabled) override; void SendFetchCameraRollItemsRequest( const proto::FetchCameraRollItemsRequest& request) override;
diff --git a/ash/components/phonehub/message_sender_unittest.cc b/ash/components/phonehub/message_sender_unittest.cc index 04a5dd6..41893e9 100644 --- a/ash/components/phonehub/message_sender_unittest.cc +++ b/ash/components/phonehub/message_sender_unittest.cc
@@ -173,5 +173,17 @@ &request, fake_connection_manager_->sent_messages().back()); } +TEST_F(MessageSenderImplTest, SendFeatureSetupRequest) { + proto::FeatureSetupRequest request; + request.set_camera_roll_setup_requested(true); + request.set_notification_setup_requested(true); + + message_sender_->SendFeatureSetupRequest(/*camera_roll=*/true, + /*notifications=*/true); + + VerifyMessage(proto::MessageType::FEATURE_SETUP_REQUEST, &request, + fake_connection_manager_->sent_messages().back()); +} + } // namespace phonehub } // namespace ash
diff --git a/ash/components/phonehub/multidevice_feature_access_manager.cc b/ash/components/phonehub/multidevice_feature_access_manager.cc index 5ef4348..2c068560 100644 --- a/ash/components/phonehub/multidevice_feature_access_manager.cc +++ b/ash/components/phonehub/multidevice_feature_access_manager.cc
@@ -20,7 +20,7 @@ NotificationAccessSetupOperation::Delegate* delegate) { // Should only be able to start the setup process if notification access is // available but not yet granted. - // TODO: check camra roll access status once setup flow is wired up + // Legacy setup flow used when FeatureSetupRequest is not supported. if (GetNotificationAccessStatus() != AccessStatus::kAvailableButNotGranted) return nullptr; @@ -29,11 +29,45 @@ auto operation = base::WrapUnique(new NotificationAccessSetupOperation( delegate, - base::BindOnce(&MultideviceFeatureAccessManager::OnSetupOperationDeleted, - weak_ptr_factory_.GetWeakPtr(), operation_id))); - id_to_operation_map_.emplace(operation_id, operation.get()); + base::BindOnce( + &MultideviceFeatureAccessManager::OnNotificationSetupOperationDeleted, + weak_ptr_factory_.GetWeakPtr(), operation_id))); + id_to_notification_operation_map_.emplace(operation_id, operation.get()); - OnSetupRequested(); + OnNotificationSetupRequested(); + return operation; +} + +std::unique_ptr<CombinedAccessSetupOperation> +MultideviceFeatureAccessManager::AttemptCombinedFeatureSetup( + bool camera_roll, + bool notifications, + CombinedAccessSetupOperation::Delegate* delegate) { + // New setup flow for combined Camera Roll and/or Notifications setup using + // FeatureSetupRequest message type. + if (!GetFeatureSetupRequestSupported()) { + return nullptr; + } + if (GetCameraRollAccessStatus() != AccessStatus::kAvailableButNotGranted && + camera_roll) { + return nullptr; + } + if (GetNotificationAccessStatus() != AccessStatus::kAvailableButNotGranted && + notifications) { + return nullptr; + } + + int operation_id = next_operation_id_; + ++next_operation_id_; + + auto operation = base::WrapUnique(new CombinedAccessSetupOperation( + delegate, + base::BindOnce( + &MultideviceFeatureAccessManager::OnCombinedSetupOperationDeleted, + weak_ptr_factory_.GetWeakPtr(), operation_id))); + id_to_combined_operation_map_.emplace(operation_id, operation.get()); + + OnCombinedSetupRequested(camera_roll, notifications); return operation; } @@ -60,35 +94,78 @@ observer.OnAppsAccessChanged(); } +void MultideviceFeatureAccessManager:: + NotifyFeatureSetupRequestSupportedChanged() { + for (auto& observer : observer_list_) + observer.OnFeatureSetupRequestSupportedChanged(); +} + void MultideviceFeatureAccessManager::SetNotificationSetupOperationStatus( NotificationAccessSetupOperation::Status new_status) { - DCHECK(IsSetupOperationInProgress()); + DCHECK(IsNotificationSetupOperationInProgress()); PA_LOG(INFO) << "Notification access setup flow - new status: " << new_status; - for (auto& it : id_to_operation_map_) - it.second->NotifyStatusChanged(new_status); + for (auto& it : id_to_notification_operation_map_) + it.second->NotifyNotificationStatusChanged(new_status); if (NotificationAccessSetupOperation::IsFinalStatus(new_status)) - id_to_operation_map_.clear(); + id_to_notification_operation_map_.clear(); } -bool MultideviceFeatureAccessManager::IsSetupOperationInProgress() const { - return !id_to_operation_map_.empty(); +bool MultideviceFeatureAccessManager::IsNotificationSetupOperationInProgress() + const { + return !id_to_notification_operation_map_.empty(); } -void MultideviceFeatureAccessManager::OnSetupOperationDeleted( +void MultideviceFeatureAccessManager::OnNotificationSetupOperationDeleted( int operation_id) { - auto it = id_to_operation_map_.find(operation_id); - if (it == id_to_operation_map_.end()) + auto it = id_to_notification_operation_map_.find(operation_id); + if (it == id_to_notification_operation_map_.end()) return; - id_to_operation_map_.erase(it); + id_to_notification_operation_map_.erase(it); - if (id_to_operation_map_.empty()) + if (id_to_notification_operation_map_.empty()) PA_LOG(INFO) << "Notification access setup operation has ended."; } +void MultideviceFeatureAccessManager::SetCombinedSetupOperationStatus( + CombinedAccessSetupOperation::Status new_status) { + DCHECK(IsCombinedSetupOperationInProgress()); + + PA_LOG(INFO) << "Combined access setup flow - new status: " << new_status; + + for (auto& it : id_to_combined_operation_map_) + it.second->NotifyCombinedStatusChanged(new_status); + + if (CombinedAccessSetupOperation::IsFinalStatus(new_status)) + id_to_combined_operation_map_.clear(); +} + +bool MultideviceFeatureAccessManager::IsCombinedSetupOperationInProgress() + const { + return !id_to_combined_operation_map_.empty(); +} + +void MultideviceFeatureAccessManager::OnNotificationSetupRequested() {} + +void MultideviceFeatureAccessManager::OnCombinedSetupRequested( + bool camera_roll, + bool notifications) {} + +void MultideviceFeatureAccessManager::OnCombinedSetupOperationDeleted( + int operation_id) { + auto it = id_to_combined_operation_map_.find(operation_id); + if (it == id_to_combined_operation_map_.end()) + return; + + id_to_combined_operation_map_.erase(it); + + if (id_to_combined_operation_map_.empty()) + PA_LOG(INFO) << "Combined access setup operation has ended."; +} + void MultideviceFeatureAccessManager::Observer::OnNotificationAccessChanged() { // Optional method, inherit class doesn't have to implement this } @@ -101,6 +178,11 @@ // Optional method, inherit class doesn't have to implement this } +void MultideviceFeatureAccessManager::Observer:: + OnFeatureSetupRequestSupportedChanged() { + // Optional method, inherit class doesn't have to implement this +} + std::ostream& operator<<(std::ostream& stream, MultideviceFeatureAccessManager::AccessStatus status) { switch (status) {
diff --git a/ash/components/phonehub/multidevice_feature_access_manager.h b/ash/components/phonehub/multidevice_feature_access_manager.h index e61e32e..7d46f8c 100644 --- a/ash/components/phonehub/multidevice_feature_access_manager.h +++ b/ash/components/phonehub/multidevice_feature_access_manager.h
@@ -7,6 +7,7 @@ #include <ostream> +#include "ash/components/phonehub/combined_access_setup_operation.h" #include "ash/components/phonehub/notification_access_setup_operation.h" #include "ash/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h" #include "base/containers/flat_map.h" @@ -28,12 +29,15 @@ // has not yet been granted. If there is no active Phone Hub connection, we // assume that the last access value seen is the current value. // -// Additionally, this class provides an API for requesting the notification -// access setup flow via AttemptNotificationSetup(). +// This class provides two methods for requesting access permissions on the +// connected Android device: // -// In order for user to use camera roll feature, users need to explicit give -// consent for the feature to be enabled on the phone via the feature setup -// dialog. +// AttemptNotificationSetup() is the legacy setup flow that only supports setup +// of the Notifications feature. +// +// AttemptCombinedFeatureSetup() is the new setup flow that supports the +// Notifications and/or Camera Roll features. New features requiring setup +// should be added to this method's flow. class MultideviceFeatureAccessManager { public: // Status of a feature's access. Numerical values are stored in prefs and @@ -77,6 +81,10 @@ // Called when apps access has changed; use // GetAppsAccessStatus() for the new status. virtual void OnAppsAccessChanged(); + + // Called when FeatureSetupRequestSupported has changed; use + // GetFeatureSetupRequestSupported() for the new status. + virtual void OnFeatureSetupRequestSupportedChanged(); }; MultideviceFeatureAccessManager(MultideviceFeatureAccessManager&) = delete; @@ -96,6 +104,8 @@ virtual bool IsAccessRequestAllowed( multidevice_setup::mojom::Feature feature) = 0; + virtual bool GetFeatureSetupRequestSupported() const = 0; + // Returns the reason notification access status is prohibited. The return // result is valid if the current access status (from GetAccessStatus()) // is AccessStatus::kProhibited. Otherwise, the result is undefined and should @@ -121,6 +131,22 @@ std::unique_ptr<NotificationAccessSetupOperation> AttemptNotificationSetup( NotificationAccessSetupOperation::Delegate* delegate); + // Starts an attempt to enable the access for multiple features. |delegate| + // will be updated with the status of the flow as long as the operation object + // returned by this function remains instantiated. + // + // To cancel an ongoing setup attempt, delete the operation. If a setup + // attempt fails, clients can retry by calling AttemptCombinedFeatureSetup() + // again to start a new attempt. + // + // If a requested feature's access has already been granted, or the + // FeatureSetupRequest message is not supported on the phone, this function + // returns null. + std::unique_ptr<CombinedAccessSetupOperation> AttemptCombinedFeatureSetup( + bool camera_roll, + bool notifications, + CombinedAccessSetupOperation::Delegate* delegate); + void AddObserver(Observer* observer); void RemoveObserver(Observer* observer); @@ -130,12 +156,17 @@ void NotifyNotificationAccessChanged(); void NotifyCameraRollAccessChanged(); void NotifyAppsAccessChanged(); + void NotifyFeatureSetupRequestSupportedChanged(); void SetNotificationSetupOperationStatus( NotificationAccessSetupOperation::Status new_status); + void SetCombinedSetupOperationStatus( + CombinedAccessSetupOperation::Status new_status); - bool IsSetupOperationInProgress() const; + bool IsNotificationSetupOperationInProgress() const; + bool IsCombinedSetupOperationInProgress() const; - virtual void OnSetupRequested() {} + virtual void OnNotificationSetupRequested(); + virtual void OnCombinedSetupRequested(bool camera_roll, bool notifications); private: friend class MultideviceFeatureAccessManagerImplTest; @@ -149,12 +180,19 @@ // Sets the internal AccessStatus but does not send a request for // a new status to the remote phone device. virtual void SetCameraRollAccessStatusInternal( - AccessStatus camera_roll_access_status) = 0; + AccessStatus access_status) = 0; + // Sets internal status tracking if feature setup request message is + // supported by connected phone. + virtual void SetFeatureSetupRequestSupportedInternal(bool supported) = 0; - void OnSetupOperationDeleted(int operation_id); + void OnNotificationSetupOperationDeleted(int operation_id); + void OnCombinedSetupOperationDeleted(int operation_id); int next_operation_id_ = 0; - base::flat_map<int, NotificationAccessSetupOperation*> id_to_operation_map_; + base::flat_map<int, NotificationAccessSetupOperation*> + id_to_notification_operation_map_; + base::flat_map<int, CombinedAccessSetupOperation*> + id_to_combined_operation_map_; base::ObserverList<Observer> observer_list_; base::WeakPtrFactory<MultideviceFeatureAccessManager> weak_ptr_factory_{this}; };
diff --git a/ash/components/phonehub/multidevice_feature_access_manager_impl.cc b/ash/components/phonehub/multidevice_feature_access_manager_impl.cc index 26ed03d..bbd88897 100644 --- a/ash/components/phonehub/multidevice_feature_access_manager_impl.cc +++ b/ash/components/phonehub/multidevice_feature_access_manager_impl.cc
@@ -42,6 +42,7 @@ registry->RegisterBooleanPref(prefs::kHasDismissedSetupRequiredUi, false); registry->RegisterBooleanPref(prefs::kNeedsOneTimeNotificationAccessUpdate, true); + registry->RegisterBooleanPref(prefs::kFeatureSetupRequestSupported, false); } MultideviceFeatureAccessManagerImpl::MultideviceFeatureAccessManagerImpl( @@ -114,6 +115,11 @@ return static_cast<AccessStatus>(status); } +bool MultideviceFeatureAccessManagerImpl::GetFeatureSetupRequestSupported() + const { + return pref_service_->GetBoolean(prefs::kFeatureSetupRequestSupported); +} + MultideviceFeatureAccessManagerImpl::AccessProhibitedReason MultideviceFeatureAccessManagerImpl::GetNotificationAccessProhibitedReason() const { @@ -157,33 +163,80 @@ static_cast<int>(reason)); NotifyNotificationAccessChanged(); - if (!IsSetupOperationInProgress()) + if (IsNotificationSetupOperationInProgress()) { + switch (access_status) { + case AccessStatus::kProhibited: + SetNotificationSetupOperationStatus( + NotificationAccessSetupOperation::Status:: + kProhibitedFromProvidingAccess); + break; + case AccessStatus::kAccessGranted: + SetNotificationSetupOperationStatus( + NotificationAccessSetupOperation::Status::kCompletedSuccessfully); + break; + case AccessStatus::kAvailableButNotGranted: + // Intentionally blank; the operation status should not change. + break; + } + } else if (IsCombinedSetupOperationInProgress()) { + switch (access_status) { + case AccessStatus::kProhibited: + SetCombinedSetupOperationStatus(CombinedAccessSetupOperation::Status:: + kProhibitedFromProvidingAccess); + break; + case AccessStatus::kAccessGranted: + combined_setup_notifications_pending_ = false; + break; + case AccessStatus::kAvailableButNotGranted: + // Intentionally blank; the operation status should not change. + break; + } + if (!combined_setup_notifications_pending_ && + !combined_setup_camera_roll_pending_) { + SetCombinedSetupOperationStatus( + CombinedAccessSetupOperation::Status::kCompletedSuccessfully); + } + } +} + +void MultideviceFeatureAccessManagerImpl::SetCameraRollAccessStatusInternal( + AccessStatus access_status) { + PA_LOG(INFO) << "Camera Roll access: " << GetCameraRollAccessStatus() + << " => " << access_status; + pref_service_->SetInteger(prefs::kCameraRollAccessStatus, + static_cast<int>(access_status)); + NotifyCameraRollAccessChanged(); + + if (!IsCombinedSetupOperationInProgress()) { return; + } switch (access_status) { case AccessStatus::kProhibited: - SetNotificationSetupOperationStatus( - NotificationAccessSetupOperation::Status:: - kProhibitedFromProvidingAccess); + SetCombinedSetupOperationStatus( + CombinedAccessSetupOperation::Status::kProhibitedFromProvidingAccess); break; case AccessStatus::kAccessGranted: - SetNotificationSetupOperationStatus( - NotificationAccessSetupOperation::Status::kCompletedSuccessfully); + combined_setup_camera_roll_pending_ = false; break; case AccessStatus::kAvailableButNotGranted: // Intentionally blank; the operation status should not change. break; } + if (!combined_setup_notifications_pending_ && + !combined_setup_camera_roll_pending_) { + SetCombinedSetupOperationStatus( + CombinedAccessSetupOperation::Status::kCompletedSuccessfully); + } } -void MultideviceFeatureAccessManagerImpl::SetCameraRollAccessStatusInternal( - AccessStatus camera_roll_access_status) { - pref_service_->SetInteger(prefs::kCameraRollAccessStatus, - static_cast<int>(camera_roll_access_status)); - NotifyCameraRollAccessChanged(); +void MultideviceFeatureAccessManagerImpl:: + SetFeatureSetupRequestSupportedInternal(bool supported) { + pref_service_->SetBoolean(prefs::kFeatureSetupRequestSupported, supported); + NotifyFeatureSetupRequestSupportedChanged(); } -void MultideviceFeatureAccessManagerImpl::OnSetupRequested() { +void MultideviceFeatureAccessManagerImpl::OnNotificationSetupRequested() { PA_LOG(INFO) << "Notification access setup flow started."; switch (feature_status_provider_->GetStatus()) { @@ -210,10 +263,47 @@ } } -void MultideviceFeatureAccessManagerImpl::OnFeatureStatusChanged() { - if (!IsSetupOperationInProgress()) - return; +void MultideviceFeatureAccessManagerImpl::OnCombinedSetupRequested( + bool camera_roll, + bool notifications) { + combined_setup_camera_roll_pending_ = camera_roll; + combined_setup_notifications_pending_ = notifications; + PA_LOG(INFO) << "Combined access setup flow started."; + switch (feature_status_provider_->GetStatus()) { + // We're already connected, so request that the UI be shown on the phone. + case FeatureStatus::kEnabledAndConnected: + SendShowCombinedAccessSetupRequest(); + break; + // We're already connecting, so wait until a connection succeeds before + // trying to send a message + case FeatureStatus::kEnabledAndConnecting: + SetCombinedSetupOperationStatus( + CombinedAccessSetupOperation::Status::kConnecting); + break; + // We are not connected, so schedule a connection; once the + // connection succeeds, we'll send the message in OnFeatureStatusChanged(). + case FeatureStatus::kEnabledButDisconnected: + SetCombinedSetupOperationStatus( + CombinedAccessSetupOperation::Status::kConnecting); + connection_scheduler_->ScheduleConnectionNow(); + break; + default: + NOTREACHED(); + break; + } +} + +void MultideviceFeatureAccessManagerImpl::OnFeatureStatusChanged() { + if (IsNotificationSetupOperationInProgress()) { + FeatureStatusChangedNotificationAccessSetup(); + } else if (IsCombinedSetupOperationInProgress()) { + FeatureStatusChangedCombinedAccessSetup(); + } +} + +void MultideviceFeatureAccessManagerImpl:: + FeatureStatusChangedNotificationAccessSetup() { const FeatureStatus previous_feature_status = current_feature_status_; current_feature_status_ = feature_status_provider_->GetStatus(); @@ -245,6 +335,38 @@ } void MultideviceFeatureAccessManagerImpl:: + FeatureStatusChangedCombinedAccessSetup() { + const FeatureStatus previous_feature_status = current_feature_status_; + current_feature_status_ = feature_status_provider_->GetStatus(); + + if (previous_feature_status == current_feature_status_) + return; + + // If we were previously connecting and could not establish a connection, + // send a timeout state. + if (previous_feature_status == FeatureStatus::kEnabledAndConnecting && + current_feature_status_ != FeatureStatus::kEnabledAndConnected) { + SetCombinedSetupOperationStatus( + CombinedAccessSetupOperation::Status::kTimedOutConnecting); + return; + } + + // If we were previously connected and are now no longer connected, send a + // connection disconnected state. + if (previous_feature_status == FeatureStatus::kEnabledAndConnected && + current_feature_status_ != FeatureStatus::kEnabledAndConnected) { + SetCombinedSetupOperationStatus( + CombinedAccessSetupOperation::Status::kConnectionDisconnected); + return; + } + + if (current_feature_status_ == FeatureStatus::kEnabledAndConnected) { + SendShowCombinedAccessSetupRequest(); + return; + } +} + +void MultideviceFeatureAccessManagerImpl:: SendShowNotificationAccessSetupRequest() { message_sender_->SendShowNotificationAccessSetupRequest(); SetNotificationSetupOperationStatus( @@ -252,6 +374,14 @@ kSentMessageToPhoneAndWaitingForResponse); } +void MultideviceFeatureAccessManagerImpl::SendShowCombinedAccessSetupRequest() { + message_sender_->SendFeatureSetupRequest( + combined_setup_camera_roll_pending_, + combined_setup_notifications_pending_); + SetCombinedSetupOperationStatus(CombinedAccessSetupOperation::Status:: + kSentMessageToPhoneAndWaitingForResponse); +} + bool MultideviceFeatureAccessManagerImpl::HasAccessStatusChanged( AccessStatus access_status, AccessProhibitedReason reason) {
diff --git a/ash/components/phonehub/multidevice_feature_access_manager_impl.h b/ash/components/phonehub/multidevice_feature_access_manager_impl.h index a3098b5b..9348649fc 100644 --- a/ash/components/phonehub/multidevice_feature_access_manager_impl.h +++ b/ash/components/phonehub/multidevice_feature_access_manager_impl.h
@@ -45,15 +45,17 @@ AccessStatus GetNotificationAccessStatus() const override; AccessProhibitedReason GetNotificationAccessProhibitedReason() const override; void SetNotificationAccessStatusInternal( - AccessStatus notification_access_status, + AccessStatus access_status, AccessProhibitedReason reason) override; - void SetCameraRollAccessStatusInternal( - AccessStatus camera_roll_access_status) override; + void SetCameraRollAccessStatusInternal(AccessStatus access_status) override; AccessStatus GetCameraRollAccessStatus() const override; AccessStatus GetAppsAccessStatus() const override; bool IsAccessRequestAllowed( multidevice_setup::mojom::Feature feature) override; - void OnSetupRequested() override; + bool GetFeatureSetupRequestSupported() const override; + void SetFeatureSetupRequestSupportedInternal(bool supported) override; + void OnNotificationSetupRequested() override; + void OnCombinedSetupRequested(bool camera_roll, bool notifications) override; bool HasMultideviceFeatureSetupUiBeenDismissed() const override; void DismissSetupRequiredUi() override; @@ -61,7 +63,11 @@ // FeatureStatusProvider::Observer: void OnFeatureStatusChanged() override; + void FeatureStatusChangedNotificationAccessSetup(); + void FeatureStatusChangedCombinedAccessSetup(); + void SendShowNotificationAccessSetupRequest(); + void SendShowCombinedAccessSetupRequest(); bool HasAccessStatusChanged(AccessStatus access_status, AccessProhibitedReason reason); @@ -75,6 +81,9 @@ // Registers preference value change listeners. PrefChangeRegistrar pref_change_registrar_; + + bool combined_setup_notifications_pending_ = false; + bool combined_setup_camera_roll_pending_ = false; }; } // namespace phonehub
diff --git a/ash/components/phonehub/multidevice_feature_access_manager_impl_unittest.cc b/ash/components/phonehub/multidevice_feature_access_manager_impl_unittest.cc index 945c7d7..e8c1a69 100644 --- a/ash/components/phonehub/multidevice_feature_access_manager_impl_unittest.cc +++ b/ash/components/phonehub/multidevice_feature_access_manager_impl_unittest.cc
@@ -6,6 +6,7 @@ #include <memory> +#include "ash/components/phonehub/combined_access_setup_operation.h" #include "ash/components/phonehub/fake_connection_scheduler.h" #include "ash/components/phonehub/fake_feature_status_provider.h" #include "ash/components/phonehub/fake_message_sender.h" @@ -41,22 +42,25 @@ void OnCameraRollAccessChanged() override { ++num_calls_; } // MultideviceFeatureAccessManager::Observer: + void OnFeatureSetupRequestSupportedChanged() override { ++num_calls_; } + + // MultideviceFeatureAccessManager::Observer: void OnAppsAccessChanged() override { ++num_calls_; } private: size_t num_calls_ = 0; }; -class FakeOperationDelegate +class FakeNotificationAccessSetupOperationDelegate : public NotificationAccessSetupOperation::Delegate { public: - FakeOperationDelegate() = default; - ~FakeOperationDelegate() override = default; + FakeNotificationAccessSetupOperationDelegate() = default; + ~FakeNotificationAccessSetupOperationDelegate() override = default; NotificationAccessSetupOperation::Status status() const { return status_; } // NotificationAccessSetupOperation::Delegate: - void OnStatusChange( + void OnNotificationStatusChange( NotificationAccessSetupOperation::Status new_status) override { status_ = new_status; } @@ -66,6 +70,25 @@ NotificationAccessSetupOperation::Status::kConnecting; }; +class FakeCombinedAccessSetupOperationDelegate + : public CombinedAccessSetupOperation::Delegate { + public: + FakeCombinedAccessSetupOperationDelegate() = default; + ~FakeCombinedAccessSetupOperationDelegate() override = default; + + CombinedAccessSetupOperation::Status status() const { return status_; } + + // CombinedAccessSetupOperation::Delegate: + void OnCombinedStatusChange( + CombinedAccessSetupOperation::Status new_status) override { + status_ = new_status; + } + + private: + CombinedAccessSetupOperation::Status status_ = + CombinedAccessSetupOperation::Status::kConnecting; +}; + } // namespace class MultideviceFeatureAccessManagerImplTest : public testing::Test { @@ -97,13 +120,16 @@ void InitializeAccessStatus( AccessStatus notification_expected_status, AccessStatus camera_roll_expected_status, - AccessProhibitedReason reason = AccessProhibitedReason::kUnknown) { + AccessProhibitedReason reason = AccessProhibitedReason::kUnknown, + bool feature_setup_request_supported = false) { pref_service_.SetInteger(prefs::kNotificationAccessStatus, static_cast<int>(notification_expected_status)); pref_service_.SetInteger(prefs::kCameraRollAccessStatus, static_cast<int>(camera_roll_expected_status)); pref_service_.SetInteger(prefs::kNotificationAccessProhibitedReason, static_cast<int>(reason)); + pref_service_.SetBoolean(prefs::kFeatureSetupRequestSupported, + feature_setup_request_supported); SetNeedsOneTimeNotificationAccessUpdate(/*needs_update=*/false); manager_ = std::make_unique<MultideviceFeatureAccessManagerImpl>( &pref_service_, fake_multidevice_setup_client_.get(), @@ -120,7 +146,11 @@ NotificationAccessSetupOperation::Status GetNotificationAccessSetupOperationStatus() { - return fake_delegate_.status(); + return fake_notification_delegate_.status(); + } + + CombinedAccessSetupOperation::Status GetCombinedAccessSetupOperationStatus() { + return fake_combined_delegate_.status(); } void VerifyNotificationAccessGrantedState(AccessStatus expected_status) { @@ -154,18 +184,31 @@ EXPECT_EQ(expected_status, manager_->GetAppsAccessStatus()); } + void VerifyFeatureSetupRequestSupported(bool expected) { + EXPECT_EQ(expected, + pref_service_.GetBoolean(prefs::kFeatureSetupRequestSupported)); + EXPECT_EQ(expected, manager_->GetFeatureSetupRequestSupported()); + } + bool HasMultideviceFeatureSetupUiBeenDismissed() { return manager_->HasMultideviceFeatureSetupUiBeenDismissed(); } void DismissSetupRequiredUi() { manager_->DismissSetupRequiredUi(); } - std::unique_ptr<NotificationAccessSetupOperation> StartSetupOperation() { - return manager_->AttemptNotificationSetup(&fake_delegate_); + std::unique_ptr<NotificationAccessSetupOperation> + StartNotificationSetupOperation() { + return manager_->AttemptNotificationSetup(&fake_notification_delegate_); + } + std::unique_ptr<CombinedAccessSetupOperation> StartCombinedSetupOperation( + bool camera_roll, + bool notifications) { + return manager_->AttemptCombinedFeatureSetup(camera_roll, notifications, + &fake_combined_delegate_); } - bool IsSetupOperationInProgress() { - return manager_->IsSetupOperationInProgress(); + bool IsNotificationSetupOperationInProgress() { + return manager_->IsNotificationSetupOperationInProgress(); } void SetNotificationAccessStatusInternal(AccessStatus status, @@ -177,6 +220,10 @@ manager_->SetCameraRollAccessStatusInternal(status); } + void SetFeatureSetupRequestSupportedInternal(bool supported) { + manager_->SetFeatureSetupRequestSupportedInternal(supported); + } + void SetFeatureStatus(FeatureStatus status) { fake_feature_status_provider_->SetStatus(status); } @@ -193,6 +240,10 @@ return fake_message_sender_->show_notification_access_setup_request_count(); } + size_t GetCombinedAccessSetupRequestCallCount() const { + return fake_message_sender_->GetFeatureSetupRequestCallCount(); + } + size_t GetNumObserverCalls() const { return fake_observer_.num_calls(); } void SetNeedsOneTimeNotificationAccessUpdate(bool needs_update) { @@ -213,7 +264,8 @@ TestingPrefServiceSimple pref_service_; FakeObserver fake_observer_; - FakeOperationDelegate fake_delegate_; + FakeNotificationAccessSetupOperationDelegate fake_notification_delegate_; + FakeCombinedAccessSetupOperationDelegate fake_combined_delegate_; std::unique_ptr<multidevice_setup::FakeMultiDeviceSetupClient> fake_multidevice_setup_client_; std::unique_ptr<FakeFeatureStatusProvider> fake_feature_status_provider_; @@ -278,7 +330,7 @@ // Cannot start the notification access setup flow if notification and camera // roll access have already been granted. - auto operation = StartSetupOperation(); + auto operation = StartNotificationSetupOperation(); EXPECT_FALSE(operation); } @@ -301,7 +353,7 @@ GetNotificationAccessSetupOperationStatus()); // Simulate setup operation is in progress. This will trigger a sent // request. - auto operation = StartSetupOperation(); + auto operation = StartNotificationSetupOperation(); EXPECT_TRUE(operation); EXPECT_EQ(1u, GetNumShowNotificationAccessSetupRequestCount()); EXPECT_EQ(NotificationAccessSetupOperation::Status:: @@ -326,7 +378,7 @@ // Start a setup operation with enabled but disconnected status and access // not granted. - auto operation = StartSetupOperation(); + auto operation = StartNotificationSetupOperation(); EXPECT_TRUE(operation); EXPECT_EQ(1u, GetNumScheduleConnectionNowCalls()); @@ -361,7 +413,7 @@ // Start a setup operation with enabled but disconnected status and access // not granted. - auto operation = StartSetupOperation(); + auto operation = StartNotificationSetupOperation(); EXPECT_TRUE(operation); EXPECT_EQ(1u, GetNumScheduleConnectionNowCalls()); @@ -399,7 +451,7 @@ // Start a setup operation with enabled and connecting status and access // not granted. - auto operation = StartSetupOperation(); + auto operation = StartNotificationSetupOperation(); EXPECT_TRUE(operation); // Simulate changing states from connecting to connected. @@ -431,7 +483,7 @@ // Start a setup operation with enabled and connected status and access // not granted. - auto operation = StartSetupOperation(); + auto operation = StartNotificationSetupOperation(); EXPECT_TRUE(operation); // Verify that the request message has been sent and our operation status @@ -459,7 +511,7 @@ VerifyNotificationAccessGrantedState(AccessStatus::kAvailableButNotGranted); VerifyCameraRollAccessGrantedState(AccessStatus::kAvailableButNotGranted); - auto operation = StartSetupOperation(); + auto operation = StartNotificationSetupOperation(); EXPECT_TRUE(operation); // Simulate a disconnection and expect that status has been updated. @@ -478,7 +530,7 @@ VerifyNotificationAccessGrantedState(AccessStatus::kAvailableButNotGranted); VerifyCameraRollAccessGrantedState(AccessStatus::kAvailableButNotGranted); - auto operation = StartSetupOperation(); + auto operation = StartNotificationSetupOperation(); EXPECT_TRUE(operation); EXPECT_EQ(1u, GetNumShowNotificationAccessSetupRequestCount()); @@ -498,7 +550,7 @@ VerifyNotificationAccessGrantedState(AccessStatus::kAvailableButNotGranted); VerifyCameraRollAccessGrantedState(AccessStatus::kAvailableButNotGranted); - auto operation = StartSetupOperation(); + auto operation = StartNotificationSetupOperation(); EXPECT_TRUE(operation); EXPECT_EQ(1u, GetNumShowNotificationAccessSetupRequestCount()); @@ -678,5 +730,215 @@ EXPECT_EQ(3u, GetNumObserverCalls()); } +TEST_F(MultideviceFeatureAccessManagerImplTest, + FlipFeatureSetupRequestSupportedOn) { + InitializeAccessStatus(AccessStatus::kAvailableButNotGranted, + AccessStatus::kAvailableButNotGranted); + VerifyFeatureSetupRequestSupported(false); + + SetFeatureSetupRequestSupportedInternal(true); + VerifyFeatureSetupRequestSupported(true); + EXPECT_EQ(1u, GetNumObserverCalls()); +} + +TEST_F(MultideviceFeatureAccessManagerImplTest, + CombinedFeatureSetup_FeatureSetupRequestNotSupported) { + InitializeAccessStatus(AccessStatus::kAvailableButNotGranted, + AccessStatus::kAvailableButNotGranted); + VerifyNotificationAccessGrantedState(AccessStatus::kAvailableButNotGranted); + VerifyCameraRollAccessGrantedState(AccessStatus::kAvailableButNotGranted); + VerifyFeatureSetupRequestSupported(false); + + // Cannot start the combined access setup flow if FeatureSetupRequest is not + // supported. + auto operation = + StartCombinedSetupOperation(/*camera_roll=*/true, /*notifications=*/true); + EXPECT_FALSE(operation); +} + +TEST_F(MultideviceFeatureAccessManagerImplTest, + CombinedFeatureSetup_AllFeaturesGranted_AllFeaturesRequested) { + InitializeAccessStatus(AccessStatus::kAccessGranted, + AccessStatus::kAccessGranted, + AccessProhibitedReason::kUnknown, + /*feature_setup_request_supported=*/true); + VerifyNotificationAccessGrantedState(AccessStatus::kAccessGranted); + VerifyCameraRollAccessGrantedState(AccessStatus::kAccessGranted); + VerifyFeatureSetupRequestSupported(true); + + // Cannot start the combined access setup flow if requested feature access + // has already been granted. + auto operation = + StartCombinedSetupOperation(/*camera_roll=*/true, /*notifications=*/true); + EXPECT_FALSE(operation); +} + +TEST_F(MultideviceFeatureAccessManagerImplTest, + CombinedFeatureSetup_CameraRollGranted_AllFeaturesRequested) { + InitializeAccessStatus(AccessStatus::kAvailableButNotGranted, + AccessStatus::kAccessGranted, + AccessProhibitedReason::kUnknown, + /*feature_setup_request_supported=*/true); + VerifyNotificationAccessGrantedState(AccessStatus::kAvailableButNotGranted); + VerifyCameraRollAccessGrantedState(AccessStatus::kAccessGranted); + VerifyFeatureSetupRequestSupported(true); + + // Cannot start the combined access setup flow if requested feature access + // has already been granted. + auto operation = + StartCombinedSetupOperation(/*camera_roll=*/true, /*notifications=*/true); + EXPECT_FALSE(operation); +} + +TEST_F(MultideviceFeatureAccessManagerImplTest, + CombinedFeatureSetup_NotificationsGranted_AllFeaturesRequested) { + InitializeAccessStatus(AccessStatus::kAccessGranted, + AccessStatus::kAvailableButNotGranted, + AccessProhibitedReason::kUnknown, + /*feature_setup_request_supported=*/true); + VerifyNotificationAccessGrantedState(AccessStatus::kAccessGranted); + VerifyCameraRollAccessGrantedState(AccessStatus::kAvailableButNotGranted); + VerifyFeatureSetupRequestSupported(true); + + // Cannot start the combined access setup flow if requested feature access + // has already been granted. + auto operation = + StartCombinedSetupOperation(/*camera_roll=*/true, /*notifications=*/true); + EXPECT_FALSE(operation); +} + +TEST_F(MultideviceFeatureAccessManagerImplTest, + CombinedFeatureSetup_CameraRollGranted_NotificationsRequested) { + InitializeAccessStatus(AccessStatus::kAvailableButNotGranted, + AccessStatus::kAccessGranted, + AccessProhibitedReason::kUnknown, + /*feature_setup_request_supported=*/true); + VerifyNotificationAccessGrantedState(AccessStatus::kAvailableButNotGranted); + VerifyCameraRollAccessGrantedState(AccessStatus::kAccessGranted); + VerifyFeatureSetupRequestSupported(true); + + // Can start the combined access setup flow if requested feature access is not + // granted, even if other feature is granted. + auto operation = StartCombinedSetupOperation(/*camera_roll=*/false, + /*notifications=*/true); + EXPECT_TRUE(operation); +} + +TEST_F(MultideviceFeatureAccessManagerImplTest, + CombinedFeatureSetup_NotificationsGranted_CameraRollRequested) { + InitializeAccessStatus(AccessStatus::kAccessGranted, + AccessStatus::kAvailableButNotGranted, + AccessProhibitedReason::kUnknown, + /*feature_setup_request_supported=*/true); + VerifyNotificationAccessGrantedState(AccessStatus::kAccessGranted); + VerifyCameraRollAccessGrantedState(AccessStatus::kAvailableButNotGranted); + VerifyFeatureSetupRequestSupported(true); + + // Can start the combined access setup flow if requested feature access is not + // granted, even if other feature is granted. + auto operation = StartCombinedSetupOperation(/*camera_roll=*/true, + /*notifications=*/false); + EXPECT_TRUE(operation); +} + +TEST_F(MultideviceFeatureAccessManagerImplTest, + CombinedFeatureSetup_FullSetupFromDisconnected) { + // Set initial state to disconnected. + SetFeatureStatus(FeatureStatus::kEnabledButDisconnected); + + InitializeAccessStatus(AccessStatus::kAvailableButNotGranted, + AccessStatus::kAvailableButNotGranted, + AccessProhibitedReason::kUnknown, + /*feature_setup_request_supported=*/true); + VerifyNotificationAccessGrantedState(AccessStatus::kAvailableButNotGranted); + VerifyCameraRollAccessGrantedState(AccessStatus::kAvailableButNotGranted); + VerifyFeatureSetupRequestSupported(true); + + // Start combined setup operation + auto operation = StartCombinedSetupOperation(/*camera_roll=*/true, + /*notifications=*/true); + EXPECT_TRUE(operation); + EXPECT_EQ(0u, GetCombinedAccessSetupRequestCallCount()); + EXPECT_EQ(CombinedAccessSetupOperation::Status::kConnecting, + GetCombinedAccessSetupOperationStatus()); + EXPECT_EQ(1u, GetNumScheduleConnectionNowCalls()); + + // Simulate changing state to connecting. + SetFeatureStatus(FeatureStatus::kEnabledAndConnecting); + EXPECT_EQ(0u, GetCombinedAccessSetupRequestCallCount()); + EXPECT_EQ(CombinedAccessSetupOperation::Status::kConnecting, + GetCombinedAccessSetupOperationStatus()); + EXPECT_EQ(1u, GetNumScheduleConnectionNowCalls()); + + // Simulate changing state to connected. + SetFeatureStatus(FeatureStatus::kEnabledAndConnected); + EXPECT_EQ(1u, GetCombinedAccessSetupRequestCallCount()); + EXPECT_EQ(CombinedAccessSetupOperation::Status:: + kSentMessageToPhoneAndWaitingForResponse, + GetCombinedAccessSetupOperationStatus()); + + // Simulate Camera Roll being granted on phone. + SetCameraRollAccessStatusInternal(AccessStatus::kAccessGranted); + VerifyCameraRollAccessGrantedState(AccessStatus::kAccessGranted); + EXPECT_EQ(CombinedAccessSetupOperation::Status:: + kSentMessageToPhoneAndWaitingForResponse, + GetCombinedAccessSetupOperationStatus()); + + // Simulate Notifications being granted on phone. + SetNotificationAccessStatusInternal(AccessStatus::kAccessGranted, + AccessProhibitedReason::kUnknown); + VerifyNotificationAccessGrantedState(AccessStatus::kAccessGranted); + EXPECT_EQ(CombinedAccessSetupOperation::Status::kCompletedSuccessfully, + GetCombinedAccessSetupOperationStatus()); +} + +TEST_F(MultideviceFeatureAccessManagerImplTest, + CombinedFeatureSetup_SimulateTimeout) { + // Set initial state to connecting. + SetFeatureStatus(FeatureStatus::kEnabledAndConnecting); + + InitializeAccessStatus(AccessStatus::kAvailableButNotGranted, + AccessStatus::kAvailableButNotGranted, + AccessProhibitedReason::kUnknown, + /*feature_setup_request_supported=*/true); + VerifyNotificationAccessGrantedState(AccessStatus::kAvailableButNotGranted); + VerifyCameraRollAccessGrantedState(AccessStatus::kAvailableButNotGranted); + VerifyFeatureSetupRequestSupported(true); + + // Start combined setup operation + auto operation = StartCombinedSetupOperation(/*camera_roll=*/true, + /*notifications=*/true); + EXPECT_TRUE(operation); + + // Simulate a disconnection and expect that status has been updated. + SetFeatureStatus(FeatureStatus::kEnabledButDisconnected); + EXPECT_EQ(CombinedAccessSetupOperation::Status::kTimedOutConnecting, + GetCombinedAccessSetupOperationStatus()); +} + +TEST_F(MultideviceFeatureAccessManagerImplTest, + CombinedFeatureSetup_SimulateDisconnect) { + // Set initial state to connected. + SetFeatureStatus(FeatureStatus::kEnabledAndConnected); + + InitializeAccessStatus(AccessStatus::kAvailableButNotGranted, + AccessStatus::kAvailableButNotGranted, + AccessProhibitedReason::kUnknown, + /*feature_setup_request_supported=*/true); + VerifyNotificationAccessGrantedState(AccessStatus::kAvailableButNotGranted); + VerifyCameraRollAccessGrantedState(AccessStatus::kAvailableButNotGranted); + VerifyFeatureSetupRequestSupported(true); + + // Start combined setup operation + auto operation = StartCombinedSetupOperation(/*camera_roll=*/true, + /*notifications=*/true); + EXPECT_TRUE(operation); + + // Simulate a disconnection and expect that status has been updated. + SetFeatureStatus(FeatureStatus::kEnabledButDisconnected); + EXPECT_EQ(CombinedAccessSetupOperation::Status::kConnectionDisconnected, + GetCombinedAccessSetupOperationStatus()); +} + } // namespace phonehub } // namespace ash
diff --git a/ash/components/phonehub/notification_access_setup_operation.cc b/ash/components/phonehub/notification_access_setup_operation.cc index 1b4e914..e1d86c2 100644 --- a/ash/components/phonehub/notification_access_setup_operation.cc +++ b/ash/components/phonehub/notification_access_setup_operation.cc
@@ -56,7 +56,8 @@ std::move(destructor_callback_).Run(); } -void NotificationAccessSetupOperation::NotifyStatusChanged(Status new_status) { +void NotificationAccessSetupOperation::NotifyNotificationStatusChanged( + Status new_status) { base::UmaHistogramEnumeration("PhoneHub.NotificationAccessSetup.AllStatuses", new_status); if (new_status == Status::kCompletedSuccessfully) { @@ -68,7 +69,7 @@ } current_status_ = new_status; - delegate_->OnStatusChange(new_status); + delegate_->OnNotificationStatusChange(new_status); } std::ostream& operator<<(std::ostream& stream,
diff --git a/ash/components/phonehub/notification_access_setup_operation.h b/ash/components/phonehub/notification_access_setup_operation.h index efa59d3..b1f0773 100644 --- a/ash/components/phonehub/notification_access_setup_operation.h +++ b/ash/components/phonehub/notification_access_setup_operation.h
@@ -69,7 +69,7 @@ virtual ~Delegate() = default; // Called when status of the setup flow has changed. - virtual void OnStatusChange(Status new_status) = 0; + virtual void OnNotificationStatusChange(Status new_status) = 0; }; NotificationAccessSetupOperation(const NotificationAccessSetupOperation&) = @@ -84,7 +84,7 @@ NotificationAccessSetupOperation(Delegate* delegate, base::OnceClosure destructor_callback); - void NotifyStatusChanged(Status new_status); + void NotifyNotificationStatusChanged(Status new_status); absl::optional<Status> current_status_; const base::TimeTicks start_timestamp_ = base::TimeTicks::Now();
diff --git a/ash/components/phonehub/phone_status_processor.cc b/ash/components/phonehub/phone_status_processor.cc index 4651daf6..7a7a649 100644 --- a/ash/components/phonehub/phone_status_processor.cc +++ b/ash/components/phonehub/phone_status_processor.cc
@@ -284,6 +284,10 @@ recent_apps_interaction_handler_->set_user_states( GetUserStates(phone_properties.user_states())); } + + multidevice_feature_access_manager_->SetFeatureSetupRequestSupportedInternal( + phone_properties.feature_setup_config() + .feature_setup_request_supported()); } void PhoneStatusProcessor::MaybeSetPhoneModelName(
diff --git a/ash/components/phonehub/phone_status_processor_unittest.cc b/ash/components/phonehub/phone_status_processor_unittest.cc index 86fc413..a42ccb4 100644 --- a/ash/components/phonehub/phone_status_processor_unittest.cc +++ b/ash/components/phonehub/phone_status_processor_unittest.cc
@@ -180,6 +180,9 @@ proto::CameraRollAccessState* access_state = expected_phone_properties->mutable_camera_roll_access_state(); access_state->set_feature_enabled(true); + proto::FeatureSetupConfig* feature_setup_config = + expected_phone_properties->mutable_feature_setup_config(); + feature_setup_config->set_feature_setup_request_supported(true); expected_phone_properties->add_user_states(); proto::UserState* mutable_user_state = @@ -215,6 +218,8 @@ EXPECT_EQ( MultideviceFeatureAccessManager::AccessStatus::kAccessGranted, fake_multidevice_feature_access_manager_->GetCameraRollAccessStatus()); + EXPECT_TRUE(fake_multidevice_feature_access_manager_ + ->GetFeatureSetupRequestSupported()); EXPECT_EQ(ScreenLockManager::LockStatus::kUnknown, fake_screen_lock_manager_->GetLockStatus()); @@ -273,6 +278,9 @@ proto::CameraRollAccessState* access_state = expected_phone_properties->mutable_camera_roll_access_state(); access_state->set_feature_enabled(false); + proto::FeatureSetupConfig* feature_setup_config = + expected_phone_properties->mutable_feature_setup_config(); + feature_setup_config->set_feature_setup_request_supported(false); expected_phone_properties->add_user_states(); proto::UserState* mutable_user_state = @@ -307,6 +315,8 @@ EXPECT_EQ( MultideviceFeatureAccessManager::AccessStatus::kAvailableButNotGranted, fake_multidevice_feature_access_manager_->GetCameraRollAccessStatus()); + EXPECT_FALSE(fake_multidevice_feature_access_manager_ + ->GetFeatureSetupRequestSupported()); EXPECT_EQ(ScreenLockManager::LockStatus::kLockedOff, fake_screen_lock_manager_->GetLockStatus());
diff --git a/ash/components/phonehub/pref_names.cc b/ash/components/phonehub/pref_names.cc index 1ff0e5a..67fbab8 100644 --- a/ash/components/phonehub/pref_names.cc +++ b/ash/components/phonehub/pref_names.cc
@@ -65,6 +65,11 @@ // pref stores the vector value associated with Notification::AppMetadata. const char kRecentAppsHistory[] = "cros.phonehub.recent_apps_history"; +// Whether the phone supports setting up multiple features at the same time +// using the FeatureSetupRequest. +const char kFeatureSetupRequestSupported[] = + "cros.phonehub.feature_setup_request_supported"; + } // namespace prefs } // namespace phonehub } // namespace ash
diff --git a/ash/components/phonehub/pref_names.h b/ash/components/phonehub/pref_names.h index 1e2a49de..b4ae8bbc 100644 --- a/ash/components/phonehub/pref_names.h +++ b/ash/components/phonehub/pref_names.h
@@ -18,6 +18,7 @@ extern const char kNeedsOneTimeNotificationAccessUpdate[]; extern const char kScreenLockStatus[]; extern const char kRecentAppsHistory[]; +extern const char kFeatureSetupRequestSupported[]; } // namespace prefs } // namespace phonehub
diff --git a/ash/public/cpp/desk_template.cc b/ash/public/cpp/desk_template.cc index bd23c3b7..bb4874d 100644 --- a/ash/public/cpp/desk_template.cc +++ b/ash/public/cpp/desk_template.cc
@@ -35,9 +35,6 @@ case AppType::LACROS: return false; case AppType::ARC_APP: - if (!features::AreDesksTemplatesEnabled()) - return false; - break; case AppType::BROWSER: case AppType::CHROME_APP: case AppType::SYSTEM_APP:
diff --git a/ash/shelf/shelf_layout_manager_unittest.cc b/ash/shelf/shelf_layout_manager_unittest.cc index 18a18db..e1110e43 100644 --- a/ash/shelf/shelf_layout_manager_unittest.cc +++ b/ash/shelf/shelf_layout_manager_unittest.cc
@@ -59,6 +59,7 @@ #include "ash/system/status_area_widget_test_helper.h" #include "ash/system/unified/unified_system_tray.h" #include "ash/test/ash_test_base.h" +#include "ash/test/test_widget_builder.h" #include "ash/wallpaper/wallpaper_controller_impl.h" #include "ash/wm/lock_state_controller.h" #include "ash/wm/overview/overview_controller.h" @@ -1310,6 +1311,29 @@ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf_2->GetAutoHideState()); } +TEST_F(ShelfLayoutManagerTest, FullscreenWidgetHidesShelf) { + Shelf* shelf = GetPrimaryShelf(); + // Create a normal window. + views::Widget* widget = TestWidgetBuilder() + .SetBounds(gfx::Rect(11, 22, 300, 400)) + .BuildOwnedByNativeWidget(); + ASSERT_FALSE(widget->IsFullscreen()); + + // Shelf defaults to visible. + EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState()); + + // Fullscreen window hides it. + widget->SetFullscreen(true); + EXPECT_EQ(SHELF_HIDDEN, shelf->GetVisibilityState()); + + // Restoring the window restores it. + widget->Restore(); + EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState()); + + // Clean up. + widget->Close(); +} + // Tests that the shelf is only hidden for a fullscreen window at the front and // toggles visibility when another window is activated. TEST_F(ShelfLayoutManagerTest, FullscreenWindowInFrontHidesShelf) {
diff --git a/ash/shelf/shelf_unittest.cc b/ash/shelf/shelf_unittest.cc index 76dc61b..5e34c9a4 100644 --- a/ash/shelf/shelf_unittest.cc +++ b/ash/shelf/shelf_unittest.cc
@@ -25,6 +25,8 @@ #include "base/strings/stringprintf.h" #include "base/threading/thread_task_runner_handle.h" #include "components/session_manager/session_manager_types.h" +#include "ui/aura/client/aura_constants.h" +#include "ui/wm/core/window_util.h" namespace ash { namespace { @@ -110,6 +112,34 @@ shelf_model()->RemoveItemAt(index); } +// Various assertions around auto-hide behavior. +TEST_F(ShelfTest, ToggleAutoHide) { + std::unique_ptr<aura::Window> window = + std::make_unique<aura::Window>(nullptr); + window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + window->SetType(aura::client::WINDOW_TYPE_NORMAL); + window->Init(ui::LAYER_TEXTURED); + ParentWindowInPrimaryRootWindow(window.get()); + window->Show(); + wm::ActivateWindow(window.get()); + + Shelf* shelf = GetPrimaryShelf(); + shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways); + EXPECT_EQ(ShelfAutoHideBehavior::kAlways, shelf->auto_hide_behavior()); + + shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever); + EXPECT_EQ(ShelfAutoHideBehavior::kNever, shelf->auto_hide_behavior()); + + window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + EXPECT_EQ(ShelfAutoHideBehavior::kNever, shelf->auto_hide_behavior()); + + shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways); + EXPECT_EQ(ShelfAutoHideBehavior::kAlways, shelf->auto_hide_behavior()); + + shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever); + EXPECT_EQ(ShelfAutoHideBehavior::kNever, shelf->auto_hide_behavior()); +} + // Tests if shelf is hidden on secondary display after the primary display is // changed. TEST_F(ShelfTest, ShelfHiddenOnScreenOnSecondaryDisplay) {
diff --git a/ash/shell.cc b/ash/shell.cc index 78ad21c..1a9eccb 100644 --- a/ash/shell.cc +++ b/ash/shell.cc
@@ -115,6 +115,7 @@ #include "ash/system/brightness_control_delegate.h" #include "ash/system/caps_lock_notification_controller.h" #include "ash/system/firmware_update/firmware_update_notification_controller.h" +#include "ash/system/geolocation/geolocation_controller.h" #include "ash/system/hps/hps_orientation_controller.h" #include "ash/system/keyboard_brightness/keyboard_brightness_controller.h" #include "ash/system/keyboard_brightness_control_delegate.h" @@ -835,14 +836,17 @@ // Removes itself as an observer of |pref_service_|. shelf_controller_.reset(); - // NightLightControllerImpl depends on the PrefService as well as the window - // tree host manager, and must be destructed before them. crbug.com/724231. + // NightLightControllerImpl depends on the PrefService, the window tree host + // manager, and `geolocation_controller_`, so it must be destructed before + // them. crbug.com/724231. night_light_controller_ = nullptr; // Similarly for DockedMagnifierController. docked_magnifier_controller_ = nullptr; // Similarly for PrivacyScreenController. privacy_screen_controller_ = nullptr; + geolocation_controller_.reset(); + // NearbyShareDelegateImpl must be destroyed before SessionController and // NearbyShareControllerImpl. nearby_share_delegate_.reset(); @@ -1070,8 +1074,12 @@ ui::ColorProviderManager::Get().AppendColorProviderInitializer( base::BindRepeating(AddAshColorMixer)); - // Night Light depends on the display manager, the display color manager, and - // aura::Env, so initialize it after all have been initialized. + geolocation_controller_ = std::make_unique<GeolocationController>( + shell_delegate_->GetGeolocationUrlLoaderFactory()); + + // Night Light depends on the display manager, the display color manager, + // aura::Env, and geolocation controller, so initialize it after all have + // been initialized. night_light_controller_ = std::make_unique<NightLightControllerImpl>(); // Privacy Screen depends on the display manager, so initialize it after
diff --git a/ash/shell.h b/ash/shell.h index bfad360..4a24de05 100644 --- a/ash/shell.h +++ b/ash/shell.h
@@ -129,6 +129,7 @@ class FocusCycler; class FrameThrottlingController; class FullscreenMagnifierController; +class GeolocationController; class HighContrastController; class HighlighterController; class HoldingSpaceController; @@ -429,6 +430,9 @@ FullscreenMagnifierController* fullscreen_magnifier_controller() { return fullscreen_magnifier_controller_.get(); } + GeolocationController* geolocation_controller() { + return geolocation_controller_.get(); + } HighlighterController* highlighter_controller() { return highlighter_controller_.get(); } @@ -490,9 +494,6 @@ NightLightControllerImpl* night_light_controller() { return night_light_controller_.get(); } - PrivacyScreenController* privacy_screen_controller() { - return privacy_screen_controller_.get(); - } OverlayEventFilter* overlay_filter() { return overlay_filter_.get(); } ParentAccessController* parent_access_controller() { return parent_access_controller_.get(); @@ -512,6 +513,9 @@ PowerEventObserver* power_event_observer() { return power_event_observer_.get(); } + PrivacyScreenController* privacy_screen_controller() { + return privacy_screen_controller_.get(); + } quick_pair::Mediator* quick_pair_mediator() { return quick_pair_mediator_.get(); } @@ -768,6 +772,7 @@ firmware_update_notification_controller_; std::unique_ptr<FocusCycler> focus_cycler_; std::unique_ptr<FloatController> float_controller_; + std::unique_ptr<GeolocationController> geolocation_controller_; std::unique_ptr<HoldingSpaceController> holding_space_controller_; std::unique_ptr<HpsNotifyController> hps_notify_controller_; std::unique_ptr<HpsOrientationController> hps_orientation_controller_;
diff --git a/ash/shell_delegate.h b/ash/shell_delegate.h index d1e9423..5f4da634 100644 --- a/ash/shell_delegate.h +++ b/ash/shell_delegate.h
@@ -69,7 +69,7 @@ // Returns the geolocation loader factory used to initialize geolocation // provider. virtual scoped_refptr<network::SharedURLLoaderFactory> - GetGeolocationSharedURLLoaderFactory() const = 0; + GetGeolocationUrlLoaderFactory() const = 0; // Check whether the current tab of the browser window can go back. virtual bool CanGoBack(gfx::NativeWindow window) const = 0;
diff --git a/ash/shell_unittest.cc b/ash/shell_unittest.cc index 2076fbf..9b9f3d7 100644 --- a/ash/shell_unittest.cc +++ b/ash/shell_unittest.cc
@@ -34,13 +34,11 @@ #include "ash/wallpaper/wallpaper_widget_controller.h" #include "ash/wm/desks/desks_util.h" #include "ash/wm/overview/overview_controller.h" -#include "ash/wm/window_util.h" #include "base/command_line.h" #include "base/containers/flat_set.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/thread_task_runner_handle.h" #include "components/account_id/account_id.h" -#include "ui/aura/client/aura_constants.h" #include "ui/aura/env.h" #include "ui/aura/window.h" #include "ui/aura/window_event_dispatcher.h" @@ -429,65 +427,6 @@ widget->Close(); } -TEST_F(ShellTest, FullscreenWindowHidesShelf) { - ExpectAllContainers(); - - // Create a normal window. It is not maximized. - views::Widget* widget = TestWidgetBuilder() - .SetBounds(gfx::Rect(11, 22, 300, 400)) - .BuildOwnedByNativeWidget(); - EXPECT_FALSE(widget->IsMaximized()); - - // Shelf defaults to visible. - EXPECT_EQ(SHELF_VISIBLE, Shell::GetPrimaryRootWindowController() - ->GetShelfLayoutManager() - ->visibility_state()); - - // Fullscreen window hides it. - widget->SetFullscreen(true); - EXPECT_EQ(SHELF_HIDDEN, Shell::GetPrimaryRootWindowController() - ->GetShelfLayoutManager() - ->visibility_state()); - - // Restoring the window restores it. - widget->Restore(); - EXPECT_EQ(SHELF_VISIBLE, Shell::GetPrimaryRootWindowController() - ->GetShelfLayoutManager() - ->visibility_state()); - - // Clean up. - widget->Close(); -} - -// Various assertions around auto-hide behavior. -// TODO(jamescook): Move this to ShelfTest. -TEST_F(ShellTest, ToggleAutoHide) { - std::unique_ptr<aura::Window> window = - std::make_unique<aura::Window>(nullptr); - window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); - window->SetType(aura::client::WINDOW_TYPE_NORMAL); - window->Init(ui::LAYER_TEXTURED); - ParentWindowInPrimaryRootWindow(window.get()); - window->Show(); - wm::ActivateWindow(window.get()); - - Shelf* shelf = GetPrimaryShelf(); - shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways); - EXPECT_EQ(ShelfAutoHideBehavior::kAlways, shelf->auto_hide_behavior()); - - shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever); - EXPECT_EQ(ShelfAutoHideBehavior::kNever, shelf->auto_hide_behavior()); - - window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); - EXPECT_EQ(ShelfAutoHideBehavior::kNever, shelf->auto_hide_behavior()); - - shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways); - EXPECT_EQ(ShelfAutoHideBehavior::kAlways, shelf->auto_hide_behavior()); - - shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever); - EXPECT_EQ(ShelfAutoHideBehavior::kNever, shelf->auto_hide_behavior()); -} - // Tests that the cursor-filter is ahead of the drag-drop controller in the // pre-target list. TEST_F(ShellTest, TestPreTargetHandlerOrder) {
diff --git a/ash/style/ash_color_provider.cc b/ash/style/ash_color_provider.cc index 93332df3..cb18404 100644 --- a/ash/style/ash_color_provider.cc +++ b/ash/style/ash_color_provider.cc
@@ -280,10 +280,8 @@ return false; } - // Keep it at dark mode if it is not in an active user session or - // kDarkLightMode feature is not enabled. - // TODO(minch): Besides OOBE, make LIGHT as the color mode for other - // non-active user session as well while enabling D/L feature. + // Keep the color mode as DARK in login screen or when dark/light mode feature + // is not enabled. if (!active_user_pref_service_ || !features::IsDarkLightModeEnabled()) return true; return active_user_pref_service_->GetBoolean(prefs::kDarkModeEnabled);
diff --git a/ash/system/bluetooth/bluetooth_feature_pod_controller.cc b/ash/system/bluetooth/bluetooth_feature_pod_controller.cc index 8645ea5..06c5b0ff 100644 --- a/ash/system/bluetooth/bluetooth_feature_pod_controller.cc +++ b/ash/system/bluetooth/bluetooth_feature_pod_controller.cc
@@ -81,7 +81,10 @@ const { return first_connected_device_.has_value() && first_connected_device_.value().battery_info && - first_connected_device_.value().battery_info->default_properties; + (first_connected_device_.value().battery_info->default_properties || + first_connected_device_.value().battery_info->left_bud_info || + first_connected_device_.value().battery_info->right_bud_info || + first_connected_device_.value().battery_info->case_info); } const gfx::VectorIcon& BluetoothFeaturePodController::ComputeButtonIcon() @@ -103,6 +106,31 @@ return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BLUETOOTH); } +int BluetoothFeaturePodController:: + GetFirstConnectedDeviceBatteryLevelForDisplay() const { + // If there are any multiple battery details, we should prioritize showing + // them over the default battery in order to match the Quick Settings + // Bluetooth sub-page battery details shown. Android only shows the left bud + // if there are multiple batteries, so we match that here, and if it doesn't + // exist, we prioritize the right bud battery, then the case battery, if they + // exist over the default battery in order to match any detailed battery + // shown on the sub-page. + if (first_connected_device_.value().battery_info->left_bud_info) + return first_connected_device_.value() + .battery_info->left_bud_info->battery_percentage; + + if (first_connected_device_.value().battery_info->right_bud_info) + return first_connected_device_.value() + .battery_info->right_bud_info->battery_percentage; + + if (first_connected_device_.value().battery_info->case_info) + return first_connected_device_.value() + .battery_info->case_info->battery_percentage; + + return first_connected_device_.value() + .battery_info->default_properties->battery_percentage; +} + std::u16string BluetoothFeaturePodController::ComputeButtonSubLabel() const { if (!button_->IsToggled()) return l10n_util::GetStringUTF16( @@ -117,8 +145,7 @@ return l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_BLUETOOTH_DEVICE_BATTERY_PERCENTAGE_LABEL, base::NumberToString16( - first_connected_device_.value() - .battery_info->default_properties->battery_percentage)); + GetFirstConnectedDeviceBatteryLevelForDisplay())); } return l10n_util::GetStringUTF16( IDS_ASH_STATUS_TRAY_BLUETOOTH_DEVICE_CONNECTED_LABEL); @@ -140,8 +167,7 @@ IDS_ASH_STATUS_TRAY_BLUETOOTH_DEVICE_CONNECTED_WITH_BATTERY_TOOLTIP, first_connected_device_.value().device_name, base::NumberToString16( - first_connected_device_.value() - .battery_info->default_properties->battery_percentage)); + GetFirstConnectedDeviceBatteryLevelForDisplay())); } return l10n_util::GetStringFUTF16( IDS_ASH_STATUS_TRAY_BLUETOOTH_DEVICE_CONNECTED_TOOLTIP,
diff --git a/ash/system/bluetooth/bluetooth_feature_pod_controller.h b/ash/system/bluetooth/bluetooth_feature_pod_controller.h index 7537d5c..b1526ce 100644 --- a/ash/system/bluetooth/bluetooth_feature_pod_controller.h +++ b/ash/system/bluetooth/bluetooth_feature_pod_controller.h
@@ -53,6 +53,7 @@ }; bool DoesFirstConnectedDeviceHaveBatteryInfo() const; + int GetFirstConnectedDeviceBatteryLevelForDisplay() const; const gfx::VectorIcon& ComputeButtonIcon() const; std::u16string ComputeButtonLabel() const;
diff --git a/ash/system/bluetooth/bluetooth_feature_pod_controller_unittest.cc b/ash/system/bluetooth/bluetooth_feature_pod_controller_unittest.cc index 5174bc7..25b30ff 100644 --- a/ash/system/bluetooth/bluetooth_feature_pod_controller_unittest.cc +++ b/ash/system/bluetooth/bluetooth_feature_pod_controller_unittest.cc
@@ -46,6 +46,9 @@ const char* kDeviceNickname = "fancy squares"; const char* kDevicePublicName = "Rubik's Cube"; constexpr uint8_t kBatteryPercentage = 27; +constexpr uint8_t kLeftBudBatteryPercentage = 23; +constexpr uint8_t kRightBudBatteryPercentage = 11; +constexpr uint8_t kCaseBatteryPercentage = 77; // How many devices to "pair" for tests that require multiple connected devices. constexpr int kMultipleDeviceCount = 3; @@ -80,6 +83,31 @@ return battery_info; } + DeviceBatteryInfoPtr CreateMultipleBatteryInfo( + absl::optional<int> left_bud_battery, + absl::optional<int> case_battery, + absl::optional<int> right_bud_battery) { + DeviceBatteryInfoPtr battery_info = DeviceBatteryInfo::New(); + + if (left_bud_battery) { + battery_info->left_bud_info = BatteryProperties::New(); + battery_info->left_bud_info->battery_percentage = + left_bud_battery.value(); + } + + if (case_battery) { + battery_info->case_info = BatteryProperties::New(); + battery_info->case_info->battery_percentage = case_battery.value(); + } + + if (right_bud_battery) { + battery_info->right_bud_info = BatteryProperties::New(); + battery_info->right_bud_info->battery_percentage = + right_bud_battery.value(); + } + return battery_info; + } + void ExpectBluetoothDetailedViewFocused() { EXPECT_TRUE(tray_view()->detailed_view()); const FeaturePodIconButton::Views& children = @@ -300,6 +328,48 @@ } TEST_F(BluetoothFeaturePodControllerTest, + HasCorrectMetadataWithOneDevice_MultipleBatteries) { + SetSystemState(BluetoothSystemState::kEnabled); + + const std::u16string public_name = base::ASCIIToUTF16(kDevicePublicName); + auto paired_device = PairedBluetoothDeviceProperties::New(); + paired_device->device_properties = BluetoothDeviceProperties::New(); + paired_device->device_properties->public_name = public_name; + paired_device->device_properties->connection_state = + DeviceConnectionState::kConnected; + paired_device->device_properties->battery_info = + CreateMultipleBatteryInfo(/*left_bud_battery=*/kLeftBudBatteryPercentage, + /*case_battery=*/kCaseBatteryPercentage, + /*right_battery=*/kRightBudBatteryPercentage); + SetConnectedDevice(paired_device); + + const ash::FeaturePodLabelButton* label_button = feature_pod_label_button(); + EXPECT_EQ(l10n_util::GetStringFUTF16( + IDS_ASH_STATUS_TRAY_BLUETOOTH_DEVICE_BATTERY_PERCENTAGE_LABEL, + base::NumberToString16(kLeftBudBatteryPercentage)), + label_button->GetSubLabelText()); + + paired_device->device_properties->battery_info = + CreateMultipleBatteryInfo(/*left_bud_battery=*/absl::nullopt, + /*case_battery=*/kCaseBatteryPercentage, + /*right_battery=*/kRightBudBatteryPercentage); + SetConnectedDevice(paired_device); + EXPECT_EQ(l10n_util::GetStringFUTF16( + IDS_ASH_STATUS_TRAY_BLUETOOTH_DEVICE_BATTERY_PERCENTAGE_LABEL, + base::NumberToString16(kRightBudBatteryPercentage)), + label_button->GetSubLabelText()); + + paired_device->device_properties->battery_info = CreateMultipleBatteryInfo( + /*left_bud_battery=*/absl::nullopt, + /*case_battery=*/kCaseBatteryPercentage, /*right_battery=*/absl::nullopt); + SetConnectedDevice(paired_device); + EXPECT_EQ(l10n_util::GetStringFUTF16( + IDS_ASH_STATUS_TRAY_BLUETOOTH_DEVICE_BATTERY_PERCENTAGE_LABEL, + base::NumberToString16(kCaseBatteryPercentage)), + label_button->GetSubLabelText()); +} + +TEST_F(BluetoothFeaturePodControllerTest, HasCorrectMetadataWithMultipleDevice) { SetSystemState(BluetoothSystemState::kEnabled);
diff --git a/ash/system/eche/eche_tray.cc b/ash/system/eche/eche_tray.cc index 7451be5..1eaa9207 100644 --- a/ash/system/eche/eche_tray.cc +++ b/ash/system/eche/eche_tray.cc
@@ -7,7 +7,7 @@ #include <algorithm> #include "ash/accessibility/accessibility_controller_impl.h" -#include "ash/constants/ash_features.h" +#include "ash/components/multidevice/logging/logging.h" #include "ash/public/cpp/ash_web_view.h" #include "ash/public/cpp/ash_web_view_factory.h" #include "ash/public/cpp/shell_window_ids.h" @@ -27,6 +27,7 @@ #include "ash/system/tray/tray_container.h" #include "ash/system/tray/tray_popup_utils.h" #include "ash/system/tray/tray_utils.h" +#include "ash/webui/eche_app_ui/mojom/eche_app.mojom.h" #include "base/bind.h" #include "base/callback_forward.h" #include "components/account_id/account_id.h" @@ -62,9 +63,9 @@ constexpr int kIconSize = 22; constexpr int kHeaderHeight = 40; -constexpr int kHeaderHorizontalInteriorMargins = 12; +constexpr int kHeaderHorizontalInteriorMargins = 0; constexpr gfx::Insets kHeaderDefaultSpacing = - gfx::Insets(/*vertical=*/0, /*horizontal=*/8); + gfx::Insets(/*vertical=*/0, /*horizontal=*/6); constexpr gfx::Insets kBubblePadding(/*vertical=*/8, /*horizontal=*/8); @@ -195,6 +196,23 @@ HideBubbleWithView(bubble_view); } +void EcheTray::OnStreamStatusChanged(eche_app::mojom::StreamStatus status) { + switch (status) { + case eche_app::mojom::StreamStatus::kStreamStatusStarted: + ShowBubble(); + break; + case eche_app::mojom::StreamStatus::kStreamStatusStopped: + PurgeAndClose(); + break; + case eche_app::mojom::StreamStatus::kStreamStatusInitializing: + // Ignores initializing stream status. + break; + case eche_app::mojom::StreamStatus::kStreamStatusUnknown: + PA_LOG(WARNING) << "Unexpected stream status"; + break; + } +} + void EcheTray::OnLockStateChanged(bool locked) { if (bubble_ && locked) PurgeAndClose(); @@ -330,7 +348,7 @@ ->SetInteriorMargin( gfx::Insets(/*vertical=*/0, /*horizontal=*/kHeaderHorizontalInteriorMargins)) - .SetCollapseMargins(true) + .SetCollapseMargins(false) .SetMinimumCrossAxisSize(kHeaderHeight) .SetDefault(views::kMarginsKey, kHeaderDefaultSpacing) .SetCrossAxisAlignment(views::LayoutAlignment::kCenter);
diff --git a/ash/system/eche/eche_tray.h b/ash/system/eche/eche_tray.h index 9941cfa..795e86c0 100644 --- a/ash/system/eche/eche_tray.h +++ b/ash/system/eche/eche_tray.h
@@ -12,6 +12,7 @@ #include "ash/session/session_controller_impl.h" #include "ash/system/eche/eche_icon_loading_indicator_view.h" #include "ash/system/tray/tray_background_view.h" +#include "ash/webui/eche_app_ui/mojom/eche_app.mojom.h" #include "base/gtest_prod_util.h" #include "components/session_manager/session_manager_types.h" #include "ui/views/controls/button/button.h" @@ -97,6 +98,12 @@ void HideBubble(); + // Receives the `status` change when the video streaming is started or + // stopped. Controls the bubble widget based on the different `status` + // changes. There are two cases: 1. Shows the bubble when the streaming is + // started. 2. Purges and closes the bubble when the streaming is stopped. + void OnStreamStatusChanged(eche_app::mojom::StreamStatus status); + // Set up the params and init the bubble. // Note: This function makes the bubble active and makes the // TrayBackgroundView's background inkdrop activate.
diff --git a/ash/system/eche/eche_tray_unittest.cc b/ash/system/eche/eche_tray_unittest.cc index 688ca8b..d6586f8 100644 --- a/ash/system/eche/eche_tray_unittest.cc +++ b/ash/system/eche/eche_tray_unittest.cc
@@ -32,8 +32,7 @@ void SetUp() override { feature_list_.InitWithFeatures( /*enabled_features=*/{chromeos::features::kEcheSWA, - chromeos::features::kEcheCustomWidget, - chromeos::features::kEcheSWAInBackground}, + chromeos::features::kEcheCustomWidget}, /*disabled_features=*/{}); DCHECK(test_web_view_factory_.get()); @@ -140,4 +139,38 @@ EXPECT_FALSE(phone_hub_tray()->eche_loading_indicator()->GetAnimating()); } +TEST_F(EcheTrayTest, EcheTrayCreatesBubbleButStreamStatusChanged) { + // 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()); + + // Allow us to create the bubble but it is not visible until we need this + // bubble to show up. + eche_tray()->LoadBubble(GURL("http://google.com"), gfx::Image()); + + 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()); + + // When the streaming status changes, the bubble should show up. + eche_tray()->OnStreamStatusChanged( + eche_app::mojom::StreamStatus::kStreamStatusStarted); + // 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()); + + // Change the streaming status, the bubble should be closed. + eche_tray()->OnStreamStatusChanged( + eche_app::mojom::StreamStatus::kStreamStatusStopped); + // Wait for the tray bubble widget to close. + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(eche_tray()->is_active()); + EXPECT_FALSE(eche_tray()->GetVisible()); +} + } // namespace ash
diff --git a/ash/system/geolocation/geolocation_controller.cc b/ash/system/geolocation/geolocation_controller.cc index 3e351d08..b71560a 100644 --- a/ash/system/geolocation/geolocation_controller.cc +++ b/ash/system/geolocation/geolocation_controller.cc
@@ -39,7 +39,8 @@ GeolocationController::GeolocationController( scoped_refptr<network::SharedURLLoaderFactory> factory) - : provider_(std::move(factory), + : factory_(factory.get()), + provider_(std::move(factory), SimpleGeolocationProvider::DefaultGeolocationProviderURL()), backoff_delay_(kMinimumDelayAfterFailure), timer_(std::make_unique<base::OneShotTimer>()) {
diff --git a/ash/system/geolocation/geolocation_controller.h b/ash/system/geolocation/geolocation_controller.h index bcb155a3..d5297a89 100644 --- a/ash/system/geolocation/geolocation_controller.h +++ b/ash/system/geolocation/geolocation_controller.h
@@ -89,6 +89,8 @@ static base::TimeDelta GetNextRequestDelayAfterSuccessForTesting(); + network::SharedURLLoaderFactory* GetFactoryForTesting() { return factory_; } + void SetTimerForTesting(std::unique_ptr<base::OneShotTimer> timer); void SetClockForTesting(base::Clock* clock); @@ -128,6 +130,8 @@ // the chances of getting inaccurate values, especially around DST changes. base::Time GetSunRiseSet(bool sunrise) const; + network::SharedURLLoaderFactory* const factory_; + // The IP-based geolocation provider. SimpleGeolocationProvider provider_;
diff --git a/ash/system/geolocation/geolocation_controller_test_util.cc b/ash/system/geolocation/geolocation_controller_test_util.cc new file mode 100644 index 0000000..f9adfff --- /dev/null +++ b/ash/system/geolocation/geolocation_controller_test_util.cc
@@ -0,0 +1,38 @@ +// 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/geolocation/geolocation_controller_test_util.h" + +namespace ash { + +// ----------------------------------------------------------------------------- +// GeolocationControllerObserver: + +void GeolocationControllerObserver::OnGeopositionChanged( + bool possible_change_in_timezone) { + position_received_num_++; + possible_change_in_timezone_ = possible_change_in_timezone; +} + +// ----------------------------------------------------------------------------- +// GeopositionResponsesWaiter: + +GeopositionResponsesWaiter::GeopositionResponsesWaiter() { + GeolocationController::Get()->AddObserver(this); +} + +GeopositionResponsesWaiter::~GeopositionResponsesWaiter() { + GeolocationController::Get()->RemoveObserver(this); +} + +void GeopositionResponsesWaiter::Wait() { + run_loop_.Run(); +} + +void GeopositionResponsesWaiter::OnGeopositionChanged( + bool possible_change_in_timezone) { + run_loop_.Quit(); +} + +} // namespace ash \ No newline at end of file
diff --git a/ash/system/geolocation/geolocation_controller_test_util.h b/ash/system/geolocation/geolocation_controller_test_util.h new file mode 100644 index 0000000..d8ba1c6 --- /dev/null +++ b/ash/system/geolocation/geolocation_controller_test_util.h
@@ -0,0 +1,62 @@ +// 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_GEOLOCATION_GEOLOCATION_CONTROLLER_TEST_UTIL_H_ +#define ASH_SYSTEM_GEOLOCATION_GEOLOCATION_CONTROLLER_TEST_UTIL_H_ + +#include "ash/system/geolocation/geolocation_controller.h" +#include "base/run_loop.h" + +namespace ash { + +// An observer class to GeolocationController which updates sunset and sunrise +// time. +class GeolocationControllerObserver : public GeolocationController::Observer { + public: + GeolocationControllerObserver() = default; + + GeolocationControllerObserver(const GeolocationControllerObserver&) = delete; + GeolocationControllerObserver& operator=( + const GeolocationControllerObserver&) = delete; + + ~GeolocationControllerObserver() override = default; + + // GeolocationController::Observer: + void OnGeopositionChanged(bool possible_change_in_timezone) override; + + int position_received_num() const { return position_received_num_; } + bool possible_change_in_timezone() const { + return possible_change_in_timezone_; + } + + private: + // The number of times a new position is received. + int position_received_num_ = 0; + bool possible_change_in_timezone_ = false; +}; + +// Used for waiting for the geoposition to be responded from the server to all +// observers. +class GeopositionResponsesWaiter : public GeolocationController::Observer { + public: + GeopositionResponsesWaiter(); + + GeopositionResponsesWaiter(const GeopositionResponsesWaiter&) = delete; + GeopositionResponsesWaiter& operator=(const GeopositionResponsesWaiter&) = + delete; + + ~GeopositionResponsesWaiter() override; + + void Wait(); + + // GeolocationController::Observer: + void OnGeopositionChanged(bool possible_change_in_timezone) override; + + private: + base::RunLoop run_loop_; +}; + +} // namespace ash + +#endif // ASH_SYSTEM_GEOLOCATION_GEOLOCATION_CONTROLLER_TEST_UTIL_H_ \ No newline at end of file
diff --git a/ash/system/geolocation/geolocation_controller_unittest.cc b/ash/system/geolocation/geolocation_controller_unittest.cc index 0736e71..c40276d 100644 --- a/ash/system/geolocation/geolocation_controller_unittest.cc +++ b/ash/system/geolocation/geolocation_controller_unittest.cc
@@ -4,6 +4,9 @@ #include "ash/system/geolocation/geolocation_controller.h" +#include "ash/shell.h" +#include "ash/system/geolocation/geolocation_controller_test_util.h" +#include "ash/system/geolocation/test_geolocation_url_loader_factory.h" #include "ash/system/time/time_of_day.h" #include "ash/test/ash_test_base.h" #include "base/strings/utf_string_conversions.h" @@ -27,82 +30,30 @@ return chromeos::system::TimezoneSettings::GetTimezoneID(timezone); } -// An observer class to GeolocationController which updates sunset and sunrise -// time. -class GeolocationControllerObserver : public GeolocationController::Observer { - public: - GeolocationControllerObserver() = default; - - GeolocationControllerObserver(const GeolocationControllerObserver&) = delete; - GeolocationControllerObserver& operator=( - const GeolocationControllerObserver&) = delete; - - ~GeolocationControllerObserver() override = default; - - // TODO(crbug.com/1269915): Add `sunset_` and `sunrise_` and update their - // values when receiving the new position. - void OnGeopositionChanged(bool possible_change_in_timezone) override { - position_received_num_++; - } - - int position_received_num() const { return position_received_num_; } - - private: - // The number of times a new position is received. - int position_received_num_ = 0; -}; - -// A fake implementation of GeolocationController that doesn't perform any -// actual geoposition requests. -class FakeGeolocationController : public GeolocationController { - public: - FakeGeolocationController(base::SimpleTestClock* test_clock, - std::unique_ptr<base::MockOneShotTimer> mock_timer) - : GeolocationController(/*url_context_getter=*/nullptr) { - SetTimerForTesting(std::move(mock_timer)); - SetClockForTesting(test_clock); - } - - FakeGeolocationController(const FakeGeolocationController&) = delete; - FakeGeolocationController& operator=(const FakeGeolocationController&) = - delete; - - ~FakeGeolocationController() override = default; - - void set_position_to_send(const Geoposition& position) { - position_to_send_ = position; - } - - const Geoposition& position_to_send() const { return position_to_send_; } - - int geoposition_requests_num() const { return geoposition_requests_num_; } - - private: - // GeolocationController: - void RequestGeoposition() override { - OnGeoposition(position_to_send_, /*server_error=*/false, base::TimeDelta()); - ++geoposition_requests_num_; - } - - // The position to send to the controller the next time OnGeoposition is - // invoked. - Geoposition position_to_send_; - - // The number of new geoposition requests that have been triggered. - int geoposition_requests_num_ = 0; -}; - // Base test fixture. class GeolocationControllerTest : public AshTestBase { public: - GeolocationControllerTest() { + GeolocationControllerTest() = default; + GeolocationControllerTest(const GeolocationControllerTest&) = delete; + GeolocationControllerTest& operator=(const GeolocationControllerTest&) = + delete; + + ~GeolocationControllerTest() override = default; + + // AshTestBase: + void SetUp() override { + AshTestBase::SetUp(); + controller_ = ash::Shell::Get()->geolocation_controller(); + test_clock_.SetNow(base::Time::Now()); std::unique_ptr<base::MockOneShotTimer> mock_timer = std::make_unique<base::MockOneShotTimer>(); mock_timer_ptr_ = mock_timer.get(); - controller_ = std::make_unique<FakeGeolocationController>( - &test_clock_, std::move(mock_timer)); + controller_->SetTimerForTesting(std::move(mock_timer)); + controller_->SetClockForTesting(&test_clock_); + factory_ = static_cast<TestGeolocationUrlLoaderFactory*>( + controller_->GetFactoryForTesting()); // Prepare a valid geoposition. Geoposition position; @@ -111,120 +62,154 @@ position.status = Geoposition::STATUS_OK; position.accuracy = 10; position.timestamp = base::Time::Now(); - controller_->set_position_to_send(position); + SetServerPosition(position); } - GeolocationControllerTest(const GeolocationControllerTest&) = delete; - GeolocationControllerTest& operator=(const GeolocationControllerTest&) = - delete; + GeolocationController* controller() const { return controller_; } + base::SimpleTestClock* test_clock() { return &test_clock_; } + base::MockOneShotTimer* mock_timer_ptr() const { return mock_timer_ptr_; } + const Geoposition& position() const { return position_; } - ~GeolocationControllerTest() override = default; + // Fires the timer of the scheduler to request geoposition and wait for all + // observers to receive the latest geoposition from the server. + void FireTimerToFetchGeoposition() { + GeopositionResponsesWaiter waiter; + // Make sure that the timer is running indicating that the client runs + // the scheduler. + EXPECT_TRUE(mock_timer_ptr()->IsRunning()); + // Fast forward the scheduler to reach the time when the controller + // requests for geoposition from the server in + // `GeolocationController::RequestGeoposition`. + mock_timer_ptr_->Fire(); + // Waits for the observers to receive the geoposition from the server. + waiter.Wait(); + } - protected: - std::unique_ptr<FakeGeolocationController> controller_; + // Sets the geoposition to be returned from the `factory_` upon the + // `GeolocationController` request. + void SetServerPosition(const Geoposition& position) { + position_ = position; + factory_->set_position(position_); + } + + private: + GeolocationController* controller_; base::SimpleTestClock test_clock_; base::MockOneShotTimer* mock_timer_ptr_; + TestGeolocationUrlLoaderFactory* factory_; + Geoposition position_; }; +// Tests adding and removing an observer should request and stop receiving +// a position update. +TEST_F(GeolocationControllerTest, Observer) { + EXPECT_FALSE(mock_timer_ptr()->IsRunning()); + + // Add an observer should start the timer requesting the geoposition. + GeolocationControllerObserver observer; + controller()->AddObserver(&observer); + FireTimerToFetchGeoposition(); + EXPECT_EQ(1, observer.position_received_num()); + + // Check that the timer fires another schedule after a successful request. + EXPECT_TRUE(mock_timer_ptr()->IsRunning()); + + // Removing an observer should stop the timer. + controller()->RemoveObserver(&observer); + EXPECT_FALSE(mock_timer_ptr()->IsRunning()); + EXPECT_EQ(1, observer.position_received_num()); +} + // Tests adding and removing observer and make sure that only observing ones // receive the position updates. TEST_F(GeolocationControllerTest, MultipleObservers) { - EXPECT_EQ(0, controller_->geoposition_requests_num()); - EXPECT_FALSE(mock_timer_ptr_->IsRunning()); + EXPECT_FALSE(mock_timer_ptr()->IsRunning()); // Add an observer should start the timer requesting for the first // geoposition request. GeolocationControllerObserver observer1; - controller_->AddObserver(&observer1); - EXPECT_TRUE(mock_timer_ptr_->IsRunning()); - mock_timer_ptr_->Fire(); - EXPECT_EQ(1, controller_->geoposition_requests_num()); + controller()->AddObserver(&observer1); + FireTimerToFetchGeoposition(); EXPECT_EQ(1, observer1.position_received_num()); + EXPECT_TRUE(mock_timer_ptr()->IsRunning()); // Since `OnGeoposition()` handling a geoposition update always schedule // the next geoposition request, the timer should keep running and // update position periodically. - EXPECT_TRUE(mock_timer_ptr_->IsRunning()); - mock_timer_ptr_->Fire(); - EXPECT_EQ(2, controller_->geoposition_requests_num()); + FireTimerToFetchGeoposition(); EXPECT_EQ(2, observer1.position_received_num()); + EXPECT_TRUE(mock_timer_ptr()->IsRunning()); // Adding `observer2` should not interrupt the request flow. Check that both // observers receive the new position. GeolocationControllerObserver observer2; - controller_->AddObserver(&observer2); - mock_timer_ptr_->Fire(); - EXPECT_EQ(3, controller_->geoposition_requests_num()); + controller()->AddObserver(&observer2); + FireTimerToFetchGeoposition(); EXPECT_EQ(3, observer1.position_received_num()); EXPECT_EQ(1, observer2.position_received_num()); + EXPECT_TRUE(mock_timer_ptr()->IsRunning()); // Remove `observer1` and make sure that the timer is still running. // Only `observer2` should receive the new position. - controller_->RemoveObserver(&observer1); - mock_timer_ptr_->Fire(); - EXPECT_EQ(4, controller_->geoposition_requests_num()); + controller()->RemoveObserver(&observer1); + FireTimerToFetchGeoposition(); EXPECT_EQ(3, observer1.position_received_num()); EXPECT_EQ(2, observer2.position_received_num()); + EXPECT_TRUE(mock_timer_ptr()->IsRunning()); // Removing `observer2` should stop the timer. The request count should // not change. - controller_->RemoveObserver(&observer2); - EXPECT_FALSE(mock_timer_ptr_->IsRunning()); - EXPECT_EQ(4, controller_->geoposition_requests_num()); + controller()->RemoveObserver(&observer2); + EXPECT_FALSE(mock_timer_ptr()->IsRunning()); EXPECT_EQ(3, observer1.position_received_num()); EXPECT_EQ(2, observer2.position_received_num()); } // Tests that controller only pushes valid positions. TEST_F(GeolocationControllerTest, InvalidPositions) { - EXPECT_EQ(0, controller_->geoposition_requests_num()); GeolocationControllerObserver observer; // Update to an invalid position - Geoposition position = controller_->position_to_send(); - position.status = Geoposition::STATUS_TIMEOUT; - controller_->set_position_to_send(position); - EXPECT_FALSE(mock_timer_ptr_->IsRunning()); - controller_->AddObserver(&observer); - EXPECT_TRUE(mock_timer_ptr_->IsRunning()); + Geoposition invalid_position(position()); + invalid_position.error_code = 10; + SetServerPosition(invalid_position); + EXPECT_FALSE(mock_timer_ptr()->IsRunning()); + controller()->AddObserver(&observer); // If the position is invalid, the controller won't push the geoposition // update to its observers. - mock_timer_ptr_->Fire(); - EXPECT_EQ(1, controller_->geoposition_requests_num()); + EXPECT_TRUE(mock_timer_ptr()->IsRunning()); + mock_timer_ptr()->Fire(); + // Wait for the request and response to finish. + base::RunLoop().RunUntilIdle(); EXPECT_EQ(0, observer.position_received_num()); - - // If the position is valid, the controller pushes the update to observers. - position.status = Geoposition::STATUS_OK; - controller_->set_position_to_send(position); - mock_timer_ptr_->Fire(); - EXPECT_EQ(2, controller_->geoposition_requests_num()); - EXPECT_EQ(1, observer.position_received_num()); + // With error response, the server will retry with another timer which we + // have no control over, so `mock_timer_ptr_` will not be running (refers to + // `SimpleGeolocationRequest::Retry()` for more detail). + EXPECT_FALSE(mock_timer_ptr()->IsRunning()); } // Tests that timezone changes result. TEST_F(GeolocationControllerTest, TimezoneChanges) { - EXPECT_EQ(0, controller_->geoposition_requests_num()); - EXPECT_FALSE(mock_timer_ptr_->IsRunning()); - controller_->SetCurrentTimezoneIdForTesting(u"America/Los_Angeles"); + EXPECT_FALSE(mock_timer_ptr()->IsRunning()); + controller()->SetCurrentTimezoneIdForTesting(u"America/Los_Angeles"); // Add an observer. GeolocationControllerObserver observer; - controller_->AddObserver(&observer); + controller()->AddObserver(&observer); EXPECT_EQ(0, observer.position_received_num()); - EXPECT_TRUE(mock_timer_ptr_->IsRunning()); - mock_timer_ptr_->Fire(); - EXPECT_EQ(1, controller_->geoposition_requests_num()); + FireTimerToFetchGeoposition(); EXPECT_EQ(1, observer.position_received_num()); - EXPECT_EQ(u"America/Los_Angeles", controller_->current_timezone_id()); + EXPECT_EQ(u"America/Los_Angeles", controller()->current_timezone_id()); + EXPECT_TRUE(mock_timer_ptr()->IsRunning()); // A new timezone results in new geoposition request. auto timezone = CreateTimezone("Asia/Tokyo"); - controller_->TimezoneChanged(*timezone); - mock_timer_ptr_->Fire(); - EXPECT_EQ(2, controller_->geoposition_requests_num()); + controller()->TimezoneChanged(*timezone); + FireTimerToFetchGeoposition(); EXPECT_EQ(2, observer.position_received_num()); - EXPECT_EQ(GetTimezoneId(*timezone), controller_->current_timezone_id()); + EXPECT_EQ(GetTimezoneId(*timezone), controller()->current_timezone_id()); + EXPECT_TRUE(mock_timer_ptr()->IsRunning()); } // Tests obtaining sunset/sunrise time when there is no valid geoposition, for @@ -237,9 +222,9 @@ // If geoposition is unset, the controller should return the default sunset // and sunrise time . - EXPECT_EQ(controller_->GetSunsetTime(), + EXPECT_EQ(controller()->GetSunsetTime(), TimeOfDay(kDefaultSunsetTimeOffsetMinutes).ToTimeToday()); - EXPECT_EQ(controller_->GetSunriseTime(), + EXPECT_EQ(controller()->GetSunriseTime(), TimeOfDay(kDefaultSunriseTimeOffsetMinutes).ToTimeToday()); } @@ -248,13 +233,22 @@ TEST_F(GeolocationControllerTest, GetSunRiseSet) { base::Time now; EXPECT_TRUE(base::Time::FromUTCString("23 Dec 2021 12:00:00", &now)); - test_clock_.SetNow(now); + test_clock()->SetNow(now); base::Time sunrise; EXPECT_TRUE(base::Time::FromUTCString("23 Dec 2021 04:14:36.626", &sunrise)); base::Time sunset; EXPECT_TRUE(base::Time::FromUTCString("23 Dec 2021 14:59:58.459", &sunset)); + // Add an observer and make sure that sunset and sunrise time are not + // updated until the timer is fired. + GeolocationControllerObserver observer1; + controller()->AddObserver(&observer1); + EXPECT_TRUE(mock_timer_ptr()->IsRunning()); + EXPECT_NE(controller()->GetSunsetTime(), sunset); + EXPECT_NE(controller()->GetSunriseTime(), sunrise); + EXPECT_EQ(0, observer1.position_received_num()); + // Prepare a valid geoposition. Geoposition position; position.latitude = 23.5; @@ -263,16 +257,14 @@ position.accuracy = 10; position.timestamp = now; - GeolocationControllerObserver observer1; - controller_->AddObserver(&observer1); - EXPECT_TRUE(mock_timer_ptr_->IsRunning()); - controller_->set_position_to_send(position); - - EXPECT_EQ(0, controller_->geoposition_requests_num()); - mock_timer_ptr_->Fire(); - EXPECT_EQ(1, controller_->geoposition_requests_num()); - EXPECT_EQ(controller_->GetSunsetTime(), sunset); - EXPECT_EQ(controller_->GetSunriseTime(), sunrise); + // Test that after sending the new position, sunrise and sunset time are + // updated correctly. + SetServerPosition(position); + FireTimerToFetchGeoposition(); + EXPECT_EQ(1, observer1.position_received_num()); + EXPECT_EQ(controller()->GetSunsetTime(), sunset); + EXPECT_EQ(controller()->GetSunriseTime(), sunrise); + EXPECT_TRUE(mock_timer_ptr()->IsRunning()); } } // namespace
diff --git a/ash/system/geolocation/test_geolocation_url_loader_factory.cc b/ash/system/geolocation/test_geolocation_url_loader_factory.cc index 18b3c9d..155204f 100644 --- a/ash/system/geolocation/test_geolocation_url_loader_factory.cc +++ b/ash/system/geolocation/test_geolocation_url_loader_factory.cc
@@ -66,6 +66,10 @@ return nullptr; } +void TestGeolocationUrlLoaderFactory::ClearResponses() { + test_url_loader_factory_.ClearResponses(); +} + TestGeolocationUrlLoaderFactory::~TestGeolocationUrlLoaderFactory() = default; } // namespace ash
diff --git a/ash/system/geolocation/test_geolocation_url_loader_factory.h b/ash/system/geolocation/test_geolocation_url_loader_factory.h index 0653ee5..61416d1 100644 --- a/ash/system/geolocation/test_geolocation_url_loader_factory.h +++ b/ash/system/geolocation/test_geolocation_url_loader_factory.h
@@ -39,6 +39,9 @@ void set_position(Geoposition position) { position_ = position; } const Geoposition& position() const { return position_; } + // Clears all added responses in `test_url_loader_factory_`. + void ClearResponses(); + protected: ~TestGeolocationUrlLoaderFactory() override;
diff --git a/ash/system/network/fake_network_detailed_network_view.cc b/ash/system/network/fake_network_detailed_network_view.cc new file mode 100644 index 0000000..0d08f96 --- /dev/null +++ b/ash/system/network/fake_network_detailed_network_view.cc
@@ -0,0 +1,23 @@ +// 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/network/fake_network_detailed_network_view.h" + +#include "ash/system/network/network_detailed_network_view.h" + +namespace ash { +namespace tray { + +FakeNetworkDetailedNetworkView::FakeNetworkDetailedNetworkView( + Delegate* delegate) + : NetworkDetailedNetworkView(delegate) {} + +FakeNetworkDetailedNetworkView::~FakeNetworkDetailedNetworkView() = default; + +views::View* FakeNetworkDetailedNetworkView::GetAsView() { + return this; +} + +} // namespace tray +} // namespace ash \ No newline at end of file
diff --git a/ash/system/network/fake_network_detailed_network_view.h b/ash/system/network/fake_network_detailed_network_view.h new file mode 100644 index 0000000..682eab4 --- /dev/null +++ b/ash/system/network/fake_network_detailed_network_view.h
@@ -0,0 +1,35 @@ +// 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_NETWORK_FAKE_NETWORK_DETAILED_NETWORK_VIEW_H_ +#define ASH_SYSTEM_NETWORK_FAKE_NETWORK_DETAILED_NETWORK_VIEW_H_ + +#include "ash/ash_export.h" +#include "ash/system/network/network_detailed_network_view.h" +#include "ui/views/view.h" + +namespace ash { +namespace tray { + +// Fake implementation of NetworkDetailedNetworkView. +class ASH_EXPORT FakeNetworkDetailedNetworkView + : public NetworkDetailedNetworkView, + public views::View { + public: + explicit FakeNetworkDetailedNetworkView(Delegate* delegate); + FakeNetworkDetailedNetworkView(const FakeNetworkDetailedNetworkView&) = + delete; + FakeNetworkDetailedNetworkView& operator=( + const FakeNetworkDetailedNetworkView&) = delete; + ~FakeNetworkDetailedNetworkView() override; + + private: + // NetworkDetailedNetworkView: + views::View* GetAsView() override; +}; + +} // namespace tray +} // namespace ash + +#endif // ASH_SYSTEM_NETWORK_FAKE_NETWORK_DETAILED_NETWORK_VIEW_H_
diff --git a/ash/system/network/network_list_view_controller.cc b/ash/system/network/network_list_view_controller.cc new file mode 100644 index 0000000..40b7d0bd --- /dev/null +++ b/ash/system/network/network_list_view_controller.cc
@@ -0,0 +1,33 @@ +// 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/network/network_list_view_controller.h" + +#include "ash/system/network/network_detailed_network_view.h" +#include "ash/system/network/network_list_view_controller_impl.h" + +namespace ash { + +namespace tray { + +namespace { +NetworkListViewController::Factory* g_test_factory = nullptr; +} // namespace + +std::unique_ptr<NetworkListViewController> +NetworkListViewController::Factory::Create( + tray::NetworkDetailedNetworkView* network_detailed_network_view) { + if (g_test_factory) + return g_test_factory->CreateForTesting(); // IN-TEST + return std::make_unique<NetworkListViewControllerImpl>( + network_detailed_network_view); +} + +void NetworkListViewController::Factory::SetFactoryForTesting( + Factory* test_factory) { + g_test_factory = test_factory; +} + +} // namespace tray +} // namespace ash \ No newline at end of file
diff --git a/ash/system/network/network_list_view_controller.h b/ash/system/network/network_list_view_controller.h new file mode 100644 index 0000000..487d391 --- /dev/null +++ b/ash/system/network/network_list_view_controller.h
@@ -0,0 +1,48 @@ +// 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_NETWORK_NETWORK_LIST_VIEW_CONTROLLER_H_ +#define ASH_SYSTEM_NETWORK_NETWORK_LIST_VIEW_CONTROLLER_H_ + +#include "ash/ash_export.h" +#include "ash/system/network/network_detailed_network_view.h" + +namespace ash { +namespace tray { + +// This class defines the interface used to add, modify, and remove networks +// from the network list of the detailed network device page within the quick +// settings. This class includes the definition of the factory used to create +// instances of implementations of this class. +class ASH_EXPORT NetworkListViewController { + public: + class Factory { + public: + Factory(const Factory&) = delete; + const Factory& operator=(const Factory&) = delete; + virtual ~Factory() = default; + + static std::unique_ptr<NetworkListViewController> Create( + tray::NetworkDetailedNetworkView* network_detailed_network_view); + static void SetFactoryForTesting(Factory* test_factory); + + protected: + Factory() = default; + + virtual std::unique_ptr<NetworkListViewController> CreateForTesting() = 0; + }; + + NetworkListViewController(const NetworkListViewController&) = delete; + NetworkListViewController& operator=(const NetworkListViewController&) = + delete; + virtual ~NetworkListViewController() = default; + + protected: + NetworkListViewController() = default; +}; + +} // namespace tray +} // namespace ash + +#endif // ASH_SYSTEM_NETWORK_NETWORK_LIST_VIEW_CONTROLLER_H_
diff --git a/ash/system/network/network_list_view_controller_impl.cc b/ash/system/network/network_list_view_controller_impl.cc new file mode 100644 index 0000000..d76f9999 --- /dev/null +++ b/ash/system/network/network_list_view_controller_impl.cc
@@ -0,0 +1,42 @@ +// 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/network/network_list_view_controller_impl.h" + +#include "ash/constants/ash_features.h" +#include "ash/shell.h" +#include "ash/system/model/system_tray_model.h" +#include "ash/system/network/network_detailed_network_view.h" +#include "ash/system/network/tray_network_state_model.h" + +namespace ash { +namespace tray { + +NetworkListViewControllerImpl::NetworkListViewControllerImpl( + NetworkDetailedNetworkView* network_detailed_network_view) + : network_detailed_network_view_(network_detailed_network_view) { + DCHECK(ash::features::IsQuickSettingsNetworkRevampEnabled()); + DCHECK(network_detailed_network_view_); + Shell::Get()->system_tray_model()->network_state_model()->AddObserver(this); +} + +NetworkListViewControllerImpl::~NetworkListViewControllerImpl() { + Shell::Get()->system_tray_model()->network_state_model()->RemoveObserver( + this); +} + +void NetworkListViewControllerImpl::ActiveNetworkStateChanged() { + // TODO(b/207089013): Implement this function. +} + +void NetworkListViewControllerImpl::NetworkListChanged() { + // TODO(b/207089013): Implement this function. +} + +void NetworkListViewControllerImpl::DeviceStateListChanged() { + // TODO(b/207089013): Implement this function. +} + +} // namespace tray +} // namespace ash \ No newline at end of file
diff --git a/ash/system/network/network_list_view_controller_impl.h b/ash/system/network/network_list_view_controller_impl.h new file mode 100644 index 0000000..658f3339 --- /dev/null +++ b/ash/system/network/network_list_view_controller_impl.h
@@ -0,0 +1,47 @@ +// 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_NETWORK_NETWORK_LIST_VIEW_CONTROLLER_IMPL_H_ +#define ASH_SYSTEM_NETWORK_NETWORK_LIST_VIEW_CONTROLLER_IMPL_H_ + +#include "ash/ash_export.h" + +#include "ash/system/network/network_list_view_controller.h" +#include "ash/system/network/tray_network_state_observer.h" + +namespace ash { +namespace tray { + +class NetworkDetailedNetworkView; + +// Implementation of NetworkListViewController +class ASH_EXPORT NetworkListViewControllerImpl + : public TrayNetworkStateObserver, + public NetworkListViewController { + public: + NetworkListViewControllerImpl( + NetworkDetailedNetworkView* network_detailed_network_view); + NetworkListViewControllerImpl(const NetworkListViewController&) = delete; + NetworkListViewControllerImpl& operator=( + const NetworkListViewControllerImpl&) = delete; + ~NetworkListViewControllerImpl() override; + + protected: + tray::NetworkDetailedNetworkView* network_detailed_network_view() { + return network_detailed_network_view_; + } + + private: + // TrayNetworkStateObserver: + void ActiveNetworkStateChanged() override; + void NetworkListChanged() override; + void DeviceStateListChanged() override; + + tray::NetworkDetailedNetworkView* network_detailed_network_view_; +}; + +} // namespace tray +} // namespace ash + +#endif // ASH_SYSTEM_NETWORK_NETWORK_LIST_VIEW_CONTROLLER_IMPL_H_
diff --git a/ash/system/network/network_list_view_controller_unittest.cc b/ash/system/network/network_list_view_controller_unittest.cc new file mode 100644 index 0000000..959a57b --- /dev/null +++ b/ash/system/network/network_list_view_controller_unittest.cc
@@ -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. + +#include "ash/system/network/network_list_view_controller_impl.h" + +#include <memory> + +#include "ash/constants/ash_features.h" +#include "ash/system/network/fake_network_detailed_network_view.h" +#include "ash/test/ash_test_base.h" +#include "base/test/scoped_feature_list.h" + +namespace ash { +namespace tray { + +class NetworkListViewControllerTest : public AshTestBase { + public: + void SetUp() override { + AshTestBase::SetUp(); + + feature_list_.InitAndEnableFeature(features::kQuickSettingsNetworkRevamp); + + fake_network_detailed_network_view_ = + std::make_unique<FakeNetworkDetailedNetworkView>( + /*delegate=*/nullptr); + + network_list_view_controller_impl_ = + std::make_unique<NetworkListViewControllerImpl>( + fake_network_detailed_network_view_.get()); + } + + void TearDown() override { + network_list_view_controller_impl_.reset(); + fake_network_detailed_network_view_.reset(); + + AshTestBase::TearDown(); + } + + FakeNetworkDetailedNetworkView* fake_network_detailed_network_view() { + return fake_network_detailed_network_view_.get(); + } + + NetworkListViewController* network_list_view_controller_impl() { + return network_list_view_controller_impl_.get(); + } + + private: + base::test::ScopedFeatureList feature_list_; + std::unique_ptr<FakeNetworkDetailedNetworkView> + fake_network_detailed_network_view_; + std::unique_ptr<NetworkListViewControllerImpl> + network_list_view_controller_impl_; +}; + +TEST_F(NetworkListViewControllerTest, CanConstruct) { + EXPECT_TRUE(true); +} + +} // namespace tray +} // namespace ash \ No newline at end of file
diff --git a/ash/system/scheduled_feature/scheduled_feature_unittest.cc b/ash/system/scheduled_feature/scheduled_feature_unittest.cc index 5934338..5b4027c 100644 --- a/ash/system/scheduled_feature/scheduled_feature_unittest.cc +++ b/ash/system/scheduled_feature/scheduled_feature_unittest.cc
@@ -15,12 +15,13 @@ #include "ash/session/test_session_controller_client.h" #include "ash/shell.h" #include "ash/system/geolocation/geolocation_controller.h" +#include "ash/system/geolocation/geolocation_controller_test_util.h" +#include "ash/system/geolocation/test_geolocation_url_loader_factory.h" #include "ash/system/scheduled_feature/scheduled_feature.h" #include "ash/system/time/time_of_day.h" #include "ash/test/ash_test_base.h" #include "ash/test/ash_test_helper.h" #include "ash/test_shell_delegate.h" -#include "base/bind.h" #include "base/command_line.h" #include "base/memory/ptr_util.h" #include "base/strings/pattern.h" @@ -70,69 +71,6 @@ const char* GetFeatureName() const override { return "TestFeature"; } }; -// An observer class to GeolocationController which updates sunset and sunrise -// time. -class GeolocationControllerObserver : public GeolocationController::Observer { - public: - GeolocationControllerObserver() = default; - - GeolocationControllerObserver(const GeolocationControllerObserver&) = delete; - GeolocationControllerObserver& operator=( - const GeolocationControllerObserver&) = delete; - - ~GeolocationControllerObserver() override = default; - - // TODO(crbug.com/1269915): Add `sunset_` and `sunrise_` and update their - // values when receiving the new position. - void OnGeopositionChanged(bool possible_change_in_timezone) override { - position_received_num_++; - possible_change_in_timezone_ = possible_change_in_timezone; - } - - int position_received_num() const { return position_received_num_; } - bool possible_change_in_timezone() const { - return possible_change_in_timezone_; - } - - private: - // The number of times a new position is received. - int position_received_num_ = 0; - bool possible_change_in_timezone_ = false; -}; - -// A fake implementation of GeolocationController that doesn't perform any -// actual geoposition requests. -class FakeGeolocationController : public GeolocationController { - public: - FakeGeolocationController(base::SimpleTestClock* test_clock, - std::unique_ptr<base::MockOneShotTimer> mock_timer) - : GeolocationController(/*url_context_getter=*/nullptr) { - SetTimerForTesting(std::move(mock_timer)); - SetClockForTesting(test_clock); - } - - FakeGeolocationController(const FakeGeolocationController&) = delete; - FakeGeolocationController& operator=(const FakeGeolocationController&) = - delete; - - ~FakeGeolocationController() override = default; - - void set_position_to_send(const Geoposition& position) { - position_to_send_ = position; - } - - protected: - // GeolocationController: - void RequestGeoposition() override { - OnGeoposition(position_to_send_, /*server_error=*/false, base::TimeDelta()); - } - - private: - // The position to send to the observer by the controller the next time - // `OnGeoposition()` is invoked. - Geoposition position_to_send_; -}; - class ScheduledFeatureTest : public NoSessionAshTestBase { public: ScheduledFeatureTest() = default; @@ -150,7 +88,15 @@ AccountId::FromUserEmail(kUser2Email)); } - ScheduledFeature* feature() { return feature_.get(); } + TestScheduledFeature* feature() const { return feature_.get(); } + GeolocationController* geolocation_controller() { + return geolocation_controller_; + } + base::SimpleTestClock* test_clock() { return &test_clock_; } + const base::MockOneShotTimer* mock_timer_ptr() const { + return mock_timer_ptr_; + } + TestGeolocationUrlLoaderFactory* factory() const { return factory_; } // AshTestBase: void SetUp() override { @@ -161,12 +107,6 @@ // Simulate user 1 login. SimulateNewUserFirstLogin(kUser1Email); - std::unique_ptr<base::MockOneShotTimer> mock_timer = - std::make_unique<base::MockOneShotTimer>(); - mock_timer_ptr_ = mock_timer.get(); - geolocation_controller_ = std::make_unique<FakeGeolocationController>( - &test_clock_, std::move(mock_timer)); - // Use user prefs of NightLight, which is an example of ScheduledFeature. feature_ = std::make_unique<TestScheduledFeature>( prefs::kNightLightEnabled, prefs::kNightLightScheduleType, @@ -177,13 +117,28 @@ feature_->OnActiveUserPrefServiceChanged( Shell::Get()->session_controller()->GetActivePrefService()); + // Set the clock of geolocation controller to our test clock to control the + // time now. + geolocation_controller_ = ash::Shell::Get()->geolocation_controller(); base::Time now; EXPECT_TRUE(base::Time::FromUTCString("23 Dec 2021 12:00:00", &now)); - test_clock_.SetNow(now); + test_clock()->SetNow(now); + geolocation_controller()->SetClockForTesting(&test_clock_); + + // Set the timer of geolocation controller to `mock_timer` to allow us to + // trigger new geoposition receiving. + std::unique_ptr<base::MockOneShotTimer> mock_timer = + std::make_unique<base::MockOneShotTimer>(); + mock_timer_ptr_ = mock_timer.get(); + geolocation_controller()->SetTimerForTesting(std::move(mock_timer)); + + // `factory_` allows the test to control the value of geoposition + // that the geolocation provider sends back upon geolocation request. + factory_ = static_cast<TestGeolocationUrlLoaderFactory*>( + geolocation_controller()->GetFactoryForTesting()); } void TearDown() override { - geolocation_controller_.reset(); feature_.reset(); NoSessionAshTestBase::TearDown(); } @@ -227,11 +182,35 @@ return TimeOfDay(hour * 60).SetClock(&test_clock_); } - protected: + // Fires the timer of the scheduler to request geoposition and wait for all + // observers to receive the latest geoposition from the server. + void FireTimerToFetchGeoposition() { + GeopositionResponsesWaiter waiter; + // Make sure that the timer is running indicating that the client runs + // the scheduler. + EXPECT_TRUE(mock_timer_ptr()->IsRunning()); + // Fast forward the scheduler to reach the time when the controller + // requests for geoposition from the server in + // `GeolocationController::RequestGeoposition`. + mock_timer_ptr_->Fire(); + // Waits for the observers to receive the geoposition from the server. + waiter.Wait(); + } + + // Sets the geoposition to be returned from the `factory_` upon the + // `GeolocationController` request. + void SetServerPosition(const Geoposition& position) { + position_ = position; + factory_->set_position(position_); + } + + private: std::unique_ptr<TestScheduledFeature> feature_; - std::unique_ptr<FakeGeolocationController> geolocation_controller_; + GeolocationController* geolocation_controller_; base::SimpleTestClock test_clock_; base::MockOneShotTimer* mock_timer_ptr_; + TestGeolocationUrlLoaderFactory* factory_; + Geoposition position_; }; // Tests that switching users retrieves the feature settings for the active @@ -267,7 +246,7 @@ // types. TEST_F(ScheduledFeatureTest, ScheduleNoneToCustomTransition) { // Now is 6:00 PM. - test_clock_.SetNow(MakeTimeOfDay(6, AmPm::kPM).ToTimeToday()); + test_clock()->SetNow(MakeTimeOfDay(6, AmPm::kPM).ToTimeToday()); SetFeatureEnabled(false); feature()->SetScheduleType(ScheduledFeature::ScheduleType::kNone); // Start time is at 3:00 PM and end time is at 8:00 PM. @@ -301,7 +280,7 @@ // Tests what happens when the time now reaches the end of the feature // interval when the feature mode is on. TEST_F(ScheduledFeatureTest, TestCustomScheduleReachingEndTime) { - test_clock_.SetNow(MakeTimeOfDay(6, AmPm::kPM).ToTimeToday()); + test_clock()->SetNow(MakeTimeOfDay(6, AmPm::kPM).ToTimeToday()); feature()->SetCustomStartTime(MakeTimeOfDay(3, AmPm::kPM)); feature()->SetCustomEndTime(MakeTimeOfDay(8, AmPm::kPM)); feature()->SetScheduleType(ScheduledFeature::ScheduleType::kCustom); @@ -316,7 +295,7 @@ // start end & now // // Now is 8:00 PM. - test_clock_.SetNow(MakeTimeOfDay(8, AmPm::kPM).ToTimeToday()); + test_clock()->SetNow(MakeTimeOfDay(8, AmPm::kPM).ToTimeToday()); feature()->timer()->FireNow(); EXPECT_FALSE(GetEnabled()); // The timer should still be running, but now scheduling the start at 3:00 PM @@ -335,7 +314,7 @@ // | | | // start end now // - test_clock_.SetNow(MakeTimeOfDay(11, AmPm::kPM).ToTimeToday()); + test_clock()->SetNow(MakeTimeOfDay(11, AmPm::kPM).ToTimeToday()); feature()->SetCustomStartTime(MakeTimeOfDay(3, AmPm::kPM)); feature()->SetCustomEndTime(MakeTimeOfDay(8, AmPm::kPM)); feature()->SetScheduleType(ScheduledFeature::ScheduleType::kCustom); @@ -370,7 +349,7 @@ // | | | // now start end // - test_clock_.SetNow(MakeTimeOfDay(4, AmPm::kPM).ToTimeToday()); // 4:00 PM. + test_clock()->SetNow(MakeTimeOfDay(4, AmPm::kPM).ToTimeToday()); // 4:00 PM. SetFeatureEnabled(false); feature()->SetScheduleType(ScheduledFeature::ScheduleType::kNone); feature()->SetCustomStartTime(MakeTimeOfDay(6, AmPm::kPM)); // 6:00 PM. @@ -406,33 +385,33 @@ // Set time now to 10:00 AM. base::Time current_time = MakeTimeOfDay(10, AmPm::kAM).ToTimeToday(); - test_clock_.SetNow(current_time); + test_clock()->SetNow(current_time); EXPECT_FALSE(feature()->timer()->IsRunning()); feature()->SetScheduleType(ScheduledFeature::ScheduleType::kSunsetToSunrise); EXPECT_FALSE(GetEnabled()); EXPECT_TRUE(feature()->timer()->IsRunning()); - EXPECT_EQ(geolocation_controller_->GetSunsetTime() - current_time, + EXPECT_EQ(geolocation_controller()->GetSunsetTime() - current_time, feature()->timer()->GetCurrentDelay()); // Firing a timer should to advance the time to sunset and automatically turn // on the feature. - current_time = geolocation_controller_->GetSunsetTime(); - test_clock_.SetNow(current_time); + current_time = geolocation_controller()->GetSunsetTime(); + test_clock()->SetNow(current_time); feature()->timer()->FireNow(); EXPECT_TRUE(feature()->timer()->IsRunning()); EXPECT_TRUE(GetEnabled()); EXPECT_EQ( - geolocation_controller_->GetSunriseTime() + base::Days(1) - current_time, + geolocation_controller()->GetSunriseTime() + base::Days(1) - current_time, feature()->timer()->GetCurrentDelay()); // Firing a timer should advance the time to sunrise and automatically turn // off the feature. - current_time = geolocation_controller_->GetSunriseTime(); - test_clock_.SetNow(current_time); + current_time = geolocation_controller()->GetSunriseTime(); + test_clock()->SetNow(current_time); feature()->timer()->FireNow(); EXPECT_FALSE(GetEnabled()); EXPECT_TRUE(feature()->timer()->IsRunning()); - EXPECT_EQ(geolocation_controller_->GetSunsetTime() - current_time, + EXPECT_EQ(geolocation_controller()->GetSunsetTime() - current_time, feature()->timer()->GetCurrentDelay()); } @@ -451,28 +430,29 @@ // now sunset sunrise // - // Prepare a valid geoposition. - const Geoposition position = CreateGeoposition( - kFakePosition1_Latitude, kFakePosition1_Longitude, test_clock_.Now()); - GeolocationControllerObserver observer1; - geolocation_controller_->AddObserver(&observer1); - EXPECT_TRUE(mock_timer_ptr_->IsRunning()); - geolocation_controller_->set_position_to_send(position); + geolocation_controller()->AddObserver(&observer1); + EXPECT_TRUE(mock_timer_ptr()->IsRunning()); EXPECT_FALSE(observer1.possible_change_in_timezone()); - // Fire timer to fetch position update. - mock_timer_ptr_->Fire(); + // Prepare a valid geoposition. + const Geoposition position = CreateGeoposition( + kFakePosition1_Latitude, kFakePosition1_Longitude, test_clock()->Now()); + + // Set and fetch position update. + SetServerPosition(position); + FireTimerToFetchGeoposition(); EXPECT_TRUE(observer1.possible_change_in_timezone()); - const base::Time sunset_time1 = geolocation_controller_->GetSunsetTime(); - const base::Time sunrise_time1 = geolocation_controller_->GetSunriseTime(); + EXPECT_TRUE(mock_timer_ptr()->IsRunning()); + const base::Time sunset_time1 = geolocation_controller()->GetSunsetTime(); + const base::Time sunrise_time1 = geolocation_controller()->GetSunriseTime(); // Our assumption is that GeolocationController gives us sunrise time // earlier in the same day before sunset. ASSERT_GT(sunset_time1, sunrise_time1); ASSERT_LT(sunset_time1 - base::Days(1), sunrise_time1); // Set time now to be 4 hours before sunset. - test_clock_.SetNow(sunset_time1 - base::Hours(4)); + test_clock()->SetNow(sunset_time1 - base::Hours(4)); // Expect that timer is running and the start is scheduled after 4 hours. EXPECT_FALSE(feature()->GetEnabled()); @@ -482,16 +462,16 @@ EXPECT_EQ(base::Hours(4), feature()->timer()->GetCurrentDelay()); // Simulate reaching sunset. - test_clock_.SetNow(sunset_time1); // Now is sunset time of the position1. + test_clock()->SetNow(sunset_time1); // Now is sunset time of the position1. feature()->timer()->FireNow(); EXPECT_TRUE(feature()->GetEnabled()); // Timer is running scheduling the end at sunrise of the second day. EXPECT_TRUE(feature()->timer()->IsRunning()); - EXPECT_EQ(sunrise_time1 + base::Days(1) - test_clock_.Now(), + EXPECT_EQ(sunrise_time1 + base::Days(1) - test_clock()->Now(), feature()->timer()->GetCurrentDelay()); // Simulate reaching sunrise. - test_clock_.SetNow(sunrise_time1); // Now is sunrise time of the position1 + test_clock()->SetNow(sunrise_time1); // Now is sunrise time of the position1 // Now simulate user changing position. // Position 2 sunset and sunrise times. @@ -502,35 +482,39 @@ // const Geoposition position2 = CreateGeoposition( - kFakePosition2_Latitude, kFakePosition2_Longitude, test_clock_.Now()); - geolocation_controller_->set_position_to_send(position2); - mock_timer_ptr_->Fire(); + kFakePosition2_Latitude, kFakePosition2_Longitude, test_clock()->Now()); + // Replace a response `position` with `position2`. + factory()->ClearResponses(); + SetServerPosition(position2); + FireTimerToFetchGeoposition(); EXPECT_TRUE(observer1.possible_change_in_timezone()); - const base::Time sunset_time2 = geolocation_controller_->GetSunsetTime(); - const base::Time sunrise_time2 = geolocation_controller_->GetSunriseTime(); + EXPECT_TRUE(mock_timer_ptr()->IsRunning()); + + const base::Time sunset_time2 = geolocation_controller()->GetSunsetTime(); + const base::Time sunrise_time2 = geolocation_controller()->GetSunriseTime(); // We choose the second location such that the new sunrise time is later // in the day compared to the old sunrise time, which is also the current // time. - ASSERT_GT(test_clock_.Now(), sunset_time2); - ASSERT_LT(test_clock_.Now(), sunset_time2 + base::Days(1)); - ASSERT_GT(test_clock_.Now(), sunrise_time2); - ASSERT_LT(test_clock_.Now(), sunrise_time2 + base::Days(1)); + ASSERT_GT(test_clock()->Now(), sunset_time2); + ASSERT_LT(test_clock()->Now(), sunset_time2 + base::Days(1)); + ASSERT_GT(test_clock()->Now(), sunrise_time2); + ASSERT_LT(test_clock()->Now(), sunrise_time2 + base::Days(1)); // Expect that the scheduled end delay has been updated to sunrise of the // same (second) day in location 2, and the status hasn't changed. EXPECT_TRUE(feature()->GetEnabled()); EXPECT_TRUE(feature()->timer()->IsRunning()); - EXPECT_EQ(sunrise_time2 + base::Days(1) - test_clock_.Now(), + EXPECT_EQ(sunrise_time2 + base::Days(1) - test_clock()->Now(), feature()->timer()->GetCurrentDelay()); // Simulate reaching sunrise. - test_clock_.SetNow(sunrise_time2 + - base::Days(1)); // Now is sunrise time of the position2. + test_clock()->SetNow(sunrise_time2 + + base::Days(1)); // Now is sunrise time of the position2. feature()->timer()->FireNow(); EXPECT_FALSE(feature()->GetEnabled()); // Timer is running scheduling the start at the sunset of the next day. EXPECT_TRUE(feature()->timer()->IsRunning()); - EXPECT_EQ(sunset_time2 + base::Days(1) - test_clock_.Now(), + EXPECT_EQ(sunset_time2 + base::Days(1) - test_clock()->Now(), feature()->timer()->GetCurrentDelay()); } @@ -538,7 +522,7 @@ // correctly if the time has changed meanwhile. TEST_F(ScheduledFeatureTest, CustomScheduleOnResume) { // Now is 4:00 PM. - test_clock_.SetNow(MakeTimeOfDay(4, AmPm::kPM).ToTimeToday()); + test_clock()->SetNow(MakeTimeOfDay(4, AmPm::kPM).ToTimeToday()); feature()->SetEnabled(false); // Start time is at 6:00 PM and end time is at 10:00 PM. The feature should be // off. @@ -558,7 +542,7 @@ // Now simulate that the device was suspended for 3 hours, and the time now // is 7:00 PM when the devices was resumed. Expect that the feature turns on. - test_clock_.SetNow(MakeTimeOfDay(7, AmPm::kPM).ToTimeToday()); + test_clock()->SetNow(MakeTimeOfDay(7, AmPm::kPM).ToTimeToday()); feature()->SuspendDone(base::TimeDelta::Max()); EXPECT_TRUE(feature()->GetEnabled()); @@ -575,7 +559,7 @@ // Case 1: "Now" is less than both "end" and "start". TEST_F(ScheduledFeatureTest, CustomScheduleInvertedStartAndEndTimesCase1) { // Now is 4:00 AM. - test_clock_.SetNow(MakeTimeOfDay(4, AmPm::kAM).ToTimeToday()); + test_clock()->SetNow(MakeTimeOfDay(4, AmPm::kAM).ToTimeToday()); SetFeatureEnabled(false); // Start time is at 9:00 PM and end time is at 6:00 AM. "Now" is less than // both. The feature should be on. @@ -597,7 +581,7 @@ // Case 2: "Now" is between "end" and "start". TEST_F(ScheduledFeatureTest, CustomScheduleInvertedStartAndEndTimesCase2) { // Now is 6:00 AM. - test_clock_.SetNow(MakeTimeOfDay(6, AmPm::kAM).ToTimeToday()); + test_clock()->SetNow(MakeTimeOfDay(6, AmPm::kAM).ToTimeToday()); SetFeatureEnabled(false); // Start time is at 9:00 PM and end time is at 4:00 AM. "Now" is between both. // The feature should be off. @@ -619,7 +603,7 @@ // Case 3: "Now" is greater than both "start" and "end". TEST_F(ScheduledFeatureTest, CustomScheduleInvertedStartAndEndTimesCase3) { // Now is 11:00 PM. - test_clock_.SetNow(MakeTimeOfDay(11, AmPm::kPM).ToTimeToday()); + test_clock()->SetNow(MakeTimeOfDay(11, AmPm::kPM).ToTimeToday()); SetFeatureEnabled(false); // Start time is at 9:00 PM and end time is at 4:00 AM. "Now" is greater than // both. the feature should be on. @@ -658,7 +642,7 @@ // 2pm 4pm 7pm 10pm 9am // - test_clock_.SetNow(MakeTimeOfDay(2, kPM).ToTimeToday()); + test_clock()->SetNow(MakeTimeOfDay(2, kPM).ToTimeToday()); feature()->SetCustomStartTime(MakeTimeOfDay(3, kPM)); feature()->SetCustomEndTime(MakeTimeOfDay(8, kPM)); feature()->SetScheduleType(ScheduledFeature::ScheduleType::kCustom); @@ -688,7 +672,7 @@ // Apply the test's case fake time, and fire the timer if there's a change // expected in the feature's status. - test_clock_.SetNow(test_case.fake_now); + test_clock()->SetNow(test_case.fake_now); if (user_1_previous_status != test_case.user_1_expected_status) feature()->timer()->FireNow(); user_1_previous_status = test_case.user_1_expected_status; @@ -730,7 +714,7 @@ TEST_F(ScheduledFeatureTest, ManualStatusToggleCanPersistAfterResumeFromSuspend) { - test_clock_.SetNow(MakeTimeOfDay(11, kAM).ToTimeToday()); + test_clock()->SetNow(MakeTimeOfDay(11, kAM).ToTimeToday()); feature()->SetCustomStartTime(MakeTimeOfDay(3, kPM)); feature()->SetCustomEndTime(MakeTimeOfDay(8, kPM)); @@ -746,18 +730,18 @@ // Simulate suspend and then resume at 2:00 PM (which is outside the user's // custom schedule). However, the manual toggle to on should be kept. - test_clock_.SetNow(MakeTimeOfDay(2, kPM).ToTimeToday()); + test_clock()->SetNow(MakeTimeOfDay(2, kPM).ToTimeToday()); feature()->SuspendDone(base::TimeDelta{}); EXPECT_TRUE(feature()->GetEnabled()); // Suspend again and resume at 5:00 PM (which is within the user's custom // schedule). The schedule should be applied normally. - test_clock_.SetNow(MakeTimeOfDay(5, kPM).ToTimeToday()); + test_clock()->SetNow(MakeTimeOfDay(5, kPM).ToTimeToday()); feature()->SuspendDone(base::TimeDelta{}); EXPECT_TRUE(feature()->GetEnabled()); // Suspend and resume at 9:00 PM and expect the feature to be off. - test_clock_.SetNow(MakeTimeOfDay(9, kPM).ToTimeToday()); + test_clock()->SetNow(MakeTimeOfDay(9, kPM).ToTimeToday()); feature()->SuspendDone(base::TimeDelta{}); EXPECT_FALSE(feature()->GetEnabled()); }
diff --git a/ash/test_shell_delegate.cc b/ash/test_shell_delegate.cc index bbcda14..0f0dc61 100644 --- a/ash/test_shell_delegate.cc +++ b/ash/test_shell_delegate.cc
@@ -53,7 +53,7 @@ } scoped_refptr<network::SharedURLLoaderFactory> -TestShellDelegate::GetGeolocationSharedURLLoaderFactory() const { +TestShellDelegate::GetGeolocationUrlLoaderFactory() const { return static_cast<scoped_refptr<network::SharedURLLoaderFactory>>( base::MakeRefCounted<TestGeolocationUrlLoaderFactory>()); }
diff --git a/ash/test_shell_delegate.h b/ash/test_shell_delegate.h index 10afec5..d2619f40 100644 --- a/ash/test_shell_delegate.h +++ b/ash/test_shell_delegate.h
@@ -45,7 +45,7 @@ std::unique_ptr<DesksTemplatesDelegate> CreateDesksTemplatesDelegate() const override; scoped_refptr<network::SharedURLLoaderFactory> - GetGeolocationSharedURLLoaderFactory() const override; + GetGeolocationUrlLoaderFactory() const override; bool CanGoBack(gfx::NativeWindow window) const override; void SetTabScrubberChromeOSEnabled(bool enabled) override; bool ShouldWaitForTouchPressAck(gfx::NativeWindow window) override;
diff --git a/ash/webui/eche_app_ui/BUILD.gn b/ash/webui/eche_app_ui/BUILD.gn index dcb6a24..7d8921d 100644 --- a/ash/webui/eche_app_ui/BUILD.gn +++ b/ash/webui/eche_app_ui/BUILD.gn
@@ -37,8 +37,6 @@ "eche_connector.h", "eche_connector_impl.cc", "eche_connector_impl.h", - "eche_display_stream_handler.cc", - "eche_display_stream_handler.h", "eche_feature_status_provider.cc", "eche_feature_status_provider.h", "eche_message_receiver.cc", @@ -55,6 +53,10 @@ "eche_recent_app_click_handler.h", "eche_signaler.cc", "eche_signaler.h", + "eche_stream_status_change_handler.cc", + "eche_stream_status_change_handler.h", + "eche_tray_stream_status_observer.cc", + "eche_tray_stream_status_observer.h", "eche_uid_provider.cc", "eche_uid_provider.h", "feature_status.cc", @@ -128,7 +130,6 @@ "apps_access_setup_operation_unittest.cc", "eche_app_manager_unittest.cc", "eche_connector_impl_unittest.cc", - "eche_display_stream_handler_unittest.cc", "eche_feature_status_provider_unittest.cc", "eche_message_receiver_impl_unittest.cc", "eche_notification_click_handler_unittest.cc", @@ -136,6 +137,8 @@ "eche_presence_manager_unittest.cc", "eche_recent_app_click_handler_unittest.cc", "eche_signaler_unittest.cc", + "eche_stream_status_change_handler_unittest.cc", + "eche_tray_stream_status_observer_unittest.cc", "eche_uid_provider_unittest.cc", "launch_app_helper_unittest.cc", "system_info_provider_unittest.cc",
diff --git a/ash/webui/eche_app_ui/eche_app_manager.cc b/ash/webui/eche_app_ui/eche_app_manager.cc index ef1737ab..a6633fb 100644 --- a/ash/webui/eche_app_ui/eche_app_manager.cc +++ b/ash/webui/eche_app_ui/eche_app_manager.cc
@@ -5,15 +5,17 @@ #include "ash/webui/eche_app_ui/eche_app_manager.h" #include "ash/components/phonehub/phone_hub_manager.h" +#include "ash/constants/ash_features.h" #include "ash/public/cpp/network_config_service.h" #include "ash/services/secure_channel/public/cpp/client/connection_manager_impl.h" #include "ash/webui/eche_app_ui/apps_access_manager_impl.h" #include "ash/webui/eche_app_ui/eche_connector_impl.h" -#include "ash/webui/eche_app_ui/eche_display_stream_handler.h" #include "ash/webui/eche_app_ui/eche_message_receiver_impl.h" #include "ash/webui/eche_app_ui/eche_notification_generator.h" #include "ash/webui/eche_app_ui/eche_presence_manager.h" #include "ash/webui/eche_app_ui/eche_signaler.h" +#include "ash/webui/eche_app_ui/eche_stream_status_change_handler.h" +#include "ash/webui/eche_app_ui/eche_tray_stream_status_observer.h" #include "ash/webui/eche_app_ui/eche_uid_provider.h" #include "ash/webui/eche_app_ui/launch_app_helper.h" #include "ash/webui/eche_app_ui/system_info.h" @@ -40,8 +42,7 @@ presence_monitor_client, LaunchAppHelper::LaunchEcheAppFunction launch_eche_app_function, LaunchAppHelper::CloseEcheAppFunction close_eche_app_function, - LaunchAppHelper::LaunchNotificationFunction launch_notification_function, - StreamStatusChangedFunction stream_status_changed_function) + LaunchAppHelper::LaunchNotificationFunction launch_notification_function) : connection_manager_( std::make_unique<secure_channel::ConnectionManagerImpl>( multidevice_setup_client, @@ -61,7 +62,8 @@ launch_eche_app_function, close_eche_app_function, launch_notification_function)), - display_stream_handler_(std::make_unique<EcheDisplayStreamHandler>()), + stream_status_change_handler_( + std::make_unique<EcheStreamStatusChangeHandler>()), eche_notification_click_handler_( std::make_unique<EcheNotificationClickHandler>( phone_hub_manager, @@ -96,13 +98,15 @@ pref_service, multidevice_setup_client, connection_manager_.get())), - stream_status_changed_function_( - std::move(stream_status_changed_function)) { + eche_tray_stream_status_observer_( + features::IsEcheCustomWidgetEnabled() + ? std::make_unique<EcheTrayStreamStatusObserver>( + stream_status_change_handler_.get()) + : nullptr) { ash::GetNetworkConfigService( remote_cros_network_config_.BindNewPipeAndPassReceiver()); system_info_provider_ = std::make_unique<SystemInfoProvider>( std::move(system_info), remote_cros_network_config_.get()); - display_stream_handler_->AddObserver(this); } EcheAppManager::~EcheAppManager() = default; @@ -129,16 +133,7 @@ void EcheAppManager::BindDisplayStreamHandlerInterface( mojo::PendingReceiver<mojom::DisplayStreamHandler> receiver) { - display_stream_handler_->Bind(std::move(receiver)); -} - -void EcheAppManager::OnStartStreaming() { - stream_status_changed_function_.Run( - mojom::StreamStatus::kStreamStatusStarted); -} - -void EcheAppManager::OnStreamStatusChanged(mojom::StreamStatus status) { - stream_status_changed_function_.Run(status); + stream_status_change_handler_->Bind(std::move(receiver)); } AppsAccessManager* EcheAppManager::GetAppsAccessManager() { @@ -149,6 +144,7 @@ // are initialized in the constructor. void EcheAppManager::Shutdown() { system_info_provider_.reset(); + eche_tray_stream_status_observer_.reset(); apps_access_manager_.reset(); notification_generator_.reset(); eche_recent_app_click_handler_.reset(); @@ -158,8 +154,7 @@ signaler_.reset(); eche_connector_.reset(); eche_notification_click_handler_.reset(); - display_stream_handler_->RemoveObserver(this); - display_stream_handler_.reset(); + stream_status_change_handler_.reset(); launch_app_helper_.reset(); feature_status_provider_.reset(); connection_manager_.reset();
diff --git a/ash/webui/eche_app_ui/eche_app_manager.h b/ash/webui/eche_app_ui/eche_app_manager.h index e91fde0..b9c31c0a 100644 --- a/ash/webui/eche_app_ui/eche_app_manager.h +++ b/ash/webui/eche_app_ui/eche_app_manager.h
@@ -14,7 +14,6 @@ #include "ash/services/secure_channel/public/cpp/client/presence_monitor_client_impl.h" // TODO(https://crbug.com/1164001): move to forward declaration. #include "ash/services/secure_channel/public/cpp/client/secure_channel_client.h" -#include "ash/webui/eche_app_ui/eche_display_stream_handler.h" #include "ash/webui/eche_app_ui/eche_feature_status_provider.h" #include "ash/webui/eche_app_ui/eche_notification_click_handler.h" #include "ash/webui/eche_app_ui/eche_recent_app_click_handler.h" @@ -47,19 +46,14 @@ class SystemInfo; class SystemInfoProvider; class AppsAccessManager; -class EcheDisplayStreamHandler; +class EcheStreamStatusChangeHandler; +class EcheTrayStreamStatusObserver; // Implements the core logic of the EcheApp and exposes interfaces via its // public API. Implemented as a KeyedService since it depends on other // KeyedService instances. -class EcheAppManager : public KeyedService, - public EcheDisplayStreamHandler::Observer { +class EcheAppManager : public KeyedService { public: - using StreamStatusChangedFunction = - base::RepeatingCallback<void(const mojom::StreamStatus status)>; - - // TODO(b/223321926): clean up callback functions from constructor to a - // specific class EcheAppManager(PrefService* pref_service, std::unique_ptr<SystemInfo> system_info, phonehub::PhoneHubManager*, @@ -70,8 +64,7 @@ presence_monitor_client, LaunchAppHelper::LaunchEcheAppFunction, LaunchAppHelper::CloseEcheAppFunction, - LaunchAppHelper::LaunchNotificationFunction, - StreamStatusChangedFunction); + LaunchAppHelper::LaunchNotificationFunction); ~EcheAppManager() override; EcheAppManager(const EcheAppManager&) = delete; @@ -97,15 +90,11 @@ // KeyedService: void Shutdown() override; - // EcheDisplayStreamHandler::Observer: - void OnStartStreaming() override; - void OnStreamStatusChanged(mojom::StreamStatus status) override; - private: std::unique_ptr<secure_channel::ConnectionManager> connection_manager_; std::unique_ptr<EcheFeatureStatusProvider> feature_status_provider_; std::unique_ptr<LaunchAppHelper> launch_app_helper_; - std::unique_ptr<EcheDisplayStreamHandler> display_stream_handler_; + std::unique_ptr<EcheStreamStatusChangeHandler> stream_status_change_handler_; std::unique_ptr<EcheNotificationClickHandler> eche_notification_click_handler_; std::unique_ptr<EcheConnector> eche_connector_; @@ -119,7 +108,8 @@ remote_cros_network_config_; std::unique_ptr<SystemInfoProvider> system_info_provider_; std::unique_ptr<AppsAccessManager> apps_access_manager_; - StreamStatusChangedFunction stream_status_changed_function_; + std::unique_ptr<EcheTrayStreamStatusObserver> + eche_tray_stream_status_observer_; }; } // namespace eche_app
diff --git a/ash/webui/eche_app_ui/eche_app_manager_unittest.cc b/ash/webui/eche_app_ui/eche_app_manager_unittest.cc index c981523..17a4ca0 100644 --- a/ash/webui/eche_app_ui/eche_app_manager_unittest.cc +++ b/ash/webui/eche_app_ui/eche_app_manager_unittest.cc
@@ -15,7 +15,7 @@ #include "ash/services/secure_channel/public/cpp/client/fake_secure_channel_client.h" #include "ash/services/secure_channel/public/cpp/client/presence_monitor_client.h" #include "ash/services/secure_channel/public/cpp/client/presence_monitor_client_impl.h" -#include "ash/webui/eche_app_ui/eche_display_stream_handler.h" +#include "ash/webui/eche_app_ui/eche_stream_status_change_handler.h" #include "ash/webui/eche_app_ui/launch_app_helper.h" #include "ash/webui/eche_app_ui/system_info.h" #include "base/bind.h" @@ -46,8 +46,6 @@ const absl::optional<std::u16string>& message, std::unique_ptr<LaunchAppHelper::NotificationInfo> info) {} -void StreamStatusChangedFunction(const mojom::StreamStatus status) {} - class FakePresenceMonitorClient : public secure_channel::PresenceMonitorClient { public: FakePresenceMonitorClient() = default; @@ -119,8 +117,7 @@ std::move(fake_presence_monitor_client), base::BindRepeating(&LaunchEcheAppFunction), base::BindRepeating(&CloseEcheAppFunction), - base::BindRepeating(&LaunchNotificationFunction), - base::BindRepeating(&StreamStatusChangedFunction)); + base::BindRepeating(&LaunchNotificationFunction)); } mojo::Remote<mojom::SignalingMessageExchanger>&
diff --git a/ash/webui/eche_app_ui/eche_display_stream_handler.cc b/ash/webui/eche_app_ui/eche_display_stream_handler.cc deleted file mode 100644 index 0618a38..0000000 --- a/ash/webui/eche_app_ui/eche_display_stream_handler.cc +++ /dev/null
@@ -1,55 +0,0 @@ -// 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/webui/eche_app_ui/eche_display_stream_handler.h" - -#include "ash/components/multidevice/logging/logging.h" -#include "ash/webui/eche_app_ui/launch_app_helper.h" - -namespace ash { -namespace eche_app { - -EcheDisplayStreamHandler::EcheDisplayStreamHandler() = default; - -EcheDisplayStreamHandler::~EcheDisplayStreamHandler() = default; - -void EcheDisplayStreamHandler::StartStreaming() { - PA_LOG(INFO) << "echeapi EcheDisplayStreamHandler StartStreaming"; - NotifyStartStreaming(); -} - -void EcheDisplayStreamHandler::OnStreamStatusChanged( - mojom::StreamStatus status) { - PA_LOG(INFO) << "echeapi EcheDisplayStreamHandler OnStreamStatusChanged " - << status; - NotifyStreamStatusChanged(status); -} - -void EcheDisplayStreamHandler::Bind( - mojo::PendingReceiver<mojom::DisplayStreamHandler> receiver) { - display_stream_receiver_.reset(); - display_stream_receiver_.Bind(std::move(receiver)); -} - -void EcheDisplayStreamHandler::AddObserver(Observer* observer) { - observer_list_.AddObserver(observer); -} - -void EcheDisplayStreamHandler::RemoveObserver(Observer* observer) { - observer_list_.RemoveObserver(observer); -} - -void EcheDisplayStreamHandler::NotifyStartStreaming() { - for (auto& observer : observer_list_) - observer.OnStartStreaming(); -} - -void EcheDisplayStreamHandler::NotifyStreamStatusChanged( - mojom::StreamStatus status) { - for (auto& observer : observer_list_) - observer.OnStreamStatusChanged(status); -} - -} // namespace eche_app -} // namespace ash
diff --git a/ash/webui/eche_app_ui/eche_display_stream_handler.h b/ash/webui/eche_app_ui/eche_display_stream_handler.h deleted file mode 100644 index 0696119..0000000 --- a/ash/webui/eche_app_ui/eche_display_stream_handler.h +++ /dev/null
@@ -1,59 +0,0 @@ -// 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_WEBUI_ECHE_APP_UI_ECHE_DISPLAY_STREAM_HANDLER_H_ -#define ASH_WEBUI_ECHE_APP_UI_ECHE_DISPLAY_STREAM_HANDLER_H_ - -#include "ash/webui/eche_app_ui/mojom/eche_app.mojom.h" -#include "base/observer_list.h" -#include "base/observer_list_types.h" -#include "mojo/public/cpp/bindings/receiver.h" - -namespace ash { -namespace eche_app { - -// Implements the EcheDisplayStreamHandler interface to allow the WebUI to sync -// the status of the display stream for Eche, e.g. When the display stream is -// started in the Eche Web, we can register `Observer` and get this status via -// `OnStartStreaming` event. -// TODO(paulzchen): Consider using `DisplayStreamEventHandler` to replace -// `DisplayStreamHandler`. -class EcheDisplayStreamHandler : public mojom::DisplayStreamHandler { - public: - class Observer : public base::CheckedObserver { - public: - ~Observer() override = default; - - virtual void OnStartStreaming() = 0; - virtual void OnStreamStatusChanged(mojom::StreamStatus status) = 0; - }; - - EcheDisplayStreamHandler(); - ~EcheDisplayStreamHandler() override; - - EcheDisplayStreamHandler(const EcheDisplayStreamHandler&) = delete; - EcheDisplayStreamHandler& operator=(const EcheDisplayStreamHandler&) = delete; - - // mojom::DisplayStreamHandler: - void StartStreaming() override; - void OnStreamStatusChanged(mojom::StreamStatus status) override; - - void AddObserver(Observer* observer); - void RemoveObserver(Observer* observer); - - void Bind(mojo::PendingReceiver<mojom::DisplayStreamHandler> receiver); - - protected: - void NotifyStartStreaming(); - void NotifyStreamStatusChanged(mojom::StreamStatus status); - - private: - mojo::Receiver<mojom::DisplayStreamHandler> display_stream_receiver_{this}; - base::ObserverList<Observer> observer_list_; -}; - -} // namespace eche_app -} // namespace ash - -#endif // ASH_WEBUI_ECHE_APP_UI_ECHE_DISPLAY_STREAM_HANDLER_H_
diff --git a/ash/webui/eche_app_ui/eche_stream_status_change_handler.cc b/ash/webui/eche_app_ui/eche_stream_status_change_handler.cc new file mode 100644 index 0000000..756f2a7 --- /dev/null +++ b/ash/webui/eche_app_ui/eche_stream_status_change_handler.cc
@@ -0,0 +1,55 @@ +// 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/webui/eche_app_ui/eche_stream_status_change_handler.h" + +#include "ash/components/multidevice/logging/logging.h" +#include "ash/webui/eche_app_ui/launch_app_helper.h" + +namespace ash { +namespace eche_app { + +EcheStreamStatusChangeHandler::EcheStreamStatusChangeHandler() = default; + +EcheStreamStatusChangeHandler::~EcheStreamStatusChangeHandler() = default; + +void EcheStreamStatusChangeHandler::StartStreaming() { + PA_LOG(INFO) << "echeapi EcheStreamStatusChangeHandler StartStreaming"; + NotifyStartStreaming(); +} + +void EcheStreamStatusChangeHandler::OnStreamStatusChanged( + mojom::StreamStatus status) { + PA_LOG(INFO) << "echeapi EcheStreamStatusChangeHandler OnStreamStatusChanged " + << status; + NotifyStreamStatusChanged(status); +} + +void EcheStreamStatusChangeHandler::Bind( + mojo::PendingReceiver<mojom::DisplayStreamHandler> receiver) { + display_stream_receiver_.reset(); + display_stream_receiver_.Bind(std::move(receiver)); +} + +void EcheStreamStatusChangeHandler::AddObserver(Observer* observer) { + observer_list_.AddObserver(observer); +} + +void EcheStreamStatusChangeHandler::RemoveObserver(Observer* observer) { + observer_list_.RemoveObserver(observer); +} + +void EcheStreamStatusChangeHandler::NotifyStartStreaming() { + for (auto& observer : observer_list_) + observer.OnStartStreaming(); +} + +void EcheStreamStatusChangeHandler::NotifyStreamStatusChanged( + mojom::StreamStatus status) { + for (auto& observer : observer_list_) + observer.OnStreamStatusChanged(status); +} + +} // namespace eche_app +} // namespace ash
diff --git a/ash/webui/eche_app_ui/eche_stream_status_change_handler.h b/ash/webui/eche_app_ui/eche_stream_status_change_handler.h new file mode 100644 index 0000000..06ae6c4b --- /dev/null +++ b/ash/webui/eche_app_ui/eche_stream_status_change_handler.h
@@ -0,0 +1,58 @@ +// 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_WEBUI_ECHE_APP_UI_ECHE_STREAM_STATUS_CHANGE_HANDLER_H_ +#define ASH_WEBUI_ECHE_APP_UI_ECHE_STREAM_STATUS_CHANGE_HANDLER_H_ + +#include "ash/webui/eche_app_ui/mojom/eche_app.mojom.h" +#include "base/observer_list.h" +#include "base/observer_list_types.h" +#include "mojo/public/cpp/bindings/receiver.h" + +namespace ash { +namespace eche_app { + +// Implements the DisplayStreamHandler interface to allow the WebUI to sync the +// status of the video streaming for Eche, e.g. When the video streaming is +// started in the Eche Web, we can register `Observer` and get this status via +// `OnStartStreaming` and `OnStreamStatusChanged` event. +class EcheStreamStatusChangeHandler : public mojom::DisplayStreamHandler { + public: + class Observer : public base::CheckedObserver { + public: + ~Observer() override = default; + + virtual void OnStartStreaming() = 0; + virtual void OnStreamStatusChanged(mojom::StreamStatus status) = 0; + }; + + EcheStreamStatusChangeHandler(); + ~EcheStreamStatusChangeHandler() override; + + EcheStreamStatusChangeHandler(const EcheStreamStatusChangeHandler&) = delete; + EcheStreamStatusChangeHandler& operator=( + const EcheStreamStatusChangeHandler&) = delete; + + // mojom::DisplayStreamHandler: + void StartStreaming() override; + void OnStreamStatusChanged(mojom::StreamStatus status) override; + + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + void Bind(mojo::PendingReceiver<mojom::DisplayStreamHandler> receiver); + + protected: + void NotifyStartStreaming(); + void NotifyStreamStatusChanged(mojom::StreamStatus status); + + private: + mojo::Receiver<mojom::DisplayStreamHandler> display_stream_receiver_{this}; + base::ObserverList<Observer> observer_list_; +}; + +} // namespace eche_app +} // namespace ash + +#endif // ASH_WEBUI_ECHE_APP_UI_ECHE_STREAM_STATUS_CHANGE_HANDLER_H_
diff --git a/ash/webui/eche_app_ui/eche_display_stream_handler_unittest.cc b/ash/webui/eche_app_ui/eche_stream_status_change_handler_unittest.cc similarity index 73% rename from ash/webui/eche_app_ui/eche_display_stream_handler_unittest.cc rename to ash/webui/eche_app_ui/eche_stream_status_change_handler_unittest.cc index 4db83d6..50575bc 100644 --- a/ash/webui/eche_app_ui/eche_display_stream_handler_unittest.cc +++ b/ash/webui/eche_app_ui/eche_stream_status_change_handler_unittest.cc
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "ash/webui/eche_app_ui/eche_display_stream_handler.h" +#include "ash/webui/eche_app_ui/eche_stream_status_change_handler.h" #include "testing/gtest/include/gtest/gtest.h" @@ -10,7 +10,7 @@ namespace eche_app { namespace { -class FakeObserver : public EcheDisplayStreamHandler::Observer { +class FakeObserver : public EcheStreamStatusChangeHandler::Observer { public: FakeObserver() = default; ~FakeObserver() override = default; @@ -22,7 +22,7 @@ return last_notified_stream_status_; } - // EcheDisplayStreamHandler::Observer: + // EcheStreamStatusChangeHandler::Observer: void OnStartStreaming() override { ++num_start_streaming_calls_; } void OnStreamStatusChanged(mojom::StreamStatus status) override { last_notified_stream_status_ = status; @@ -36,17 +36,18 @@ } // namespace -class EcheDisplayStreamHandlerTest : public testing::Test { +class EcheStreamStatusChangeHandlerTest : public testing::Test { protected: - EcheDisplayStreamHandlerTest() = default; - EcheDisplayStreamHandlerTest(const EcheDisplayStreamHandlerTest&) = delete; - EcheDisplayStreamHandlerTest& operator=(const EcheDisplayStreamHandlerTest&) = + EcheStreamStatusChangeHandlerTest() = default; + EcheStreamStatusChangeHandlerTest(const EcheStreamStatusChangeHandlerTest&) = delete; - ~EcheDisplayStreamHandlerTest() override = default; + EcheStreamStatusChangeHandlerTest& operator=( + const EcheStreamStatusChangeHandlerTest&) = delete; + ~EcheStreamStatusChangeHandlerTest() override = default; // testing::Test: void SetUp() override { - handler_ = std::make_unique<EcheDisplayStreamHandler>(); + handler_ = std::make_unique<EcheStreamStatusChangeHandler>(); handler_->AddObserver(&fake_observer_); } @@ -69,15 +70,15 @@ private: FakeObserver fake_observer_; - std::unique_ptr<EcheDisplayStreamHandler> handler_; + std::unique_ptr<EcheStreamStatusChangeHandler> handler_; }; -TEST_F(EcheDisplayStreamHandlerTest, StartStreaming) { +TEST_F(EcheStreamStatusChangeHandlerTest, StartStreaming) { StartStreaming(); EXPECT_EQ(1u, GetNumObserverStartStreamingCalls()); } -TEST_F(EcheDisplayStreamHandlerTest, OnStreamStatusChanged) { +TEST_F(EcheStreamStatusChangeHandlerTest, OnStreamStatusChanged) { NotifyStreamStatus(mojom::StreamStatus::kStreamStatusInitializing); EXPECT_EQ(mojom::StreamStatus::kStreamStatusInitializing, GetObservedStreamStatus());
diff --git a/ash/webui/eche_app_ui/eche_tray_stream_status_observer.cc b/ash/webui/eche_app_ui/eche_tray_stream_status_observer.cc new file mode 100644 index 0000000..fc0fadb7 --- /dev/null +++ b/ash/webui/eche_app_ui/eche_tray_stream_status_observer.cc
@@ -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. + +#include "ash/webui/eche_app_ui/eche_tray_stream_status_observer.h" + +#include "ash/root_window_controller.h" +#include "ash/shell.h" +#include "ash/system/eche/eche_tray.h" +#include "ash/webui/eche_app_ui/eche_stream_status_change_handler.h" +#include "ui/gfx/image/image.h" + +namespace ash { + +EcheTray* GetEcheTray() { + return Shell::GetPrimaryRootWindowController() + ->GetStatusAreaWidget() + ->eche_tray(); +} + +namespace eche_app { + +void LaunchBubble(const GURL& url, const gfx::Image& icon) { + auto* eche_tray = ash::GetEcheTray(); + DCHECK(eche_tray); + eche_tray->LoadBubble(url, icon); +} + +void CloseBubble() { + auto* eche_tray = ash::GetEcheTray(); + if (eche_tray) + eche_tray->PurgeAndClose(); + return; +} + +EcheTrayStreamStatusObserver::EcheTrayStreamStatusObserver( + EcheStreamStatusChangeHandler* stream_status_change_handler) { + observed_session_.Observe(stream_status_change_handler); +} + +EcheTrayStreamStatusObserver::~EcheTrayStreamStatusObserver() = default; + +void EcheTrayStreamStatusObserver::OnStartStreaming() { + OnStreamStatusChanged(mojom::StreamStatus::kStreamStatusStarted); +} + +void EcheTrayStreamStatusObserver::OnStreamStatusChanged( + mojom::StreamStatus status) { + GetEcheTray()->OnStreamStatusChanged(status); +} + +} // namespace eche_app +} // namespace ash
diff --git a/ash/webui/eche_app_ui/eche_tray_stream_status_observer.h b/ash/webui/eche_app_ui/eche_tray_stream_status_observer.h new file mode 100644 index 0000000..b560b8b --- /dev/null +++ b/ash/webui/eche_app_ui/eche_tray_stream_status_observer.h
@@ -0,0 +1,56 @@ +// 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_WEBUI_ECHE_APP_UI_ECHE_TRAY_STREAM_STATUS_OBSERVER_H_ +#define ASH_WEBUI_ECHE_APP_UI_ECHE_TRAY_STREAM_STATUS_OBSERVER_H_ + +#include "ash/webui/eche_app_ui/eche_stream_status_change_handler.h" +#include "ash/webui/eche_app_ui/mojom/eche_app.mojom.h" +#include "base/scoped_observation.h" +#include "url/gurl.h" + +namespace gfx { +class Image; +} // namespace gfx + +namespace ash { +namespace eche_app { + +// It is called from chrome/browser/ash/eche_app/eche_app_manager_factory.cc. +// Move all logic about Eche tray to here because we don't want to call +// `GetEcheTray` everywhere. +void LaunchBubble(const GURL& url, const gfx::Image& icon); + +// It is called from chrome/browser/ash/eche_app/eche_app_manager_factory.cc. +void CloseBubble(); + +// The observer that observes the stream status change and notifies `EcheTray` +// show/hide/close the bubble when Eche starts/stops streaming. +// TODO(b/226687249): Implement this observer in EcheTray directly if we fix the +// package dependency error between //eche_app_ui and //ash. +class EcheTrayStreamStatusObserver + : public EcheStreamStatusChangeHandler::Observer { + public: + EcheTrayStreamStatusObserver( + EcheStreamStatusChangeHandler* stream_status_change_handler); + ~EcheTrayStreamStatusObserver() override; + + EcheTrayStreamStatusObserver(const EcheTrayStreamStatusObserver&) = delete; + EcheTrayStreamStatusObserver& operator=(const EcheTrayStreamStatusObserver&) = + delete; + + // EcheStreamStatusChangeHandler::Observer: + void OnStartStreaming() override; + void OnStreamStatusChanged(mojom::StreamStatus status) override; + + private: + base::ScopedObservation<EcheStreamStatusChangeHandler, + EcheStreamStatusChangeHandler::Observer> + observed_session_{this}; +}; + +} // namespace eche_app +} // namespace ash + +#endif // ASH_WEBUI_ECHE_APP_UI_ECHE_TRAY_STREAM_STATUS_OBSERVER_H_
diff --git a/ash/webui/eche_app_ui/eche_tray_stream_status_observer_unittest.cc b/ash/webui/eche_app_ui/eche_tray_stream_status_observer_unittest.cc new file mode 100644 index 0000000..8f332ca --- /dev/null +++ b/ash/webui/eche_app_ui/eche_tray_stream_status_observer_unittest.cc
@@ -0,0 +1,133 @@ +// 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/webui/eche_app_ui/eche_tray_stream_status_observer.h" + +#include "ash/constants/ash_features.h" +#include "ash/system/eche/eche_tray.h" +#include "ash/system/status_area_widget_test_helper.h" +#include "ash/test/ash_test_base.h" +#include "ash/test/ash_test_suite.h" +#include "ash/test/test_ash_web_view_factory.h" +#include "ash/webui/eche_app_ui/eche_stream_status_change_handler.h" +#include "base/test/scoped_feature_list.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/image/image.h" + +namespace ash { +namespace eche_app { + +class EcheTrayStreamStatusObserverTest : public AshTestBase { + protected: + EcheTrayStreamStatusObserverTest() = default; + EcheTrayStreamStatusObserverTest(const EcheTrayStreamStatusObserverTest&) = + delete; + EcheTrayStreamStatusObserverTest& operator=( + const EcheTrayStreamStatusObserverTest&) = delete; + ~EcheTrayStreamStatusObserverTest() override = default; + + // AshTestBase: + void SetUp() override { + scoped_feature_list_.InitWithFeatures( + /*enabled_features=*/{}, + /*disabled_features=*/{features::kEcheSWAInBackground}); + DCHECK(test_web_view_factory_.get()); + ui::ResourceBundle::CleanupSharedInstance(); + AshTestSuite::LoadTestResources(); + AshTestBase::SetUp(); + eche_tray_ = + ash::StatusAreaWidgetTestHelper::GetStatusAreaWidget()->eche_tray(); + + stream_status_change_handler_ = + std::make_unique<EcheStreamStatusChangeHandler>(); + observer_ = std::make_unique<EcheTrayStreamStatusObserver>( + stream_status_change_handler_.get()); + } + + void TearDown() override { + observer_.reset(); + stream_status_change_handler_.reset(); + AshTestBase::TearDown(); + } + + void OnStartStreaming() { observer_->OnStartStreaming(); } + + void OnStreamStatusChanged(mojom::StreamStatus status) { + observer_->OnStreamStatusChanged(status); + } + + EcheTray* eche_tray() { return eche_tray_; } + + private: + base::test::ScopedFeatureList scoped_feature_list_; + EcheTray* eche_tray_ = nullptr; + std::unique_ptr<EcheStreamStatusChangeHandler> stream_status_change_handler_; + std::unique_ptr<EcheTrayStreamStatusObserver> observer_; + // Calling the factory constructor is enough to set it up. + std::unique_ptr<TestAshWebViewFactory> test_web_view_factory_ = + std::make_unique<TestAshWebViewFactory>(); +}; + +TEST_F(EcheTrayStreamStatusObserverTest, LaunchBubble) { + LaunchBubble(GURL("http://google.com"), gfx::Image()); + + // Wait for Eche Tray to load Eche Web to complete. + base::RunLoop().RunUntilIdle(); + // Bubble widget should be created after launch. + EXPECT_TRUE(eche_tray()->get_bubble_wrapper_for_test()); +} + +TEST_F(EcheTrayStreamStatusObserverTest, CloseBubble) { + CloseBubble(); + + // Wait for Eche Web to close. + base::RunLoop().RunUntilIdle(); + // Eche tray should be visible after close. + EXPECT_FALSE(eche_tray()->is_active()); +} + +TEST_F(EcheTrayStreamStatusObserverTest, OnStartStreaming) { + OnStartStreaming(); + + // Wait for Eche Tray to load Eche Web to complete. + base::RunLoop().RunUntilIdle(); + // The bubble should not be created if LaunchBubble be called before. + EXPECT_FALSE(eche_tray()->get_bubble_wrapper_for_test()); + + LaunchBubble(GURL("http://google.com"), gfx::Image()); + + // Wait for Eche Tray to load Eche Web to complete. + base::RunLoop().RunUntilIdle(); + // The bubble widget should be created but not be activated yet. + EXPECT_TRUE(eche_tray()->get_bubble_wrapper_for_test()); + EXPECT_FALSE(eche_tray()->is_active()); + + OnStartStreaming(); + + // Wait for the bubble to show up. + base::RunLoop().RunUntilIdle(); + // The bubble widget should be activated. + EXPECT_TRUE(eche_tray()->get_bubble_wrapper_for_test()); + EXPECT_TRUE(eche_tray()->is_active()); +} + +TEST_F(EcheTrayStreamStatusObserverTest, OnStreamStatusChanged) { + LaunchBubble(GURL("http://google.com"), gfx::Image()); + OnStreamStatusChanged(mojom::StreamStatus::kStreamStatusStarted); + + // Wait for Eche Tray to load Eche Web to complete. + base::RunLoop().RunUntilIdle(); + // Eche tray should be visible when streaming is active + EXPECT_TRUE(eche_tray()->get_bubble_wrapper_for_test()); + + OnStreamStatusChanged(mojom::StreamStatus::kStreamStatusStopped); + + // Wait for Eche Web to close. + base::RunLoop().RunUntilIdle(); + // Eche tray should not be visible when streaming is finished + EXPECT_FALSE(eche_tray()->is_active()); +} + +} // namespace eche_app +} // namespace ash
diff --git a/ash/webui/personalization_app/README.md b/ash/webui/personalization_app/README.md index ea79185..da9bba1 100644 --- a/ash/webui/personalization_app/README.md +++ b/ash/webui/personalization_app/README.md
@@ -6,20 +6,20 @@ - Follow https://chromium.googlesource.com/chromium/src/+/HEAD/docs/vscode.md - Config `tsconfig.json`: - Create or update `${PATH_TO_CHROMIUM}/src/ash/webui/personalization_app/resources/tsconfig.json` + - Remember to replace `out/${YOUR_BUILD}` with your out directory. ``` { "__comment__": [ "This file is used by local typescript language server. It is manually", "maintained to be close to the corresponding ts_library() target in BUILD.gn. ", - "Be sure to replace locally 'out/Debug' with your out dir if it", - "is different. Or change your out dir to 'out/Debug'." + "Be sure to replace locally 'out/${YOUR_BUILD}' with your out dir" ], "extends": "./tsconfig_base.json", "compilerOptions": { "composite": true, "rootDirs": [ ".", - "../../../../out/Debug/gen/ash/webui/personalization_app/resources/preprocessed" + "../../../../out/${YOUR_BUILD}/gen/ash/webui/personalization_app/resources/preprocessed" ], "moduleResolution": "node", "noEmit": true, @@ -28,10 +28,10 @@ "./*" ], "chrome://resources/*": [ - "../../../../out/Debug/gen/ui/webui/resources/preprocessed/*" + "../../../../out/${YOUR_BUILD}/gen/ui/webui/resources/preprocessed/*" ], "//resources/*": [ - "../../../../out/Debug/gen/ui/webui/resources/preprocessed/*" + "../../../../out/${YOUR_BUILD}/gen/ui/webui/resources/preprocessed/*" ], "chrome://resources/polymer/v3_0/*": [ "../../../../third_party/polymer/v3_0/components-chromium/*" @@ -70,14 +70,14 @@ "compilerOptions": { "rootDirs": [ ".", - "../../../../../../out/Debug/gen/chrome/test/data/webui/chromeos/personalization_app" + "../../../../../../out/${YOUR_BUILD}/gen/chrome/test/data/webui/chromeos/personalization_app" ], "paths": { "chrome://resources/*": [ - "../../../../../../out/Debug/gen/ui/webui/resources/preprocessed/*" + "../../../../../../out/${YOUR_BUILD}/gen/ui/webui/resources/preprocessed/*" ], "//resources/*": [ - "../../../../../../out/Debug/gen/ui/webui/resources/preprocessed/*" + "../../../../../../out/${YOUR_BUILD}/gen/ui/webui/resources/preprocessed/*" ], "chrome://resources/polymer/v3_0/*": [ "../../../../../../third_party/polymer/v3_0/components-chromium/*" @@ -95,10 +95,10 @@ "../../../../../../tools/typescript/definitions/*" ], "chrome://personalization/*": [ - "../../../../../../out/Debug/gen/ash/webui/personalization_app/resources/tsc/*" + "../../../../../../out/${YOUR_BUILD}/gen/ash/webui/personalization_app/resources/tsc/*" ], "chrome://webui-test/*": [ - "../../../../../../out/Debug/gen/chrome/test/data/webui/tsc/*" + "../../../../../../out/${YOUR_BUILD}/gen/chrome/test/data/webui/tsc/*" ] } },
diff --git a/ash/wm/desks/desks_textfield.cc b/ash/wm/desks/desks_textfield.cc index 2771e68..31ec315 100644 --- a/ash/wm/desks/desks_textfield.cc +++ b/ash/wm/desks/desks_textfield.cc
@@ -171,6 +171,10 @@ } SkColor DesksTextfield::GetBackgroundColor() const { + // Admin desk templates may be read only. + if (GetReadOnly()) + return SK_ColorTRANSPARENT; + return HasFocus() || IsMouseHovered() ? AshColorProvider::Get()->GetControlsLayerColor( AshColorProvider::ControlsLayerType::
diff --git a/ash/wm/desks/templates/desks_templates_item_view.cc b/ash/wm/desks/templates/desks_templates_item_view.cc index 3fe47bb9..8e7a2ce 100644 --- a/ash/wm/desks/templates/desks_templates_item_view.cc +++ b/ash/wm/desks/templates/desks_templates_item_view.cc
@@ -155,7 +155,13 @@ .SetController(this) .SetText(template_name) .SetAccessibleName(template_name) - .SetReadOnly(!desk_template_->IsModifiable()), + .SetReadOnly(!desk_template_->IsModifiable()) + // Use the focus behavior specified by the + // subclass of `DesksTemplatesNameView` unless the + // template is not modifiable. + .SetFocusBehavior(desk_template_->IsModifiable() + ? GetFocusBehavior() + : FocusBehavior::NEVER), views::Builder<views::ImageView>() .SetPreferredSize( gfx::Size(kManagedStatusIndicatorSize, @@ -204,15 +210,19 @@ l10n_util::GetStringUTF16(IDS_ASH_DESKS_TEMPLATES_USE_TEMPLATE_BUTTON), PillButton::Type::kIconless, /*icon=*/nullptr)); - delete_button_ = hover_container_->AddChildView(std::make_unique<CloseButton>( - base::BindRepeating(&DesksTemplatesItemView::OnDeleteButtonPressed, - weak_ptr_factory_.GetWeakPtr()), - CloseButton::Type::kMedium)); - delete_button_->SetVectorIcon(kDeleteIcon); - delete_button_->SetTooltipText(l10n_util::GetStringUTF16( - IDS_ASH_DESKS_TEMPLATES_DELETE_DIALOG_CONFIRM_BUTTON)); - delete_button_->SetBackgroundColor(color_provider->GetControlsLayerColor( - AshColorProvider::ControlsLayerType::kControlBackgroundColorInactive)); + // Users cannot delete admin templates. + if (!is_admin_managed) { + delete_button_ = + hover_container_->AddChildView(std::make_unique<CloseButton>( + base::BindRepeating(&DesksTemplatesItemView::OnDeleteButtonPressed, + weak_ptr_factory_.GetWeakPtr()), + CloseButton::Type::kMedium)); + delete_button_->SetVectorIcon(kDeleteIcon); + delete_button_->SetTooltipText(l10n_util::GetStringUTF16( + IDS_ASH_DESKS_TEMPLATES_DELETE_DIALOG_CONFIRM_BUTTON)); + delete_button_->SetBackgroundColor(color_provider->GetControlsLayerColor( + AshColorProvider::ControlsLayerType::kControlBackgroundColorInactive)); + } name_view_observation_.Observe(name_view_); @@ -316,12 +326,14 @@ if (previous_name_view_width != name_view_->width()) OnTemplateNameChanged(desk_template_->template_name()); - const gfx::Size delete_button_size = delete_button_->GetPreferredSize(); - DCHECK_EQ(delete_button_size.width(), delete_button_size.height()); - delete_button_->SetBoundsRect( - gfx::Rect(width() - delete_button_size.width() - kDeleteButtonMargin, - kDeleteButtonMargin, delete_button_size.width(), - delete_button_size.height())); + if (delete_button_) { + const gfx::Size delete_button_size = delete_button_->GetPreferredSize(); + DCHECK_EQ(delete_button_size.width(), delete_button_size.height()); + delete_button_->SetBoundsRect( + gfx::Rect(width() - delete_button_size.width() - kDeleteButtonMargin, + kDeleteButtonMargin, delete_button_size.width(), + delete_button_size.height())); + } const gfx::Size launch_button_preferred_size = launch_button_->CalculatePreferredSize();
diff --git a/ash/wm/desks/templates/desks_templates_unittest.cc b/ash/wm/desks/templates/desks_templates_unittest.cc index b7570eb..a59674f 100644 --- a/ash/wm/desks/templates/desks_templates_unittest.cc +++ b/ash/wm/desks/templates/desks_templates_unittest.cc
@@ -86,12 +86,18 @@ // for testing the names and times of the UI directly. void AddEntry(const base::GUID& uuid, const std::string& name, + base::Time created_time) { + AddEntry(uuid, name, created_time, DeskTemplateSource::kUser, + std::make_unique<app_restore::RestoreData>()); + } + + void AddEntry(const base::GUID& uuid, + const std::string& name, base::Time created_time, - std::unique_ptr<app_restore::RestoreData> restore_data = - std::make_unique<app_restore::RestoreData>()) { + DeskTemplateSource source, + std::unique_ptr<app_restore::RestoreData> restore_data) { auto desk_template = std::make_unique<DeskTemplate>( - uuid.AsLowercaseString(), DeskTemplateSource::kUser, name, - created_time); + uuid.AsLowercaseString(), source, name, created_time); desk_template->set_desk_restore_data(std::move(restore_data)); AddEntry(std::move(desk_template)); @@ -1021,6 +1027,7 @@ TEST_F(DesksTemplatesTest, IconsOrder) { // Create a `DeskTemplate` using which has 5 apps and each app has 1 window. AddEntry(base::GUID::GenerateRandomV4(), "template_1", base::Time::Now(), + DeskTemplateSource::kUser, CreateRestoreData(std::vector<int>(5, 1))); OpenOverviewAndShowTemplatesGrid(); @@ -1086,7 +1093,7 @@ restore_data->ModifyWindowInfo(kAppId2, kWindowId2, window_info_2); AddEntry(base::GUID::GenerateRandomV4(), "template_1", base::Time::Now(), - std::move(restore_data)); + DeskTemplateSource::kUser, std::move(restore_data)); OpenOverviewAndShowTemplatesGrid(); @@ -1116,7 +1123,7 @@ std::vector<int> window_info( kNumOverflowApps + DesksTemplatesIconContainer::kMaxIcons, 1); AddEntry(base::GUID::GenerateRandomV4(), "template_1", base::Time::Now(), - CreateRestoreData(window_info)); + DeskTemplateSource::kUser, CreateRestoreData(window_info)); OpenOverviewAndShowTemplatesGrid(); @@ -1153,7 +1160,7 @@ std::vector<int> window_info( kNumOverflowApps + DesksTemplatesIconContainer::kMaxIcons, 2); AddEntry(base::GUID::GenerateRandomV4(), "template_1", base::Time::Now(), - CreateRestoreData(window_info)); + DeskTemplateSource::kUser, CreateRestoreData(window_info)); OpenOverviewAndShowTemplatesGrid(); @@ -1204,18 +1211,18 @@ } // Tests that apps with multiple window are counted correctly. -// _______________________________________________________________________________ -// | _________ _________ _________________ _________________ _________ -// | | | | | | | | | | | | | | | -// | | I | | I | | I + 1 | | I | + 1 | | + 3 | -// | | |_______| |_______| |_______|_______| |_______|_______| |_______| -// | -// |_____________________________________________________________________________| +// _________________________________________________________________________ +// | ________ ________ ________________ ________________ ________ | +// | | | | | | | | | | | | | | +// | | I | | I | | I + 1 | | I | + 1 | | + 3 | | +// | |_______| |_______| |_______|_______| |_______|_______| |_______| | +// |_______________________________________________________________________| // TEST_F(DesksTemplatesTest, IconViewMultipleWindows) { // Create a `DeskTemplate` that contains some apps with multiple windows and // more than kMaxIcons windows. The grid should appear like the above diagram. AddEntry(base::GUID::GenerateRandomV4(), "template_1", base::Time::Now(), + DeskTemplateSource::kUser, CreateRestoreData(std::vector<int>{1, 1, 2, 2, 3})); // Enter overview and show the Desks Templates Grid. @@ -1262,7 +1269,7 @@ TEST_F(DesksTemplatesTest, IconViewMoreThan99Windows) { // Create a `DeskTemplate` using which has 1 app with 101 windows. AddEntry(base::GUID::GenerateRandomV4(), "template_1", base::Time::Now(), - CreateRestoreData(std::vector<int>{101})); + DeskTemplateSource::kUser, CreateRestoreData(std::vector<int>{101})); // Enter overview and show the Desks Templates Grid. OpenOverviewAndShowTemplatesGrid(); @@ -1294,7 +1301,7 @@ // `DesksTemplatesIconContainer::kMaxIcons` apps and each app has 1 window. std::vector<int> window_info(DesksTemplatesIconContainer::kMaxIcons, 1); AddEntry(base::GUID::GenerateRandomV4(), "template_1", base::Time::Now(), - CreateRestoreData(window_info)); + DeskTemplateSource::kUser, CreateRestoreData(window_info)); OpenOverviewAndShowTemplatesGrid(); @@ -1318,7 +1325,7 @@ // of those app ids to be unavailable. std::vector<int> window_info(4, 1); AddEntry(base::GUID::GenerateRandomV4(), "template", base::Time::Now(), - CreateRestoreData(window_info)); + DeskTemplateSource::kUser, CreateRestoreData(window_info)); // `CreateRestoreData` creates the windows with app ids of "0", "1", "2", etc. // Set 2 of those app ids to be unavailable. @@ -1351,7 +1358,7 @@ // of those app ids to be unavailable. std::vector<int> window_info(8, 1); AddEntry(base::GUID::GenerateRandomV4(), "template", base::Time::Now(), - CreateRestoreData(window_info)); + DeskTemplateSource::kUser, CreateRestoreData(window_info)); // `CreateRestoreData` creates the windows with app ids of "0", "1", "2", etc. // Set 2 of those app ids to be unavailable. @@ -1383,7 +1390,7 @@ // Create a `DeskTemplate` which has 10 apps and each app has 1 window. std::vector<int> window_info(10, 1); AddEntry(base::GUID::GenerateRandomV4(), "template", base::Time::Now(), - CreateRestoreData(window_info)); + DeskTemplateSource::kUser, CreateRestoreData(window_info)); // Set all 10 app ids to be unavailable. auto* delegate = static_cast<TestDesksTemplatesDelegate*>( @@ -3244,4 +3251,32 @@ gfx::RectF(save_desk_as_template_widget->GetWindowBoundsInScreen()))); } +TEST_F(DesksTemplatesTest, AdminTemplate) { + AddEntry(base::GUID::GenerateRandomV4(), "template", base::Time::Now(), + DeskTemplateSource::kPolicy, + std::make_unique<app_restore::RestoreData>()); + + OpenOverviewAndShowTemplatesGrid(); + + // Tests that the name is read only and not focusable. + DesksTemplatesItemView* item_view = + GetItemViewFromTemplatesGrid(/*grid_item_index=*/0); + DesksTemplatesNameView* name_view = item_view->name_view(); + EXPECT_TRUE(name_view->GetReadOnly()); + EXPECT_FALSE(name_view->IsFocusable()); + + // Tests that there is an admin message in the time view and that the delete + // button is not created. + DesksTemplatesItemViewTestApi test_api(item_view); + EXPECT_EQ(u"Shared by your administrator", test_api.time_view()->GetText()); + EXPECT_FALSE(test_api.delete_button()); + + // Tests that the name view cannot be tabbed into for admin templates, as they + // aren't editable anyhow. + SendKey(ui::VKEY_TAB); + EXPECT_EQ(item_view, GetHighlightedView()); + SendKey(ui::VKEY_TAB); + EXPECT_NE(name_view, GetHighlightedView()); +} + } // namespace ash
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc index f6c9148..02e576d 100644 --- a/ash/wm/overview/overview_grid.cc +++ b/ash/wm/overview/overview_grid.cc
@@ -284,7 +284,7 @@ // Show plus icon if drag a tab from a multi-tab window. widget->SetContentsView(std::make_unique<DropTargetView>( - dragged_window->GetProperty(ash::kTabDraggingSourceWindowKey))); + dragged_window->GetProperty(kTabDraggingSourceWindowKey))); aura::Window* drop_target_window = widget->GetNativeWindow(); drop_target_window->parent()->StackChildAtBottom(drop_target_window); widget->Show(); @@ -293,7 +293,7 @@ // Creates `save_desk_as_template_widget_`. It contains a button that saves the // active desk as a template. -std::unique_ptr<views::Widget> SaveDeskAsTemplateWidget( +std::unique_ptr<views::Widget> CreateSaveDeskAsTemplateWidget( aura::Window* root_window) { views::Widget::InitParams params; params.type = views::Widget::InitParams::TYPE_POPUP; @@ -1898,7 +1898,8 @@ } if (!save_desk_as_template_widget_) { - save_desk_as_template_widget_ = SaveDeskAsTemplateWidget(root_window_); + save_desk_as_template_widget_ = + CreateSaveDeskAsTemplateWidget(root_window_); save_desk_as_template_widget_->SetContentsView( std::make_unique<SaveDeskTemplateButton>(base::BindRepeating( &OverviewGrid::OnSaveDeskAsTemplateButtonPressed, @@ -2456,7 +2457,7 @@ ->disable_app_id_check_for_desk_templates() || !full_restore::GetAppId(window).empty()); int addend = increment ? 1 : -1; - if (!ash::DeskTemplate::IsAppTypeSupported(window) || !has_restore_id) + if (!DeskTemplate::IsAppTypeSupported(window) || !has_restore_id) num_unsupported_windows_ += addend; else if (Shell::Get()->desks_templates_delegate()->IsIncognitoWindow(window)) num_incognito_windows_ += addend;
diff --git a/ash/wm/overview/overview_highlight_controller.cc b/ash/wm/overview/overview_highlight_controller.cc index 9c8670c..24c1b71 100644 --- a/ash/wm/overview/overview_highlight_controller.cc +++ b/ash/wm/overview/overview_highlight_controller.cc
@@ -225,7 +225,11 @@ for (DesksTemplatesItemView* template_item : templates_grid_view->grid_items()) { traversable_views.push_back(template_item); - traversable_views.push_back(template_item->name_view()); + + // Admin templates names cannot be edited or focused. + DesksTemplatesNameView* name_view = template_item->name_view(); + if (name_view->IsFocusable()) + traversable_views.push_back(template_item->name_view()); } } else { for (auto& item : grid->window_list())
diff --git a/base/BUILD.gn b/base/BUILD.gn index 46d1d5c..0ec830b0 100644 --- a/base/BUILD.gn +++ b/base/BUILD.gn
@@ -1334,8 +1334,6 @@ "debug/proc_maps_linux.cc", "debug/proc_maps_linux.h", "files/dir_reader_linux.h", - "files/file_path_watcher_linux.cc", - "files/file_path_watcher_linux.h", "files/file_util_linux.cc", "files/scoped_file_linux.cc", "process/internal_linux.cc", @@ -1349,6 +1347,13 @@ ] } + if (is_linux || is_chromeos || is_android || is_fuchsia) { + sources += [ + "files/file_path_watcher_inotify.cc", + "files/file_path_watcher_inotify.h", + ] + } + if (!is_nacl) { sources += [ "base_paths.cc", @@ -1683,8 +1688,6 @@ "debug/elf_reader.h", "debug/proc_maps_linux.cc", "debug/proc_maps_linux.h", - "files/file_path_watcher_linux.cc", - "files/file_path_watcher_linux.h", "power_monitor/power_monitor_device_source_android.cc", "process/internal_linux.cc", "process/internal_linux.h", @@ -1758,7 +1761,6 @@ "files/file_descriptor_watcher_posix.cc", "files/file_descriptor_watcher_posix.h", "files/file_enumerator_posix.cc", - "files/file_path_watcher_fuchsia.cc", "files/file_posix.cc", "files/file_util_fuchsia.cc", "files/file_util_posix.cc", @@ -3696,9 +3698,6 @@ "task/thread_pool/task_tracker_posix_unittest.cc", ] - # TODO(crbug.com/851641): FilePatchWatcherImpl is not implemented. - sources -= [ "files/file_path_watcher_unittest.cc" ] - deps += [ ":test_interface_impl", ":test_log_listener_safe",
diff --git a/base/files/file_path_watcher_linux.cc b/base/files/file_path_watcher_inotify.cc similarity index 95% rename from base/files/file_path_watcher_linux.cc rename to base/files/file_path_watcher_inotify.cc index 9297ece..5a6b6f40 100644 --- a/base/files/file_path_watcher_linux.cc +++ b/base/files/file_path_watcher_inotify.cc
@@ -27,7 +27,7 @@ #include "base/containers/contains.h" #include "base/files/file_enumerator.h" #include "base/files/file_path.h" -#include "base/files/file_path_watcher_linux.h" +#include "base/files/file_path_watcher_inotify.h" #include "base/files/file_util.h" #include "base/lazy_instance.h" #include "base/location.h" @@ -48,6 +48,8 @@ namespace { +#if !BUILDFLAG(IS_FUCHSIA) + // The /proc path to max_user_watches. constexpr char kInotifyMaxUserWatchesPath[] = "/proc/sys/fs/inotify/max_user_watches"; @@ -61,6 +63,8 @@ // /proc/sys/fs/inotify/max_user_watches fails. constexpr size_t kDefaultInotifyMaxUserWatches = 8192u; +#endif // !BUILDFLAG(IS_FUCHSIA) + class FilePathWatcherImpl; class InotifyReader; @@ -70,6 +74,10 @@ // Get the maximum number of inotify watches can be used by a FilePathWatcher // instance. This is based on /proc/sys/fs/inotify/max_user_watches entry. size_t GetMaxNumberOfInotifyWatches() { +#if BUILDFLAG(IS_FUCHSIA) + // Fuchsia has no limit on the number of watches. + return std::numeric_limits<int>::max(); +#else static const size_t max = []() { size_t max_number_of_inotify_watches = 0u; @@ -82,6 +90,7 @@ return max_number_of_inotify_watches / kExpectedFilePathWatchers; }(); return g_override_max_inotify_watches ? g_override_max_inotify_watches : max; +#endif // if BUILDFLAG(IS_FUCHSIA) } class InotifyReaderThreadDelegate final : public PlatformThread::Delegate { @@ -208,8 +217,7 @@ // - Only if the target being watched is a symbolic link. struct WatchEntry { explicit WatchEntry(const FilePath::StringType& dirname) - : watch(InotifyReader::kInvalidWatch), - subdir(dirname) {} + : watch(InotifyReader::kInvalidWatch), subdir(dirname) {} InotifyReader::Watch watch; FilePath::StringType subdir; @@ -334,8 +342,8 @@ return PlatformThread::CreateNonJoinable(0, &thread_delegate_); } -InotifyReader::Watch InotifyReader::AddWatch( - const FilePath& path, FilePathWatcherImpl* watcher) { +InotifyReader::Watch InotifyReader::AddWatch(const FilePath& path, + FilePathWatcherImpl* watcher) { if (!valid_) return kInvalidWatch; @@ -347,8 +355,7 @@ ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::WILL_BLOCK); Watch watch = inotify_add_watch(inotify_fd_, path.value().c_str(), IN_ATTRIB | IN_CREATE | IN_DELETE | - IN_CLOSE_WRITE | IN_MOVE | - IN_ONLYDIR); + IN_CLOSE_WRITE | IN_MOVE | IN_ONLYDIR); if (watch == kInvalidWatch) return kInvalidWatch; @@ -440,10 +447,9 @@ continue; // Check whether a path component of |target_| changed. - bool change_on_target_path = - child.empty() || - (child == watch_entry.linkname) || - (child == watch_entry.subdir); + bool change_on_target_path = child.empty() || + (child == watch_entry.linkname) || + (child == watch_entry.subdir); // Check if the change references |target_| or a direct child of |target_|. bool target_changed; @@ -452,8 +458,8 @@ // |target_| = "/path/to/foo", this is for "foo". Here, check either: // - the target has no symlink: it is the target and it changed. // - the target has a symlink, and it matches |child|. - target_changed = (watch_entry.linkname.empty() || - child == watch_entry.linkname); + target_changed = + (watch_entry.linkname.empty() || child == watch_entry.linkname); } else { // The fired watch is for a WatchEntry with a subdir. Thus for a given // |target_| = "/path/to/foo", this is for {"/", "/path", "/path/to"}. @@ -492,8 +498,7 @@ // disappears in this case. // - One of the parent directories appears. The event corresponding to // the target appearing might have been missed in this case, so recheck. - if (target_changed || - (change_on_target_path && deleted) || + if (target_changed || (change_on_target_path && deleted) || (change_on_target_path && created && PathExists(target_))) { if (!did_update) { if (!UpdateRecursiveWatches(fired_watch, is_dir)) { @@ -700,11 +705,9 @@ // rather than followed. Following symlinks can easily lead to the undesirable // situation where the entire file system is being watched. FileEnumerator enumerator( - path, - true /* recursive enumeration */, + path, true /* recursive enumeration */, FileEnumerator::DIRECTORIES | FileEnumerator::SHOW_SYM_LINKS); - for (FilePath current = enumerator.Next(); - !current.empty(); + for (FilePath current = enumerator.Next(); !current.empty(); current = enumerator.Next()) { DCHECK(enumerator.GetInfo().IsDirectory()); @@ -762,6 +765,10 @@ bool FilePathWatcherImpl::AddWatchForBrokenSymlink(const FilePath& path, WatchEntry* watch_entry) { +#if BUILDFLAG(IS_FUCHSIA) + // Fuchsia does not support symbolic links. + return false; +#else // BUILDFLAG(IS_FUCHSIA) DCHECK_EQ(InotifyReader::kInvalidWatch, watch_entry->watch); FilePath link; if (!ReadSymbolicLink(path, &link)) @@ -782,12 +789,13 @@ // TODO(craig) Symlinks only work if the parent directory for the target // exist. Ideally we should make sure we've watched all the components of // the symlink path for changes. See crbug.com/91561 for details. - DPLOG(WARNING) << "Watch failed for " << link.DirName().value(); + DPLOG(WARNING) << "Watch failed for " << link.DirName().value(); return true; } watch_entry->watch = watch; watch_entry->linkname = link.BaseName().value(); return true; +#endif // BUILDFLAG(IS_FUCHSIA) } bool FilePathWatcherImpl::HasValidWatchVector() const {
diff --git a/base/files/file_path_watcher_linux.h b/base/files/file_path_watcher_inotify.h similarity index 77% rename from base/files/file_path_watcher_linux.h rename to base/files/file_path_watcher_inotify.h index 0b27676..cc49ae8 100644 --- a/base/files/file_path_watcher_linux.h +++ b/base/files/file_path_watcher_inotify.h
@@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef BASE_FILES_FILE_PATH_WATCHER_LINUX_H_ -#define BASE_FILES_FILE_PATH_WATCHER_LINUX_H_ +#ifndef BASE_FILES_FILE_PATH_WATCHER_INOTIFY_H_ +#define BASE_FILES_FILE_PATH_WATCHER_INOTIFY_H_ #include <stddef.h> @@ -20,4 +20,4 @@ } // namespace base -#endif // BASE_FILES_FILE_PATH_WATCHER_LINUX_H_ +#endif // BASE_FILES_FILE_PATH_WATCHER_INOTIFY_H_
diff --git a/base/files/file_path_watcher_unittest.cc b/base/files/file_path_watcher_unittest.cc index 58d16505..f4487c8 100644 --- a/base/files/file_path_watcher_unittest.cc +++ b/base/files/file_path_watcher_unittest.cc
@@ -44,8 +44,8 @@ #include "base/files/file_descriptor_watcher_posix.h" #endif // BUILDFLAG(IS_POSIX) -#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) -#include "base/files/file_path_watcher_linux.h" +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA) +#include "base/files/file_path_watcher_inotify.h" #include "base/format_macros.h" #endif @@ -156,7 +156,7 @@ class FilePathWatcherTest : public testing::Test { public: FilePathWatcherTest() -#if BUILDFLAG(IS_POSIX) +#if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA) : task_environment_(test::TaskEnvironment::MainThreadType::IO) #endif { @@ -255,7 +255,13 @@ } // Verify that moving the file into place is caught. -TEST_F(FilePathWatcherTest, MovedFile) { +#if BUILDFLAG(IS_FUCHSIA) +// TODO(crbug.com/851641): Re-enable for Fuchsia when inotify is fixed. +#define MAYBE_MovedFile DISABLED_MovedFile +#else +#define MAYBE_MovedFile MovedFile +#endif +TEST_F(FilePathWatcherTest, MAYBE_MovedFile) { FilePath source_file(temp_dir_.GetPath().AppendASCII("source")); ASSERT_TRUE(WriteFile(source_file, "content")); @@ -269,7 +275,13 @@ ASSERT_TRUE(WaitForEvents()); } -TEST_F(FilePathWatcherTest, DeletedFile) { +#if BUILDFLAG(IS_FUCHSIA) +// TODO(crbug.com/851641): Re-enable for Fuchsia when inotify is fixed. +#define MAYBE_DeletedFile DISABLED_DeletedFile +#else +#define MAYBE_DeletedFile DeletedFile +#endif +TEST_F(FilePathWatcherTest, MAYBE_DeletedFile) { ASSERT_TRUE(WriteFile(test_file(), "content")); FilePathWatcher watcher; @@ -330,7 +342,13 @@ ASSERT_TRUE(WriteFile(test_file(), "content")); } -TEST_F(FilePathWatcherTest, MultipleWatchersSingleFile) { +#if BUILDFLAG(IS_FUCHSIA) +// TODO(crbug.com/851641): Re-enable for Fuchsia when inotify is fixed. +#define MAYBE_MultipleWatchersSingleFile DISABLED_MultipleWatchersSingleFile +#else +#define MAYBE_MultipleWatchersSingleFile MultipleWatchersSingleFile +#endif +TEST_F(FilePathWatcherTest, MAYBE_MultipleWatchersSingleFile) { FilePathWatcher watcher1, watcher2; std::unique_ptr<TestDelegate> delegate1(new TestDelegate(collector())); std::unique_ptr<TestDelegate> delegate2(new TestDelegate(collector())); @@ -345,7 +363,13 @@ // Verify that watching a file whose parent directory doesn't exist yet works if // the directory and file are created eventually. -TEST_F(FilePathWatcherTest, NonExistentDirectory) { +#if BUILDFLAG(IS_FUCHSIA) +// TODO(crbug.com/851641): Re-enable for Fuchsia when inotify is fixed. +#define MAYBE_NonExistentDirectory DISABLED_NonExistentDirectory +#else +#define MAYBE_NonExistentDirectory NonExistentDirectory +#endif +TEST_F(FilePathWatcherTest, MAYBE_NonExistentDirectory) { FilePathWatcher watcher; FilePath dir(temp_dir_.GetPath().AppendASCII("dir")); FilePath file(dir.AppendASCII("file")); @@ -371,7 +395,13 @@ // Exercises watch reconfiguration for the case that directories on the path // are rapidly created. -TEST_F(FilePathWatcherTest, DirectoryChain) { +#if BUILDFLAG(IS_FUCHSIA) +// TODO(crbug.com/851641): Re-enable for Fuchsia when inotify is fixed. +#define MAYBE_DirectoryChain DISABLED_DirectoryChain +#else +#define MAYBE_DirectoryChain DirectoryChain +#endif +TEST_F(FilePathWatcherTest, MAYBE_DirectoryChain) { FilePath path(temp_dir_.GetPath()); std::vector<std::string> dir_names; for (int i = 0; i < 20; i++) { @@ -402,7 +432,13 @@ ASSERT_TRUE(WaitForEvents()); } -TEST_F(FilePathWatcherTest, DisappearingDirectory) { +#if BUILDFLAG(IS_FUCHSIA) +// TODO(crbug.com/851641): Re-enable for Fuchsia when inotify is fixed. +#define MAYBE_DisappearingDirectory DISABLED_DisappearingDirectory +#else +#define MAYBE_DisappearingDirectory DisappearingDirectory +#endif +TEST_F(FilePathWatcherTest, MAYBE_DisappearingDirectory) { FilePathWatcher watcher; FilePath dir(temp_dir_.GetPath().AppendASCII("dir")); FilePath file(dir.AppendASCII("file")); @@ -417,7 +453,13 @@ } // Tests that a file that is deleted and reappears is tracked correctly. -TEST_F(FilePathWatcherTest, DeleteAndRecreate) { +#if BUILDFLAG(IS_FUCHSIA) +// TODO(crbug.com/851641): Re-enable for Fuchsia when inotify is fixed. +#define MAYBE_DeleteAndRecreate DISABLED_DeleteAndRecreate +#else +#define MAYBE_DeleteAndRecreate DeleteAndRecreate +#endif +TEST_F(FilePathWatcherTest, MAYBE_DeleteAndRecreate) { ASSERT_TRUE(WriteFile(test_file(), "content")); FilePathWatcher watcher; std::unique_ptr<TestDelegate> delegate(new TestDelegate(collector())); @@ -433,7 +475,13 @@ ASSERT_TRUE(WaitForEvents()); } -TEST_F(FilePathWatcherTest, WatchDirectory) { +#if BUILDFLAG(IS_FUCHSIA) +// TODO(crbug.com/851641): Re-enable for Fuchsia when inotify is fixed. +#define MAYBE_WatchDirectory DISABLED_WatchDirectory +#else +#define MAYBE_WatchDirectory WatchDirectory +#endif +TEST_F(FilePathWatcherTest, MAYBE_WatchDirectory) { FilePathWatcher watcher; FilePath dir(temp_dir_.GetPath().AppendASCII("dir")); FilePath file1(dir.AppendASCII("file1")); @@ -466,7 +514,13 @@ ASSERT_TRUE(WaitForEvents()); } -TEST_F(FilePathWatcherTest, MoveParent) { +#if BUILDFLAG(IS_FUCHSIA) +// TODO(crbug.com/851641): Re-enable for Fuchsia when inotify is fixed. +#define MAYBE_MoveParent DISABLED_MoveParent +#else +#define MAYBE_MoveParent MoveParent +#endif +TEST_F(FilePathWatcherTest, MAYBE_MoveParent) { FilePathWatcher file_watcher; FilePathWatcher subdir_watcher; FilePath dir(temp_dir_.GetPath().AppendASCII("dir")); @@ -492,7 +546,13 @@ ASSERT_TRUE(WaitForEvents()); } -TEST_F(FilePathWatcherTest, RecursiveWatch) { +#if BUILDFLAG(IS_FUCHSIA) +// TODO(crbug.com/851641): Re-enable for Fuchsia when inotify is fixed. +#define MAYBE_RecursiveWatch DISABLED_RecursiveWatch +#else +#define MAYBE_RecursiveWatch RecursiveWatch +#endif +TEST_F(FilePathWatcherTest, MAYBE_RecursiveWatch) { FilePathWatcher watcher; FilePath dir(temp_dir_.GetPath().AppendASCII("dir")); std::unique_ptr<TestDelegate> delegate(new TestDelegate(collector())); @@ -615,7 +675,13 @@ } #endif // BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_ANDROID) -TEST_F(FilePathWatcherTest, MoveChild) { +#if BUILDFLAG(IS_FUCHSIA) +// TODO(crbug.com/851641): Re-enable for Fuchsia when inotify is fixed. +#define MAYBE_MoveChild DISABLED_MoveChild +#else +#define MAYBE_MoveChild MoveChild +#endif +TEST_F(FilePathWatcherTest, MAYBE_MoveChild) { FilePathWatcher file_watcher; FilePathWatcher subdir_watcher; FilePath source_dir(temp_dir_.GetPath().AppendASCII("source")); @@ -642,15 +708,19 @@ } // Verify that changing attributes on a file is caught -#if BUILDFLAG(IS_ANDROID) +#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA) +// TODO(crbug.com/851641): Re-enable for Fuchsia when inotify is fixed. + // Apps cannot change file attributes on Android in /sdcard as /sdcard uses the // "fuse" file system, while /data uses "ext4". Running these tests in /data // would be preferable and allow testing file attributes and symlinks. // TODO(pauljensen): Re-enable when crbug.com/475568 is fixed and SetUp() places // the |temp_dir_| in /data. -#define FileAttributesChanged DISABLED_FileAttributesChanged +#define MAYBE_FileAttributesChanged DISABLED_FileAttributesChanged +#else +#define MAYBE_FileAttributesChanged FileAttributesChanged #endif // BUILDFLAG(IS_ANDROID) -TEST_F(FilePathWatcherTest, FileAttributesChanged) { +TEST_F(FilePathWatcherTest, MAYBE_FileAttributesChanged) { ASSERT_TRUE(WriteFile(test_file(), "content")); FilePathWatcher watcher; std::unique_ptr<TestDelegate> delegate(new TestDelegate(collector()));
diff --git a/base/win/win_util.cc b/base/win/win_util.cc index 7d78505a..2da6cbc 100644 --- a/base/win/win_util.cc +++ b/base/win/win_util.cc
@@ -58,6 +58,7 @@ #include "base/win/scoped_hstring.h" #include "base/win/scoped_propvariant.h" #include "base/win/shlwapi.h" +#include "base/win/static_constants.h" #include "base/win/windows_version.h" #include "third_party/abseil-cpp/absl/types/optional.h" @@ -864,6 +865,32 @@ return current_session_id != glass_session_id; } +#if !defined(OFFICIAL_BUILD) +bool IsAppVerifierEnabled(const std::wstring& process_name) { + RegKey key; + + // Look for GlobalFlag in the IFEO\chrome.exe key. If it is present then + // Application Verifier or gflags.exe are configured. Most GlobalFlag + // settings are experimentally determined to be incompatible with renderer + // code integrity and a safe set is not known so any GlobalFlag entry is + // assumed to mean that Application Verifier (or pageheap) are enabled. + // The settings are propagated to both 64-bit WOW6432Node versions of the + // registry on 64-bit Windows, so only one check is needed. + return key.Open( + HKEY_LOCAL_MACHINE, + (L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File " + L"Execution Options\\" + + process_name) + .c_str(), + KEY_READ | KEY_WOW64_64KEY) == ERROR_SUCCESS && + key.HasValue(L"GlobalFlag"); +} +#endif // !defined(OFFICIAL_BUILD) + +bool IsAppVerifierLoaded() { + return GetModuleHandleA(kApplicationVerifierDllName); +} + ScopedDomainStateForTesting::ScopedDomainStateForTesting(bool state) : initial_state_(IsEnrolledToDomain()) { *GetDomainEnrollmentStateStorage() = state;
diff --git a/base/win/win_util.h b/base/win/win_util.h index 242b7e3..abcb246 100644 --- a/base/win/win_util.h +++ b/base/win/win_util.h
@@ -232,6 +232,17 @@ // Returns true if current session is a remote session. BASE_EXPORT bool IsCurrentSessionRemote(); +#if !defined(OFFICIAL_BUILD) +// IsAppVerifierEnabled() indicates whether a newly created process will get +// Application Verifier or pageheap injected into it. Only available in +// unofficial builds to prevent abuse. +BASE_EXPORT bool IsAppVerifierEnabled(const std::wstring& process_name); +#endif // !defined(OFFICIAL_BUILD) + +// IsAppVerifierLoaded() indicates whether Application Verifier is *already* +// loaded into the current process. +BASE_EXPORT bool IsAppVerifierLoaded(); + // Allows changing the domain enrolled state for the life time of the object. // The original state is restored upon destruction. class BASE_EXPORT ScopedDomainStateForTesting {
diff --git a/build/OWNERS.setnoparent b/build/OWNERS.setnoparent index ec033cf..f0cd791 100644 --- a/build/OWNERS.setnoparent +++ b/build/OWNERS.setnoparent
@@ -69,3 +69,7 @@ # that can make infra changes. file://infra/config/groups/cq-usage/CQ_USAGE_OWNERS file://infra/config/groups/sheriff-rotations/CHROMIUM_OWNERS + +# Origin Trials owners are responsible for determining trials that need to be +# completed manually. +file://third_party/blink/common/origin_trials/OT_OWNERS
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1 index d36ddce..22221e6 100644 --- a/build/fuchsia/linux.sdk.sha1 +++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@ -7.20220325.2.1 +7.20220325.3.1
diff --git a/build/fuchsia/linux_internal.sdk.sha1 b/build/fuchsia/linux_internal.sdk.sha1 index d36ddce..22221e6 100644 --- a/build/fuchsia/linux_internal.sdk.sha1 +++ b/build/fuchsia/linux_internal.sdk.sha1
@@ -1 +1 @@ -7.20220325.2.1 +7.20220325.3.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1 index d36ddce..22221e6 100644 --- a/build/fuchsia/mac.sdk.sha1 +++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@ -7.20220325.2.1 +7.20220325.3.1
diff --git a/build/util/BUILD.gn b/build/util/BUILD.gn index 2745449..834cc1ed 100644 --- a/build/util/BUILD.gn +++ b/build/util/BUILD.gn
@@ -30,7 +30,6 @@ group("test_results") { data = [ - "//.vpython", "//.vpython3", "//build/util/lib/__init__.py", "//build/util/lib/results/",
diff --git a/build/util/generate_wrapper.gni b/build/util/generate_wrapper.gni index d63720b..316d6b9 100644 --- a/build/util/generate_wrapper.gni +++ b/build/util/generate_wrapper.gni
@@ -20,8 +20,6 @@ # build product. Paths can be relative to the containing gn file # or source-absolute. # executable_args: List of arguments to write into the wrapper. -# use_vpython3: If false, invoke the generated wrapper with vpython instead -# of vpython3. # # Example wrapping a checked-in script: # generate_wrapper("sample_wrapper") { @@ -70,7 +68,10 @@ if (!defined(data)) { data = [] } - data += [ _wrapper_script ] + data += [ + _wrapper_script, + "//.vpython3", + ] outputs = [ _wrapper_script ] _rebased_executable_to_wrap = @@ -92,10 +93,6 @@ _script_language, ] - if (!defined(invoker.use_vpython3) || invoker.use_vpython3) { - args += [ "--use-vpython3" ] - data += [ "//.vpython3" ] - } args += [ "--" ] args += _wrapped_arguments
diff --git a/build/util/generate_wrapper.py b/build/util/generate_wrapper.py index 07167e86..ce264ef 100755 --- a/build/util/generate_wrapper.py +++ b/build/util/generate_wrapper.py
@@ -1,4 +1,4 @@ -#!/usr/bin/env vpython +#!/usr/bin/env python3 # Copyright 2019 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. @@ -15,7 +15,7 @@ # The interpreter doesn't know about the script, so we have bash # inject the script location. BASH_TEMPLATE = textwrap.dedent("""\ - #!/usr/bin/env {vpython} + #!/usr/bin/env vpython3 _SCRIPT_LOCATION = __file__ {script} """) @@ -27,7 +27,7 @@ # directly. BATCH_TEMPLATE = textwrap.dedent("""\ @SETLOCAL ENABLEDELAYEDEXPANSION \ - & {vpython}.bat -x "%~f0" %* \ + & vpython3.bat -x "%~f0" %* \ & EXIT /B !ERRORLEVEL! _SCRIPT_LOCATION = __file__ {script} @@ -172,8 +172,7 @@ executable_path=str(args.executable), executable_args=str(args.executable_args)) template = SCRIPT_TEMPLATES[args.script_language] - wrapper_script.write( - template.format(script=py_contents, vpython=args.vpython)) + wrapper_script.write(template.format(script=py_contents)) os.chmod(args.wrapper_script, 0o750) return 0 @@ -195,12 +194,6 @@ '--script-language', choices=SCRIPT_TEMPLATES.keys(), help='Language in which the wrapper script will be written.') - parser.add_argument('--use-vpython3', - dest='vpython', - action='store_const', - const='vpython3', - default='vpython', - help='Use vpython3 instead of vpython') parser.add_argument( 'executable_args', nargs='*', help='Arguments to wrap into the executable.')
diff --git a/chrome/VERSION b/chrome/VERSION index 2d0a593..c3d1215 100644 --- a/chrome/VERSION +++ b/chrome/VERSION
@@ -1,4 +1,4 @@ MAJOR=102 MINOR=0 -BUILD=4965 +BUILD=4966 PATCH=0
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java index 0f25002..2f3fcb8 100644 --- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java +++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java
@@ -283,7 +283,8 @@ mIsStartSurfaceEnabled ? this::initializeSecondaryTasksSurface : null, mIsStartSurfaceEnabled, mActivity, mBrowserControlsManager, this::isActivityFinishingOrDestroyed, excludeMVTiles, excludeQueryTiles, - startSurfaceOneshotSupplier, hadWarmStart, jankTracker); + startSurfaceOneshotSupplier, hadWarmStart, jankTracker, + mTasksSurface != null ? mTasksSurface::initializeMVTiles : null); // Show feed loading image. if (mStartSurfaceMediator.shouldShowFeedPlaceholder()) {
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 ca74f81..78a0d3eb 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
@@ -698,23 +698,10 @@ } /** - * When state is SHOWN_HOMEPAGE or SHOWING_HOMEPAGE or SHOWING_START, state surface homepage is - * showing. When state is StartSurfaceState.SHOWING_PREVIOUS and the previous state is - * SHOWN_HOMEPAGE or NOT_SHOWN, homepage is showing. * @return Whether start surface homepage is showing. */ private boolean isShowingStartSurfaceHomepage() { - @StartSurfaceState - int currentState = mController.getStartSurfaceState(); - @StartSurfaceState - int previousState = mController.getPreviousStartSurfaceState(); - - return currentState == StartSurfaceState.SHOWN_HOMEPAGE - || currentState == StartSurfaceState.SHOWING_HOMEPAGE - || currentState == StartSurfaceState.SHOWING_START - || (currentState == StartSurfaceState.SHOWING_PREVIOUS - && (previousState == StartSurfaceState.SHOWN_HOMEPAGE - || previousState == StartSurfaceState.NOT_SHOWN)); + return mController.isShowingStartSurfaceHomepage(); } private boolean isHidingStartSurfaceHomepage() {
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java index 459e41c..644b9a0 100644 --- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java +++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
@@ -106,6 +106,7 @@ private final ObserverList<StartSurface.StateObserver> mStateObservers = new ObserverList<>(); private final boolean mHadWarmStart; private final boolean mExcludeQueryTiles; + private final Runnable mInitializeMVTilesRunnable; // Boolean histogram used to record whether cached // ChromePreferenceKeys.FEED_ARTICLES_LIST_VISIBLE is consistent with @@ -174,7 +175,7 @@ BrowserControlsStateProvider browserControlsStateProvider, ActivityStateChecker activityStateChecker, boolean excludeMVTiles, boolean excludeQueryTiles, OneshotSupplier<StartSurface> startSurfaceSupplier, - boolean hadWarmStart, JankTracker jankTracker) { + boolean hadWarmStart, JankTracker jankTracker, Runnable initializeMVTilesRunnable) { mController = controller; mTabSwitcherContainer = tabSwitcherContainer; mTabModelSelector = tabModelSelector; @@ -190,6 +191,7 @@ mHadWarmStart = hadWarmStart; mJankTracker = jankTracker; mLaunchOrigin = NewTabPageLaunchOrigin.UNKNOWN; + mInitializeMVTilesRunnable = initializeMVTilesRunnable; if (mPropertyModel != null) { assert mIsStartSurfaceEnabled; @@ -345,11 +347,15 @@ LensMetrics.recordShown(LensEntryPoint.TASKS_SURFACE, shouldShowLensButton); mPropertyModel.set(IS_LENS_BUTTON_VISIBLE, shouldShowLensButton); + // This is for Instant Start when overview is already visible while the omnibox, Feed + // and MV tiles haven't been set. if (mController.overviewVisible()) { mOmniboxStub.addUrlFocusChangeListener(mUrlFocusChangeListener); - if (mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE - && mExploreSurfaceCoordinatorFactory != null) { - setExploreSurfaceVisibility(!mIsIncognito); + if (mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE) { + if (mExploreSurfaceCoordinatorFactory != null) { + setExploreSurfaceVisibility(!mIsIncognito); + } + if (mInitializeMVTilesRunnable != null) mInitializeMVTilesRunnable.run(); } } } @@ -502,14 +508,14 @@ setSecondaryTasksSurfaceVisibility( /* isVisible= */ true, /* skipUpdateController = */ true); } else if (mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE) { - setExploreSurfaceVisibility(!mIsIncognito && mExploreSurfaceCoordinatorFactory != null); boolean hasNormalTab = getNormalTabCount() > 0; // If new home surface for home button is enabled, MV tiles and carousel tab switcher // will not show. + setMVTilesVisibility(!mIsIncognito && !mHideMVForNewSurface); setTabCarouselVisibility( hasNormalTab && !mIsIncognito && !mHideTabCarouselForNewSurface); - setMVTilesVisibility(!mIsIncognito && !mHideMVForNewSurface); + setExploreSurfaceVisibility(!mIsIncognito && mExploreSurfaceCoordinatorFactory != null); // TODO(qinmin): show query tiles when flag is enabled. setQueryTilesVisibility(false); setFakeBoxVisibility(!mIsIncognito); @@ -691,6 +697,19 @@ && mStartSurfaceState != StartSurfaceState.DISABLED; } + @Override + public boolean isShowingStartSurfaceHomepage() { + // When state is SHOWN_HOMEPAGE or SHOWING_HOMEPAGE or SHOWING_START, state surface homepage + // is showing. When state is StartSurfaceState.SHOWING_PREVIOUS and the previous state is + // SHOWN_HOMEPAGE or NOT_SHOWN, homepage is showing. + return mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE + || mStartSurfaceState == StartSurfaceState.SHOWING_HOMEPAGE + || mStartSurfaceState == StartSurfaceState.SHOWING_START + || (mStartSurfaceState == StartSurfaceState.SHOWING_PREVIOUS + && (mPreviousStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE + || mPreviousStartSurfaceState == StartSurfaceState.NOT_SHOWN)); + } + // Implements TabSwitcher.OverviewModeObserver. @Override public void startedShowing() { @@ -921,7 +940,8 @@ } private void setMVTilesVisibility(boolean isVisible) { - if (mExcludeMVTiles || isVisible == mPropertyModel.get(MV_TILES_VISIBLE)) return; + if (mExcludeMVTiles) return; + if (isVisible && mInitializeMVTilesRunnable != null) mInitializeMVTilesRunnable.run(); mPropertyModel.set(MV_TILES_VISIBLE, isVisible); }
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceMVTilesTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceMVTilesTest.java index 9fb9987a..3e9860d 100644 --- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceMVTilesTest.java +++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceMVTilesTest.java
@@ -215,6 +215,12 @@ TabUiTestHelper.enterTabSwitcher(cta); TestThreadUtils.runOnUiThreadBlocking(() -> { + Assert.assertFalse(startSurfaceCoordinator.isMVTilesInitializedForTesting()); + }); + + TestThreadUtils.runOnUiThreadBlocking(() -> cta.getTabCreator(false).launchNTP()); + onViewWaiting(withId(org.chromium.chrome.start_surface.R.id.primary_tasks_surface_view)); + TestThreadUtils.runOnUiThreadBlocking(() -> { Assert.assertTrue(startSurfaceCoordinator.isMVTilesInitializedForTesting()); }); }
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java index be516a4..1f72bab 100644 --- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java +++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
@@ -832,8 +832,8 @@ } ChromeTabbedActivity cta = mActivityTestRule.getActivity(); - CriteriaHelper.pollUiThread( - () -> cta.getLayoutManager() != null && cta.getLayoutManager().overviewVisible()); + StartSurfaceTestUtils.waitForOverviewVisible( + mLayoutChangedCallbackHelper, mCurrentlyActiveLayout); StartSurfaceTestUtils.waitForTabModel(cta); TestThreadUtils.runOnUiThreadBlocking( () -> { cta.getTabModelSelector().getModel(false).closeAllTabs(); });
diff --git a/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java b/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java index aedea8f0..edb4c6d 100644 --- a/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java +++ b/chrome/android/features/start_surface/internal/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java
@@ -142,6 +142,8 @@ private PrefService mPrefService; @Mock private OneshotSupplier<StartSurface> mStartSurfaceSupplier; + @Mock + private Runnable mInitializeMVTilesRunnable; @Captor private ArgumentCaptor<TabModelSelectorObserver> mTabModelSelectorObserverCaptor; @Captor @@ -1263,6 +1265,20 @@ assertThat(mPropertyModel.get(IS_TAB_CAROUSEL_TITLE_VISIBLE), equalTo(false)); } + @Test + public void testInitializeMVTilesWhenShownHomepage() { + doReturn(false).when(mTabModelSelector).isIncognitoSelected(); + doReturn(mVoiceRecognitionHandler).when(mOmniboxStub).getVoiceRecognitionHandler(); + doReturn(true).when(mVoiceRecognitionHandler).isVoiceSearchEnabled(); + doReturn(2).when(mNormalTabModel).getCount(); + doReturn(true).when(mTabModelSelector).isTabStateInitialized(); + + StartSurfaceMediator mediator = + createStartSurfaceMediator(/* isStartSurfaceEnabled= */ true, false); + mediator.setOverviewState(StartSurfaceState.SHOWN_HOMEPAGE); + verify(mInitializeMVTilesRunnable).run(); + } + private StartSurfaceMediator createStartSurfaceMediator( boolean isStartSurfaceEnabled, boolean excludeMVTiles) { return createStartSurfaceMediator( @@ -1289,7 +1305,7 @@ isStartSurfaceEnabled, ContextUtils.getApplicationContext(), mBrowserControlsStateProvider, mActivityStateChecker, excludeMVTiles, true /* excludeQueryTiles */, mStartSurfaceSupplier, hadWarmStart, - new DummyJankTracker()); + new DummyJankTracker(), mInitializeMVTilesRunnable); return mediator; } }
diff --git a/chrome/android/features/start_surface/public/java/src/org/chromium/chrome/features/start_surface/StartSurface.java b/chrome/android/features/start_surface/public/java/src/org/chromium/chrome/features/start_surface/StartSurface.java index a5ca3c7..1bf2d9b 100644 --- a/chrome/android/features/start_surface/public/java/src/org/chromium/chrome/features/start_surface/StartSurface.java +++ b/chrome/android/features/start_surface/public/java/src/org/chromium/chrome/features/start_surface/StartSurface.java
@@ -199,6 +199,11 @@ * @return The Tab switcher container view. */ ViewGroup getTabSwitcherContainer(); + + /* + * Returns whether start surface homepage is showing. + */ + boolean isShowingStartSurfaceHomepage(); } /**
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurface.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurface.java index 1440443..e1261a9a 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurface.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurface.java
@@ -33,6 +33,13 @@ void initialize(); /** + * Called to initialize MV tiles. + * It should be called before MV tiles is showing. + * It might be called many times. + */ + void initializeMVTiles(); + + /** * Set the listener to get the {@link Layout#onTabSelecting} event from the Grid Tab Switcher. * @param listener The {@link TabSwitcher.OnTabSelectingListener} to use. */
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceCoordinator.java index a9936b4..7ba06c1 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceCoordinator.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceCoordinator.java
@@ -167,14 +167,30 @@ @Override public void initialize() { assert LibraryLoader.getInstance().isInitialized(); - if (!mIsMVTilesInitialized && mMostVisitedCoordinator != null) { - initializeMVTiles(); - mIsMVTilesInitialized = true; - } mMediator.initialize(); } @Override + public void initializeMVTiles() { + if (!LibraryLoader.getInstance().isInitialized() || mIsMVTilesInitialized + || mMostVisitedCoordinator == null) { + return; + } + + Profile profile = Profile.getLastUsedRegularProfile(); + MostVisitedTileNavigationDelegate navigationDelegate = + new MostVisitedTileNavigationDelegate(mActivity, profile, mParentTabSupplier); + mSuggestionsUiDelegate = + new MostVisitedSuggestionsUiDelegate(navigationDelegate, profile, mSnackbarManager); + mTileGroupDelegate = + new TileGroupDelegateImpl(mActivity, profile, navigationDelegate, mSnackbarManager); + + mMostVisitedCoordinator.initWithNative( + mSuggestionsUiDelegate, mTileGroupDelegate, enabled -> {}); + mIsMVTilesInitialized = true; + } + + @Override public void setOnTabSelectingListener(TabSwitcher.OnTabSelectingListener listener) { if (mTabSwitcher != null) { mTabSwitcher.setOnTabSelectingListener(listener); @@ -272,19 +288,6 @@ return mIsMVTilesInitialized; } - private void initializeMVTiles() { - Profile profile = Profile.getLastUsedRegularProfile(); - MostVisitedTileNavigationDelegate navigationDelegate = - new MostVisitedTileNavigationDelegate(mActivity, profile, mParentTabSupplier); - mSuggestionsUiDelegate = - new MostVisitedSuggestionsUiDelegate(navigationDelegate, profile, mSnackbarManager); - mTileGroupDelegate = - new TileGroupDelegateImpl(mActivity, profile, navigationDelegate, mSnackbarManager); - - mMostVisitedCoordinator.initWithNative( - mSuggestionsUiDelegate, mTileGroupDelegate, enabled -> {}); - } - /** Suggestions UI Delegate for constructing the TileGroup. */ private class MostVisitedSuggestionsUiDelegate extends SuggestionsUiDelegateImpl { public MostVisitedSuggestionsUiDelegate(SuggestionsNavigationDelegate navigationDelegate,
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingUtilities.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingUtilities.java index 688033c..dc0d089f 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingUtilities.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingUtilities.java
@@ -98,9 +98,8 @@ return getPriceTrackingEnabled() || getPriceTrackingNotificationsEnabled(); } - /** - * Update SharedPreferences when users turn on/off the feature tracking prices on tabs. - */ + // TODO(crbug.com/1307949): Clean up this api. + @Deprecated public static void flipTrackPricesOnTabs() { final boolean enableTrackPricesOnTabs = SHARED_PREFERENCES_MANAGER.readBoolean( TRACK_PRICES_ON_TABS, isPriceTrackingEnabled()); @@ -108,6 +107,13 @@ } /** + * Update SharedPreferences when users turn on/off the feature tracking prices on tabs. + */ + public static void setTrackPricesOnTabsEnabled(boolean enabled) { + SHARED_PREFERENCES_MANAGER.writeBoolean(TRACK_PRICES_ON_TABS, enabled); + } + + /** * @return Whether the track prices on tabs is turned on by users. */ public static boolean isTrackPricesOnTabsEnabled() { @@ -256,20 +262,19 @@ */ public static boolean allowUsersToDisablePriceAnnotations() { if (FeatureList.isInitialized()) { - return isPriceTrackingEnabled() + return isPriceTrackingEligible() && ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean( ChromeFeatureList.COMMERCE_PRICE_TRACKING, ALLOW_DISABLE_PRICE_ANNOTATIONS_PARAM, true); } - return isPriceTrackingEnabled(); + return isPriceTrackingEligible(); } + // TODO(crbug.com/1307949): Clean up price tracking menu. /** * @return whether we should show the PriceTrackingSettings menu item in grid tab switcher. */ public static boolean shouldShowPriceTrackingMenu() { - return isPriceTrackingEligible() - && (allowUsersToDisablePriceAnnotations() - || getPriceTrackingNotificationsEnabled()); + return false; } }
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingDialogTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingDialogTest.java index ecc6504..9273376 100644 --- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingDialogTest.java +++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingDialogTest.java
@@ -45,6 +45,7 @@ import org.chromium.base.test.util.CommandLineFlags; import org.chromium.base.test.util.CriteriaHelper; +import org.chromium.base.test.util.DisabledTest; import org.chromium.base.test.util.Feature; import org.chromium.base.test.util.FlakyTest; import org.chromium.base.test.util.Restriction; @@ -73,6 +74,7 @@ "force-fieldtrials=Study/Group"}) @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE}) @Features.DisableFeatures({ChromeFeatureList.START_SURFACE_ANDROID}) +@DisabledTest(message = "crbug.com/1307949") public class PriceTrackingDialogTest { // clang-format on private static final String BASE_PARAMS =
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewHolderTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewHolderTest.java index c257dbdd..6dfd26ef 100644 --- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewHolderTest.java +++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewHolderTest.java
@@ -861,29 +861,39 @@ @Test @MediumTest - @UiThreadTest public void testPriceDropEndToEnd() { - ShoppingPersistedTabData.enablePriceTrackingWithOptimizationGuideForTesting(); - ShoppingPersistedTabData.onDeferredStartup(); - PersistedTabDataConfiguration.setUseTestConfig(true); - setPriceTrackingEnabledForTesting(true); - mockCurrencyFormatter(); - mockUrlUtilities(); - mockOptimizationGuideResponse(OptimizationGuideDecision.TRUE, ANY_PRICE_TRACKING_DATA); - MockTab tab = (MockTab) MockTab.createAndInitialize(1, false); - tab.setGurlOverrideForTesting(TEST_GURL); - tab.setIsInitialized(true); - CriticalPersistedTabData.from(tab).setTimestampMillis(System.currentTimeMillis()); - TabListMediator.ShoppingPersistedTabDataFetcher fetcher = - new TabListMediator.ShoppingPersistedTabDataFetcher(tab, null); - mGridModel.set(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER, fetcher); - testGridSelected(mTabGridView, mGridModel); - PriceCardView priceCardView = mTabGridView.findViewById(R.id.price_info_box_outer); - TextView currentPrice = mTabGridView.findViewById(R.id.current_price); - TextView previousPrice = mTabGridView.findViewById(R.id.previous_price); - Assert.assertEquals(EXPECTED_PRICE, currentPrice.getText()); - Assert.assertEquals(EXPECTED_PREVIOUS_PRICE, previousPrice.getText()); - Assert.assertEquals(EXPECTED_CONTENT_DESCRIPTION, priceCardView.getContentDescription()); + TestThreadUtils.runOnUiThreadBlocking(() -> { + ShoppingPersistedTabData.onDeferredStartup(); + ShoppingPersistedTabData.enablePriceTrackingWithOptimizationGuideForTesting(); + PersistedTabDataConfiguration.setUseTestConfig(true); + setPriceTrackingEnabledForTesting(true); + mockCurrencyFormatter(); + mockUrlUtilities(); + mockOptimizationGuideResponse(OptimizationGuideDecision.TRUE, ANY_PRICE_TRACKING_DATA); + MockTab tab = (MockTab) MockTab.createAndInitialize(1, false); + tab.setGurlOverrideForTesting(TEST_GURL); + tab.setIsInitialized(true); + CriticalPersistedTabData.from(tab).setTimestampMillis(System.currentTimeMillis()); + TabListMediator.ShoppingPersistedTabDataFetcher fetcher = + new TabListMediator.ShoppingPersistedTabDataFetcher(tab, null); + mGridModel.set(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER, fetcher); + testGridSelected(mTabGridView, mGridModel); + }); + CriteriaHelper.pollUiThread( + () + -> EXPECTED_PRICE.equals( + ((TextView) mTabGridView.findViewById(R.id.current_price)) + .getText())); + CriteriaHelper.pollUiThread( + () + -> EXPECTED_PREVIOUS_PRICE.equals( + ((TextView) mTabGridView.findViewById(R.id.previous_price)) + .getText())); + CriteriaHelper.pollUiThread(() + -> EXPECTED_CONTENT_DESCRIPTION.equals( + ((PriceCardView) mTabGridView.findViewById( + R.id.price_info_box_outer)) + .getContentDescription())); } private void mockCurrencyFormatter() {
diff --git a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/FeedV2TestHelper.java b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/FeedV2TestHelper.java index 4ceecff..bffddfe4 100644 --- a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/FeedV2TestHelper.java +++ b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/FeedV2TestHelper.java
@@ -64,6 +64,7 @@ enumNames.put("kClosedDialog", FeedUserActionType.CLOSED_DIALOG); enumNames.put("kShowSnackbar", FeedUserActionType.SHOW_SNACKBAR); enumNames.put("kOpenedNativeContextMenu", FeedUserActionType.OPENED_NATIVE_CONTEXT_MENU); + enumNames.put("kTappedFollowButton", FeedUserActionType.TAPPED_FOLLOW_BUTTON); return getEnumHistogramValues("ContentSuggestions.Feed.UserActions", enumNames); }
diff --git a/chrome/android/java/res/xml/google_services_preferences.xml b/chrome/android/java/res/xml/google_services_preferences.xml index 380f674..df95c184 100644 --- a/chrome/android/java/res/xml/google_services_preferences.xml +++ b/chrome/android/java/res/xml/google_services_preferences.xml
@@ -39,6 +39,11 @@ android:title="@string/prefs_autofill_assistant_title" android:summary="@string/prefs_autofill_assistant_get_help_summary" android:persistent="false"/> + <org.chromium.components.browser_ui.settings.ChromeSwitchPreference + android:key="price_tracking_annotations" + android:title="@string/track_prices_on_tabs" + android:summary="@string/track_prices_on_tabs_description" + android:persistent="false"/> <org.chromium.components.browser_ui.settings.ChromeBasePreference android:key="autofill_assistant_subsection" android:title="@string/prefs_autofill_assistant_title"
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/GoogleServicesSettings.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/GoogleServicesSettings.java index d3e8926..7cc5a75 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/GoogleServicesSettings.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/GoogleServicesSettings.java
@@ -29,6 +29,7 @@ import org.chromium.chrome.browser.signin.services.IdentityServicesProvider; import org.chromium.chrome.browser.signin.services.SigninManager; import org.chromium.chrome.browser.signin.services.UnifiedConsentServiceBridge; +import org.chromium.chrome.browser.tasks.tab_management.PriceTrackingUtilities; import org.chromium.chrome.browser.ui.signin.SignOutDialogFragment; import org.chromium.components.autofill_assistant.AssistantFeatures; import org.chromium.components.autofill_assistant.AutofillAssistantPreferencesUtil; @@ -64,6 +65,8 @@ public static final String PREF_AUTOFILL_ASSISTANT_SUBSECTION = "autofill_assistant_subsection"; @VisibleForTesting public static final String PREF_METRICS_SETTINGS = "metrics_settings"; + @VisibleForTesting + public static final String PREF_PRICE_TRACKING_ANNOTATIONS = "price_tracking_annotations"; private final PrefService mPrefService = UserPrefs.get(Profile.getLastUsedRegularProfile()); private final PrivacyPreferencesManagerImpl mPrivacyPrefManager = @@ -75,6 +78,7 @@ private ChromeSwitchPreference mSearchSuggestions; private ChromeSwitchPreference mUsageAndCrashReporting; private ChromeSwitchPreference mUrlKeyedAnonymizedData; + private ChromeSwitchPreference mPriceTrackingAnnotations; private @Nullable ChromeSwitchPreference mAutofillAssistant; private @Nullable Preference mContextualSearch; @@ -137,6 +141,17 @@ removePreference(getPreferenceScreen(), mContextualSearch); mContextualSearch = null; } + + mPriceTrackingAnnotations = + (ChromeSwitchPreference) findPreference(PREF_PRICE_TRACKING_ANNOTATIONS); + if (!PriceTrackingUtilities.allowUsersToDisablePriceAnnotations()) { + removePreference(getPreferenceScreen(), mPriceTrackingAnnotations); + mPriceTrackingAnnotations = null; + } else { + mPriceTrackingAnnotations.setOnPreferenceChangeListener(this); + mPriceTrackingAnnotations.setManagedPreferenceDelegate(mManagedPreferenceDelegate); + } + updatePreferences(); } @@ -208,6 +223,8 @@ Profile.getLastUsedRegularProfile(), (boolean) newValue); } else if (PREF_AUTOFILL_ASSISTANT.equals(key)) { setAutofillAssistantSwitchValue((boolean) newValue); + } else if (PREF_PRICE_TRACKING_ANNOTATIONS.equals(key)) { + PriceTrackingUtilities.setTrackPricesOnTabsEnabled((boolean) newValue); } return true; } @@ -234,6 +251,10 @@ mContextualSearch.setSummary( isContextualSearchEnabled ? R.string.text_on : R.string.text_off); } + if (mPriceTrackingAnnotations != null) { + mPriceTrackingAnnotations.setChecked( + PriceTrackingUtilities.isTrackPricesOnTabsEnabled()); + } } private ChromeManagedPreferenceDelegate createManagedPreferenceDelegate() {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/OverviewAppMenuTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/OverviewAppMenuTest.java index 4977d018..6d0a65c 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/OverviewAppMenuTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/OverviewAppMenuTest.java
@@ -23,7 +23,6 @@ import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.browser.layouts.LayoutTestUtils; import org.chromium.chrome.browser.layouts.LayoutType; -import org.chromium.chrome.browser.tasks.tab_management.PriceTrackingUtilities; import org.chromium.chrome.browser.ui.appmenu.AppMenuTestSupport; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; import org.chromium.chrome.test.ChromeTabbedActivityTestRule; @@ -198,138 +197,6 @@ mActivityTestRule.getAppMenuCoordinator(), R.id.menu_group_tabs)); } - @Test - @SmallTest - @Feature({"Browser", "Main"}) - @Features.EnableFeatures({ChromeFeatureList.COMMERCE_PRICE_TRACKING + "<Study"}) - @Features.DisableFeatures({ChromeFeatureList.START_SURFACE_ANDROID}) - @CommandLineFlags.Add({"force-fieldtrials=Study/Group", - "force-fieldtrial-params=Study.Group:enable_price_tracking/false"}) - public void - testTrackPriceOnTabsIsDisabled() throws Exception { - TestThreadUtils.runOnUiThreadBlocking(() -> { - PriceTrackingUtilities.setIsSignedInAndSyncEnabledForTesting(true); - AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false); - }); - - assertNull(AppMenuTestSupport.getMenuItemPropertyModel( - mActivityTestRule.getAppMenuCoordinator(), R.id.track_prices_row_menu_id)); - } - - @Test - @SmallTest - @Feature({"Browser", "Main"}) - @Features.EnableFeatures({ChromeFeatureList.COMMERCE_PRICE_TRACKING + "<Study"}) - @Features.DisableFeatures({ChromeFeatureList.START_SURFACE_ANDROID}) - @CommandLineFlags.Add({"force-fieldtrials=Study/Group", - "force-fieldtrial-params=Study.Group:enable_price_tracking/true" - + "/allow_disable_price_annotations/true"}) - public void - testTrackPriceOnTabsIsEnabled() throws Exception { - TestThreadUtils.runOnUiThreadBlocking(() -> { - PriceTrackingUtilities.setIsSignedInAndSyncEnabledForTesting(true); - AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false); - }); - - assertNotNull(AppMenuTestSupport.getMenuItemPropertyModel( - mActivityTestRule.getAppMenuCoordinator(), R.id.track_prices_row_menu_id)); - } - - @Test - @SmallTest - @Feature({"Browser", "Main"}) - @Features.EnableFeatures({ChromeFeatureList.COMMERCE_PRICE_TRACKING + "<Study"}) - @Features.DisableFeatures({ChromeFeatureList.START_SURFACE_ANDROID}) - @CommandLineFlags.Add({"force-fieldtrials=Study/Group", - "force-fieldtrial-params=Study.Group:enable_price_tracking/true" - + "/allow_disable_price_annotations/true"}) - public void - testTrackPriceOnTabsIsDisabledInIncognitoMode() throws Exception { - TestThreadUtils.runOnUiThreadBlocking(() -> { - PriceTrackingUtilities.setIsSignedInAndSyncEnabledForTesting(true); - mActivityTestRule.getActivity().getTabModelSelector().selectModel(true); - AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false); - }); - - assertNull(AppMenuTestSupport.getMenuItemPropertyModel( - mActivityTestRule.getAppMenuCoordinator(), R.id.track_prices_row_menu_id)); - } - - @Test - @SmallTest - @Feature({"Browser", "Main"}) - @Features.EnableFeatures({ChromeFeatureList.COMMERCE_PRICE_TRACKING + "<Study"}) - @Features.DisableFeatures({ChromeFeatureList.START_SURFACE_ANDROID}) - @CommandLineFlags.Add({"force-fieldtrials=Study/Group", - "force-fieldtrial-params=Study.Group:enable_price_tracking/true" - + "/allow_disable_price_annotations/true"}) - public void - testTrackPriceOnTabsIsDisabledIfSyncDisabledOrNotSignedIn() throws Exception { - TestThreadUtils.runOnUiThreadBlocking(() -> { - PriceTrackingUtilities.setIsSignedInAndSyncEnabledForTesting(false); - AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false); - }); - - assertNull(AppMenuTestSupport.getMenuItemPropertyModel( - mActivityTestRule.getAppMenuCoordinator(), R.id.track_prices_row_menu_id)); - } - - @Test - @SmallTest - @Feature({"Browser", "Main"}) - @Features.EnableFeatures({ChromeFeatureList.COMMERCE_PRICE_TRACKING + "<Study"}) - @Features.DisableFeatures({ChromeFeatureList.START_SURFACE_ANDROID}) - @CommandLineFlags.Add({"force-fieldtrials=Study/Group", - "force-fieldtrial-params=Study.Group:enable_price_tracking/true" - + "/allow_disable_price_annotations/false/enable_price_notification/false"}) - public void - testTrackPriceOnTabsIsDisabledIfNoSettingsAvailable() throws Exception { - TestThreadUtils.runOnUiThreadBlocking(() -> { - PriceTrackingUtilities.setIsSignedInAndSyncEnabledForTesting(true); - AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false); - }); - - assertNull(AppMenuTestSupport.getMenuItemPropertyModel( - mActivityTestRule.getAppMenuCoordinator(), R.id.track_prices_row_menu_id)); - } - - @Test - @SmallTest - @Feature({"Browser", "Main"}) - @Features.EnableFeatures({ChromeFeatureList.START_SURFACE_ANDROID, - ChromeFeatureList.COMMERCE_PRICE_TRACKING + "<Study"}) - @CommandLineFlags.Add({"force-fieldtrials=Study/Group", - "force-fieldtrial-params=Study.Group:enable_price_tracking/false"}) - public void - testTrackPriceOnTabsIsDisabledWithStartSurface() throws Exception { - TestThreadUtils.runOnUiThreadBlocking(() -> { - PriceTrackingUtilities.setIsSignedInAndSyncEnabledForTesting(true); - AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false); - }); - - assertNull(AppMenuTestSupport.getMenuItemPropertyModel( - mActivityTestRule.getAppMenuCoordinator(), R.id.track_prices_row_menu_id)); - } - - @Test - @SmallTest - @Feature({"Browser", "Main"}) - @Features.EnableFeatures({ChromeFeatureList.START_SURFACE_ANDROID, - ChromeFeatureList.COMMERCE_PRICE_TRACKING + "<Study"}) - @CommandLineFlags.Add({"force-fieldtrials=Study/Group", - "force-fieldtrial-params=Study.Group:enable_price_tracking/true" - + "/allow_disable_price_annotations/true"}) - public void - testTrackPriceOnTabsIsEnabledWithStartSurface() throws Exception { - TestThreadUtils.runOnUiThreadBlocking(() -> { - PriceTrackingUtilities.setIsSignedInAndSyncEnabledForTesting(true); - AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false); - }); - - assertNotNull(AppMenuTestSupport.getMenuItemPropertyModel( - mActivityTestRule.getAppMenuCoordinator(), R.id.track_prices_row_menu_id)); - } - private void verifyTabSwitcherMenu() { assertNotNull(AppMenuTestSupport.getMenuItemPropertyModel( mActivityTestRule.getAppMenuCoordinator(), R.id.new_tab_menu_id));
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/settings/AutofillPaymentMethodsFragmentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/settings/AutofillPaymentMethodsFragmentTest.java index 2819b2c9..5859d32e 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/settings/AutofillPaymentMethodsFragmentTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/settings/AutofillPaymentMethodsFragmentTest.java
@@ -21,7 +21,6 @@ import org.chromium.base.test.util.Batch; import org.chromium.chrome.browser.autofill.AutofillTestHelper; import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard; -import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.settings.SettingsActivity; import org.chromium.chrome.browser.settings.SettingsActivityTestRule; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; @@ -133,51 +132,6 @@ assertThat(title).contains("1111"); } - @Test - @MediumTest - @Features.EnableFeatures({ChromeFeatureList.AUTOFILL_ENABLE_GOOGLE_ISSUED_CARD}) - public void testGoogleIssuedServerCard_displaysGoogleSpecificTitle() throws Exception { - mAutofillTestHelper.addServerCreditCard( - SAMPLE_CARD_VISA, /* nickname= */ "", CARD_ISSUER_GOOGLE); - - SettingsActivity activity = mSettingsActivityTestRule.startSettingsActivity(); - - Preference cardPreference = getPreferenceScreen(activity).getPreference(1); - String title = cardPreference.getTitle().toString(); - assertThat(title).contains("Plex Visa"); - assertThat(title).contains("1111"); - } - - @Test - @MediumTest - @Features.DisableFeatures({ChromeFeatureList.AUTOFILL_ENABLE_GOOGLE_ISSUED_CARD}) - public void testGoogleIssuedServerCard_expOff_cardNotDisplayed() throws Exception { - mAutofillTestHelper.addServerCreditCard( - SAMPLE_CARD_VISA, /* nickname= */ "", CARD_ISSUER_GOOGLE); - - SettingsActivity activity = mSettingsActivityTestRule.startSettingsActivity(); - - // Verify that the preferences on the initial screen map to Save and Fill toggle + Add Card - // button + Payment Apps. - Assert.assertEquals(3, getPreferenceScreen(activity).getPreferenceCount()); - } - - @Test - @MediumTest - @Features.EnableFeatures({ChromeFeatureList.AUTOFILL_ENABLE_GOOGLE_ISSUED_CARD}) - public void testGoogleIssuedServerCardWithNickname_displaysNicknameAndLastFourAsTitle() - throws Exception { - mAutofillTestHelper.addServerCreditCard( - SAMPLE_CARD_VISA, "Test nickname", CARD_ISSUER_GOOGLE); - - SettingsActivity activity = mSettingsActivityTestRule.startSettingsActivity(); - - Preference cardPreference = getPreferenceScreen(activity).getPreference(1); - String title = cardPreference.getTitle().toString(); - assertThat(title).contains("Test nickname"); - assertThat(title).contains("1111"); - } - private static PreferenceScreen getPreferenceScreen(SettingsActivity activity) { return ((AutofillPaymentMethodsFragment) activity.getMainFragment()).getPreferenceScreen(); }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/GoogleServicesSettingsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/GoogleServicesSettingsTest.java index 2d3a2640..cf57b50d 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/GoogleServicesSettingsTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/GoogleServicesSettingsTest.java
@@ -30,6 +30,7 @@ import org.chromium.chrome.browser.settings.SettingsActivityTestRule; import org.chromium.chrome.browser.signin.services.IdentityServicesProvider; import org.chromium.chrome.browser.sync.settings.GoogleServicesSettings; +import org.chromium.chrome.browser.tasks.tab_management.PriceTrackingUtilities; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; import org.chromium.chrome.test.ChromeTabbedActivityTestRule; import org.chromium.chrome.test.util.browser.Features.DisableFeatures; @@ -329,6 +330,74 @@ }); } + @Test + @LargeTest + @Feature({"Preference"}) + @EnableFeatures({ChromeFeatureList.COMMERCE_PRICE_TRACKING + "<Study"}) + @CommandLineFlags.Add({"force-fieldtrials=Study/Group", + "force-fieldtrial-params=Study.Group:enable_price_tracking/true" + + "/allow_disable_price_annotations/true"}) + public void + testPriceTrackingAnnotations() { + TestThreadUtils.runOnUiThreadBlocking( + () -> PriceTrackingUtilities.setIsSignedInAndSyncEnabledForTesting(true)); + + final GoogleServicesSettings googleServicesSettings = startGoogleServicesSettings(); + + TestThreadUtils.runOnUiThreadBlocking(() -> { + ChromeSwitchPreference priceAnnotationsSwitch = + (ChromeSwitchPreference) googleServicesSettings.findPreference( + GoogleServicesSettings.PREF_PRICE_TRACKING_ANNOTATIONS); + Assert.assertTrue(priceAnnotationsSwitch.isVisible()); + Assert.assertTrue(priceAnnotationsSwitch.isChecked()); + + priceAnnotationsSwitch.performClick(); + Assert.assertFalse(PriceTrackingUtilities.isTrackPricesOnTabsEnabled()); + priceAnnotationsSwitch.performClick(); + Assert.assertTrue(PriceTrackingUtilities.isTrackPricesOnTabsEnabled()); + }); + } + + @Test + @LargeTest + @Feature({"Preference"}) + @EnableFeatures({ChromeFeatureList.COMMERCE_PRICE_TRACKING + "<Study"}) + @CommandLineFlags.Add({"force-fieldtrials=Study/Group", + "force-fieldtrial-params=Study.Group:enable_price_tracking/true" + + "/allow_disable_price_annotations/false"}) + public void + testPriceTrackingAnnotations_FeatureDisabled() { + TestThreadUtils.runOnUiThreadBlocking( + () -> PriceTrackingUtilities.setIsSignedInAndSyncEnabledForTesting(true)); + + final GoogleServicesSettings googleServicesSettings = startGoogleServicesSettings(); + + TestThreadUtils.runOnUiThreadBlocking(() -> { + Assert.assertNull(googleServicesSettings.findPreference( + GoogleServicesSettings.PREF_PRICE_TRACKING_ANNOTATIONS)); + }); + } + + @Test + @LargeTest + @Feature({"Preference"}) + @EnableFeatures({ChromeFeatureList.COMMERCE_PRICE_TRACKING + "<Study"}) + @CommandLineFlags.Add({"force-fieldtrials=Study/Group", + "force-fieldtrial-params=Study.Group:enable_price_tracking/true" + + "/allow_disable_price_annotations/true"}) + public void + testPriceTrackingAnnotations_NotSignedIn() { + TestThreadUtils.runOnUiThreadBlocking( + () -> PriceTrackingUtilities.setIsSignedInAndSyncEnabledForTesting(false)); + + final GoogleServicesSettings googleServicesSettings = startGoogleServicesSettings(); + + TestThreadUtils.runOnUiThreadBlocking(() -> { + Assert.assertNull(googleServicesSettings.findPreference( + GoogleServicesSettings.PREF_PRICE_TRACKING_ANNOTATIONS)); + }); + } + private void setAutofillAssistantSwitchValue(boolean newValue) { AutofillAssistantPreferencesUtil.setAssistantEnabledPreference(newValue); }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataDeferredStartupTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataDeferredStartupTest.java index 0124be1..577379f 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataDeferredStartupTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataDeferredStartupTest.java
@@ -129,9 +129,10 @@ }); }); ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore); + TestThreadUtils.runOnUiThreadBlocking( + () -> { ShoppingPersistedTabData.onDeferredStartup(); }); final Semaphore newSemaphore = new Semaphore(0); TestThreadUtils.runOnUiThreadBlocking(() -> { - ShoppingPersistedTabData.onDeferredStartup(); ShoppingPersistedTabData.from(tab, (shoppingPersistedTabData) -> { Assert.assertNotNull(shoppingPersistedTabData); Assert.assertEquals(ShoppingPersistedTabDataTestUtils.UPDATED_PRICE_MICROS,
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataLegacyTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataLegacyTest.java index 84727414a..c2ba08c 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataLegacyTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataLegacyTest.java
@@ -161,7 +161,7 @@ ShoppingPersistedTabDataTestUtils.acquireSemaphore(updateSemaphore); return ShoppingPersistedTabDataTestUtils.getTimeLastUpdatedOnUiThread(tab); } - @UiThreadTest + @SmallTest @Test @CommandLineFlags.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataTest.java index e324bd7..f623602 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataTest.java
@@ -295,7 +295,6 @@ mOptimizationGuideBridgeJniMock, 1); } - @UiThreadTest @SmallTest @Test @CommandLineFlags. @@ -593,7 +592,6 @@ Assert.assertNull(shoppingPersistedTabData.getPriceDrop()); } - @UiThreadTest @SmallTest @Test @CommandLineFlags. @@ -617,7 +615,6 @@ ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore); } - @UiThreadTest @SmallTest @Test @CommandLineFlags. @@ -641,7 +638,6 @@ ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore); } - @UiThreadTest @SmallTest @Test @CommandLineFlags. @@ -664,7 +660,6 @@ ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore); } - @UiThreadTest @SmallTest @Test public void testSPTDNullOptimizationGuideFalse() { @@ -849,30 +844,32 @@ Assert.assertTrue(shoppingPersistedTabData.needsUpdate()); } - @UiThreadTest @SmallTest @Test public void testIncognitoTabDisabled() throws TimeoutException { TabImpl tab = mock(TabImpl.class); doReturn(true).when(tab).isIncognito(); CallbackHelper callbackHelper = new CallbackHelper(); - ShoppingPersistedTabData.from(tab, (res) -> { - Assert.assertNull(res); - callbackHelper.notifyCalled(); + TestThreadUtils.runOnUiThreadBlocking(() -> { + ShoppingPersistedTabData.from(tab, (res) -> { + Assert.assertNull(res); + callbackHelper.notifyCalled(); + }); }); callbackHelper.waitForCallback(0); } - @UiThreadTest @SmallTest @Test public void testCustomTabsDisabled() throws TimeoutException { TabImpl tab = mock(TabImpl.class); doReturn(true).when(tab).isCustomTab(); CallbackHelper callbackHelper = new CallbackHelper(); - ShoppingPersistedTabData.from(tab, (res) -> { - Assert.assertNull(res); - callbackHelper.notifyCalled(); + TestThreadUtils.runOnUiThreadBlocking(() -> { + ShoppingPersistedTabData.from(tab, (res) -> { + Assert.assertNull(res); + callbackHelper.notifyCalled(); + }); }); callbackHelper.waitForCallback(0); }
diff --git a/chrome/app/chrome_command_ids.h b/chrome/app/chrome_command_ids.h index e046c85..a65dc7d 100644 --- a/chrome/app/chrome_command_ids.h +++ b/chrome/app/chrome_command_ids.h
@@ -410,6 +410,7 @@ #if BUILDFLAG(GOOGLE_CHROME_BRANDING) #define IDC_MEDIA_TOOLBAR_CONTEXT_REPORT_CAST_ISSUE 51209 #endif +#define IDC_MEDIA_TOOLBAR_CONTEXT_SHOW_OTHER_SESSIONS 51210 // Context menu items for media stream status tray #define IDC_MEDIA_STREAM_DEVICE_STATUS_TRAY 51300
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index ba98555..36e3e64b 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd
@@ -5118,7 +5118,10 @@ <!-- End Extension Settings Overridden Dialog strings. --> <!-- Force Installed Deprecated Apps Deletion Dialog strings. --> <message name="IDS_FORCE_INSTALLED_DEPRECATED_APPS_CONTENT" desc="Content of the force installed deprecated app dialog"> - Your administrator installed "<ph name="EXTENSION_NAME">$1<ex>Google Dos</ex></ph>" but this Chrome App is no longer supported. Contact your administrator to remove it. <ph name="LEARN_MORE">$2<ex>Learn more</ex></ph> + Your administrator installed "<ph name="EXTENSION_NAME">$1<ex>Google Dos</ex></ph>" but this Chrome App is no longer supported. Contact your administrator to remove it. + </message> + <message name="IDS_FORCE_INSTALLED_DEPRECATED_APPS_LEARN_MORE_AX_LABEL" desc="Accessibility label text for IDS_DEPRECATED_APPS_LEARN_MORE link"> + Learn more about unsupported Chrome Apps </message> <!-- End Force Installed Deprecated Apps Deletion Dialog strings. --> <!-- Deprecated Apps Deletion Dialog strings. -->
diff --git a/chrome/app/generated_resources_grd/IDS_FORCE_INSTALLED_DEPRECATED_APPS_CONTENT.png.sha1 b/chrome/app/generated_resources_grd/IDS_FORCE_INSTALLED_DEPRECATED_APPS_CONTENT.png.sha1 index d217217..8ec600b 100644 --- a/chrome/app/generated_resources_grd/IDS_FORCE_INSTALLED_DEPRECATED_APPS_CONTENT.png.sha1 +++ b/chrome/app/generated_resources_grd/IDS_FORCE_INSTALLED_DEPRECATED_APPS_CONTENT.png.sha1
@@ -1 +1 @@ -aadc71c61061278c7c23c480cf863c2004f48caa \ No newline at end of file +767a804ffadd4122174e17e900980fc7eb77f42b \ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_FORCE_INSTALLED_DEPRECATED_APPS_LEARN_MORE_AX_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_FORCE_INSTALLED_DEPRECATED_APPS_LEARN_MORE_AX_LABEL.png.sha1 new file mode 100644 index 0000000..8ec600b --- /dev/null +++ b/chrome/app/generated_resources_grd/IDS_FORCE_INSTALLED_DEPRECATED_APPS_LEARN_MORE_AX_LABEL.png.sha1
@@ -0,0 +1 @@ +767a804ffadd4122174e17e900980fc7eb77f42b \ No newline at end of file
diff --git a/chrome/app/gmc_strings.grdp b/chrome/app/gmc_strings.grdp index 4ccedf1..c4c0f57 100644 --- a/chrome/app/gmc_strings.grdp +++ b/chrome/app/gmc_strings.grdp
@@ -41,7 +41,9 @@ Control the media you're casting </message> <message name="IDS_MEDIA_TOOLBAR_CONTEXT_REPORT_CAST_ISSUE" desc="Title of the toolbar button's context menu item, which, on click, opens a page to report an issue with Cast."> - Report an issue with Google Cast + Report an issue with Google Cast </message> - + <message name="IDS_MEDIA_TOOLBAR_CONTEXT_SHOW_OTHER_SESSIONS" desc="Title of a menu item which, on click, toggles (shows or hides) Cast sessions started by other devices on the same network."> + Show other Cast sessions + </message> </grit-part>
diff --git a/chrome/app/gmc_strings_grdp/IDS_MEDIA_TOOLBAR_CONTEXT_SHOW_OTHER_SESSIONS.png.sha1 b/chrome/app/gmc_strings_grdp/IDS_MEDIA_TOOLBAR_CONTEXT_SHOW_OTHER_SESSIONS.png.sha1 new file mode 100644 index 0000000..e206e36 --- /dev/null +++ b/chrome/app/gmc_strings_grdp/IDS_MEDIA_TOOLBAR_CONTEXT_SHOW_OTHER_SESSIONS.png.sha1
@@ -0,0 +1 @@ +0dacfcd0f5492038e54bb18e493b299a3e8c10cb \ No newline at end of file
diff --git a/chrome/app/profiles_strings.grdp b/chrome/app/profiles_strings.grdp index 6a1fe31..ae5d1b2c 100644 --- a/chrome/app/profiles_strings.grdp +++ b/chrome/app/profiles_strings.grdp
@@ -522,9 +522,15 @@ <message name="IDS_ENTERPRISE_WELCOME_PROFILE_REQUIRED_TITLE" desc="Title of the enteprise profile welcome screen when a profile is required. It is shown after a user signs into a managed account outside of the profile creation flow."> Your organization requires a profile </message> + <message name="IDS_ENTERPRISE_WELCOME_PROFILE_WILL_BE_MANAGED_TITLE" desc="Title of the enteprise profile welcome screen when a managed profile will be created. It is shown after a user signs into a managed account outside of the profile creation flow."> + Your organization will manage this profile + </message> <message name="IDS_ENTERPRISE_PROFILE_WELCOME_CREATE_PROFILE_BUTTON" desc="Label of the proceed button on the enterprise profile welcome screen when a user signed in an enterprise account outside of the profile creation flow. The label informs the user that a profile will be created."> Create </message> + <message name="IDS_ENTERPRISE_PROFILE_WELCOME_LINK_DATA_CHECKBOX" desc="Label of the checkbox on the enterprise profile welcome screen that allows a user to link existing data to the new profile. The label informs the user that a profile will be created."> + Add existing browsing data (bookmarks, passwords, history) to profile + </message> <!-- Profile Picker --> <message name="IDS_PROFILE_PICKER_ADD_SPACE_BUTTON" desc="Text for the add space button on the profile picker main view">
diff --git a/chrome/app/profiles_strings_grdp/IDS_ENTERPRISE_PROFILE_WELCOME_LINK_DATA_CHECKBOX.png.sha1 b/chrome/app/profiles_strings_grdp/IDS_ENTERPRISE_PROFILE_WELCOME_LINK_DATA_CHECKBOX.png.sha1 new file mode 100644 index 0000000..3c3d951 --- /dev/null +++ b/chrome/app/profiles_strings_grdp/IDS_ENTERPRISE_PROFILE_WELCOME_LINK_DATA_CHECKBOX.png.sha1
@@ -0,0 +1 @@ +7333620e29370717085ba5a843c35e9c81012d83 \ No newline at end of file
diff --git a/chrome/app/profiles_strings_grdp/IDS_ENTERPRISE_WELCOME_PROFILE_WILL_BE_MANAGED_TITLE.png.sha1 b/chrome/app/profiles_strings_grdp/IDS_ENTERPRISE_WELCOME_PROFILE_WILL_BE_MANAGED_TITLE.png.sha1 new file mode 100644 index 0000000..3c3d951 --- /dev/null +++ b/chrome/app/profiles_strings_grdp/IDS_ENTERPRISE_WELCOME_PROFILE_WILL_BE_MANAGED_TITLE.png.sha1
@@ -0,0 +1 @@ +7333620e29370717085ba5a843c35e9c81012d83 \ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn index db6fc6a..30281f61 100644 --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn
@@ -808,6 +808,8 @@ "metrics/variations/chrome_variations_service_client.cc", "metrics/variations/chrome_variations_service_client.h", "native_window_notification_source.h", + "navigation_predictor/anchor_element_preloader.cc", + "navigation_predictor/anchor_element_preloader.h", "navigation_predictor/navigation_predictor.cc", "navigation_predictor/navigation_predictor.h", "navigation_predictor/navigation_predictor_features.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index 26f5a34..3ed4057 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc
@@ -3215,6 +3215,9 @@ {"ash-bento-bar", flag_descriptions::kBentoBarName, flag_descriptions::kBentoBarDescription, kOsCrOS, FEATURE_VALUE_TYPE(ash::features::kBentoBar)}, + {"ash-capture-mode-selfie-cam", flag_descriptions::kCaptureSelfieCamName, + flag_descriptions::kCaptureSelfieCamDescription, kOsCrOS, + FEATURE_VALUE_TYPE(ash::features::kCaptureModeSelfieCamera)}, {"ash-drag-window-to-new-desk", flag_descriptions::kDragWindowToNewDeskName, flag_descriptions::kDragWindowToNewDeskDescription, kOsCrOS, FEATURE_VALUE_TYPE(ash::features::kDragWindowToNewDesk)}, @@ -4728,9 +4731,6 @@ {"enable-universal-links", flag_descriptions::kEnableUniversalLinksName, flag_descriptions::kEnableUniversalLinksDescription, kOsMac, FEATURE_VALUE_TYPE(features::kEnableUniveralLinks)}, - {"new-usb-backend", flag_descriptions::kNewUsbBackendName, - flag_descriptions::kNewUsbBackendDescription, kOsMac, - FEATURE_VALUE_TYPE(device::kNewUsbBackend)}, #endif // BUILDFLAG(IS_MAC) #if BUILDFLAG(IS_ANDROID) @@ -6841,11 +6841,6 @@ flag_descriptions::kEnableExperimentalCookieFeaturesDescription, kOsAll, MULTI_VALUE_TYPE(kEnableExperimentalCookieFeaturesChoices)}, - {"autofill-enable-google-issued-card", - flag_descriptions::kAutofillEnableGoogleIssuedCardName, - flag_descriptions::kAutofillEnableGoogleIssuedCardDescription, kOsAll, - FEATURE_VALUE_TYPE(autofill::features::kAutofillEnableGoogleIssuedCard)}, - {"permission-chip", flag_descriptions::kPermissionChipName, flag_descriptions::kPermissionChipDescription, kOsDesktop, FEATURE_VALUE_TYPE(permissions::features::kPermissionChip)}, @@ -7170,8 +7165,8 @@ // Use a command-line parameter instead of a FEATURE_VALUE_TYPE to enable // multiple related features when they are available. SINGLE_VALUE_TYPE_AND_VALUE(switches::kEnableFeatures, - "PrivacySandboxAdsAPIsOverride" - "Fledge,BrowsingTopics,ConversionMeasurement" + "PrivacySandboxAdsAPIsOverride," + "Fledge,BrowsingTopics,ConversionMeasurement," "OverridePrivacySandboxSettingsLocalTesting")}, {"animated-image-resume", flag_descriptions::kAnimatedImageResumeName,
diff --git a/chrome/browser/android/resource_id.h b/chrome/browser/android/resource_id.h index 7e9cba9..84afa632 100644 --- a/chrome/browser/android/resource_id.h +++ b/chrome/browser/android/resource_id.h
@@ -83,7 +83,6 @@ LINK_RESOURCE_ID(IDR_AUTOFILL_CC_TROY, R.drawable.troy_card) LINK_RESOURCE_ID(IDR_AUTOFILL_CC_UNIONPAY, R.drawable.unionpay_card) LINK_RESOURCE_ID(IDR_AUTOFILL_CC_VISA, R.drawable.visa_card) -LINK_RESOURCE_ID(IDR_AUTOFILL_GOOGLE_ISSUED_CARD, R.drawable.google_pay_plex) LINK_RESOURCE_ID(IDR_AUTOFILL_GOOGLE_PAY, R.drawable.google_pay) // Use DECLARE_RESOURCE_ID here as these resources are used for android only. DECLARE_RESOURCE_ID(IDR_ANDROID_AUTOFILL_CC_SCAN_NEW,
diff --git a/chrome/browser/apps/app_service/publishers/arc_apps.cc b/chrome/browser/apps/app_service/publishers/arc_apps.cc index c0e93a2..eb15eea 100644 --- a/chrome/browser/apps/app_service/publishers/arc_apps.cc +++ b/chrome/browser/apps/app_service/publishers/arc_apps.cc
@@ -1474,6 +1474,67 @@ notification_observation_.Reset(); } +void ArcApps::OnPrivacyItemsChanged( + std::vector<arc::mojom::PrivacyItemPtr> privacy_items) { + ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_); + if (!prefs) { + return; + } + + // Get the existing accessing app ids from `accessing_apps_`, and set all of + // them as false to explicitly update `AppCapabilityAccessCache` to ensure the + // access is stopped when they are not list in `privacy_items`. If they are + // still accessing, they will exist in `privacy_items`, and be set as true in + // the next loop for `privacy_items`. + base::flat_map<std::string, apps::mojom::CapabilityAccessPtr> + capability_accesses; + for (const auto& app_id : accessing_apps_) { + auto access = apps::mojom::CapabilityAccess::New(); + access->app_id = app_id; + access->camera = apps::mojom::OptionalBool::kFalse; + access->microphone = apps::mojom::OptionalBool::kFalse; + capability_accesses[app_id] = std::move(access); + } + accessing_apps_.clear(); + + // Check the new items in `privacy_items`, and update `capability_accesses` to + // set the access item as true, if the camera or the microphone is still in + // use. + for (const auto& item : privacy_items) { + arc::mojom::AppPermissionGroup permission = item->permission_group; + if (permission != arc::mojom::AppPermissionGroup::CAMERA && + permission != arc::mojom::AppPermissionGroup::MICROPHONE) { + continue; + } + + auto package_name = item->privacy_application->package_name; + for (const auto& app_id : prefs->GetAppsForPackage(package_name)) { + accessing_apps_.insert(app_id); + auto it = capability_accesses.find(app_id); + if (it == capability_accesses.end()) { + capability_accesses[app_id] = apps::mojom::CapabilityAccess::New(); + it = capability_accesses.find(app_id); + it->second->app_id = app_id; + } + if (permission == arc::mojom::AppPermissionGroup::CAMERA) { + it->second->camera = apps::mojom::OptionalBool::kTrue; + } + if (permission == arc::mojom::AppPermissionGroup::MICROPHONE) { + it->second->microphone = apps::mojom::OptionalBool::kTrue; + } + } + } + + // Write the record to `AppCapabilityAccessCache`. + for (auto& subscriber : subscribers_) { + std::vector<apps::mojom::CapabilityAccessPtr> accesses; + for (const auto& item : capability_accesses) { + accesses.push_back(item.second->Clone()); + } + subscriber->OnCapabilityAccesses(std::move(accesses)); + } +} + void ArcApps::OnInstanceUpdate(const apps::InstanceUpdate& update) { if (!update.StateChanged()) { return;
diff --git a/chrome/browser/apps/app_service/publishers/arc_apps.h b/chrome/browser/apps/app_service/publishers/arc_apps.h index 0fb9ee5..749244b 100644 --- a/chrome/browser/apps/app_service/publishers/arc_apps.h +++ b/chrome/browser/apps/app_service/publishers/arc_apps.h
@@ -11,10 +11,13 @@ #include <string> #include <vector> +#include "ash/components/arc/mojom/app_permissions.mojom.h" #include "ash/components/arc/mojom/intent_helper.mojom-forward.h" +#include "ash/components/arc/mojom/privacy_items.mojom.h" #include "ash/public/cpp/message_center/arc_notification_manager_base.h" #include "ash/public/cpp/message_center/arc_notifications_host_initializer.h" #include "base/callback.h" +#include "base/containers/flat_set.h" #include "base/gtest_prod_util.h" #include "base/memory/weak_ptr.h" #include "base/scoped_observation.h" @@ -63,7 +66,8 @@ public arc::ArcIntentHelperObserver, public ash::ArcNotificationManagerBase::Observer, public ash::ArcNotificationsHostInitializer::Observer, - public apps::InstanceRegistry::Observer { + public apps::InstanceRegistry::Observer, + public arc::mojom::PrivacyItemsHost { public: static ArcApps* Get(Profile* profile); @@ -83,6 +87,7 @@ friend class ArcAppsFactory; friend class PublisherTest; FRIEND_TEST_ALL_PREFIXES(PublisherTest, ArcAppsOnApps); + FRIEND_TEST_ALL_PREFIXES(PublisherTest, ArcApps_CapabilityAccess); using AppIdToTaskIds = std::map<std::string, std::set<int>>; using TaskIdToAppId = std::map<int, std::string>; @@ -196,6 +201,12 @@ void OnArcNotificationManagerDestroyed( ash::ArcNotificationManagerBase* notification_manager) override; + // PrivacyItemsHost overrides. + void OnPrivacyItemsChanged( + std::vector<arc::mojom::PrivacyItemPtr> privacy_items) override; + void OnMicCameraIndicatorRequirementChanged(bool flag) override {} + void OnLocationIndicatorRequirementChanged(bool flag) override {} + // apps::InstanceRegistry::Observer overrides. void OnInstanceUpdate(const apps::InstanceUpdate& update) override; void OnInstanceRegistryWillBeDestroyed( @@ -250,6 +261,9 @@ AppIdToTaskIds app_id_to_task_ids_; TaskIdToAppId task_id_to_app_id_; + // App id set which might be accessing camera or microphone. + base::flat_set<std::string> accessing_apps_; + // Handles requesting app shortcuts from Android. std::unique_ptr<arc::ArcAppShortcutsRequest> arc_app_shortcuts_request_;
diff --git a/chrome/browser/apps/app_service/publishers/publisher_unittest.cc b/chrome/browser/apps/app_service/publishers/publisher_unittest.cc index a6d8384..12ce3cc 100644 --- a/chrome/browser/apps/app_service/publishers/publisher_unittest.cc +++ b/chrome/browser/apps/app_service/publishers/publisher_unittest.cc
@@ -52,6 +52,8 @@ #include "chrome/browser/ui/app_list/internal_app/internal_app_metadata.h" #include "chrome/common/chrome_features.h" #include "chromeos/login/login_state/login_state.h" +#include "components/services/app_service/public/cpp/app_capability_access_cache.h" +#include "components/services/app_service/public/cpp/capability_access_update.h" #include "components/user_manager/scoped_user_manager.h" #endif // BUILDFLAG(IS_CHROMEOS_ASH) @@ -181,6 +183,18 @@ return arg.show_in_launcher.has_value() && arg.show_in_launcher == shown; } +#if BUILDFLAG(IS_CHROMEOS_ASH) +arc::mojom::PrivacyItemPtr CreateArcPrivacyItem( + arc::mojom::AppPermissionGroup permission, + const std::string& package_name) { + arc::mojom::PrivacyItemPtr item = arc::mojom::PrivacyItem::New(); + item->permission_group = permission; + item->privacy_application = arc::mojom::PrivacyApplication::New(); + item->privacy_application->package_name = package_name; + return item; +} +#endif // BUILDFLAG(IS_CHROMEOS_ASH) + } // namespace namespace apps { @@ -364,6 +378,22 @@ ASSERT_TRUE(base::Contains(cache.InitializedAppTypes(), app_type)); } + void VerifyCapabilityAccess(const std::string& app_id, + apps::mojom::OptionalBool accessing_camera, + apps::mojom::OptionalBool accessing_microphone) { + apps::mojom::OptionalBool camera = apps::mojom::OptionalBool::kUnknown; + apps::mojom::OptionalBool microphone = apps::mojom::OptionalBool::kUnknown; + apps::AppServiceProxyFactory::GetForProfile(profile()) + ->AppCapabilityAccessCache() + .ForOneApp(app_id, [&camera, µphone]( + const apps::CapabilityAccessUpdate& update) { + camera = update.Camera(); + microphone = update.Microphone(); + }); + EXPECT_EQ(camera, accessing_camera); + EXPECT_EQ(microphone, accessing_microphone); + } + protected: base::test::ScopedFeatureList scoped_feature_list_; @@ -447,6 +477,108 @@ arc_apps->Shutdown(); } +TEST_F(PublisherTest, ArcApps_CapabilityAccess) { + ArcAppTest arc_test; + arc_test.SetUp(profile()); + AppServiceProxyFactory::GetForProfile(profile())->FlushMojoCallsForTesting(); + ArcApps* arc_apps = apps::ArcAppsFactory::GetForProfile(profile()); + ASSERT_TRUE(arc_apps); + + const auto& fake_apps = arc_test.fake_apps(); + std::string package_name1 = fake_apps[0]->package_name; + std::string package_name2 = fake_apps[1]->package_name; + + // Install fake apps. + arc_test.app_instance()->SendRefreshAppList(arc_test.fake_apps()); + + // Set accessing Camera for `package_name1`. + { + std::vector<arc::mojom::PrivacyItemPtr> privacy_items; + privacy_items.push_back(CreateArcPrivacyItem( + arc::mojom::AppPermissionGroup::CAMERA, package_name1)); + arc_apps->OnPrivacyItemsChanged(std::move(privacy_items)); + AppServiceProxyFactory::GetForProfile(profile()) + ->FlushMojoCallsForTesting(); + VerifyCapabilityAccess( + ArcAppTest::GetAppId(*fake_apps[0]), + /*accessing_camera=*/apps::mojom::OptionalBool::kTrue, + /*accessing_microphone=*/apps::mojom::OptionalBool::kUnknown); + } + + // Cancel accessing Camera for `package_name1`. + { + std::vector<arc::mojom::PrivacyItemPtr> privacy_items; + arc_apps->OnPrivacyItemsChanged(std::move(privacy_items)); + AppServiceProxyFactory::GetForProfile(profile()) + ->FlushMojoCallsForTesting(); + VerifyCapabilityAccess( + ArcAppTest::GetAppId(*fake_apps[0]), + /*accessing_camera=*/apps::mojom::OptionalBool::kFalse, + /*accessing_microphone=*/apps::mojom::OptionalBool::kFalse); + } + + // Set accessing Camera and Microphone for `package_name1`, and accessing + // Camera for `package_name2`. + { + std::vector<arc::mojom::PrivacyItemPtr> privacy_items; + privacy_items.push_back(CreateArcPrivacyItem( + arc::mojom::AppPermissionGroup::CAMERA, package_name1)); + privacy_items.push_back(CreateArcPrivacyItem( + arc::mojom::AppPermissionGroup::MICROPHONE, package_name1)); + privacy_items.push_back(CreateArcPrivacyItem( + arc::mojom::AppPermissionGroup::CAMERA, package_name2)); + arc_apps->OnPrivacyItemsChanged(std::move(privacy_items)); + AppServiceProxyFactory::GetForProfile(profile()) + ->FlushMojoCallsForTesting(); + VerifyCapabilityAccess( + ArcAppTest::GetAppId(*fake_apps[0]), + /*accessing_camera=*/apps::mojom::OptionalBool::kTrue, + /*accessing_microphone=*/apps::mojom::OptionalBool::kTrue); + VerifyCapabilityAccess( + ArcAppTest::GetAppId(*fake_apps[1]), + /*accessing_camera=*/apps::mojom::OptionalBool::kTrue, + /*accessing_microphone=*/apps::mojom::OptionalBool::kUnknown); + } + + // Cancel accessing Microphone for `package_name1`. + { + std::vector<arc::mojom::PrivacyItemPtr> privacy_items; + privacy_items.push_back(CreateArcPrivacyItem( + arc::mojom::AppPermissionGroup::CAMERA, package_name1)); + privacy_items.push_back(CreateArcPrivacyItem( + arc::mojom::AppPermissionGroup::CAMERA, package_name2)); + arc_apps->OnPrivacyItemsChanged(std::move(privacy_items)); + AppServiceProxyFactory::GetForProfile(profile()) + ->FlushMojoCallsForTesting(); + VerifyCapabilityAccess( + ArcAppTest::GetAppId(*fake_apps[0]), + /*accessing_camera=*/apps::mojom::OptionalBool::kTrue, + /*accessing_microphone=*/apps::mojom::OptionalBool::kFalse); + VerifyCapabilityAccess( + ArcAppTest::GetAppId(*fake_apps[1]), + /*accessing_camera=*/apps::mojom::OptionalBool::kTrue, + /*accessing_microphone=*/apps::mojom::OptionalBool::kFalse); + } + + // Cancel accessing CAMERA for `package_name1` and `package_name2`. + { + std::vector<arc::mojom::PrivacyItemPtr> privacy_items; + arc_apps->OnPrivacyItemsChanged(std::move(privacy_items)); + AppServiceProxyFactory::GetForProfile(profile()) + ->FlushMojoCallsForTesting(); + VerifyCapabilityAccess( + ArcAppTest::GetAppId(*fake_apps[0]), + /*accessing_camera=*/apps::mojom::OptionalBool::kFalse, + /*accessing_microphone=*/apps::mojom::OptionalBool::kFalse); + VerifyCapabilityAccess( + ArcAppTest::GetAppId(*fake_apps[1]), + /*accessing_camera=*/apps::mojom::OptionalBool::kFalse, + /*accessing_microphone=*/apps::mojom::OptionalBool::kFalse); + } + + arc_apps->Shutdown(); +} + TEST_F(PublisherTest, BuiltinAppsOnApps) { // Verify Builtin apps are added to AppRegistryCache. for (const auto& internal_app : app_list::GetInternalAppList(profile())) {
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 c6975f7..2107b50 100644 --- a/chrome/browser/ash/eche_app/eche_app_manager_factory.cc +++ b/chrome/browser/ash/eche_app/eche_app_manager_factory.cc
@@ -8,15 +8,12 @@ #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_tray_stream_status_observer.h" #include "ash/webui/eche_app_ui/eche_uid_provider.h" #include "ash/webui/eche_app_ui/system_info.h" #include "base/bind.h" @@ -32,7 +29,6 @@ #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" @@ -47,8 +43,6 @@ #include "ui/base/l10n/l10n_util.h" #include "ui/chromeos/devicetype_utils.h" #include "ui/gfx/image/image.h" -#include "ui/views/view.h" -#include "ui/views/widget/widget.h" #include "url/gurl.h" namespace ash { @@ -56,12 +50,6 @@ namespace { -EcheTray* GetEcheTray() { - return Shell::GetPrimaryRootWindowController() - ->GetStatusAreaWidget() - ->eche_tray(); -} - // Enumeration of possible interactions with a PhoneHub notification. Keep in // sync with corresponding enum in tools/metrics/histograms/enums.xml. These // values are persisted to logs. Entries should not be renumbered and numeric @@ -72,12 +60,6 @@ kMaxValue = kOpenAppStreaming, }; -void LaunchBubble(const GURL& url, const gfx::Image& icon) { - auto* eche_tray = GetEcheTray(); - DCHECK(eche_tray); - eche_tray->LoadBubble(url, icon); -} - void LaunchWebApp(const std::string& package_name, const absl::optional<int64_t>& notification_id, const std::u16string& visible_name, @@ -199,9 +181,7 @@ // static void EcheAppManagerFactory::CloseEche(Profile* profile) { if (features::IsEcheCustomWidgetEnabled()) { - auto* eche_tray = GetEcheTray(); - if (eche_tray) - eche_tray->PurgeAndClose(); + CloseBubble(); return; } for (auto* browser : *(BrowserList::GetInstance())) { @@ -234,20 +214,6 @@ ->CloseConnectionOrLaunchErrorNotifications(); } -// static -void EcheAppManagerFactory::OnStreamStateChanged( - Profile* profile, - const mojom::StreamStatus status) { - if (status == mojom::StreamStatus::kStreamStatusStarted && - features::IsEcheCustomWidgetEnabled()) { - auto* eche_tray = GetEcheTray(); - if (eche_tray) - eche_tray->ShowBubble(); - } else if (status == mojom::StreamStatus::kStreamStatusStopped) { - CloseEche(profile); - } -} - EcheAppManagerFactory::EcheAppManagerFactory() : BrowserContextKeyedServiceFactory( "EcheAppManager", @@ -306,9 +272,7 @@ base::BindRepeating(&EcheAppManagerFactory::LaunchEcheApp, profile), base::BindRepeating(&EcheAppManagerFactory::CloseEche, profile), base::BindRepeating(&EcheAppManagerFactory::ShowNotification, - weak_ptr_factory_.GetWeakPtr(), profile), - base::BindRepeating(&EcheAppManagerFactory::OnStreamStateChanged, - profile)); + weak_ptr_factory_.GetWeakPtr(), profile)); } std::unique_ptr<SystemInfo> EcheAppManagerFactory::GetSystemInfo(
diff --git a/chrome/browser/ash/eche_app/eche_app_manager_factory.h b/chrome/browser/ash/eche_app/eche_app_manager_factory.h index 8005689..ca36bbe5 100644 --- a/chrome/browser/ash/eche_app/eche_app_manager_factory.h +++ b/chrome/browser/ash/eche_app/eche_app_manager_factory.h
@@ -100,8 +100,6 @@ const std::u16string& visible_name, const absl::optional<int64_t>& user_id, const gfx::Image& icon); - static void OnStreamStateChanged(Profile* profile, - const mojom::StreamStatus status); void SetLastLaunchedAppInfo( std::unique_ptr<LaunchedAppInfo> last_launched_app_info);
diff --git a/chrome/browser/ash/eche_app/eche_app_manager_factory_unittest.cc b/chrome/browser/ash/eche_app/eche_app_manager_factory_unittest.cc index c3bb8d6..96b6b478 100644 --- a/chrome/browser/ash/eche_app/eche_app_manager_factory_unittest.cc +++ b/chrome/browser/ash/eche_app/eche_app_manager_factory_unittest.cc
@@ -143,29 +143,6 @@ EXPECT_FALSE(eche_tray()->is_active()); } -TEST_F(EcheAppManagerFactoryTest, OnStreamStateChanged) { - const int64_t user_id = 1; - const char16_t visible_name[] = u"Fake App"; - const char package_name[] = "com.fakeapp"; - EcheAppManagerFactory::LaunchEcheApp( - GetProfile(), /*notification_id=*/absl::nullopt, package_name, - visible_name, user_id, gfx::Image()); - - // Eche tray should be visible when streaming is active - EcheAppManagerFactory::OnStreamStateChanged( - GetProfile(), mojom::StreamStatus::kStreamStatusStarted); - // Wait for Eche Tray to load Eche Web to complete - base::RunLoop().RunUntilIdle(); - EXPECT_TRUE(eche_tray()->is_active()); - - // Eche tray should not be visible when streaming is finished - EcheAppManagerFactory::OnStreamStateChanged( - GetProfile(), mojom::StreamStatus::kStreamStatusStopped); - // Wait for Eche Web to close - base::RunLoop().RunUntilIdle(); - EXPECT_FALSE(eche_tray()->is_active()); -} - TEST_F(EcheAppManagerFactoryWithBackgroundTest, LaunchEcheApp) { const int64_t user_id = 1; const char16_t visible_name[] = u"Fake App"; @@ -180,28 +157,5 @@ EXPECT_FALSE(eche_tray()->is_active()); } -TEST_F(EcheAppManagerFactoryWithBackgroundTest, OnStreamStateChanged) { - const int64_t user_id = 1; - const char16_t visible_name[] = u"Fake App"; - const char package_name[] = "com.fakeapp"; - EcheAppManagerFactory::LaunchEcheApp( - GetProfile(), /*notification_id=*/absl::nullopt, package_name, - visible_name, user_id, gfx::Image()); - - // Eche tray should be visible when streaming is active - EcheAppManagerFactory::OnStreamStateChanged( - GetProfile(), mojom::StreamStatus::kStreamStatusStarted); - // Wait for Eche Tray to load Eche Web to complete - base::RunLoop().RunUntilIdle(); - EXPECT_TRUE(eche_tray()->is_active()); - - // Eche tray should not be visible when streaming is finished - EcheAppManagerFactory::OnStreamStateChanged( - GetProfile(), mojom::StreamStatus::kStreamStatusStopped); - // Wait for Eche Web to close - base::RunLoop().RunUntilIdle(); - EXPECT_FALSE(eche_tray()->is_active()); -} - } // namespace eche_app } // namespace ash
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc index f4650bd8..1b56197 100644 --- a/chrome/browser/chrome_browser_interface_binders.cc +++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -16,6 +16,7 @@ #include "chrome/browser/dom_distiller/dom_distiller_service_factory.h" #include "chrome/browser/media/history/media_history_store.mojom.h" #include "chrome/browser/media/media_engagement_score_details.mojom.h" +#include "chrome/browser/navigation_predictor/anchor_element_preloader.h" #include "chrome/browser/navigation_predictor/navigation_predictor.h" #include "chrome/browser/password_manager/chrome_password_manager_client.h" #include "chrome/browser/predictors/network_hints_handler_impl.h" @@ -78,6 +79,7 @@ #include "services/image_annotation/public/mojom/image_annotation.mojom.h" #include "third_party/blink/public/common/features.h" #include "third_party/blink/public/mojom/credentialmanagement/credential_manager.mojom.h" +#include "third_party/blink/public/mojom/loader/anchor_element_interaction_host.mojom.h" #include "third_party/blink/public/mojom/loader/navigation_predictor.mojom.h" #include "third_party/blink/public/mojom/payments/payment_credential.mojom.h" #include "third_party/blink/public/mojom/payments/payment_request.mojom.h" @@ -629,6 +631,12 @@ map->Add<blink::mojom::AnchorElementMetricsHost>( base::BindRepeating(&NavigationPredictor::Create)); + if (base::FeatureList::IsEnabled( + blink::features::kAnchorElementInteraction)) { + map->Add<blink::mojom::AnchorElementInteractionHost>( + base::BindRepeating(&AnchorElementPreloader::Create)); + } + map->Add<dom_distiller::mojom::DistillabilityService>( base::BindRepeating(&BindDistillabilityService));
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc index 40b7f4772..61a8155 100644 --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc
@@ -4053,6 +4053,13 @@ break; } +#if !defined(OFFICIAL_BUILD) + // Disable renderer code integrity when Application Verifier or pageheap are + // enabled for chrome.exe to avoid renderer crashes. https://crbug.com/1004989 + if (base::win::IsAppVerifierEnabled(chrome::kBrowserProcessExecutableName)) + enforce_code_integrity = false; +#endif // !defined(OFFICIAL_BUILD) + if (!enforce_code_integrity) return true;
diff --git a/chrome/browser/extensions/api/downloads/downloads_api.cc b/chrome/browser/extensions/api/downloads/downloads_api.cc index d26484d..23a53f0 100644 --- a/chrome/browser/extensions/api/downloads/downloads_api.cc +++ b/chrome/browser/extensions/api/downloads/downloads_api.cc
@@ -916,8 +916,9 @@ content::BrowserContext* browser_context, Feature::Context target_context, const Extension* extension, - Event* event, - const base::DictionaryValue* listener_filter) { + const base::DictionaryValue* listener_filter, + std::unique_ptr<base::Value::List>* event_args_out, + mojom::EventFilteringInfoPtr* event_filtering_info_out) { *any_determiners = true; base::Time installed = ExtensionPrefs::Get(browser_context)->GetInstallTime(extension->id());
diff --git a/chrome/browser/extensions/api/tabs/tabs_apitest.cc b/chrome/browser/extensions/api/tabs/tabs_apitest.cc index 9f0e3b9..2d28188 100644 --- a/chrome/browser/extensions/api/tabs/tabs_apitest.cc +++ b/chrome/browser/extensions/api/tabs/tabs_apitest.cc
@@ -17,9 +17,13 @@ #include "chrome/common/chrome_switches.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" +#include "chrome/test/base/ui_test_utils.h" #include "components/prefs/pref_service.h" #include "content/public/common/content_features.h" #include "content/public/test/browser_test.h" +#include "content/public/test/browser_test_utils.h" +#include "extensions/test/result_catcher.h" +#include "extensions/test/test_extension_dir.h" #include "net/dns/mock_host_resolver.h" #if BUILDFLAG(IS_WIN) @@ -379,6 +383,67 @@ ASSERT_TRUE(RunExtensionTest("tabs/send_message")); } +// Tests that extension with "tabs" permission does not leak tab info to another +// extension without "tabs" permission. +// +// Regression test for https://crbug.com/1302959 +IN_PROC_BROWSER_TEST_F(ExtensionApiTabTest, TabsPermissionDoesNotLeakTabInfo) { + constexpr char kManifestWithTabsPermission[] = + R"({ + "name": "test", "version": "1", "manifest_version": 2, + "background": {"scripts": ["background.js"]}, + "permissions": ["tabs"] + })"; + constexpr char kBackgroundJSWithTabsPermission[] = + "chrome.tabs.onUpdated.addListener(() => {});"; + + constexpr char kManifestWithoutTabsPermission[] = + R"({ + "name": "test", "version": "1", "manifest_version": 2, + "background": {"scripts": ["background.js"]} + })"; + constexpr char kBackgroundJSWithoutTabsPermission[] = + R"( + let urlStr = '%s'; + chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) { + chrome.test.assertEq(3, Array.from(arguments).length); + // Note: we'll search within all of the arguments, just to make sure + // we don't miss any inadvertently added ones. See + // https://crbug.com/1302959 for details. + let argumentsStr = JSON.stringify(arguments); + let containsUrlStr = argumentsStr.indexOf(urlStr) != -1; + chrome.test.assertFalse(containsUrlStr); + if (tab.status == 'complete') { + chrome.test.notifyPass(); + } + }); + )"; + + GURL url = embedded_test_server()->GetURL("/title1.html"); + + // First load the extension with "tabs" permission. + // Note that order is important for this regression test. + extensions::TestExtensionDir ext_dir1; + ext_dir1.WriteManifest(kManifestWithTabsPermission); + ext_dir1.WriteFile(FILE_PATH_LITERAL("background.js"), + kBackgroundJSWithTabsPermission); + ASSERT_TRUE(LoadExtension(ext_dir1.UnpackedPath())); + + // Then load the extension without "tabs" permission. + extensions::ResultCatcher catcher; + extensions::TestExtensionDir ext_dir2; + ext_dir2.WriteManifest(kManifestWithoutTabsPermission); + ext_dir2.WriteFile(FILE_PATH_LITERAL("background.js"), + base::StringPrintf(kBackgroundJSWithoutTabsPermission, + url.spec().c_str())); + ASSERT_TRUE(LoadExtension(ext_dir2.UnpackedPath())); + + // Now open a tab and ensure the extension in |ext_dir2| does not see any info + // that is guarded by "tabs" permission. + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); + EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); +} + class IncognitoExtensionApiTabTest : public ExtensionApiTabTest, public testing::WithParamInterface<bool> { };
diff --git a/chrome/browser/extensions/api/tabs/tabs_event_router.cc b/chrome/browser/extensions/api/tabs/tabs_event_router.cc index e280973..18c3467 100644 --- a/chrome/browser/extensions/api/tabs/tabs_event_router.cc +++ b/chrome/browser/extensions/api/tabs/tabs_event_router.cc
@@ -47,8 +47,9 @@ content::BrowserContext* browser_context, Feature::Context target_context, const Extension* extension, - Event* event, - const base::DictionaryValue* listener_filter) { + const base::DictionaryValue* listener_filter, + std::unique_ptr<base::Value::List>* event_args_out, + mojom::EventFilteringInfoPtr* event_filtering_info_out) { ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior = ExtensionTabUtil::GetScrubTabBehavior(extension, target_context, contents); @@ -65,19 +66,22 @@ changed_properties.Set(property, value->Clone()); } - event->event_args->Append(base::Value(std::move(changed_properties))); - event->event_args->Append(std::move(tab_value)); + *event_args_out = std::make_unique<base::Value::List>(); + (*event_args_out)->Append(ExtensionTabUtil::GetTabId(contents)); + (*event_args_out)->Append(base::Value(std::move(changed_properties))); + (*event_args_out)->Append(std::move(tab_value)); return true; } -bool WillDispatchTabCreatedEvent(WebContents* contents, - bool active, - content::BrowserContext* browser_context, - Feature::Context target_context, - const Extension* extension, - Event* event, - const base::DictionaryValue* listener_filter) { - event->event_args->ClearList(); +bool WillDispatchTabCreatedEvent( + WebContents* contents, + bool active, + content::BrowserContext* browser_context, + Feature::Context target_context, + const Extension* extension, + const base::DictionaryValue* listener_filter, + std::unique_ptr<base::Value::List>* event_args_out, + mojom::EventFilteringInfoPtr* event_filtering_info_out) { ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior = ExtensionTabUtil::GetScrubTabBehavior(extension, target_context, contents); @@ -86,7 +90,9 @@ ->ToValue()); tab_value.SetBoolKey(tabs_constants::kSelectedKey, active); tab_value.SetBoolKey(tabs_constants::kActiveKey, active); - event->event_args->Append(std::move(tab_value)); + + *event_args_out = std::make_unique<base::Value::List>(); + (*event_args_out)->Append(std::move(tab_value)); return true; } @@ -585,24 +591,13 @@ DCHECK(!changed_property_names.empty()); DCHECK(contents); - // The state of the tab (as seen from the extension point of view) has - // changed. Send a notification to the extension. - std::unique_ptr<base::ListValue> args_base(new base::ListValue); - - // First arg: The id of the tab that changed. - args_base->Append(ExtensionTabUtil::GetTabId(contents)); - - // Second arg: An object containing the changes to the tab state. Filled in - // by WillDispatchTabUpdatedEvent as a copy of changed_properties, if the - // extension has the tabs permission. - - // Third arg: An object containing the state of the tab. Filled in by - // WillDispatchTabUpdatedEvent. Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext()); auto event = std::make_unique<Event>( events::TABS_ON_UPDATED, api::tabs::OnUpdated::kEventName, - std::move(*args_base).TakeListDeprecated(), profile); + // The event arguments depend on the extension's permission. They are set + // in WillDispatchTabUpdatedEvent(). + std::vector<base::Value>(), profile); event->user_gesture = EventRouter::USER_GESTURE_NOT_ENABLED; event->will_dispatch_callback = base::BindRepeating(&WillDispatchTabUpdatedEvent, contents,
diff --git a/chrome/browser/extensions/api/tabs/windows_event_router.cc b/chrome/browser/extensions/api/tabs/windows_event_router.cc index bb02fdd..32e03171 100644 --- a/chrome/browser/extensions/api/tabs/windows_event_router.cc +++ b/chrome/browser/extensions/api/tabs/windows_event_router.cc
@@ -59,12 +59,14 @@ WindowController::GetFilterFromWindowTypesValues(filter_value)); } -bool WillDispatchWindowEvent(WindowController* window_controller, - BrowserContext* browser_context, - Feature::Context target_context, - const Extension* extension, - Event* event, - const base::DictionaryValue* listener_filter) { +bool WillDispatchWindowEvent( + WindowController* window_controller, + BrowserContext* browser_context, + Feature::Context target_context, + const Extension* extension, + const base::DictionaryValue* listener_filter, + std::unique_ptr<base::Value::List>* event_args_out, + mojom::EventFilteringInfoPtr* event_filtering_info_out) { bool has_filter = listener_filter && listener_filter->FindKey(extensions::tabs_constants::kWindowTypesKey); @@ -75,15 +77,15 @@ return false; } - // Cleanup previous values. - event->filter_info = mojom::EventFilteringInfo::New(); + *event_filtering_info_out = mojom::EventFilteringInfo::New(); // Only set the window type if the listener has set a filter. // Otherwise we set the window visibility relative to the extension. if (has_filter) { - event->filter_info->window_type = window_controller->GetWindowTypeText(); + (*event_filtering_info_out)->window_type = + window_controller->GetWindowTypeText(); } else { - event->filter_info->has_window_exposed_by_default = true; - event->filter_info->window_exposed_by_default = true; + (*event_filtering_info_out)->has_window_exposed_by_default = true; + (*event_filtering_info_out)->window_exposed_by_default = true; } return true; } @@ -93,8 +95,9 @@ BrowserContext* browser_context, Feature::Context target_context, const Extension* extension, - Event* event, - const base::DictionaryValue* listener_filter) { + const base::DictionaryValue* listener_filter, + std::unique_ptr<base::Value::List>* event_args_out, + mojom::EventFilteringInfoPtr* event_filtering_info_out) { int window_id = extension_misc::kUnknownWindowId; Profile* new_active_context = nullptr; bool has_filter = @@ -108,19 +111,18 @@ new_active_context = window_controller->profile(); } - // Cleanup previous values. - event->filter_info = mojom::EventFilteringInfo::New(); + *event_filtering_info_out = mojom::EventFilteringInfo::New(); // Only set the window type if the listener has set a filter, // otherwise set the visibility to true (if the window is not // supposed to be visible by the extension, we will clear out the // window id later). if (has_filter) { - event->filter_info->window_type = + (*event_filtering_info_out)->window_type = window_controller ? window_controller->GetWindowTypeText() : extensions::tabs_constants::kWindowTypeValueNormal; } else { - event->filter_info->has_window_exposed_by_default = true; - event->filter_info->window_exposed_by_default = true; + (*event_filtering_info_out)->has_window_exposed_by_default = true; + (*event_filtering_info_out)->window_exposed_by_default = true; } // When switching between windows in the default and incognito profiles, @@ -135,12 +137,11 @@ bool visible_to_listener = ControllerVisibleToListener( window_controller, extension, listener_filter); + *event_args_out = std::make_unique<base::Value::List>(); if (cant_cross_incognito || !visible_to_listener) { - event->event_args->ClearList(); - event->event_args->Append(extension_misc::kUnknownWindowId); + (*event_args_out)->Append(extension_misc::kUnknownWindowId); } else { - event->event_args->ClearList(); - event->event_args->Append(window_id); + (*event_args_out)->Append(window_id); } return true; }
diff --git a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedBridge.java b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedBridge.java index eba9847..e7f1df5 100644 --- a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedBridge.java +++ b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedBridge.java
@@ -141,6 +141,11 @@ WebFeedBridgeJni.get().refreshRecommendedFeeds(callback); } + /** Increase the count of the number of times the user has followed from the web page menu. */ + public static void incrementFollowedFromWebPageMenuCount() { + WebFeedBridgeJni.get().incrementFollowedFromWebPageMenuCount(); + } + /** Container for results from a follow request. */ public static class FollowResults { /** Status of follow request. */ @@ -249,5 +254,6 @@ void refreshSubscriptions(Callback<Boolean> callback); void refreshRecommendedFeeds(Callback<Boolean> callback); void getRecentVisitCountsToHost(GURL url, Callback<int[]> callback); + void incrementFollowedFromWebPageMenuCount(); } }
diff --git a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedMainMenuItem.java b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedMainMenuItem.java index 01645cf..2993db0 100644 --- a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedMainMenuItem.java +++ b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedMainMenuItem.java
@@ -17,8 +17,11 @@ import org.chromium.base.Callback; import org.chromium.chrome.browser.feed.FeedFeatures; +import org.chromium.chrome.browser.feed.FeedServiceBridge; import org.chromium.chrome.browser.feed.R; +import org.chromium.chrome.browser.feed.StreamKind; import org.chromium.chrome.browser.feed.componentinterfaces.SurfaceCoordinator.StreamTabId; +import org.chromium.chrome.browser.feed.v2.FeedUserActionType; import org.chromium.chrome.browser.feed.webfeed.WebFeedBridge.WebFeedMetadata; import org.chromium.chrome.browser.feed.webfeed.WebFeedSnackbarController.FeedLauncher; import org.chromium.chrome.browser.preferences.Pref; @@ -152,6 +155,9 @@ FeedFeatures.setLastSeenFeedTabId(StreamTabId.FOLLOWING); } }); + WebFeedBridge.incrementFollowedFromWebPageMenuCount(); + FeedServiceBridge.reportOtherUserAction( + StreamKind.UNKNOWN, FeedUserActionType.TAPPED_FOLLOW_BUTTON); mAppMenuHandler.hideAppMenu(); }); }
diff --git a/chrome/browser/feed/android/web_feed_bridge.cc b/chrome/browser/feed/android/web_feed_bridge.cc index cface92..29d5178a 100644 --- a/chrome/browser/feed/android/web_feed_bridge.cc +++ b/chrome/browser/feed/android/web_feed_bridge.cc
@@ -14,6 +14,7 @@ #include "base/notreached.h" #include "base/task/cancelable_task_tracker.h" #include "chrome/browser/android/tab_android.h" +#include "chrome/browser/feed/android/feed_stream.h" #include "chrome/browser/feed/android/jni_headers/WebFeedBridge_jni.h" #include "chrome/browser/feed/feed_service_factory.h" #include "chrome/browser/feed/web_feed_follow_util.h" @@ -32,6 +33,8 @@ #include "third_party/abseil-cpp/absl/types/optional.h" #include "url/android/gurl_android.h" +class Profile; + namespace feed { using PageInformation = WebFeedPageInformationFetcher::PageInformation; @@ -79,6 +82,14 @@ return GetSubscriptionsForProfile(profile); } +FeedApi* GetStream() { + Profile* profile = ProfileManager::GetLastUsedProfile(); + FeedService* service = FeedServiceFactory::GetForBrowserContext(profile); + if (!service) + return nullptr; + return service->GetStream(); +} + // ToJava functions convert C++ types to Java. Used in `AdaptCallbackForJava`. bool ToJava(JNIEnv* env, WebFeedSubscriptions::RefreshResult value) { @@ -347,4 +358,13 @@ std::move(callback), &TaskTracker()); } +static void JNI_WebFeedBridge_IncrementFollowedFromWebPageMenuCount( + JNIEnv* env) { + FeedApi* stream = GetStream(); + if (!stream) + return; + + stream->IncrementFollowedFromWebPageMenuCount(); +} + } // namespace feed
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json index 4c32bcb..518f07e 100644 --- a/chrome/browser/flag-metadata.json +++ b/chrome/browser/flag-metadata.json
@@ -284,6 +284,11 @@ "expiry_milestone": 110 }, { + "name": "ash-capture-mode-selfie-cam", + "owners": [ "afakhry", "gzadina" ], + "expiry_milestone": 112 + }, + { "name": "ash-debug-shortcuts", "owners": [ "//ash/OWNERS" ], // Used by developers for debugging and to dump extra information to logs @@ -410,11 +415,6 @@ "expiry_milestone": 103 }, { - "name": "autofill-enable-google-issued-card", - "owners": [ "siashah" ], - "expiry_milestone": 92 - }, - { "name": "autofill-enable-merchant-bound-virtual-cards", "owners": [ "siashah", "siyua" ], "expiry_milestone": 105 @@ -4188,11 +4188,6 @@ "expiry_milestone": 105 }, { - "name": "new-usb-backend", - "owners": [ "reillyg@chromium.org" ], - "expiry_milestone": 100 - }, - { "name": "new-window-app-menu", "owners": [ "jinsukkim" ], "expiry_milestone": 100
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc index d2bd35e..98ff2f8 100644 --- a/chrome/browser/flag_descriptions.cc +++ b/chrome/browser/flag_descriptions.cc
@@ -318,12 +318,6 @@ "Controls if different width limits are used for the popup that provides " "Autofill suggestions, depending on the type of data that is filled."; -const char kAutofillEnableGoogleIssuedCardName[] = - "Enable Autofill Google-issued card"; -const char kAutofillEnableGoogleIssuedCardDescription[] = - "When enabled, Google-issued cards will be available in the autofill " - "suggestions."; - const char kAutofillEnableMerchantBoundVirtualCardsName[] = "Offer merchant bound virtual cards in Autofill"; const char kAutofillEnableMerchantBoundVirtualCardsDescription[] = @@ -1663,10 +1657,6 @@ "owned by the System Profile. This requires " "#destroy-profile-on-browser-close."; -const char kNewUsbBackendName[] = "Enable new USB backend"; -const char kNewUsbBackendDescription[] = - "Enables the new experimental USB backend for macOS"; - const char kNotificationsRevampName[] = "Notifications Revamp"; const char kNotificationsRevampDescription[] = "Enable notification UI revamp and grouped web notifications."; @@ -4249,6 +4239,11 @@ "Show Monthly Calendar View with Google Calendar events to increase " "productivity by helping users view their schedules more quickly."; +const char kCaptureSelfieCamName[] = "Enable selfie camera in screen capture"; +const char kCaptureSelfieCamDescription[] = + "Enables the ability to record the selected camera feed along with screen " + "recordings for personalized demos and more."; + const char kDefaultLinkCapturingInBrowserName[] = "Default link capturing in the browser"; const char kDefaultLinkCapturingInBrowserDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h index cd2f531..9e0fce4 100644 --- a/chrome/browser/flag_descriptions.h +++ b/chrome/browser/flag_descriptions.h
@@ -186,9 +186,6 @@ extern const char kAutofillTypeSpecificPopupWidthName[]; extern const char kAutofillTypeSpecificPopupWidthDescription[]; -extern const char kAutofillEnableGoogleIssuedCardName[]; -extern const char kAutofillEnableGoogleIssuedCardDescription[]; - extern const char kAutofillEnableMerchantBoundVirtualCardsName[]; extern const char kAutofillEnableMerchantBoundVirtualCardsDescription[]; @@ -947,9 +944,6 @@ extern const char kDestroySystemProfilesName[]; extern const char kDestroySystemProfilesDescription[]; -extern const char kNewUsbBackendName[]; -extern const char kNewUsbBackendDescription[]; - extern const char kNotificationsRevampName[]; extern const char kNotificationsRevampDescription[]; @@ -2440,6 +2434,9 @@ extern const char kCalendarViewName[]; extern const char kCalendarViewDescription[]; +extern const char kCaptureSelfieCamName[]; +extern const char kCaptureSelfieCamDescription[]; + extern const char kDefaultLinkCapturingInBrowserName[]; extern const char kDefaultLinkCapturingInBrowserDescription[];
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc index e4a56d58..085765a4 100644 --- a/chrome/browser/flags/android/chrome_feature_list.cc +++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -86,7 +86,6 @@ &autofill::features::kAutofillKeyboardAccessory, &autofill::features::kAutofillManualFallbackAndroid, &autofill::features::kAutofillRefreshStyleAndroid, - &autofill::features::kAutofillEnableGoogleIssuedCard, &autofill::features::kAutofillEnableSupportForHonorificPrefixes, &autofill::features::kAutofillEnableSupportForMoreStructureInAddresses, &autofill::features::kAutofillEnableSupportForMoreStructureInNames,
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java index c0730d4..3018d2e 100644 --- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java +++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -206,8 +206,6 @@ "AutofillAllowNonHttpActivation"; public static final String AUTOFILL_CREDIT_CARD_AUTHENTICATION = "AutofillCreditCardAuthentication"; - public static final String AUTOFILL_ENABLE_GOOGLE_ISSUED_CARD = - "AutofillEnableGoogleIssuedCard"; public static final String AUTOFILL_ENABLE_SUPPORT_FOR_HONORIFIC_PREFIXES = "AutofillEnableSupportForHonorificPrefixes"; public static final String AUTOFILL_ENABLE_SUPPORT_FOR_MORE_STRUCTURE_IN_ADDRESSES =
diff --git a/chrome/browser/history_clusters/BUILD.gn b/chrome/browser/history_clusters/BUILD.gn index b784c579..1ebe397 100644 --- a/chrome/browser/history_clusters/BUILD.gn +++ b/chrome/browser/history_clusters/BUILD.gn
@@ -11,6 +11,10 @@ "java/src/org/chromium/chrome/browser/history_clusters/ClusterVisit.java", "java/src/org/chromium/chrome/browser/history_clusters/HistoryCluster.java", "java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersBridge.java", + "java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinator.java", + "java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersMediator.java", + "java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersProvider.java", + "java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersQueryManager.java", "java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersResult.java", "java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersTabHelper.java", ] @@ -18,8 +22,13 @@ deps = [ "//base:base_java", "//chrome/browser/profiles/android:java", + "//chrome/browser/ui/android/favicon:java", + "//components/browser_ui/widget/android:java", + "//components/favicon/android:java", "//content/public/android:content_java", "//third_party/androidx:androidx_annotation_annotation_java", + "//ui/android:ui_no_recycler_view_java", + "//ui/android:ui_recycler_view_java", "//url:gurl_java", ]
diff --git a/chrome/browser/history_clusters/DEPS b/chrome/browser/history_clusters/DEPS index c482170..c13d4525 100644 --- a/chrome/browser/history_clusters/DEPS +++ b/chrome/browser/history_clusters/DEPS
@@ -1,3 +1,4 @@ include_rules = [ + "+components/favicon/android", "+content/public/android", ] \ No newline at end of file
diff --git a/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersBridge.java b/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersBridge.java index 8b5284e..9b701e0 100644 --- a/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersBridge.java +++ b/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersBridge.java
@@ -16,14 +16,18 @@ @JNINamespace("history_clusters") /** JNI bridge that provides access to HistoryClusters data. */ -public class HistoryClustersBridge { +class HistoryClustersBridge { private long mNativeBridge; /* Construct a new HistoryClustersBridge. */ - public HistoryClustersBridge(Profile profile) { + HistoryClustersBridge(Profile profile) { mNativeBridge = HistoryClustersBridgeJni.get().init(profile); } + void destroy() { + HistoryClustersBridgeJni.get().destroy(mNativeBridge); + } + /* Start a new query for clusters, fetching the first page of results. */ void queryClusters(String query, Callback<HistoryClustersResult> callback) { HistoryClustersBridgeJni.get().queryClusters(mNativeBridge, this, query, callback); @@ -60,5 +64,6 @@ String query, Callback<HistoryClustersResult> callback); void loadMoreClusters(long nativeHistoryClustersBridge, HistoryClustersBridge caller, String query, Callback<HistoryClustersResult> callback); + void destroy(long nativeHistoryClustersBridge); } }
diff --git a/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinator.java b/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinator.java new file mode 100644 index 0000000..ef912f10 --- /dev/null +++ b/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinator.java
@@ -0,0 +1,42 @@ +// 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.history_clusters; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import org.chromium.chrome.browser.profiles.Profile; +import org.chromium.ui.modelutil.MVCListAdapter.ModelList; + +/** + * Root component for the HistoryClusters UI component, which displays lists of related history + * visits grouped into clusters. + */ +public class HistoryClustersCoordinator { + private final HistoryClustersQueryManager mHistoryClustersQueryManager; + private final HistoryClustersMediator mMediator; + private final ModelList mModelList; + private final Context mContext; + + /** + * Construct a new HistoryClustersCoordinator. + * @param profile The profile from which the coordinator should access history data. + * @param context Android context from which UI configuration (strings, colors etc.) should be + * derived. + */ + public HistoryClustersCoordinator(@NonNull Profile profile, @NonNull Context context) { + mContext = context; + mHistoryClustersQueryManager = new HistoryClustersQueryManager(profile); + mModelList = new ModelList(); + mMediator = new HistoryClustersMediator( + mHistoryClustersQueryManager, context, context.getResources(), mModelList, profile); + } + + public void destroy() { + mHistoryClustersQueryManager.destroy(); + mMediator.destroy(); + } +} \ No newline at end of file
diff --git a/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersMediator.java b/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersMediator.java new file mode 100644 index 0000000..27758765 --- /dev/null +++ b/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersMediator.java
@@ -0,0 +1,50 @@ +// 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.history_clusters; + +import android.content.Context; +import android.content.res.Resources; + +import androidx.annotation.NonNull; + +import org.chromium.base.Promise; +import org.chromium.chrome.browser.profiles.Profile; +import org.chromium.chrome.browser.ui.favicon.FaviconUtils; +import org.chromium.components.browser_ui.widget.RoundedIconGenerator; +import org.chromium.components.favicon.LargeIconBridge; +import org.chromium.ui.modelutil.MVCListAdapter.ModelList; + +class HistoryClustersMediator { + private final HistoryClustersProvider mHistoryClustersProvider; + private final Context mContext; + private final Resources mResources; + private final ModelList mModelList; + private final RoundedIconGenerator mIconGenerator; + private final LargeIconBridge mLargeIconBridge; + private Promise<HistoryClustersResult> mPromise; + + /** + * Create a new HistoryClustersMediator. + * @param historyClustersProvider Provider of history clusters data. + * @param context Android context from which UI configuration should be derived. + * @param resources Android resources object from which strings, colors etc. should be fetched. + * @param modelList Model list to which fetched cluster data should be pushed to. + * @param profile Profile from which we should access history/favicon data. + */ + HistoryClustersMediator(@NonNull HistoryClustersProvider historyClustersProvider, + @NonNull Context context, @NonNull Resources resources, @NonNull ModelList modelList, + @NonNull Profile profile) { + mHistoryClustersProvider = historyClustersProvider; + mModelList = modelList; + mContext = context; + mResources = resources; + mIconGenerator = FaviconUtils.createCircularIconGenerator(mContext); + mLargeIconBridge = new LargeIconBridge(profile); + } + + public void destroy() { + mLargeIconBridge.destroy(); + } +}
diff --git a/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersProvider.java b/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersProvider.java new file mode 100644 index 0000000..08a9e48e --- /dev/null +++ b/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersProvider.java
@@ -0,0 +1,29 @@ +// 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.history_clusters; + +import androidx.annotation.NonNull; + +import org.chromium.base.Promise; + +/** + * Interface for a provider of history clusters data. + */ +interface HistoryClustersProvider { + /** Request a fixed number of clusters matching the given query. */ + @NonNull + Promise<HistoryClustersResult> queryClusters(String query); + + /** + * Request more clusters matching the most recent query. {@code query} must match that most + * recent query. + */ + @NonNull + Promise<HistoryClustersResult> loadMoreClusters(String query); + + /** Remove all the visits for the given cluster from the history database. */ + @NonNull + Promise<Void> removeCluster(HistoryCluster clusterToRemove); +} \ No newline at end of file
diff --git a/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersQueryManager.java b/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersQueryManager.java new file mode 100644 index 0000000..235d167e0 --- /dev/null +++ b/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersQueryManager.java
@@ -0,0 +1,64 @@ +// 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.history_clusters; + +import androidx.annotation.NonNull; + +import org.chromium.base.Promise; +import org.chromium.base.supplier.OneShotCallback; +import org.chromium.chrome.browser.profiles.Profile; + +/** + * Implementation of {@link HistoryClustersProvider} that encapsulates a HistoryClustersBridge, + * providing a safe async interface that insulates callers from the bridge's lifecycle. + */ +class HistoryClustersQueryManager implements HistoryClustersProvider { + private final HistoryClustersBridge mBridge; + private String mQuery; + private Promise<HistoryClustersResult> mPromise; + private OneShotCallback<Profile> mBridgeCreationCallback; + + /** + * Construct a HistoryClustersQueryManager. + * @param profile The profile from which the coordinator should access history data. + */ + HistoryClustersQueryManager(Profile profile) { + mBridge = new HistoryClustersBridge(profile); + } + + /** Destroys the query manager and underlying bridge. */ + void destroy() { + mBridge.destroy(); + } + + @Override + public Promise<HistoryClustersResult> queryClusters(String query) { + if (mPromise != null && !mPromise.isFulfilled()) { + mPromise.reject(); + } + mPromise = new Promise<>(); + mQuery = query; + if (mBridge != null) { + mBridge.queryClusters(mQuery, mPromise::fulfill); + } + return mPromise; + } + + @Override + public Promise<HistoryClustersResult> loadMoreClusters(String query) { + assert mQuery != null; + assert mQuery.equals(query); + assert mPromise.isFulfilled(); + mPromise = new Promise<>(); + mBridge.loadMoreClusters(mQuery, mPromise::fulfill); + return mPromise; + } + + @NonNull + @Override + public Promise<Void> removeCluster(HistoryCluster clusterToRemove) { + return new Promise<>(); + } +}
diff --git a/chrome/browser/media/cdm_document_service_impl.cc b/chrome/browser/media/cdm_document_service_impl.cc index 16a5800a..7985b99d 100644 --- a/chrome/browser/media/cdm_document_service_impl.cc +++ b/chrome/browser/media/cdm_document_service_impl.cc
@@ -51,6 +51,7 @@ #include "base/win/security_util.h" #include "base/win/sid.h" #include "chrome/browser/media/cdm_pref_service_helper.h" +#include "chrome/browser/media/media_foundation_service_monitor.h" #include "media/cdm/win/media_foundation_cdm.h" #include "sandbox/policy/win/lpac_capability.h" #endif // BUILDFLAG(IS_WIN) @@ -363,7 +364,36 @@ void CdmDocumentServiceImpl::OnCdmEvent(media::CdmEvent event) { DVLOG(1) << __func__ << ": event=" << static_cast<int>(event); - NOTIMPLEMENTED(); + + // CdmDocumentServiceImpl is shared by all CDMs in the same RenderFrame. + // + // We choose to only report a significant playback at most once and an error + // at most once because: + // 1. A site could create many CDM instances, e.g. to prefetch licenses. This + // could cause multiple errors to be reported. + // 2. The media::Renderer could be destroyed and then recreated as part of the + // suspend/resume process (e.g. paused for long time).This could cause + // multiple significant playback to be reported. + // In both cases, our data could be skewed if we don't throttle them. + // + // If an error happens after a significant playback both will be reported. + // This is fine since MediaFoundationServiceMonitor calculates a score. + switch (event) { + case media::CdmEvent::kSignificantPlayback: + if (!has_reported_significant_playback_) { + has_reported_significant_playback_ = true; + MediaFoundationServiceMonitor::GetInstance()->OnSignificantPlayback(); + } + break; + case media::CdmEvent::kPlaybackError: + [[fallthrough]]; + case media::CdmEvent::kCdmError: + if (!has_reported_cdm_error_) { + has_reported_cdm_error_ = true; + MediaFoundationServiceMonitor::GetInstance()->OnPlaybackOrCdmError(); + } + break; + } } // This function goes over each folder located under the MediaFoundationCdm
diff --git a/chrome/browser/media/cdm_document_service_impl.h b/chrome/browser/media/cdm_document_service_impl.h index 951f9853..e18a196 100644 --- a/chrome/browser/media/cdm_document_service_impl.h +++ b/chrome/browser/media/cdm_document_service_impl.h
@@ -28,6 +28,11 @@ // Implements media::mojom::CdmDocumentService. Can only be used on the // UI thread because PlatformVerificationFlow and the pref service lives on the // UI thread. +// Ownership Note: There's one CdmDocumentServiceImpl per RenderFrame per +// service type ( MediaFoundationService or CdmService). For +// MediaFoundationService's case, this can be seen in the ownership chain of +// InterfaceFactoryImpl -> MediaFoundationCdmFactory -> MojoCdmHelper +// -> mojo::Remote<mojom::CdmDocumentService>. class CdmDocumentServiceImpl final : public content::DocumentService<media::mojom::CdmDocumentService> { public: @@ -89,6 +94,12 @@ platform_verification_flow_; #endif +#if BUILDFLAG(IS_WIN) + // See comments in OnCdmEvent() implementation. + bool has_reported_cdm_error_ = false; + bool has_reported_significant_playback_ = false; +#endif + const raw_ptr<content::RenderFrameHost> render_frame_host_; base::WeakPtrFactory<CdmDocumentServiceImpl> weak_factory_{this}; };
diff --git a/chrome/browser/media/media_foundation_service_monitor.cc b/chrome/browser/media/media_foundation_service_monitor.cc index ffb5599..7ec8905 100644 --- a/chrome/browser/media/media_foundation_service_monitor.cc +++ b/chrome/browser/media/media_foundation_service_monitor.cc
@@ -5,11 +5,23 @@ #include "chrome/browser/media/media_foundation_service_monitor.h" #include "base/logging.h" +#include "base/power_monitor/power_monitor.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/cdm_registry.h" #include "media/mojo/mojom/media_foundation_service.mojom.h" +#include "ui/display/screen.h" -const int kMaxNumberFailures = 2; +namespace { + +constexpr int kMaxNumberOfSamples = 20; +constexpr auto kGracePeriod = base::Seconds(2); +constexpr double kMaxAverageFailureScore = 0.1; // Two failures out of 20. + +constexpr int kSignificantPlayback = 0; // This must always be zero. +constexpr int kPlaybackOrCdmError = 1; +constexpr int kCrash = 1; + +} // namespace // static MediaFoundationServiceMonitor* MediaFoundationServiceMonitor::GetInstance() { @@ -17,27 +29,89 @@ return monitor; } -MediaFoundationServiceMonitor::MediaFoundationServiceMonitor() { +MediaFoundationServiceMonitor::MediaFoundationServiceMonitor() + : samples_(kMaxNumberOfSamples) { DVLOG(1) << __func__; DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + // Initialize samples with success cases so the average score won't be + // dominated by one error. + for (int i = 0; i < kMaxNumberOfSamples; ++i) + AddSample(kSignificantPlayback); + content::ServiceProcessHost::AddObserver(this); + base::PowerMonitor::AddPowerSuspendObserver(this); + display::Screen::GetScreen()->AddObserver(this); } MediaFoundationServiceMonitor::~MediaFoundationServiceMonitor() = default; void MediaFoundationServiceMonitor::OnServiceProcessCrashed( const content::ServiceProcessInfo& info) { + DVLOG(1) << __func__; DCHECK_CURRENTLY_ON(content::BrowserThread::UI); // Only interested in MediaFoundationService process. if (!info.IsService<media::mojom::MediaFoundationServiceBroker>()) return; - DLOG(ERROR) << __func__ << ": MediaFoundationService process crashed!"; + // Not checking `last_power_or_display_change_time_`; crashes are always bad. + DVLOG(1) << __func__ << ": MediaFoundationService process crashed!"; + AddSample(kCrash); +} - num_crashes_++; +void MediaFoundationServiceMonitor::OnSuspend() { + OnPowerOrDisplayChange(); +} - if (num_crashes_ >= kMaxNumberFailures) { +void MediaFoundationServiceMonitor::OnResume() { + OnPowerOrDisplayChange(); +} + +void MediaFoundationServiceMonitor::OnDisplayAdded( + const display::Display& /*new_display*/) { + OnPowerOrDisplayChange(); +} +void MediaFoundationServiceMonitor::OnDidRemoveDisplays() { + OnPowerOrDisplayChange(); +} +void MediaFoundationServiceMonitor::OnDisplayMetricsChanged( + const display::Display& /*display*/, + uint32_t /*changed_metrics*/) { + OnPowerOrDisplayChange(); +} + +void MediaFoundationServiceMonitor::OnSignificantPlayback() { + DVLOG(1) << __func__; + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + AddSample(kSignificantPlayback); +} + +void MediaFoundationServiceMonitor::OnPlaybackOrCdmError() { + DVLOG(1) << __func__; + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + if (base::Time::Now() - last_power_or_display_change_time_ < kGracePeriod) { + DVLOG(1) << "Playback or CDM error ignored since it happened right after " + "a power or display change."; + return; + } + + AddSample(kPlaybackOrCdmError); +} + +void MediaFoundationServiceMonitor::OnPowerOrDisplayChange() { + DVLOG(1) << __func__; + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + last_power_or_display_change_time_ = base::Time::Now(); +} + +void MediaFoundationServiceMonitor::AddSample(int failure_score) { + samples_.AddSample(failure_score); + + if (samples_.GetUnroundedAverage() >= kMaxAverageFailureScore) { content::CdmRegistry::GetInstance()->DisableHardwareSecureCdms(); } }
diff --git a/chrome/browser/media/media_foundation_service_monitor.h b/chrome/browser/media/media_foundation_service_monitor.h index 0a69232..b7ef3d94 100644 --- a/chrome/browser/media/media_foundation_service_monitor.h +++ b/chrome/browser/media/media_foundation_service_monitor.h
@@ -5,10 +5,16 @@ #ifndef CHROME_BROWSER_MEDIA_MEDIA_FOUNDATION_SERVICE_MONITOR_H_ #define CHROME_BROWSER_MEDIA_MEDIA_FOUNDATION_SERVICE_MONITOR_H_ +#include "base/power_monitor/moving_average.h" +#include "base/power_monitor/power_observer.h" +#include "base/time/time.h" #include "content/public/browser/service_process_host.h" +#include "ui/display/display_observer.h" -class MediaFoundationServiceMonitor - : public content::ServiceProcessHost::Observer { +class MediaFoundationServiceMonitor final + : public content::ServiceProcessHost::Observer, + public base::PowerSuspendObserver, + public display::DisplayObserver { public: // Returns the MediaFoundationServiceMonitor singleton. static MediaFoundationServiceMonitor* GetInstance(); @@ -18,15 +24,43 @@ const MediaFoundationServiceMonitor&) = delete; // ServiceProcessHost::Observer implementation. - void OnServiceProcessCrashed( - const content::ServiceProcessInfo& info) override; + void OnServiceProcessCrashed(const content::ServiceProcessInfo& info) final; + + // base::PowerSuspendObserver implementation. + void OnSuspend() final; + void OnResume() final; + + // display::DisplayObserver implementation. + void OnDisplayAdded(const display::Display& new_display) final; + void OnDidRemoveDisplays() final; + void OnDisplayMetricsChanged(const display::Display& display, + uint32_t changed_metrics) final; + + // Called when a significant playback or error happened when using + // MediaFoundationCdm. + void OnSignificantPlayback(); + void OnPlaybackOrCdmError(); private: // Make constructor/destructor private since this is a singleton. MediaFoundationServiceMonitor(); - ~MediaFoundationServiceMonitor() override; + ~MediaFoundationServiceMonitor() final; - int num_crashes_ = 0; + void OnPowerOrDisplayChange(); + + // Adds a sample with failure score. Zero means success. The higher the value + // the more severe the error is. See the .cc file for details. + void AddSample(int failure_score); + + // Last time when a power or display event happened. This is used to ignore + // playback or CDM errors caused by those events. For example, playback + // failure caused by user plugging in a non-HDCP monitor, but the content + // requires HDCP enforcement. + base::Time last_power_or_display_change_time_; + + // Keep track the last fixed length reported samples (scores). The average + // score is used to decide whether to disable hardware secure decryption. + base::MovingAverage samples_; }; #endif // CHROME_BROWSER_MEDIA_MEDIA_FOUNDATION_SERVICE_MONITOR_H_
diff --git a/chrome/browser/media/router/media_router_feature.cc b/chrome/browser/media/router/media_router_feature.cc index 8400367..2695c7b 100644 --- a/chrome/browser/media/router/media_router_feature.cc +++ b/chrome/browser/media/router/media_router_feature.cc
@@ -101,7 +101,7 @@ } void RegisterProfilePrefs(PrefRegistrySimple* registry) { - // TODO(imcheng): Migrate existing Media Router prefs to here. + // TODO(crbug.com/1308056): Migrate existing Media Router prefs to here. registry->RegisterStringPref(prefs::kMediaRouterReceiverIdHashToken, "", PrefRegistry::PUBLIC); }
diff --git a/chrome/browser/navigation_predictor/anchor_element_preloader.cc b/chrome/browser/navigation_predictor/anchor_element_preloader.cc new file mode 100644 index 0000000..550ca3f --- /dev/null +++ b/chrome/browser/navigation_predictor/anchor_element_preloader.cc
@@ -0,0 +1,39 @@ +// 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 "chrome/browser/navigation_predictor/anchor_element_preloader.h" +#include "chrome/browser/predictors/loading_predictor.h" +#include "chrome/browser/predictors/loading_predictor_factory.h" +#include "content/public/browser/browser_context.h" + +AnchorElementPreloader::AnchorElementPreloader( + content::RenderFrameHost* render_frame_host, + mojo::PendingReceiver<blink::mojom::AnchorElementInteractionHost> receiver) + : content::DocumentService<blink::mojom::AnchorElementInteractionHost>( + render_frame_host, + std::move(receiver)) {} + +void AnchorElementPreloader::Create( + content::RenderFrameHost* render_frame_host, + mojo::PendingReceiver<blink::mojom::AnchorElementInteractionHost> + receiver) { + // The object is bound to the lifetime of the |render_frame_host| and the mojo + // connection. See DocumentService for details. + new AnchorElementPreloader(render_frame_host, std::move(receiver)); +} + +void AnchorElementPreloader::OnPointerDown(const GURL& target) { + auto* loading_predictor = predictors::LoadingPredictorFactory::GetForProfile( + Profile::FromBrowserContext(render_frame_host()->GetBrowserContext())); + + if (!loading_predictor) { + return; + } + + net::SchemefulSite schemeful_site(target); + net::NetworkIsolationKey network_isolation_key(schemeful_site, + schemeful_site); + loading_predictor->PreconnectURLIfAllowed(target, /*allow_credentials=*/true, + network_isolation_key); +}
diff --git a/chrome/browser/navigation_predictor/anchor_element_preloader.h b/chrome/browser/navigation_predictor/anchor_element_preloader.h new file mode 100644 index 0000000..a6962c59 --- /dev/null +++ b/chrome/browser/navigation_predictor/anchor_element_preloader.h
@@ -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. + +#ifndef CHROME_BROWSER_NAVIGATION_PREDICTOR_ANCHOR_ELEMENT_PRELOADER_H_ +#define CHROME_BROWSER_NAVIGATION_PREDICTOR_ANCHOR_ELEMENT_PRELOADER_H_ + +#include "content/public/browser/document_service.h" +#include "third_party/blink/public/mojom/loader/anchor_element_interaction_host.mojom.h" + +class AnchorElementPreloader + : content::DocumentService<blink::mojom::AnchorElementInteractionHost> { + public: + static void Create( + content::RenderFrameHost* render_frame_host, + mojo::PendingReceiver<blink::mojom::AnchorElementInteractionHost> + receiver); + + private: + AnchorElementPreloader( + content::RenderFrameHost* render_frame_host, + mojo::PendingReceiver<blink::mojom::AnchorElementInteractionHost> + receiver); + // Preconnects to the given URL `target`. + void OnPointerDown(const GURL& target) override; +}; + +#endif // CHROME_BROWSER_NAVIGATION_PREDICTOR_ANCHOR_ELEMENT_PRELOADER_H_
diff --git a/chrome/browser/offline_pages/android/evaluation/offline_page_evaluation_bridge.cc b/chrome/browser/offline_pages/android/evaluation/offline_page_evaluation_bridge.cc index 1513e85b..8da2587 100644 --- a/chrome/browser/offline_pages/android/evaluation/offline_page_evaluation_bridge.cc +++ b/chrome/browser/offline_pages/android/evaluation/offline_page_evaluation_bridge.cc
@@ -14,8 +14,8 @@ #include "base/android/jni_array.h" #include "base/android/jni_string.h" #include "base/bind.h" -#include "base/task/post_task.h" #include "base/task/sequenced_task_runner.h" +#include "base/task/thread_pool.h" #include "chrome/android/chrome_jni_headers/OfflinePageEvaluationBridge_jni.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/offline_pages/android/background_scheduler_bridge.h" @@ -140,7 +140,7 @@ std::unique_ptr<OfflinerPolicy> policy, std::unique_ptr<Offliner> offliner) { scoped_refptr<base::SequencedTaskRunner> background_task_runner = - base::CreateSequencedTaskRunner({base::MayBlock()}); + base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()}); Profile* profile = Profile::FromBrowserContext(context); base::FilePath queue_store_path = profile->GetPath().Append(kTestRequestQueueDirname);
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc index 4bf67777..f5eb1ef8d 100644 --- a/chrome/browser/prefs/browser_prefs.cc +++ b/chrome/browser/prefs/browser_prefs.cc
@@ -261,6 +261,7 @@ #include "chrome/browser/ui/webui/whats_new/whats_new_ui.h" #include "chrome/browser/upgrade_detector/upgrade_detector.h" #include "components/live_caption/live_caption_controller.h" +#include "components/media_router/common/pref_names.h" #include "components/ntp_tiles/custom_links_manager_impl.h" #endif // BUILDFLAG(IS_ANDROID) @@ -1502,6 +1503,23 @@ registry->RegisterBooleanPref(prefs::kPrivacyGuideViewed, false); +// TODO(crbug.com/1308056): Migrate media_router prefs to +// media_router::RegisterProfilePrefs(). +#if !BUILDFLAG(IS_ANDROID) + registry->RegisterBooleanPref( + media_router::prefs::kMediaRouterMediaRemotingEnabled, true); + registry->RegisterListPref( + media_router::prefs::kMediaRouterTabMirroringSources); +#endif + +// TODO(crbug.com/1308053): Register it on ChromeOS after Cast+GMC ships on +// ChromeOS. +#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS) + registry->RegisterBooleanPref( + media_router::prefs::kMediaRouterShowCastSessionsStartedByOtherDevices, + true); +#endif + RegisterProfilePrefsForMigration(registry); }
diff --git a/chrome/browser/printing/print_backend_service_manager.h b/chrome/browser/printing/print_backend_service_manager.h index d5a1ca8..5897e1d8 100644 --- a/chrome/browser/printing/print_backend_service_manager.h +++ b/chrome/browser/printing/print_backend_service_manager.h
@@ -150,6 +150,7 @@ private: friend base::NoDestructor<PrintBackendServiceManager>; + friend class PrintBackendPrintBrowserTestBase; FRIEND_TEST_ALL_PREFIXES(PrintBackendServiceManagerTest, IsIdleTimeoutUpdateNeededForRegisteredClient); FRIEND_TEST_ALL_PREFIXES(PrintBackendServiceManagerTest,
diff --git a/chrome/browser/printing/print_browsertest.cc b/chrome/browser/printing/print_browsertest.cc index 69ef85cf..71b3f0e 100644 --- a/chrome/browser/printing/print_browsertest.cc +++ b/chrome/browser/printing/print_browsertest.cc
@@ -64,6 +64,7 @@ #include "printing/backend/test_print_backend.h" #include "printing/buildflags/buildflags.h" #include "printing/mojom/print.mojom.h" +#include "printing/page_setup.h" #include "printing/print_settings.h" #include "printing/printing_context.h" #include "printing/printing_context_factory_for_test.h" @@ -75,6 +76,7 @@ #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" #include "third_party/blink/public/common/features.h" #include "third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h" +#include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/size.h" #if BUILDFLAG(ENABLE_OOP_PRINTING) @@ -91,6 +93,12 @@ #if BUILDFLAG(ENABLE_OOP_PRINTING) using ErrorCheckCallback = base::RepeatingCallback<void(mojom::ResultCode result)>; +using OnDidUseDefaultSettingsCallback = + base::RepeatingCallback<void(mojom::ResultCode result)>; +#if BUILDFLAG(IS_WIN) +using OnDidAskUserForSettingsCallback = + base::RepeatingCallback<void(mojom::ResultCode result)>; +#endif using OnDidStartPrintingCallback = base::RepeatingCallback<void(mojom::ResultCode result, PrintJob* print_job)>; @@ -117,6 +125,10 @@ // processing was done before possibly quitting the test run loop. struct TestPrintCallbacks { ErrorCheckCallback error_check_callback; + OnDidUseDefaultSettingsCallback did_use_default_settings_callback; +#if BUILDFLAG(IS_WIN) + OnDidAskUserForSettingsCallback did_ask_user_for_settings_callback; +#endif OnDidStartPrintingCallback did_start_printing_callback; #if BUILDFLAG(IS_WIN) OnDidRenderPrintedPageCallback did_render_printed_page_callback; @@ -522,6 +534,10 @@ PrintSettings* snooped_settings() { return snooped_settings_.get(); } + const absl::optional<bool>& print_now_result() const { + return print_now_result_; + } + static TestPrintViewManager* CreateForWebContents( content::WebContents* web_contents) { auto manager = std::make_unique<TestPrintViewManager>(web_contents); @@ -531,6 +547,13 @@ return manager_ptr; } + // `PrintViewManagerBase` overrides. + bool PrintNow(content::RenderFrameHost* rfh) override { + print_now_result_ = PrintViewManager::PrintNow(rfh); + return *print_now_result_; + } + void ShowInvalidPrinterSettingsError() override {} + protected: base::RunLoop* run_loop_ = nullptr; @@ -553,6 +576,7 @@ } std::unique_ptr<PrintSettings> snooped_settings_; + absl::optional<bool> print_now_result_; }; class TestPrintViewManagerForDLP : public TestPrintViewManager { @@ -2107,6 +2131,34 @@ ~TestPrintJobWorker() override = default; private: + void OnDidUseDefaultSettings( + SettingsCallback callback, + mojom::PrintSettingsResultPtr print_settings) override { + DVLOG(1) << "Observed: use default settings"; + mojom::ResultCode result = print_settings->is_result_code() + ? print_settings->get_result_code() + : mojom::ResultCode::kSuccess; + callbacks_->error_check_callback.Run(result); + PrintJobWorkerOop::OnDidUseDefaultSettings(std::move(callback), + std::move(print_settings)); + callbacks_->did_use_default_settings_callback.Run(result); + } + +#if BUILDFLAG(IS_WIN) + void OnDidAskUserForSettings( + SettingsCallback callback, + mojom::PrintSettingsResultPtr print_settings) override { + DVLOG(1) << "Observed: ask user for settings"; + mojom::ResultCode result = print_settings->is_result_code() + ? print_settings->get_result_code() + : mojom::ResultCode::kSuccess; + callbacks_->error_check_callback.Run(result); + PrintJobWorkerOop::OnDidAskUserForSettings(std::move(callback), + std::move(print_settings)); + callbacks_->did_ask_user_for_settings_callback.Run(result); + } +#endif // BUILDFLAG(IS_WIN) + void OnDidStartPrinting(mojom::ResultCode result) override { DVLOG(1) << "Observed: start printing of document"; callbacks_->error_check_callback.Run(result); @@ -2167,6 +2219,16 @@ test_print_callbacks_.error_check_callback = base::BindRepeating(&PrintBackendPrintBrowserTestBase::ErrorCheck, base::Unretained(this)); + test_print_callbacks_.did_use_default_settings_callback = + base::BindRepeating( + &PrintBackendPrintBrowserTestBase::OnDidUseDefaultSettings, + base::Unretained(this)); +#if BUILDFLAG(IS_WIN) + test_print_callbacks_.did_ask_user_for_settings_callback = + base::BindRepeating( + &PrintBackendPrintBrowserTestBase::OnDidAskUserForSettings, + base::Unretained(this)); +#endif test_print_callbacks_.did_start_printing_callback = base::BindRepeating( &PrintBackendPrintBrowserTestBase::OnDidStartPrinting, base::Unretained(this)); @@ -2215,6 +2277,12 @@ PrintBackend::SetPrintBackendForTesting(/*print_backend=*/nullptr); #if BUILDFLAG(ENABLE_OOP_PRINTING) PrinterQuery::SetCreatePrintJobWorkerCallbackForTest(/*callback=*/nullptr); + if (UseService()) { + // Check that there is never a straggler client registration. + EXPECT_EQ( + PrintBackendServiceManager::GetInstance().GetClientsRegisteredCount(), + 0u); + } PrintBackendServiceManager::ResetForTesting(); #endif } @@ -2276,6 +2344,16 @@ void PrimeAsRepeatingErrorGenerator() { reset_errors_after_check_ = false; } + void PrimeForFailInUseDefaultSettings() { + test_printing_context_factory_.SetFailErrorOnUseDefaultSettings(); + } + +#if BUILDFLAG(IS_WIN) + void PrimeForCancelInAskUserForSettings() { + test_printing_context_factory_.SetCancelErrorOnAskUserForSettings(); + } +#endif + void PrimeForAccessDeniedErrorsInNewDocument() { test_printing_context_factory_.SetAccessDeniedErrorOnNewDocument( /*cause_errors=*/true); @@ -2293,6 +2371,16 @@ /*cause_errors=*/true); } + mojom::ResultCode use_default_settings_result() const { + return use_default_settings_result_; + } + +#if BUILDFLAG(IS_WIN) + mojom::ResultCode ask_user_for_settings_result() const { + return ask_user_for_settings_result_; + } +#endif + mojom::ResultCode start_printing_result() const { return start_printing_result_; } @@ -2322,9 +2410,19 @@ auto context = std::make_unique<TestPrintingContext>(delegate, skip_system_calls); + // Setup a sample page setup, which is needed to pass checks in + // `PrintRenderFrameHelper` that the print params are valid. + constexpr gfx::Size kPhysicalSize = gfx::Size(200, 200); + constexpr gfx::Rect kPrintableArea = gfx::Rect(0, 0, 200, 200); + const PageMargins kRequestedMargins(0, 0, 5, 5, 5, 5); + const PageSetup kPageSetup(kPhysicalSize, kPrintableArea, + kRequestedMargins, /*forced_margins=*/false, + /*text_height=*/0); + auto settings = std::make_unique<PrintSettings>(); settings->set_copies(kTestPrintSettingsCopies); settings->set_dpi(kTestPrintingDpi); + settings->set_page_setup_device_units(kPageSetup); settings->set_device_name( base::ASCIIToUTF16(base::StringPiece(printer_name_))); context->SetDeviceSettings(printer_name_, std::move(settings)); @@ -2338,6 +2436,13 @@ if (access_denied_errors_for_document_done_) context->SetDocumentDoneBlockedByPermissions(); + if (fail_on_use_default_settings_) + context->SetUseDefaultSettingsFails(); +#if BUILDFLAG(IS_WIN) + if (cancel_on_ask_user_for_settings_) + context->SetAskUserForSettingsCanceled(); +#endif + return std::move(context); } @@ -2359,6 +2464,16 @@ access_denied_errors_for_document_done_ = cause_errors; } + void SetFailErrorOnUseDefaultSettings() { + fail_on_use_default_settings_ = true; + } + +#if BUILDFLAG(IS_WIN) + void SetCancelErrorOnAskUserForSettings() { + cancel_on_ask_user_for_settings_ = true; + } +#endif + private: std::string printer_name_; bool access_denied_errors_for_new_document_ = false; @@ -2366,6 +2481,10 @@ bool access_denied_errors_for_render_page_ = false; #endif bool access_denied_errors_for_document_done_ = false; + bool fail_on_use_default_settings_ = false; +#if BUILDFLAG(IS_WIN) + bool cancel_on_ask_user_for_settings_ = false; +#endif }; #if BUILDFLAG(ENABLE_OOP_PRINTING) @@ -2382,6 +2501,18 @@ ResetForNoAccessDeniedErrors(); } + void OnDidUseDefaultSettings(mojom::ResultCode result) { + use_default_settings_result_ = result; + CheckForQuit(); + } + +#if BUILDFLAG(IS_WIN) + void OnDidAskUserForSettings(mojom::ResultCode result) { + ask_user_for_settings_result_ = result; + CheckForQuit(); + } +#endif + void OnDidStartPrinting(mojom::ResultCode result, PrintJob* print_job) { start_printing_result_ = result; print_job_ = print_job; @@ -2439,6 +2570,10 @@ #endif // BUILDFLAG(ENABLE_OOP_PRINTING) PrintJob* print_job_ = nullptr; bool reset_errors_after_check_ = true; + mojom::ResultCode use_default_settings_result_ = mojom::ResultCode::kFailed; +#if BUILDFLAG(IS_WIN) + mojom::ResultCode ask_user_for_settings_result_ = mojom::ResultCode::kFailed; +#endif mojom::ResultCode start_printing_result_ = mojom::ResultCode::kFailed; #if BUILDFLAG(IS_WIN) mojom::ResultCode render_printed_page_result_ = mojom::ResultCode::kFailed; @@ -2691,8 +2826,127 @@ EXPECT_TRUE(error_dialog_shown()); EXPECT_TRUE(stop_invoked()); } + +IN_PROC_BROWSER_TEST_F(PrintBackendPrintBrowserTestService, StartBasicPrint) { + AddPrinter("printer1"); + SetPrinterNameForSubsequentContexts("printer1"); + + ASSERT_TRUE(embedded_test_server()->Started()); + GURL url(embedded_test_server()->GetURL("/printing/test3.html")); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); + + content::WebContents* web_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + ASSERT_TRUE(web_contents); + SetUpPrintViewManager(web_contents); + StartBasicPrint(web_contents); + + // The test will get the default settings followed by asking the user for + // settings. After that a print job will be started, with a page getting + // rendered, and finally the document done notification. Wait for a call to + // `Stop()` to ensure print job wrap-up finished cleanly before completing + // the test. This results in a total of 6 calls. + SetNumExpectedMessages(/*num=*/6); + + WaitUntilCallbackReceived(); + + EXPECT_EQ(use_default_settings_result(), mojom::ResultCode::kSuccess); + EXPECT_EQ(ask_user_for_settings_result(), mojom::ResultCode::kSuccess); + EXPECT_EQ(start_printing_result(), mojom::ResultCode::kSuccess); + EXPECT_EQ(render_printed_page_result(), mojom::ResultCode::kSuccess); + EXPECT_EQ(render_printed_page_count(), 1); + EXPECT_EQ(document_done_result(), mojom::ResultCode::kSuccess); + EXPECT_TRUE(stop_invoked()); +} + +IN_PROC_BROWSER_TEST_F(PrintBackendPrintBrowserTestService, + StartBasicPrintCancel) { + PrimeForCancelInAskUserForSettings(); + + ASSERT_TRUE(embedded_test_server()->Started()); + GURL url(embedded_test_server()->GetURL("/printing/test3.html")); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); + + content::WebContents* web_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + ASSERT_TRUE(web_contents); + SetUpPrintViewManager(web_contents); + StartBasicPrint(web_contents); + + // The test will get the default settings followed by asking the user for + // settings. Since this pretends the user canceled from that, no further + // printing calls will be made. Wait for a call to `Stop()` to ensure print + // job wrap-up finished cleanly before completing + // the test. This results in a total of 3 calls. + SetNumExpectedMessages(/*num=*/3); + + WaitUntilCallbackReceived(); + + EXPECT_EQ(use_default_settings_result(), mojom::ResultCode::kSuccess); + EXPECT_EQ(ask_user_for_settings_result(), mojom::ResultCode::kCanceled); + EXPECT_TRUE(stop_invoked()); +} + +IN_PROC_BROWSER_TEST_F(PrintBackendPrintBrowserTestService, + StartBasicPrintConcurrent) { + ASSERT_TRUE(embedded_test_server()->Started()); + GURL url(embedded_test_server()->GetURL("/printing/test3.html")); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); + + content::WebContents* web_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + ASSERT_TRUE(web_contents); + TestPrintViewManager* print_view_manager = + TestPrintViewManager::CreateForWebContents(web_contents); + + // Pretend that a window has started a system print. + absl::optional<uint32_t> client_id = + PrintBackendServiceManager::GetInstance().RegisterQueryWithUiClient(); + ASSERT_TRUE(client_id.has_value()); + + // Now initiate a system print that would exist concurrently with that. + StartBasicPrint(web_contents); + + // On Windows, concurrent system print is not allowed. + // TODO(crbug.com/809738): Once Linux Wayland can be made to have a system + // dialog be modal against an application window in the browser process, + // update this to illustrate that concurrent system prints area allowed for + // Linux. + const absl::optional<bool>& result = print_view_manager->print_now_result(); + ASSERT_TRUE(result.has_value()); + EXPECT_FALSE(*result); + + // Cleanup before test shutdown. + PrintBackendServiceManager::GetInstance().UnregisterClient(*client_id); +} #endif // BUILDFLAG(IS_WIN) +IN_PROC_BROWSER_TEST_F(PrintBackendPrintBrowserTestService, + StartBasicPrintUseDefaultFails) { + PrimeForFailInUseDefaultSettings(); + + ASSERT_TRUE(embedded_test_server()->Started()); + GURL url(embedded_test_server()->GetURL("/printing/test3.html")); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); + + content::WebContents* web_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + ASSERT_TRUE(web_contents); + SetUpPrintViewManager(web_contents); + StartBasicPrint(web_contents); + + // The test will fail getting the default settings, aborting the rest of + // printing. Wait for a call to `Stop()` to ensure print job wrap-up + // finished cleanly before completing the test. This results in a total of + // 2 calls. + SetNumExpectedMessages(/*num=*/2); + + WaitUntilCallbackReceived(); + + EXPECT_EQ(use_default_settings_result(), mojom::ResultCode::kFailed); + EXPECT_TRUE(stop_invoked()); +} + #endif // BUILDFLAG(ENABLE_OOP_PRINTING) #endif // !BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/browser/printing/print_job_worker.cc b/chrome/browser/printing/print_job_worker.cc index 2730599..b11b8f3 100644 --- a/chrome/browser/printing/print_job_worker.cc +++ b/chrome/browser/printing/print_job_worker.cc
@@ -166,15 +166,10 @@ // When we delegate to a destination, we don't ask the user for settings. // TODO(mad): Ask the destination for settings. if (ask_user_for_settings) { - content::GetUIThreadTaskRunner({})->PostTask( - FROM_HERE, - base::BindOnce(&PrintJobWorker::GetSettingsWithUI, - base::Unretained(this), document_page_count, - has_selection, is_scripted, std::move(callback))); + InvokeGetSettingsWithUI(document_page_count, has_selection, is_scripted, + std::move(callback)); } else { - content::GetUIThreadTaskRunner({})->PostTask( - FROM_HERE, base::BindOnce(&PrintJobWorker::UseDefaultSettings, - base::Unretained(this), std::move(callback))); + InvokeUseDefaultSettings(std::move(callback)); } } @@ -262,6 +257,23 @@ std::move(callback).Run(printing_context_->TakeAndResetSettings(), result); } +void PrintJobWorker::InvokeUseDefaultSettings(SettingsCallback callback) { + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&PrintJobWorker::UseDefaultSettings, + base::Unretained(this), std::move(callback))); +} + +void PrintJobWorker::InvokeGetSettingsWithUI(uint32_t document_page_count, + bool has_selection, + bool is_scripted, + SettingsCallback callback) { + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, + base::BindOnce(&PrintJobWorker::GetSettingsWithUI, base::Unretained(this), + document_page_count, has_selection, is_scripted, + std::move(callback))); +} + void PrintJobWorker::GetSettingsWithUI(uint32_t document_page_count, bool has_selection, bool is_scripted,
diff --git a/chrome/browser/printing/print_job_worker.h b/chrome/browser/printing/print_job_worker.h index 465afca..975b7b6 100644 --- a/chrome/browser/printing/print_job_worker.h +++ b/chrome/browser/printing/print_job_worker.h
@@ -134,6 +134,14 @@ // Reports settings back to `callback`. void GetSettingsDone(SettingsCallback callback, mojom::ResultCode result); + // Helper functions to invoke the desired way of getting system print + // settings. + virtual void InvokeUseDefaultSettings(SettingsCallback callback); + virtual void InvokeGetSettingsWithUI(uint32_t document_page_count, + bool has_selection, + bool is_scripted, + SettingsCallback callback); + // Called on the UI thread to update the print settings. virtual void UpdatePrintSettings(base::Value new_settings, SettingsCallback callback);
diff --git a/chrome/browser/printing/print_job_worker_oop.cc b/chrome/browser/printing/print_job_worker_oop.cc index 52a13c0c..56232bf 100644 --- a/chrome/browser/printing/print_job_worker_oop.cc +++ b/chrome/browser/printing/print_job_worker_oop.cc
@@ -22,6 +22,7 @@ #include "third_party/abseil-cpp/absl/types/optional.h" #if BUILDFLAG(IS_WIN) +#include "content/public/browser/web_contents.h" #include "printing/printed_page_win.h" #endif @@ -75,6 +76,52 @@ document_name)); } +void PrintJobWorkerOop::OnDidUseDefaultSettings( + SettingsCallback callback, + mojom::PrintSettingsResultPtr print_settings) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + mojom::ResultCode result; + if (print_settings->is_result_code()) { + result = print_settings->get_result_code(); + DCHECK_NE(result, mojom::ResultCode::kSuccess); + PRINTER_LOG(ERROR) << "Error trying to use default settings: " << result; + + // TODO(crbug.com/809738) Fill in support for handling of access-denied + // result code. Blocked on crbug.com/1243873 for Windows. + } else { + VLOG(1) << "Use default settings from service complete"; + result = mojom::ResultCode::kSuccess; + printing_context()->ApplyPrintSettings(print_settings->get_settings()); + } + + GetSettingsDone(std::move(callback), result); +} + +#if BUILDFLAG(IS_WIN) +void PrintJobWorkerOop::OnDidAskUserForSettings( + SettingsCallback callback, + mojom::PrintSettingsResultPtr print_settings) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + mojom::ResultCode result; + if (print_settings->is_result_code()) { + result = print_settings->get_result_code(); + DCHECK_NE(result, mojom::ResultCode::kSuccess); + if (result != mojom::ResultCode::kCanceled) { + PRINTER_LOG(ERROR) << "Error getting settings from user: " << result; + } + + // TODO(crbug.com/809738) Fill in support for handling of access-denied + // result code. Blocked on crbug.com/1243873 for Windows. + } else { + VLOG(1) << "Ask user for settings from service complete"; + result = mojom::ResultCode::kSuccess; + printing_context()->ApplyPrintSettings(print_settings->get_settings()); + } + + GetSettingsDone(std::move(callback), result); +} +#endif // BUILDFLAG(IS_WIN) + void PrintJobWorkerOop::OnDidStartPrinting(mojom::ResultCode result) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); if (result != mojom::ResultCode::kSuccess) { @@ -193,6 +240,38 @@ // PrintBackend service. } +void PrintJobWorkerOop::InvokeUseDefaultSettings(SettingsCallback callback) { + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, + base::BindOnce(&PrintJobWorkerOop::SendUseDefaultSettings, + ui_weak_factory_.GetWeakPtr(), std::move(callback))); +} + +void PrintJobWorkerOop::InvokeGetSettingsWithUI(uint32_t document_page_count, + bool has_selection, + bool is_scripted, + SettingsCallback callback) { +#if BUILDFLAG(IS_WIN) + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, + base::BindOnce(&PrintJobWorkerOop::SendAskUserForSettings, + ui_weak_factory_.GetWeakPtr(), document_page_count, + has_selection, is_scripted, std::move(callback))); +#else + // Invoke the browser version of getting settings with the system UI: + // - macOS: It is impossible to invoke a system dialog UI from a service + // utility and have that dialog be application modal for a window that + // was launched by the browser process. + // - Linux: TODO(crbug.com/809738) Determine if Linux Wayland can be made + // to have a system dialog be modal against an application window in the + // browser process. + // - Other platforms don't have a system print UI or do not use OOP + // printing, so this does not matter. + PrintJobWorker::InvokeGetSettingsWithUI(document_page_count, has_selection, + is_scripted, std::move(callback)); +#endif +} + void PrintJobWorkerOop::UpdatePrintSettings(base::Value new_settings, SettingsCallback callback) { DCHECK_CURRENTLY_ON(BrowserThread::UI); @@ -292,8 +371,8 @@ if (print_settings->is_result_code()) { result = print_settings->get_result_code(); DCHECK_NE(result, mojom::ResultCode::kSuccess); - PRINTER_LOG(ERROR) << "Failure to update print settings for " << device_name - << " - error " << result; + PRINTER_LOG(ERROR) << "Error updating print settings for `" << device_name + << "`: " << result; // TODO(crbug.com/809738) Fill in support for handling of access-denied // result code. @@ -306,6 +385,56 @@ GetSettingsDone(std::move(callback), result); } +void PrintJobWorkerOop::SendUseDefaultSettings(SettingsCallback callback) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + DCHECK(features::kEnableOopPrintDriversJobPrint.Get()); + + PrintBackendServiceManager& service_mgr = + PrintBackendServiceManager::GetInstance(); + + service_mgr.UseDefaultSettings( + /*printer_name=*/std::string(), + base::BindOnce(&PrintJobWorkerOop::OnDidUseDefaultSettings, + ui_weak_factory_.GetWeakPtr(), std::move(callback))); +} + +#if BUILDFLAG(IS_WIN) +void PrintJobWorkerOop::SendAskUserForSettings(uint32_t document_page_count, + bool has_selection, + bool is_scripted, + SettingsCallback callback) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + DCHECK(features::kEnableOopPrintDriversJobPrint.Get()); + + if (document_page_count > kMaxPageCount) { + GetSettingsDone(std::move(callback), mojom::ResultCode::kFailed); + return; + } + + // Save the print target type from the settings, since this will be needed + // later when printing is started. + print_target_type_ = mojom::PrintTargetType::kDirectToDevice; + + content::WebContents* web_contents = GetWebContents(); + + // Running a dialog causes an exit to webpage-initiated fullscreen. + // http://crbug.com/728276 + if (web_contents && web_contents->IsFullscreen()) + web_contents->ExitFullscreen(true); + + gfx::NativeView parent_view = + web_contents ? web_contents->GetTopLevelNativeWindow() : nullptr; + + PrintBackendServiceManager& service_mgr = + PrintBackendServiceManager::GetInstance(); + service_mgr.AskUserForSettings( + /*printer_name=*/std::string(), parent_view, document_page_count, + has_selection, is_scripted, + base::BindOnce(&PrintJobWorkerOop::OnDidAskUserForSettings, + ui_weak_factory_.GetWeakPtr(), std::move(callback))); +} +#endif // BUILDFLAG(IS_WIN) + void PrintJobWorkerOop::SendStartPrinting(const std::string& device_name, const std::u16string& document_name) { DCHECK_CURRENTLY_ON(BrowserThread::UI);
diff --git a/chrome/browser/printing/print_job_worker_oop.h b/chrome/browser/printing/print_job_worker_oop.h index 20d822f..12850b2 100644 --- a/chrome/browser/printing/print_job_worker_oop.h +++ b/chrome/browser/printing/print_job_worker_oop.h
@@ -42,6 +42,14 @@ protected: // Local callback wrappers for Print Backend Service mojom call. Virtual to // support testing. + virtual void OnDidUseDefaultSettings( + SettingsCallback callback, + mojom::PrintSettingsResultPtr print_settings); +#if BUILDFLAG(IS_WIN) + virtual void OnDidAskUserForSettings( + SettingsCallback callback, + mojom::PrintSettingsResultPtr print_settings); +#endif virtual void OnDidStartPrinting(mojom::ResultCode result); #if BUILDFLAG(IS_WIN) virtual void OnDidRenderPrintedPage(uint32_t page_index, @@ -54,6 +62,11 @@ void SpoolPage(PrintedPage* page) override; #endif void OnDocumentDone() override; + void InvokeUseDefaultSettings(SettingsCallback callback) override; + void InvokeGetSettingsWithUI(uint32_t document_page_count, + bool has_selection, + bool is_scripted, + SettingsCallback callback) override; void UpdatePrintSettings(base::Value new_settings, SettingsCallback callback) override; void OnFailure() override; @@ -78,6 +91,13 @@ mojom::PrintSettingsResultPtr print_settings); // Mojo support to send messages from UI thread. + void SendUseDefaultSettings(SettingsCallback callback); +#if BUILDFLAG(IS_WIN) + void SendAskUserForSettings(uint32_t document_page_count, + bool has_selection, + bool is_scripted, + SettingsCallback callback); +#endif void SendStartPrinting(const std::string& device_name, const std::u16string& document_name); #if BUILDFLAG(IS_WIN)
diff --git a/chrome/browser/printing/print_view_manager.cc b/chrome/browser/printing/print_view_manager.cc index 8b292d4..bba6dbc 100644 --- a/chrome/browser/printing/print_view_manager.cc +++ b/chrome/browser/printing/print_view_manager.cc
@@ -106,8 +106,12 @@ return false; } - // TODO(crbug.com/809738) Register with `PrintBackendServiceManager` when - // system print is enabled out-of-process. +#if BUILDFLAG(ENABLE_OOP_PRINTING) + // Register this worker so that the service persists as long as the user + // keeps the system print dialog UI displayed. + if (!RegisterSystemPrintClient()) + return false; +#endif SetPrintingRFH(print_preview_rfh_); GetPrintRenderFrame(print_preview_rfh_)->PrintForSystemDialog();
diff --git a/chrome/browser/printing/print_view_manager_base.cc b/chrome/browser/printing/print_view_manager_base.cc index 8765dc3c..818eee6 100644 --- a/chrome/browser/printing/print_view_manager_base.cc +++ b/chrome/browser/printing/print_view_manager_base.cc
@@ -55,13 +55,10 @@ #include "printing/mojom/print.mojom.h" #include "printing/print_settings.h" #include "printing/printed_document.h" +#include "printing/printing_features.h" #include "printing/printing_utils.h" #include "ui/base/l10n/l10n_util.h" -#if BUILDFLAG(IS_WIN) -#include "printing/printing_features.h" -#endif - #if !BUILDFLAG(IS_ANDROID) #include "chrome/browser/printing/print_error_dialog.h" #endif @@ -71,6 +68,10 @@ #include "components/prefs/pref_service.h" #endif +#if BUILDFLAG(ENABLE_OOP_PRINTING) +#include "chrome/browser/printing/print_backend_service_manager.h" +#endif + #if BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING) #include "chrome/browser/win/conflicts/module_database.h" #endif @@ -324,9 +325,12 @@ if (!content::RenderFrameHost::FromID(rfh_id) || !rfh->IsRenderFrameLive()) return false; - // TODO(crbug.com/809738) Register with `PrintBackendServiceManager` when - // system print is enabled out-of-process. A corresponding unregister should - // go in `ReleasePrintJob()`. +#if BUILDFLAG(ENABLE_OOP_PRINTING) + // Register this worker so that the service persists as long as the user + // keeps the system print dialog UI displayed. + if (!RegisterSystemPrintClient()) + return false; +#endif SetPrintingRFH(rfh); GetPrintRenderFrame(rfh)->PrintRequestedPages(); @@ -465,6 +469,16 @@ GetDefaultPrintSettingsCallback callback, mojom::PrintParamsPtr params) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); +#if BUILDFLAG(ENABLE_OOP_PRINTING) + if (printing::features::kEnableOopPrintDriversJobPrint.Get() && + !params->document_cookie) { + // The attempt to use the default settings failed. There should be no + // subsequent call to get settings from the user that would normally be + // shared as part of this client registration. Immediately notify the + // service manager that this client is no longer needed. + UnregisterSystemPrintClient(); + } +#endif set_cookie(params->document_cookie); std::move(callback).Run(std::move(params)); } @@ -475,6 +489,11 @@ mojom::PrintPagesParamsPtr params) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); +#if BUILDFLAG(ENABLE_OOP_PRINTING) + // Finished getting all settings (defaults and from user), no further need + // to be registered as a system print client. + UnregisterSystemPrintClient(); +#endif if (!content::RenderProcessHost::FromID(process_id)) { // Early return if the renderer is not alive. return; @@ -610,6 +629,15 @@ mojom::PrintParams::New()); return; } +#if BUILDFLAG(ENABLE_OOP_PRINTING) + if (printing::features::kEnableOopPrintDriversJobPrint.Get() && + !service_manager_client_id_.has_value()) { + // Renderer process has requested settings outside of the expected setup. + GetDefaultPrintSettingsReply(std::move(callback), + mojom::PrintParams::New()); + return; + } +#endif content::RenderFrameHost* render_frame_host = GetCurrentTargetFrame(); auto callback_wrapper = @@ -676,6 +704,14 @@ std::move(callback).Run(CreateEmptyPrintPagesParamsPtr()); return; } +#if BUILDFLAG(ENABLE_OOP_PRINTING) + if (printing::features::kEnableOopPrintDriversJobPrint.Get() && + !service_manager_client_id_.has_value()) { + // Renderer process has requested settings outside of the expected setup. + std::move(callback).Run(CreateEmptyPrintPagesParamsPtr()); + return; + } +#endif auto callback_wrapper = base::BindOnce( &PrintViewManagerBase::ScriptedPrintReply, weak_ptr_factory_.GetWeakPtr(), std::move(callback), render_process_host->GetID()); @@ -918,6 +954,13 @@ content::RenderFrameHost* rfh = printing_rfh_; printing_rfh_ = nullptr; +#if BUILDFLAG(ENABLE_OOP_PRINTING) + // Ensure that any residual registration of printing client is released. + // This might be necessary in some abnormal cases, such as the associated + // render process having terminated. + UnregisterSystemPrintClient(); +#endif + if (!print_job_) return; @@ -1020,6 +1063,31 @@ printing_rfh_ = rfh; } +#if BUILDFLAG(ENABLE_OOP_PRINTING) +bool PrintViewManagerBase::RegisterSystemPrintClient() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + DCHECK(!service_manager_client_id_.has_value()); + service_manager_client_id_ = + PrintBackendServiceManager::GetInstance().RegisterQueryWithUiClient(); + if (!service_manager_client_id_.has_value()) { + DVLOG(1) << "Multiple system print clients not allowed, skipping user " + "request."; + return false; + } + return true; +} + +void PrintViewManagerBase::UnregisterSystemPrintClient() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + if (!service_manager_client_id_.has_value()) + return; + + PrintBackendServiceManager::GetInstance().UnregisterClient( + *service_manager_client_id_); + service_manager_client_id_.reset(); +} +#endif // BUILDFLAG(ENABLE_OOP_PRINTING) + void PrintViewManagerBase::ReleasePrinterQuery() { int current_cookie = cookie(); if (!current_cookie)
diff --git a/chrome/browser/printing/print_view_manager_base.h b/chrome/browser/printing/print_view_manager_base.h index 2661776..3a4cfa1 100644 --- a/chrome/browser/printing/print_view_manager_base.h +++ b/chrome/browser/printing/print_view_manager_base.h
@@ -121,6 +121,20 @@ void SetPrintingRFH(content::RenderFrameHost* rfh); +#if BUILDFLAG(ENABLE_OOP_PRINTING) + // Register with the `PrintBackendServiceManager` as a client for queries + // which will require a UI (the system print dialog). Some platforms have + // limitations on having multiple clients of this type; this function returns + // `false` if such a registration fails because of this restriction. In + // that case no further attempts to make the queries should be made. + bool RegisterSystemPrintClient(); + + // Unregister with the `PrintBackendServiceManager` if a client for queries + // which require a UI. This function can be called even if there is no + // current registration. + void UnregisterSystemPrintClient(); +#endif + // content::WebContentsObserver implementation. void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override; @@ -259,6 +273,11 @@ // Whether printing is enabled. BooleanPrefMember printing_enabled_; +#if BUILDFLAG(ENABLE_OOP_PRINTING) + // Client ID with the print backend service manager for this print job. + absl::optional<uint32_t> service_manager_client_id_; +#endif + const scoped_refptr<PrintQueriesQueue> queue_; base::ObserverList<Observer> observers_;
diff --git a/chrome/browser/profiles/profile.cc b/chrome/browser/profiles/profile.cc index 1e10a87..c34520b 100644 --- a/chrome/browser/profiles/profile.cc +++ b/chrome/browser/profiles/profile.cc
@@ -26,7 +26,6 @@ #include "components/keyed_service/content/browser_context_dependency_manager.h" #include "components/language/core/browser/pref_names.h" #include "components/live_caption/pref_names.h" -#include "components/media_router/common/pref_names.h" #include "components/pref_registry/pref_registry_syncable.h" #include "components/profile_metrics/browser_profile_type.h" #include "components/variations/variations.mojom.h" @@ -351,13 +350,6 @@ std::string()); #endif -#if !BUILDFLAG(IS_ANDROID) - registry->RegisterBooleanPref( - media_router::prefs::kMediaRouterMediaRemotingEnabled, true); - registry->RegisterListPref( - media_router::prefs::kMediaRouterTabMirroringSources); -#endif - registry->RegisterDictionaryPref(prefs::kWebShareVisitedTargets); registry->RegisterDictionaryPref( prefs::kProtocolHandlerPerOriginAllowedProtocols);
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/autoclick/autoclick_test.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/autoclick/autoclick_test.js index fdb885e..7904fbb 100644 --- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/autoclick/autoclick_test.js +++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/autoclick/autoclick_test.js
@@ -70,84 +70,81 @@ } }; -TEST_F('AutoclickE2ETest', 'HighlightsRootWebAreaIfNotScrollable', function() { - this.runWithLoadedTree( - 'data:text/html;charset=utf-8,<p>Cats rock!</p>', async function(root) { - const node = root.find( - {role: RoleType.STATIC_TEXT, attributes: {name: 'Cats rock!'}}); - await new Promise(resolve => { - this.mockAccessibilityPrivate.callOnScrollableBoundsForPointRequested( - // Offset slightly into the node to ensure the hittest - // happens within the node. - node.location.left + 1, node.location.top + 1, resolve); - }); - const expected = node.root.location; - const focusRings = this.mockAccessibilityPrivate.getFocusRings(); - this.assertSameRect( - this.mockAccessibilityPrivate.getScrollableBounds(), expected); - this.assertSameRect(focusRings[0].rects[0], expected); +TEST_F( + 'AutoclickE2ETest', 'HighlightsRootWebAreaIfNotScrollable', + async function() { + const root = await this.runWithLoadedTree( + 'data:text/html;charset=utf-8,<p>Cats rock!</p>'); + const node = root.find( + {role: RoleType.STATIC_TEXT, attributes: {name: 'Cats rock!'}}); + await new Promise(resolve => { + this.mockAccessibilityPrivate.callOnScrollableBoundsForPointRequested( + // Offset slightly into the node to ensure the hittest + // happens within the node. + node.location.left + 1, node.location.top + 1, resolve); }); -}); + const expected = node.root.location; + const focusRings = this.mockAccessibilityPrivate.getFocusRings(); + this.assertSameRect( + this.mockAccessibilityPrivate.getScrollableBounds(), expected); + this.assertSameRect(focusRings[0].rects[0], expected); + }); -TEST_F('AutoclickE2ETest', 'HighlightsScrollableDiv', function() { - this.runWithLoadedTree( +TEST_F('AutoclickE2ETest', 'HighlightsScrollableDiv', async function() { + const root = await this.runWithLoadedTree( 'data:text/html;charset=utf-8,' + - '<div style="width:100px;height:100px;overflow:scroll">' + - '<div style="margin:50px">cats rock! this text wraps and overflows!' + - '</div></div>', - async function(root) { - const node = root.find({ - role: RoleType.STATIC_TEXT, - attributes: {name: 'cats rock! this text wraps and overflows!'} - }); - await new Promise(resolve => { - this.mockAccessibilityPrivate.callOnScrollableBoundsForPointRequested( - // Offset slightly into the node to ensure the hittest happens - // within the node. - node.location.left + 1, node.location.top + 1, resolve); - }); - // The outer div, which is the parent of the parent of the - // text, is scrollable. - assertTrue(node.parent.parent.scrollable); - const expected = node.parent.parent.location; - const focusRings = this.mockAccessibilityPrivate.getFocusRings(); - this.assertSameRect( - this.mockAccessibilityPrivate.getScrollableBounds(), expected); - this.assertSameRect(focusRings[0].rects[0], expected); - }); + '<div style="width:100px;height:100px;overflow:scroll">' + + '<div style="margin:50px">cats rock! this text wraps and overflows!' + + '</div></div>'); + const node = root.find({ + role: RoleType.STATIC_TEXT, + attributes: {name: 'cats rock! this text wraps and overflows!'} + }); + await new Promise(resolve => { + this.mockAccessibilityPrivate.callOnScrollableBoundsForPointRequested( + // Offset slightly into the node to ensure the hittest happens + // within the node. + node.location.left + 1, node.location.top + 1, resolve); + }); + // The outer div, which is the parent of the parent of the + // text, is scrollable. + assertTrue(node.parent.parent.scrollable); + const expected = node.parent.parent.location; + const focusRings = this.mockAccessibilityPrivate.getFocusRings(); + this.assertSameRect( + this.mockAccessibilityPrivate.getScrollableBounds(), expected); + this.assertSameRect(focusRings[0].rects[0], expected); }); -TEST_F('AutoclickE2ETest', 'RemovesAndAddsAutoclick', function() { - this.runWithLoadedTree( - 'data:text/html;charset=utf-8,<p>Cats rock!</p>', async function(root) { - // Turn on screen magnifier so that when we turn off autoclick, the - // extension doesn't get unloaded and crash the test. - await new Promise(resolve => { - chrome.accessibilityFeatures.screenMagnifier.set( - {value: true}, resolve); - }); +TEST_F('AutoclickE2ETest', 'RemovesAndAddsAutoclick', async function() { + const root = await this.runWithLoadedTree( + 'data:text/html;charset=utf-8,<p>Cats rock!</p>'); + // Turn on screen magnifier so that when we turn off autoclick, the + // extension doesn't get unloaded and crash the test. + await new Promise(resolve => { + chrome.accessibilityFeatures.screenMagnifier.set({value: true}, resolve); + }); - // Toggle autoclick off and on, ensure it still works and no crashes. - await new Promise(resolve => { - chrome.accessibilityFeatures.autoclick.set({value: false}, resolve); - }); - await new Promise(resolve => { - chrome.accessibilityFeatures.autoclick.set({value: true}, resolve); - }); - const node = root.find( - {role: RoleType.STATIC_TEXT, attributes: {name: 'Cats rock!'}}); - await new Promise(resolve => { - this.mockAccessibilityPrivate.callOnScrollableBoundsForPointRequested( - // Offset slightly into the node to ensure the hittest happens - // within the node. - node.location.left + 1, node.location.top + 1, resolve); - }); - const expected = node.root.location; - const focusRings = this.mockAccessibilityPrivate.getFocusRings(); - this.assertSameRect( - this.mockAccessibilityPrivate.getScrollableBounds(), expected); - this.assertSameRect(focusRings[0].rects[0], expected); - }); + // Toggle autoclick off and on, ensure it still works and no crashes. + await new Promise(resolve => { + chrome.accessibilityFeatures.autoclick.set({value: false}, resolve); + }); + await new Promise(resolve => { + chrome.accessibilityFeatures.autoclick.set({value: true}, resolve); + }); + const node = + root.find({role: RoleType.STATIC_TEXT, attributes: {name: 'Cats rock!'}}); + await new Promise(resolve => { + this.mockAccessibilityPrivate.callOnScrollableBoundsForPointRequested( + // Offset slightly into the node to ensure the hittest happens + // within the node. + node.location.left + 1, node.location.top + 1, resolve); + }); + const expected = node.root.location; + const focusRings = this.mockAccessibilityPrivate.getFocusRings(); + this.assertSameRect( + this.mockAccessibilityPrivate.getScrollableBounds(), expected); + this.assertSameRect(focusRings[0].rects[0], expected); }); // TODO(crbug.com/978163): Add tests for when the scrollable area is scrolled
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/magnifier/magnifier_test.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/magnifier/magnifier_test.js index 8e48f48..159f2cfc 100644 --- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/magnifier/magnifier_test.js +++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/magnifier/magnifier_test.js
@@ -66,39 +66,39 @@ // Flaky: http://crbug.com/1171635 TEST_F( 'MagnifierE2ETest', 'DISABLED_MovesScreenMagnifierToFocusedElement', - function() { + async function() { const site = ` <button id="apple">Apple</button><br /> <button id="banana" style="margin-top: 400px">Banana</button> `; - this.runWithLoadedTree(site, async function(root) { - const magnifier = accessibilityCommon.getMagnifierForTest(); - magnifier.setIsInitializingForTest(false); + const root = await this.runWithLoadedTree(site); + const magnifier = accessibilityCommon.getMagnifierForTest(); + magnifier.setIsInitializingForTest(false); - const apple = root.find({attributes: {name: 'Apple'}}); - const banana = root.find({attributes: {name: 'Banana'}}); + const apple = root.find({attributes: {name: 'Apple'}}); + const banana = root.find({attributes: {name: 'Banana'}}); - // Focus and move magnifier to apple. - apple.focus(); + // Focus and move magnifier to apple. + apple.focus(); - // Verify magnifier bounds contains apple, but not banana. - let bounds = await this.getNextMagnifierBounds(); - assertTrue(RectUtil.contains(bounds, apple.location)); - assertFalse(RectUtil.contains(bounds, banana.location)); + // Verify magnifier bounds contains apple, but not banana. + let bounds = await this.getNextMagnifierBounds(); + assertTrue(RectUtil.contains(bounds, apple.location)); + assertFalse(RectUtil.contains(bounds, banana.location)); - // Focus and move magnifier to banana. - banana.focus(); + // Focus and move magnifier to banana. + banana.focus(); - // Verify magnifier bounds contains banana, but not apple. - bounds = await this.getNextMagnifierBounds(); - assertFalse(RectUtil.contains(bounds, apple.location)); - assertTrue(RectUtil.contains(bounds, banana.location)); - }); + // Verify magnifier bounds contains banana, but not apple. + bounds = await this.getNextMagnifierBounds(); + assertFalse(RectUtil.contains(bounds, apple.location)); + assertTrue(RectUtil.contains(bounds, banana.location)); }); // Disabled - flaky: https://crbug.com/1145612 TEST_F( - 'MagnifierE2ETest', 'DISABLED_MovesDockedMagnifierToActiveDescendant', function() { + 'MagnifierE2ETest', 'DISABLED_MovesDockedMagnifierToActiveDescendant', + async function() { const site = ` <div role="group" id="parent" aria-activedescendant="apple"> <div id="apple" role="treeitem">Apple</div> @@ -111,36 +111,35 @@ }); </script> `; - this.runWithLoadedTree(site, async function(root) { - // Enable docked magnifier. - await new Promise(resolve => { - chrome.accessibilityFeatures.dockedMagnifier.set( - {value: true}, resolve); - }); + const root = await this.runWithLoadedTree(site); + // Enable docked magnifier. + await new Promise(resolve => { + chrome.accessibilityFeatures.dockedMagnifier.set( + {value: true}, resolve); + }); - // Validate magnifier wants to move to root. - const rootLocation = await getNextMagnifierLocation(); - assertTrue(RectUtil.equal(rootLocation, root.location)); + // Validate magnifier wants to move to root. + const rootLocation = await getNextMagnifierLocation(); + assertTrue(RectUtil.equal(rootLocation, root.location)); - // Click parent to change active descendant from apple to banana. - const parent = root.find({role: RoleType.GROUP}); - parent.doDefault(); + // Click parent to change active descendant from apple to banana. + const parent = root.find({role: RoleType.GROUP}); + parent.doDefault(); - // Register and wait for rect from magnifier. - const rect = await getNextMagnifierLocation(); + // Register and wait for rect from magnifier. + const rect = await getNextMagnifierLocation(); - // Validate rect from magnifier is rect of banana. - const bananaNode = - root.find({role: RoleType.TREE_ITEM, attributes: {name: 'Banana'}}); - assertTrue(RectUtil.equal(rect, bananaNode.location)); - }, {returnPage: true}); + // Validate rect from magnifier is rect of banana. + const bananaNode = + root.find({role: RoleType.TREE_ITEM, attributes: {name: 'Banana'}}); + assertTrue(RectUtil.equal(rect, bananaNode.location)); }); // Flaky: http://crbug.com/1171750 TEST_F( 'MagnifierE2ETest', 'DISABLED_MovesScreenMagnifierToActiveDescendant', - function() { + async function() { const site = ` <span tabindex="1">Top</span> <div id="group" role="group" style="width: 200px" @@ -155,28 +154,27 @@ }); </script> `; - this.runWithLoadedTree(site, async function(root) { - const magnifier = accessibilityCommon.getMagnifierForTest(); - magnifier.setIsInitializingForTest(false); + const root = await this.runWithLoadedTree(site); + const magnifier = accessibilityCommon.getMagnifierForTest(); + magnifier.setIsInitializingForTest(false); - const top = root.find({attributes: {name: 'Top'}}); - const banana = root.find({attributes: {name: 'Banana'}}); - const group = root.find({role: RoleType.GROUP}); + const top = root.find({attributes: {name: 'Top'}}); + const banana = root.find({attributes: {name: 'Banana'}}); + const group = root.find({role: RoleType.GROUP}); - // Focus and move magnifier to top. - top.focus(); + // Focus and move magnifier to top. + top.focus(); - // Verify magnifier bounds don't contain banana. - let bounds = await this.getNextMagnifierBounds(); - assertFalse(RectUtil.contains(bounds, banana.location)); + // Verify magnifier bounds don't contain banana. + let bounds = await this.getNextMagnifierBounds(); + assertFalse(RectUtil.contains(bounds, banana.location)); - // Click group to change active descendant to banana. - group.doDefault(); + // Click group to change active descendant to banana. + group.doDefault(); - // Verify magnifier bounds contain banana. - bounds = await this.getNextMagnifierBounds(); - assertTrue(RectUtil.contains(bounds, banana.location)); - }); + // Verify magnifier bounds contain banana. + bounds = await this.getNextMagnifierBounds(); + assertTrue(RectUtil.contains(bounds, banana.location)); }); TEST_F( @@ -225,62 +223,60 @@ }); }); -TEST_F('MagnifierE2ETest', 'IgnoresRootNodeFocus', function() { +TEST_F('MagnifierE2ETest', 'IgnoresRootNodeFocus', async function() { const magnifier = accessibilityCommon.getMagnifierForTest(); magnifier.setIsInitializingForTest(false); - this.runWithLoadedTree('', async function(root) { - chrome.accessibilityPrivate.onMagnifierBoundsChanged.addListener( - newBounds => { - throw new Error( - 'Magnifier did not ignore focus change on document load - ' + - 'moved to following location: ' + JSON.stringify(newBounds)); - }); + await this.runWithLoadedTree(''); + chrome.accessibilityPrivate.onMagnifierBoundsChanged.addListener( + newBounds => { + throw new Error( + 'Magnifier did not ignore focus change on document load - ' + + 'moved to following location: ' + JSON.stringify(newBounds)); + }); - // Wait seven seconds to verify magnifier successfully ignored focus on root - // node. - await new Promise(resolve => setTimeout(resolve, 7000)); - }); + // Wait seven seconds to verify magnifier successfully ignored focus on root + // node. + await new Promise(resolve => setTimeout(resolve, 7000)); }); // TODO(crbug.com/1295685): Test is flaky. -TEST_F('MagnifierE2ETest', 'DISABLED_MagnifierCenterOnPoint', function() { - this.runWithLoadedTree('', async function(root) { - const magnifier = accessibilityCommon.getMagnifierForTest(); - magnifier.setIsInitializingForTest(false); +TEST_F('MagnifierE2ETest', 'DISABLED_MagnifierCenterOnPoint', async function() { + await this.runWithLoadedTree(''); + const magnifier = accessibilityCommon.getMagnifierForTest(); + magnifier.setIsInitializingForTest(false); - const movePointAssertBounds = async (targetPoint, targetBounds) => { - // Repeatedly center magnifier on |targetPoint|. - const id = setInterval(() => { - chrome.accessibilityPrivate.magnifierCenterOnPoint(targetPoint); - }, 500); + const movePointAssertBounds = async (targetPoint, targetBounds) => { + // Repeatedly center magnifier on |targetPoint|. + const id = setInterval(() => { + chrome.accessibilityPrivate.magnifierCenterOnPoint(targetPoint); + }, 500); - // Verify new magnifier bounds include |targetBounds|. - await new Promise(resolve => { - const boundsChangedListener = newBounds => { - if (RectUtil.contains(newBounds, targetBounds)) { - chrome.accessibilityPrivate.onMagnifierBoundsChanged.removeListener( - boundsChangedListener); - clearInterval(id); - resolve(); - } - }; - chrome.accessibilityPrivate.onMagnifierBoundsChanged.addListener( - boundsChangedListener); - }); - }; + // Verify new magnifier bounds include |targetBounds|. + await new Promise(resolve => { + const boundsChangedListener = newBounds => { + if (RectUtil.contains(newBounds, targetBounds)) { + chrome.accessibilityPrivate.onMagnifierBoundsChanged.removeListener( + boundsChangedListener); + clearInterval(id); + resolve(); + } + }; + chrome.accessibilityPrivate.onMagnifierBoundsChanged.addListener( + boundsChangedListener); + }); + }; - // Move magnifier to upper left of screen. - await movePointAssertBounds( - {x: 100, y: 100}, {left: 100, top: 100, width: 0, height: 0}); + // Move magnifier to upper left of screen. + await movePointAssertBounds( + {x: 100, y: 100}, {left: 100, top: 100, width: 0, height: 0}); - // Move magnifier to lower right of screen. - await movePointAssertBounds( - {x: 650, y: 450}, {left: 650, top: 450, width: 0, height: 0}); - }); + // Move magnifier to lower right of screen. + await movePointAssertBounds( + {x: 650, y: 450}, {left: 650, top: 450, width: 0, height: 0}); }); -TEST_F('MagnifierE2ETest', 'OnCaretBoundsChanged', function() { +TEST_F('MagnifierE2ETest', 'OnCaretBoundsChanged', async function() { const site = ` <input type="text" id="input" style="width: 1000px"> <button id="button">Type words</button> @@ -293,43 +289,42 @@ }); </script> `; - this.runWithLoadedTree(site, async function(root) { - const magnifier = accessibilityCommon.getMagnifierForTest(); - magnifier.setIsInitializingForTest(false); - const button = root.find({attributes: {name: 'Type words'}}); - const input = root.find({role: RoleType.TEXT_FIELD}); - input.doDefault(); + const root = await this.runWithLoadedTree(site); + const magnifier = accessibilityCommon.getMagnifierForTest(); + magnifier.setIsInitializingForTest(false); + const button = root.find({attributes: {name: 'Type words'}}); + const input = root.find({role: RoleType.TEXT_FIELD}); + input.doDefault(); - const typeWordsAssertBounds = async targetBounds => { - // Type words in the input field to move the text caret forward. - const id = setInterval(() => { - button.doDefault(); - }, 500); + const typeWordsAssertBounds = async targetBounds => { + // Type words in the input field to move the text caret forward. + const id = setInterval(() => { + button.doDefault(); + }, 500); - // Verify new magnifier bounds include |targetBounds|. - await new Promise(resolve => { - const boundsChangedListener = newBounds => { - if (RectUtil.contains(newBounds, targetBounds)) { - chrome.accessibilityPrivate.onMagnifierBoundsChanged.removeListener( - boundsChangedListener); - clearInterval(id); - resolve(); - } - }; - chrome.accessibilityPrivate.onMagnifierBoundsChanged.addListener( - boundsChangedListener); - }); - }; + // Verify new magnifier bounds include |targetBounds|. + await new Promise(resolve => { + const boundsChangedListener = newBounds => { + if (RectUtil.contains(newBounds, targetBounds)) { + chrome.accessibilityPrivate.onMagnifierBoundsChanged.removeListener( + boundsChangedListener); + clearInterval(id); + resolve(); + } + }; + chrome.accessibilityPrivate.onMagnifierBoundsChanged.addListener( + boundsChangedListener); + }); + }; - // Type words to move text cursor forward, verify magnifier contains caret. - await typeWordsAssertBounds({left: 400, top: 100, width: 0, height: 0}); + // Type words to move text cursor forward, verify magnifier contains caret. + await typeWordsAssertBounds({left: 400, top: 100, width: 0, height: 0}); - // Additional words to move caret forward, make sure magnifier follows. - await typeWordsAssertBounds({left: 800, top: 100, width: 0, height: 0}); + // Additional words to move caret forward, make sure magnifier follows. + await typeWordsAssertBounds({left: 800, top: 100, width: 0, height: 0}); - // Even more words to move caret forward, make sure magnifier follows. - await typeWordsAssertBounds({left: 1200, top: 100, width: 0, height: 0}); - }); + // Even more words to move caret forward, make sure magnifier follows. + await typeWordsAssertBounds({left: 1200, top: 100, width: 0, height: 0}); });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn index a3f5b0c..4d047674 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
@@ -35,7 +35,6 @@ "background/chromevox_state.js", "background/command_handler_interface.js", "background/event_source.js", - "background/keymaps/key_map.js", "background/output/locale_output_helper.js", "background/logging/event_stream_logger.js", "background/logging/log_store.js", @@ -64,10 +63,8 @@ "braille/pan_strategy.js", "braille/spans.js", "common/abstract_earcons.js", - "common/abstract_tts.js", "common/braille_interface.js", "common/chromevox.js", - "common/command_store.js", "common/console_tts.js", "common/extension_bridge.js", "common/key_sequence.js", @@ -110,6 +107,7 @@ "background/gesture_interface.js", "background/injected_script_loader.js", "background/keyboard_handler.js", + "background/keymaps/key_map.js", "background/live_regions.js", "background/math_handler.js", "background/media_automation_handler.js", @@ -120,6 +118,8 @@ "background/smart_sticky_mode.js", "background/logging/log.js", "braille/braille_key_event_rewriter.js", + "common/abstract_tts.js", + "common/command_store.js", "common/composite_tts.js", "common/editable_text_base.js", "common/keyboard_handler.js", @@ -382,7 +382,6 @@ "braille/spans.js", "braille/liblouis.js", "common/abstract_earcons.js", - "common/abstract_tts.js", "common/braille_interface.js", "common/chromevox.js", "common/editable_text_base.js",
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/auto_scroll_handler_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/auto_scroll_handler_test.js index a91f483..301f602d 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/auto_scroll_handler_test.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/auto_scroll_handler_test.js
@@ -24,7 +24,8 @@ 'AutoScrollHandler', '/chromevox/background/auto_scroll_handler.js'); } - runWithFakeArcSimpleScrollable(callback) { + /** @return {chrome.automation.AutomationNode} */ + async runWithFakeArcSimpleScrollable() { // This simulates a scrolling behavior of Android scrollable, where when a // scroll action is performed, a new item is added to the list. const site = ` @@ -45,19 +46,18 @@ }); </script> `; - this.runWithFakeScrollable( - site, { - numChildrenBeforeScroll_: -1, - beforeScroll: (list) => { - this.numChildrenBeforeScroll_ = list.children.length; - }, - scrollFinished: (list) => - list.children.length !== this.numChildrenBeforeScroll_ - }, - callback); + return await this.runWithFakeScrollable(site, { + numChildrenBeforeScroll_: -1, + beforeScroll: (list) => { + this.numChildrenBeforeScroll_ = list.children.length; + }, + scrollFinished: (list) => + list.children.length !== this.numChildrenBeforeScroll_ + }); } - runWithFakeArcRecyclerView(callback) { + /** @return {chrome.automation.AutomationNode} */ + async runWithFakeArcRecyclerView() { // This simulates a scrolling behavior in Android RecyclerView, where when a // scroll action is performed, the previously visible items disappear and // the new items are added to the list. @@ -86,17 +86,15 @@ }); </script> `; - this.runWithFakeScrollable( - site, { - childrenBeforeScroll_: [], - beforeScroll: (list) => { - this.childrenBeforeScroll_ = list.children; - }, - scrollFinished: (list) => list.children.length === 2 && - list.children[0] !== this.childrenBeforeScroll_[0] && - list.children[1] !== this.childrenBeforeScroll_[1] - }, - callback); + return await this.runWithFakeScrollable(site, { + childrenBeforeScroll_: [], + beforeScroll: (list) => { + this.childrenBeforeScroll_ = list.children; + }, + scrollFinished: (list) => list.children.length === 2 && + list.children[0] !== this.childrenBeforeScroll_[0] && + list.children[1] !== this.childrenBeforeScroll_[1] + }); } /** @@ -115,213 +113,213 @@ * beforeScroll: called before performing a fake scrolling (click action). * scrollFinished: return if the scrolling has finished and the event * listener can be invoked. - * @param {Function} callback + * @return {chrome.automation.AutomationNode} */ - runWithFakeScrollable(site, scrolledPredicate, callback) { - this.runWithLoadedTree(site, function(root) { - const list = root.find({role: RoleType.LIST}); - Object.defineProperty(list, 'focusable', {get: () => false}); - Object.defineProperty(list, 'scrollable', {get: () => true}); - Object.defineProperty(list, 'standardActions', { - get: () => - [chrome.automation.ActionType.SCROLL_FORWARD, - chrome.automation.ActionType.SCROLL_BACKWARD] - }); + async runWithFakeScrollable(site, scrolledPredicate) { + const root = await this.runWithLoadedTree(site); + const list = root.find({role: RoleType.LIST}); + Object.defineProperty(list, 'focusable', {get: () => false}); + Object.defineProperty(list, 'scrollable', {get: () => true}); + Object.defineProperty(list, 'standardActions', { + get: () => + [chrome.automation.ActionType.SCROLL_FORWARD, + chrome.automation.ActionType.SCROLL_BACKWARD] + }); - // Create a fake addEventListener to dispatch an event listener of - // SCROLL_POSITION_CHANGED. - let eventListener; - const originalAddEventListenerFunc = list.addEventListener.bind(list); - list.addEventListener = (eventType, callback, capture) => { - if (eventType === EventType.SCROLL_POSITION_CHANGED) { - eventListener = callback; - return; - } else if ( - eventType === EventType.SCROLL_HORIZONTAL_POSITION_CHANGED || - eventType === EventType.SCROLL_VERTICAL_POSITION_CHANGED) { - // Do nothing to prevent catching scroll events dispatched from web. + // Create a fake addEventListener to dispatch an event listener of + // SCROLL_POSITION_CHANGED. + let eventListener; + const originalAddEventListenerFunc = list.addEventListener.bind(list); + list.addEventListener = (eventType, callback, capture) => { + if (eventType === EventType.SCROLL_POSITION_CHANGED) { + eventListener = callback; + return; + } else if ( + eventType === EventType.SCROLL_HORIZONTAL_POSITION_CHANGED || + eventType === EventType.SCROLL_VERTICAL_POSITION_CHANGED) { + // Do nothing to prevent catching scroll events dispatched from web. + return; + } + originalAddEventListenerFunc(eventType, callback, capture); + }; + + // Create a fake scrollForward and scrollBackward actions. + const fakeScrollFunc = (cb) => { + scrolledPredicate.beforeScroll(list); + const listener = (ev) => { + if (!scrolledPredicate.scrollFinished(list)) { return; } - originalAddEventListenerFunc(eventType, callback, capture); + list.removeEventListener(EventType.CHILDREN_CHANGED, listener, true); + eventListener(); }; + list.addEventListener(EventType.CHILDREN_CHANGED, listener, true); - // Create a fake scrollForward and scrollBackward actions. - const fakeScrollFunc = (cb) => { - scrolledPredicate.beforeScroll(list); - const listener = (ev) => { - if (!scrolledPredicate.scrollFinished(list)) { - return; - } - list.removeEventListener(EventType.CHILDREN_CHANGED, listener, true); - eventListener(); - }; - list.addEventListener(EventType.CHILDREN_CHANGED, listener, true); + // Invoke 'click' event on the list, which updates the list items. + list.doDefault(); + cb(true); + }; + list.scrollForward = fakeScrollFunc; + list.scrollBackward = fakeScrollFunc; - // Invoke 'click' event on the list, which updates the list items. - list.doDefault(); - cb(true); - }; - list.scrollForward = fakeScrollFunc; - list.scrollBackward = fakeScrollFunc; - - callback(root); - }); + return root; } }; TEST_F( - 'ChromeVoxAutoScrollHandlerTest', 'DontScrollInSameScrollable', function() { - this.runWithFakeArcSimpleScrollable(function(root) { - const handler = new AutoScrollHandler(); + 'ChromeVoxAutoScrollHandlerTest', 'DontScrollInSameScrollable', + async function() { + const root = await this.runWithFakeArcSimpleScrollable(); + const handler = new AutoScrollHandler(); - const list = root.find({role: RoleType.LIST}); - const firstItemCursor = cursors.Range.fromNode(list.firstChild); - const lastItemCursor = cursors.Range.fromNode(list.lastChild); + const list = root.find({role: RoleType.LIST}); + const firstItemCursor = cursors.Range.fromNode(list.firstChild); + const lastItemCursor = cursors.Range.fromNode(list.lastChild); - ChromeVoxState.instance.navigateToRange(firstItemCursor); + ChromeVoxState.instance.navigateToRange(firstItemCursor); - assertTrue(handler.onCommandNavigation( - lastItemCursor, constants.Dir.FORWARD, /*pred=*/ null, - /*speechProps=*/ null)); - }); + assertTrue(handler.onCommandNavigation( + lastItemCursor, constants.Dir.FORWARD, /*pred=*/ null, + /*speechProps=*/ null)); }); TEST_F( - 'ChromeVoxAutoScrollHandlerTest', 'PreventMultipleScrolling', function() { - this.runWithFakeArcSimpleScrollable(function(root) { - const handler = new AutoScrollHandler(); + 'ChromeVoxAutoScrollHandlerTest', 'PreventMultipleScrolling', + async function() { + const root = await this.runWithFakeArcSimpleScrollable(); + const handler = new AutoScrollHandler(); - const list = root.find({role: RoleType.LIST}); - const rootCursor = cursors.Range.fromNode(root); - const firstItemCursor = cursors.Range.fromNode(list.firstChild); - const lastItemCursor = cursors.Range.fromNode(list.lastChild); + const list = root.find({role: RoleType.LIST}); + const rootCursor = cursors.Range.fromNode(root); + const firstItemCursor = cursors.Range.fromNode(list.firstChild); + const lastItemCursor = cursors.Range.fromNode(list.lastChild); - ChromeVoxState.instance.navigateToRange(lastItemCursor); + ChromeVoxState.instance.navigateToRange(lastItemCursor); - // Make scrolling action void, so that the second invocation should be - // ignored. - list.scrollForward = () => {}; + // Make scrolling action void, so that the second invocation should be + // ignored. + list.scrollForward = () => {}; - assertFalse(handler.onCommandNavigation( - rootCursor, constants.Dir.FORWARD, /*pred=*/ null, - /*speechProps=*/ null)); + assertFalse(handler.onCommandNavigation( + rootCursor, constants.Dir.FORWARD, /*pred=*/ null, + /*speechProps=*/ null)); - assertFalse(handler.onCommandNavigation( - firstItemCursor, constants.Dir.FORWARD, /*pred=*/ null, - /*speechProps=*/ null)); - }); + assertFalse(handler.onCommandNavigation( + firstItemCursor, constants.Dir.FORWARD, /*pred=*/ null, + /*speechProps=*/ null)); }); -TEST_F('ChromeVoxAutoScrollHandlerTest', 'ScrollForward', function() { +TEST_F('ChromeVoxAutoScrollHandlerTest', 'ScrollForward', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithFakeArcSimpleScrollable(function(root) { - mockFeedback.expectSpeech('1st item') - .call(doCmd('nextObject')) - .expectSpeech('2nd item') - .call(doCmd('nextObject')) - .expectSpeech('3rd item') - .call(doCmd('nextObject')) - .expectSpeech('4th item') - .call(doCmd('nextObject')) - .expectSpeech('5th item') - .replay(); - }); + const root = await this.runWithFakeArcSimpleScrollable(); + mockFeedback.expectSpeech('1st item') + .call(doCmd('nextObject')) + .expectSpeech('2nd item') + .call(doCmd('nextObject')) + .expectSpeech('3rd item') + .call(doCmd('nextObject')) + .expectSpeech('4th item') + .call(doCmd('nextObject')) + .expectSpeech('5th item') + .replay(); }); TEST_F( - 'ChromeVoxAutoScrollHandlerTest', 'ScrollForwardReturnsFalse', function() { + 'ChromeVoxAutoScrollHandlerTest', 'ScrollForwardReturnsFalse', + async function() { const mockFeedback = this.createMockFeedback(); - this.runWithFakeArcSimpleScrollable(function(root) { - const list = root.find({role: RoleType.LIST}); - list.scrollForward = (callback) => callback(false); + const root = await this.runWithFakeArcSimpleScrollable(); + const list = root.find({role: RoleType.LIST}); + list.scrollForward = (callback) => callback(false); - mockFeedback.expectSpeech('1st item') - .call(doCmd('nextObject')) - .expectSpeech('2nd item') - .call(doCmd('nextObject')) - .expectSpeech('3rd item') - .call(doCmd('nextObject')) - .expectSpeech('hello') - .call(doCmd('nextObject')) - .expectSpeech('world') - .replay(); - }); + mockFeedback.expectSpeech('1st item') + .call(doCmd('nextObject')) + .expectSpeech('2nd item') + .call(doCmd('nextObject')) + .expectSpeech('3rd item') + .call(doCmd('nextObject')) + .expectSpeech('hello') + .call(doCmd('nextObject')) + .expectSpeech('world') + .replay(); }); -TEST_F('ChromeVoxAutoScrollHandlerTest', 'RecyclerViewByObject', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithFakeArcRecyclerView(function(root) { - mockFeedback.expectSpeech('1st item') - .call(doCmd('nextObject')) - .expectSpeech('2nd item') - .call(doCmd('nextObject')) // scroll forward - .expectSpeech('3rd item') - .call(doCmd('previousObject')) // scroll backward - .expectSpeech('2nd item') - .replay(); - }); -}); +TEST_F( + 'ChromeVoxAutoScrollHandlerTest', 'RecyclerViewByObject', async function() { + const mockFeedback = this.createMockFeedback(); + const root = await this.runWithFakeArcRecyclerView(); + mockFeedback.expectSpeech('1st item') + .call(doCmd('nextObject')) + .expectSpeech('2nd item') + .call(doCmd('nextObject')) // scroll forward + .expectSpeech('3rd item') + .call(doCmd('previousObject')) // scroll backward + .expectSpeech('2nd item') + .replay(); + }); -TEST_F('ChromeVoxAutoScrollHandlerTest', 'RecyclerViewByWord', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithFakeArcRecyclerView(function(root) { - mockFeedback.expectSpeech('1st item') - .call(doCmd('nextObject')) - .expectSpeech('2nd item') - .call(doCmd('nextWord')) - .expectSpeech('item') - .call(doCmd('nextWord')) // scroll forward - .expectSpeech('3rd') - .call(doCmd('previousWord')) // scroll backward - .expectSpeech('item') - .call(doCmd('previousWord')) - .expectSpeech('2nd') - .replay(); - }); -}); +TEST_F( + 'ChromeVoxAutoScrollHandlerTest', 'RecyclerViewByWord', async function() { + const mockFeedback = this.createMockFeedback(); + const root = await this.runWithFakeArcRecyclerView(); + mockFeedback.expectSpeech('1st item') + .call(doCmd('nextObject')) + .expectSpeech('2nd item') + .call(doCmd('nextWord')) + .expectSpeech('item') + .call(doCmd('nextWord')) // scroll forward + .expectSpeech('3rd') + .call(doCmd('previousWord')) // scroll backward + .expectSpeech('item') + .call(doCmd('previousWord')) + .expectSpeech('2nd') + .replay(); + }); -TEST_F('ChromeVoxAutoScrollHandlerTest', 'RecyclerViewByCharacter', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithFakeArcRecyclerView(function(root) { - mockFeedback.expectSpeech('1st item') - .call(doCmd('nextObject')) - .expectSpeech('2nd item') - .call(doCmd('nextWord')) - .expectSpeech('item') - .call(doCmd('nextCharacter')) - .expectSpeech('t') - .call(doCmd('nextCharacter')) - .expectSpeech('e') - .call(doCmd('nextCharacter')) - .expectSpeech('m') - .call(doCmd('nextCharacter')) // scroll forward - .expectSpeech('3') - .call(doCmd('nextCharacter')) - .expectSpeech('r') - .call(doCmd('previousCharacter')) - .expectSpeech('3') - .call(doCmd('previousCharacter')) // scroll backward - .expectSpeech('m') - .call(doCmd('previousCharacter')) - .expectSpeech('e') - .replay(); - }); -}); +TEST_F( + 'ChromeVoxAutoScrollHandlerTest', 'RecyclerViewByCharacter', + async function() { + const mockFeedback = this.createMockFeedback(); + const root = await this.runWithFakeArcRecyclerView(); + mockFeedback.expectSpeech('1st item') + .call(doCmd('nextObject')) + .expectSpeech('2nd item') + .call(doCmd('nextWord')) + .expectSpeech('item') + .call(doCmd('nextCharacter')) + .expectSpeech('t') + .call(doCmd('nextCharacter')) + .expectSpeech('e') + .call(doCmd('nextCharacter')) + .expectSpeech('m') + .call(doCmd('nextCharacter')) // scroll forward + .expectSpeech('3') + .call(doCmd('nextCharacter')) + .expectSpeech('r') + .call(doCmd('previousCharacter')) + .expectSpeech('3') + .call(doCmd('previousCharacter')) // scroll backward + .expectSpeech('m') + .call(doCmd('previousCharacter')) + .expectSpeech('e') + .replay(); + }); -TEST_F('ChromeVoxAutoScrollHandlerTest', 'RecyclerViewByPredicate', function() { - // TODO(hirokisato): This test fails without '<p>unrelated content</p>' in the - // tree, because the next item of '2nd item' without scrolling is '1st item', - // and the scrollable is LCA, so auto scrolling is not invoked. We should fix - // this corner case. - const mockFeedback = this.createMockFeedback(); - this.runWithFakeArcRecyclerView(function(root) { - mockFeedback.expectSpeech('1st item') - .call(doCmd('nextSimilarItem')) - .expectSpeech('2nd item') - .call(doCmd('nextSimilarItem')) // scroll forward - .expectSpeech('3rd item') - .call(doCmd('previousSimilarItem')) // scroll backward - .expectSpeech('2nd item') - .replay(); - }); -}); +TEST_F( + 'ChromeVoxAutoScrollHandlerTest', 'RecyclerViewByPredicate', + async function() { + // TODO(hirokisato): This test fails without '<p>unrelated content</p>' in + // the tree, because the next item of '2nd item' without scrolling is '1st + // item', and the scrollable is LCA, so auto scrolling is not invoked. We + // should fix this corner case. + const mockFeedback = this.createMockFeedback(); + await this.runWithFakeArcRecyclerView(); + mockFeedback.expectSpeech('1st item') + .call(doCmd('nextSimilarItem')) + .expectSpeech('2nd item') + .call(doCmd('nextSimilarItem')) // scroll forward + .expectSpeech('3rd item') + .call(doCmd('previousSimilarItem')) // scroll backward + .expectSpeech('2nd item') + .replay(); + });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js index 42c7041..728100a 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js
@@ -39,6 +39,9 @@ 'GestureCommandHandler', '/chromevox/background/gesture_command_handler.js'); await importModule( + 'PageLoadSoundHandler', + '/chromevox/background/page_load_sound_handler.js'); + await importModule( 'PointerHandler', '/chromevox/background/pointer_handler.js'); await super.setUpDeferred(); } @@ -220,184 +223,177 @@ }); /** Tests consistency of navigating forward and backward. */ -TEST_F('ChromeVoxBackgroundTest', 'ForwardBackwardNavigation', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.linksAndHeadingsDoc, function() { - mockFeedback.expectSpeech('start').expectBraille('start'); +TEST_F( + 'ChromeVoxBackgroundTest', 'ForwardBackwardNavigation', async function() { + const mockFeedback = this.createMockFeedback(); + await this.runWithLoadedTree(this.linksAndHeadingsDoc); + mockFeedback.expectSpeech('start').expectBraille('start'); - mockFeedback.call(doCmd('nextLink')) - .expectSpeech('alpha', 'Link') - .expectBraille('alpha lnk'); - mockFeedback.call(doCmd('nextLink')) - .expectSpeech('beta', 'Link') - .expectBraille('beta lnk'); - mockFeedback.call(doCmd('nextLink')) - .expectSpeech('delta', 'Link') - .expectBraille('delta lnk'); - mockFeedback.call(doCmd('previousLink')) - .expectSpeech('beta', 'Link') - .expectBraille('beta lnk'); - mockFeedback.call(doCmd('nextHeading')) - .expectSpeech('charlie', 'Heading 1') - .expectBraille('charlie h1'); - mockFeedback.call(doCmd('nextHeading')) - .expectSpeech('foxtraut', 'Heading 2') - .expectBraille('foxtraut h2'); - mockFeedback.call(doCmd('previousHeading')) - .expectSpeech('charlie', 'Heading 1') - .expectBraille('charlie h1'); + mockFeedback.call(doCmd('nextLink')) + .expectSpeech('alpha', 'Link') + .expectBraille('alpha lnk'); + mockFeedback.call(doCmd('nextLink')) + .expectSpeech('beta', 'Link') + .expectBraille('beta lnk'); + mockFeedback.call(doCmd('nextLink')) + .expectSpeech('delta', 'Link') + .expectBraille('delta lnk'); + mockFeedback.call(doCmd('previousLink')) + .expectSpeech('beta', 'Link') + .expectBraille('beta lnk'); + mockFeedback.call(doCmd('nextHeading')) + .expectSpeech('charlie', 'Heading 1') + .expectBraille('charlie h1'); + mockFeedback.call(doCmd('nextHeading')) + .expectSpeech('foxtraut', 'Heading 2') + .expectBraille('foxtraut h2'); + mockFeedback.call(doCmd('previousHeading')) + .expectSpeech('charlie', 'Heading 1') + .expectBraille('charlie h1'); - mockFeedback.call(doCmd('nextObject')) - .expectSpeech('delta', 'Link') - .expectBraille('delta lnk'); - mockFeedback.call(doCmd('nextObject')) - .expectSpeech('echo', 'Link') - .expectBraille('echo lnk'); - mockFeedback.call(doCmd('nextObject')) - .expectSpeech('foxtraut', 'Heading 2') - .expectBraille('foxtraut h2'); - mockFeedback.call(doCmd('nextObject')) - .expectSpeech('end') - .expectBraille('end'); - mockFeedback.call(doCmd('previousObject')) - .expectSpeech('foxtraut', 'Heading 2') - .expectBraille('foxtraut h2'); - mockFeedback.call(doCmd('nextLine')).expectSpeech('foxtraut'); - mockFeedback.call(doCmd('nextLine')) - .expectSpeech('end', 'of test') - .expectBraille('endof test'); + mockFeedback.call(doCmd('nextObject')) + .expectSpeech('delta', 'Link') + .expectBraille('delta lnk'); + mockFeedback.call(doCmd('nextObject')) + .expectSpeech('echo', 'Link') + .expectBraille('echo lnk'); + mockFeedback.call(doCmd('nextObject')) + .expectSpeech('foxtraut', 'Heading 2') + .expectBraille('foxtraut h2'); + mockFeedback.call(doCmd('nextObject')) + .expectSpeech('end') + .expectBraille('end'); + mockFeedback.call(doCmd('previousObject')) + .expectSpeech('foxtraut', 'Heading 2') + .expectBraille('foxtraut h2'); + mockFeedback.call(doCmd('nextLine')).expectSpeech('foxtraut'); + mockFeedback.call(doCmd('nextLine')) + .expectSpeech('end', 'of test') + .expectBraille('endof test'); - mockFeedback.call(doCmd('jumpToTop')) - .expectSpeech('start') - .expectBraille('start'); - mockFeedback.call(doCmd('jumpToBottom')) - .expectSpeech('of test') - .expectBraille('of test'); + mockFeedback.call(doCmd('jumpToTop')) + .expectSpeech('start') + .expectBraille('start'); + mockFeedback.call(doCmd('jumpToBottom')) + .expectSpeech('of test') + .expectBraille('of test'); - mockFeedback.replay(); - }); -}); + mockFeedback.replay(); + }); -TEST_F('ChromeVoxBackgroundTest', 'CaretNavigation', function() { +TEST_F('ChromeVoxBackgroundTest', 'CaretNavigation', async function() { // TODO(plundblad): Add braille expectations when crbug.com/523285 is fixed. const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.linksAndHeadingsDoc, function() { - mockFeedback.expectSpeech('start'); - mockFeedback.call(doCmd('nextCharacter')).expectSpeech('t'); - mockFeedback.call(doCmd('nextCharacter')).expectSpeech('a'); - mockFeedback.call(doCmd('nextWord')).expectSpeech('alpha', 'Link'); - mockFeedback.call(doCmd('nextWord')).expectSpeech('beta', 'Link'); - mockFeedback.call(doCmd('previousWord')).expectSpeech('alpha', 'Link'); - mockFeedback.call(doCmd('nextWord')).expectSpeech('beta', 'Link'); - mockFeedback.call(doCmd('nextWord')).expectSpeech('charlie', 'Heading 1'); - mockFeedback.call(doCmd('nextLine')).expectSpeech('delta', 'Link'); - mockFeedback.call(doCmd('nextLine')).expectSpeech('echo', 'Link'); - mockFeedback.call(doCmd('nextLine')).expectSpeech('foxtraut', 'Heading 2'); - mockFeedback.call(doCmd('nextLine')).expectSpeech('end', 'of test'); - mockFeedback.call(doCmd('nextCharacter')).expectSpeech('n'); - mockFeedback.call(doCmd('previousCharacter')).expectSpeech('e'); - mockFeedback.call(doCmd('previousCharacter')) - .expectSpeech('t', 'Heading 2'); - mockFeedback.call(doCmd('previousWord')).expectSpeech('foxtraut'); - mockFeedback.call(doCmd('previousWord')).expectSpeech('echo', 'Link'); - mockFeedback.call(doCmd('previousCharacter')).expectSpeech('a', 'Link'); - mockFeedback.call(doCmd('previousCharacter')).expectSpeech('t'); - mockFeedback.call(doCmd('nextWord')).expectSpeech('echo', 'Link'); - mockFeedback.replay(); - }); + await this.runWithLoadedTree(this.linksAndHeadingsDoc); + mockFeedback.expectSpeech('start'); + mockFeedback.call(doCmd('nextCharacter')).expectSpeech('t'); + mockFeedback.call(doCmd('nextCharacter')).expectSpeech('a'); + mockFeedback.call(doCmd('nextWord')).expectSpeech('alpha', 'Link'); + mockFeedback.call(doCmd('nextWord')).expectSpeech('beta', 'Link'); + mockFeedback.call(doCmd('previousWord')).expectSpeech('alpha', 'Link'); + mockFeedback.call(doCmd('nextWord')).expectSpeech('beta', 'Link'); + mockFeedback.call(doCmd('nextWord')).expectSpeech('charlie', 'Heading 1'); + mockFeedback.call(doCmd('nextLine')).expectSpeech('delta', 'Link'); + mockFeedback.call(doCmd('nextLine')).expectSpeech('echo', 'Link'); + mockFeedback.call(doCmd('nextLine')).expectSpeech('foxtraut', 'Heading 2'); + mockFeedback.call(doCmd('nextLine')).expectSpeech('end', 'of test'); + mockFeedback.call(doCmd('nextCharacter')).expectSpeech('n'); + mockFeedback.call(doCmd('previousCharacter')).expectSpeech('e'); + mockFeedback.call(doCmd('previousCharacter')).expectSpeech('t', 'Heading 2'); + mockFeedback.call(doCmd('previousWord')).expectSpeech('foxtraut'); + mockFeedback.call(doCmd('previousWord')).expectSpeech('echo', 'Link'); + mockFeedback.call(doCmd('previousCharacter')).expectSpeech('a', 'Link'); + mockFeedback.call(doCmd('previousCharacter')).expectSpeech('t'); + mockFeedback.call(doCmd('nextWord')).expectSpeech('echo', 'Link'); + mockFeedback.replay(); }); /** Tests that individual buttons are stops for move-by-word functionality. */ TEST_F( 'ChromeVoxBackgroundTest', 'CaretNavigationMoveThroughButtonByWord', - function() { + async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.buttonDoc, function() { - mockFeedback.expectSpeech('start'); - mockFeedback.call(doCmd('nextObject')) - .expectSpeech('hello button one', 'Button'); - mockFeedback.call(doCmd('previousWord')).expectSpeech('start'); - mockFeedback.call(doCmd('nextWord')).expectSpeech('hello'); - mockFeedback.call(doCmd('nextWord')).expectSpeech('button'); - mockFeedback.call(doCmd('nextWord')).expectSpeech('one'); - mockFeedback.call(doCmd('nextWord')).expectSpeech('cats'); - mockFeedback.call(doCmd('nextWord')).expectSpeech('hello'); - mockFeedback.call(doCmd('nextWord')).expectSpeech('button'); - mockFeedback.call(doCmd('nextWord')).expectSpeech('two'); - mockFeedback.call(doCmd('nextWord')).expectSpeech('end'); - mockFeedback.call(doCmd('previousWord')).expectSpeech('two'); - mockFeedback.call(doCmd('previousWord')).expectSpeech('button'); - mockFeedback.call(doCmd('previousWord')).expectSpeech('hello'); - mockFeedback.call(doCmd('previousWord')).expectSpeech('cats'); - mockFeedback.call(doCmd('previousWord')).expectSpeech('one'); - mockFeedback.call(doCmd('previousWord')).expectSpeech('button'); - mockFeedback.call(doCmd('previousWord')).expectSpeech('hello'); - mockFeedback.replay(); - }); + await this.runWithLoadedTree(this.buttonDoc); + mockFeedback.expectSpeech('start'); + mockFeedback.call(doCmd('nextObject')) + .expectSpeech('hello button one', 'Button'); + mockFeedback.call(doCmd('previousWord')).expectSpeech('start'); + mockFeedback.call(doCmd('nextWord')).expectSpeech('hello'); + mockFeedback.call(doCmd('nextWord')).expectSpeech('button'); + mockFeedback.call(doCmd('nextWord')).expectSpeech('one'); + mockFeedback.call(doCmd('nextWord')).expectSpeech('cats'); + mockFeedback.call(doCmd('nextWord')).expectSpeech('hello'); + mockFeedback.call(doCmd('nextWord')).expectSpeech('button'); + mockFeedback.call(doCmd('nextWord')).expectSpeech('two'); + mockFeedback.call(doCmd('nextWord')).expectSpeech('end'); + mockFeedback.call(doCmd('previousWord')).expectSpeech('two'); + mockFeedback.call(doCmd('previousWord')).expectSpeech('button'); + mockFeedback.call(doCmd('previousWord')).expectSpeech('hello'); + mockFeedback.call(doCmd('previousWord')).expectSpeech('cats'); + mockFeedback.call(doCmd('previousWord')).expectSpeech('one'); + mockFeedback.call(doCmd('previousWord')).expectSpeech('button'); + mockFeedback.call(doCmd('previousWord')).expectSpeech('hello'); + mockFeedback.replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'SelectSingleBasic', function() { +TEST_F('ChromeVoxBackgroundTest', 'SelectSingleBasic', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.formsDoc, function() { - mockFeedback.expectSpeech('apple', 'has pop up', 'Collapsed') - .expectBraille('apple btn +popup +3 +') - .call(press(KeyCode.DOWN)) - .expectSpeech('grape', /2 of 3/) - .expectBraille('grape 2/3') - .call(press(KeyCode.DOWN)) - .expectSpeech('banana', /3 of 3/) - .expectBraille('banana 3/3'); - mockFeedback.replay(); - }); + await this.runWithLoadedTree(this.formsDoc); + mockFeedback.expectSpeech('apple', 'has pop up', 'Collapsed') + .expectBraille('apple btn +popup +3 +') + .call(press(KeyCode.DOWN)) + .expectSpeech('grape', /2 of 3/) + .expectBraille('grape 2/3') + .call(press(KeyCode.DOWN)) + .expectSpeech('banana', /3 of 3/) + .expectBraille('banana 3/3'); + mockFeedback.replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'ContinuousRead', function() { +TEST_F('ChromeVoxBackgroundTest', 'ContinuousRead', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.linksAndHeadingsDoc, function() { - mockFeedback.expectSpeech('start') - .call(doCmd('readFromHere')) - .expectSpeech( - 'start', 'alpha', 'Link', 'beta', 'Link', 'charlie', 'Heading 1'); - mockFeedback.replay(); - }); + await this.runWithLoadedTree(this.linksAndHeadingsDoc); + mockFeedback.expectSpeech('start') + .call(doCmd('readFromHere')) + .expectSpeech( + 'start', 'alpha', 'Link', 'beta', 'Link', 'charlie', 'Heading 1'); + mockFeedback.replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'InitialFocus', function() { +TEST_F('ChromeVoxBackgroundTest', 'InitialFocus', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree('<a href="a">a</a>', function(rootNode) { - mockFeedback.expectSpeech('a').expectSpeech('Link'); - mockFeedback.replay(); - }); + await this.runWithLoadedTree('<a href="a">a</a>'); + mockFeedback.expectSpeech('a').expectSpeech('Link'); + mockFeedback.replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'AriaLabel', function() { +TEST_F('ChromeVoxBackgroundTest', 'AriaLabel', async function() { const mockFeedback = this.createMockFeedback(); const site = '<a aria-label="foo" href="a">a</a>'; - this.runWithLoadedTree(site, function(rootNode) { - rootNode.find({role: RoleType.LINK}).focus(); - mockFeedback.expectSpeech('foo') - .expectSpeech('Link') - .expectSpeech('Press Search+Space to activate') - .expectBraille('foo lnk'); - mockFeedback.replay(); - }); + const rootNode = await this.runWithLoadedTree(site); + rootNode.find({role: RoleType.LINK}).focus(); + mockFeedback.expectSpeech('foo') + .expectSpeech('Link') + .expectSpeech('Press Search+Space to activate') + .expectBraille('foo lnk'); + mockFeedback.replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'ShowContextMenu', function() { +TEST_F('ChromeVoxBackgroundTest', 'ShowContextMenu', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree('<p>before</p><a href="a">a</a>', function(rootNode) { - const go = rootNode.find({role: RoleType.LINK}); - mockFeedback.call(go.focus.bind(go)) - .expectSpeech('a', 'Link') - .call(doCmd('contextMenu')) - .expectSpeech(/menu opened/) - .call(press(KeyCode.ESCAPE)) - .expectSpeech('a', 'Link'); - mockFeedback.replay(); - }.bind(this)); + const rootNode = + await this.runWithLoadedTree('<p>before</p><a href="a">a</a>'); + const go = rootNode.find({role: RoleType.LINK}); + mockFeedback.call(go.focus.bind(go)) + .expectSpeech('a', 'Link') + .call(doCmd('contextMenu')) + .expectSpeech(/menu opened/) + .call(press(KeyCode.ESCAPE)) + .expectSpeech('a', 'Link'); + mockFeedback.replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'BrailleRouting', function() { +TEST_F('ChromeVoxBackgroundTest', 'BrailleRouting', async function() { const mockFeedback = this.createMockFeedback(); const route = function(position) { assertTrue(ChromeVoxState.instance.onBrailleKeyEvent( @@ -417,74 +413,71 @@ }, false); </script> `; - this.runWithLoadedTree(site, function(rootNode) { - const button1 = - rootNode.find({role: RoleType.BUTTON, attributes: {name: 'Click me'}}); - const textField = rootNode.find({role: RoleType.TEXT_FIELD}); - mockFeedback.expectBraille('start') - .call(button1.focus.bind(button1)) - .expectBraille(/^Click me btn/) - .call(route.bind(null, 5)) - .expectBraille(/Focus me btn/) - .call(textField.focus.bind(textField)) - .expectBraille('Edit me ed', {startIndex: 0}) - .call(route.bind(null, 3)) - .expectBraille('Edit me ed', {startIndex: 3}) - .call(function() { - assertEquals(3, textField.textSelStart); - }); - mockFeedback.replay(); - }); + const rootNode = await this.runWithLoadedTree(site); + const button1 = + rootNode.find({role: RoleType.BUTTON, attributes: {name: 'Click me'}}); + const textField = rootNode.find({role: RoleType.TEXT_FIELD}); + mockFeedback.expectBraille('start') + .call(button1.focus.bind(button1)) + .expectBraille(/^Click me btn/) + .call(route.bind(null, 5)) + .expectBraille(/Focus me btn/) + .call(textField.focus.bind(textField)) + .expectBraille('Edit me ed', {startIndex: 0}) + .call(route.bind(null, 3)) + .expectBraille('Edit me ed', {startIndex: 3}) + .call(function() { + assertEquals(3, textField.textSelStart); + }); + mockFeedback.replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'FocusInputElement', function() { +TEST_F('ChromeVoxBackgroundTest', 'FocusInputElement', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <input id="name" value="Lancelot"> <input id="quest" value="Grail"> <input id="color" value="Blue"> `; - this.runWithLoadedTree(site, function(rootNode) { - const name = rootNode.find({attributes: {value: 'Lancelot'}}); - const quest = rootNode.find({attributes: {value: 'Grail'}}); - const color = rootNode.find({attributes: {value: 'Blue'}}); + const rootNode = await this.runWithLoadedTree(site); + const name = rootNode.find({attributes: {value: 'Lancelot'}}); + const quest = rootNode.find({attributes: {value: 'Grail'}}); + const color = rootNode.find({attributes: {value: 'Blue'}}); - mockFeedback.call(quest.focus.bind(quest)) - .expectSpeech('Grail', 'Edit text') - .call(color.focus.bind(color)) - .expectSpeech('Blue', 'Edit text') - .call(name.focus.bind(name)) - .expectNextSpeechUtteranceIsNot('Blue') - .expectSpeech('Lancelot', 'Edit text'); - mockFeedback.replay(); - }.bind(this)); + mockFeedback.call(quest.focus.bind(quest)) + .expectSpeech('Grail', 'Edit text') + .call(color.focus.bind(color)) + .expectSpeech('Blue', 'Edit text') + .call(name.focus.bind(name)) + .expectNextSpeechUtteranceIsNot('Blue') + .expectSpeech('Lancelot', 'Edit text'); + mockFeedback.replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'UseEditableState', function() { +TEST_F('ChromeVoxBackgroundTest', 'UseEditableState', async function() { const site = ` <input type="text"></input> <p tabindex=0>hi</p> `; - this.runWithLoadedTree(site, async function(rootNode) { - const nonEditable = rootNode.find({role: RoleType.PARAGRAPH}); - const editable = rootNode.find({role: RoleType.TEXT_FIELD}); + const rootNode = await this.runWithLoadedTree(site); + const nonEditable = rootNode.find({role: RoleType.PARAGRAPH}); + const editable = rootNode.find({role: RoleType.TEXT_FIELD}); - nonEditable.focus(); - await new Promise(resolve => { - this.listenOnce(nonEditable, 'focus', resolve); - }); - assertTrue(!DesktopAutomationInterface.instance.textEditHandler); - - editable.focus(); - await new Promise(resolve => { - this.listenOnce(editable, 'focus', resolve); - }); - assertNotNullNorUndefined( - DesktopAutomationInterface.instance.textEditHandler); + nonEditable.focus(); + await new Promise(resolve => { + this.listenOnce(nonEditable, 'focus', resolve); }); + assertTrue(!DesktopAutomationInterface.instance.textEditHandler); + + editable.focus(); + await new Promise(resolve => { + this.listenOnce(editable, 'focus', resolve); + }); + assertNotNullNorUndefined( + DesktopAutomationInterface.instance.textEditHandler); }); -TEST_F('ChromeVoxBackgroundTest', 'EarconsForControls', function() { +TEST_F('ChromeVoxBackgroundTest', 'EarconsForControls', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>Initial focus will be on something that's not a control.</p> @@ -497,38 +490,37 @@ <select><option>2</option></select> <input type=range value=5> `; - this.runWithLoadedTree(site, function(rootNode) { - mockFeedback.call(doCmd('nextObject')) - .expectSpeech('MyLink') - .expectEarcon(Earcon.LINK) - .call(doCmd('nextObject')) - .expectSpeech('MyButton') - .expectEarcon(Earcon.BUTTON) - .call(doCmd('nextObject')) - .expectSpeech('Check box') - .expectEarcon(Earcon.CHECK_OFF) - .call(doCmd('nextObject')) - .expectSpeech('Check box') - .expectEarcon(Earcon.CHECK_ON) - .call(doCmd('nextObject')) - .expectSpeech('Edit text') - .expectEarcon(Earcon.EDITABLE_TEXT) + const rootNode = await this.runWithLoadedTree(site); + mockFeedback.call(doCmd('nextObject')) + .expectSpeech('MyLink') + .expectEarcon(Earcon.LINK) + .call(doCmd('nextObject')) + .expectSpeech('MyButton') + .expectEarcon(Earcon.BUTTON) + .call(doCmd('nextObject')) + .expectSpeech('Check box') + .expectEarcon(Earcon.CHECK_OFF) + .call(doCmd('nextObject')) + .expectSpeech('Check box') + .expectEarcon(Earcon.CHECK_ON) + .call(doCmd('nextObject')) + .expectSpeech('Edit text') + .expectEarcon(Earcon.EDITABLE_TEXT) - // Editable text Search re-mappings are in effect. - .call(doCmd('toggleStickyMode')) - .expectSpeech('Sticky mode enabled') - .call(doCmd('nextObject')) - .expectSpeech('List box') - .expectEarcon(Earcon.LISTBOX) - .call(doCmd('nextObject')) - .expectSpeech('Button', 'has pop up') - .expectEarcon(Earcon.POP_UP_BUTTON) - .call(doCmd('nextObject')) - .expectSpeech(/Slider/) - .expectEarcon(Earcon.SLIDER); + // Editable text Search re-mappings are in effect. + .call(doCmd('toggleStickyMode')) + .expectSpeech('Sticky mode enabled') + .call(doCmd('nextObject')) + .expectSpeech('List box') + .expectEarcon(Earcon.LISTBOX) + .call(doCmd('nextObject')) + .expectSpeech('Button', 'has pop up') + .expectEarcon(Earcon.POP_UP_BUTTON) + .call(doCmd('nextObject')) + .expectSpeech(/Slider/) + .expectEarcon(Earcon.SLIDER); - mockFeedback.replay(); - }.bind(this)); + mockFeedback.replay(); }); TEST_F('ChromeVoxBackgroundTest', 'GlobsToRegExp', function() { @@ -545,52 +537,50 @@ })(); }); -TEST_F('ChromeVoxBackgroundTest', 'ShouldNotFocusIframe', function() { +TEST_F('ChromeVoxBackgroundTest', 'ShouldNotFocusIframe', async function() { const site = ` <iframe tabindex=0 src="data:text/html,<p>Inside</p>"></iframe> <button>outside</button> `; - this.runWithLoadedTree(site, function(root) { - const iframe = root.find({role: RoleType.IFRAME}); - const button = root.find({role: RoleType.BUTTON}); + const root = await this.runWithLoadedTree(site); + const iframe = root.find({role: RoleType.IFRAME}); + const button = root.find({role: RoleType.BUTTON}); - assertEquals('iframe', iframe.role); - assertEquals('button', button.role); + assertEquals('iframe', iframe.role); + assertEquals('button', button.role); - let didFocus = false; - iframe.addEventListener('focus', function() { - didFocus = true; - }); - const b = ChromeVoxState.instance; - b.currentRange_ = cursors.Range.fromNode(button); - doCmd('previousElement'); - assertFalse(didFocus); - }.bind(this)); + let didFocus = false; + iframe.addEventListener('focus', function() { + didFocus = true; + }); + const b = ChromeVoxState.instance; + b.currentRange_ = cursors.Range.fromNode(button); + doCmd('previousElement'); + assertFalse(didFocus); }); -TEST_F('ChromeVoxBackgroundTest', 'ShouldFocusLink', function() { +TEST_F('ChromeVoxBackgroundTest', 'ShouldFocusLink', async function() { const site = ` <div><a href="#">mylink</a></div> <button>after</button> `; - this.runWithLoadedTree(site, function(root) { - const link = root.find({role: RoleType.LINK}); - const button = root.find({role: RoleType.BUTTON}); + const root = await this.runWithLoadedTree(site); + const link = root.find({role: RoleType.LINK}); + const button = root.find({role: RoleType.BUTTON}); - assertEquals('link', link.role); - assertEquals('button', button.role); + assertEquals('link', link.role); + assertEquals('button', button.role); - const didFocus = false; - link.addEventListener('focus', this.newCallback(function() { - // Success - })); - const b = ChromeVoxState.instance; - b.currentRange_ = cursors.Range.fromNode(button); - doCmd('previousElement'); - }); + const didFocus = false; + link.addEventListener('focus', this.newCallback(function() { + // Success + })); + const b = ChromeVoxState.instance; + b.currentRange_ = cursors.Range.fromNode(button); + doCmd('previousElement'); }); -TEST_F('ChromeVoxBackgroundTest', 'NoisySlider', function() { +TEST_F('ChromeVoxBackgroundTest', 'NoisySlider', async function() { const mockFeedback = this.createMockFeedback(); // Slider aria-valuetext must change otherwise blink suppresses event. const site = ` @@ -606,21 +596,20 @@ update(); </script> `; - this.runWithLoadedTree(site, function(root) { - const go = root.find({role: RoleType.BUTTON}); - const slider = root.find({role: RoleType.SLIDER}); - const focusButton = go.focus.bind(go); - const focusSlider = slider.focus.bind(slider); - mockFeedback.call(focusButton) - .expectNextSpeechUtteranceIsNot('noisy') - .call(focusSlider) - .expectSpeech('noisy') - .expectSpeech('noisy') - .replay(); - }.bind(this)); + const root = await this.runWithLoadedTree(site); + const go = root.find({role: RoleType.BUTTON}); + const slider = root.find({role: RoleType.SLIDER}); + const focusButton = go.focus.bind(go); + const focusSlider = slider.focus.bind(slider); + mockFeedback.call(focusButton) + .expectNextSpeechUtteranceIsNot('noisy') + .call(focusSlider) + .expectSpeech('noisy') + .expectSpeech('noisy') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'Checkbox', function() { +TEST_F('ChromeVoxBackgroundTest', 'Checkbox', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div id="go" role="checkbox">go</div> @@ -637,38 +626,36 @@ }); </script> `; - this.runWithLoadedTree(site, function(root) { - const cbx = root.find({role: RoleType.CHECK_BOX}); - const click = cbx.doDefault.bind(cbx); - const focus = cbx.focus.bind(cbx); - mockFeedback.call(focus) - .expectSpeech('go') - .expectSpeech('Check box') - .expectSpeech('Not checked') - .call(click) - .expectSpeech('go') - .expectSpeech('Check box') - .expectSpeech('Checked') - .call(click) - .expectSpeech('go') - .expectSpeech('Check box') - .expectSpeech('Not checked') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + const cbx = root.find({role: RoleType.CHECK_BOX}); + const click = cbx.doDefault.bind(cbx); + const focus = cbx.focus.bind(cbx); + mockFeedback.call(focus) + .expectSpeech('go') + .expectSpeech('Check box') + .expectSpeech('Not checked') + .call(click) + .expectSpeech('go') + .expectSpeech('Check box') + .expectSpeech('Checked') + .call(click) + .expectSpeech('go') + .expectSpeech('Check box') + .expectSpeech('Not checked') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'MixedCheckbox', function() { +TEST_F('ChromeVoxBackgroundTest', 'MixedCheckbox', async function() { const mockFeedback = this.createMockFeedback(); const site = '<div id="go" role="checkbox" aria-checked="mixed">go</div>'; - this.runWithLoadedTree(site, function(root) { - mockFeedback.expectSpeech('go', 'Check box', 'Partially checked').replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.expectSpeech('go', 'Check box', 'Partially checked').replay(); }); /** Tests navigating into and out of iframes using nextButton */ TEST_F( 'ChromeVoxBackgroundTest', 'ForwardNavigationThroughIframeButtons', - function() { + async function() { const mockFeedback = this.createMockFeedback(); let running = false; @@ -698,21 +685,20 @@ mockFeedback.replay(); }.bind(this); - this.runWithLoadedTree(this.iframesDoc, function(rootNode) { - chrome.automation.getDesktop(function(desktopNode) { - runTestIfIframeIsLoaded(rootNode); + const rootNode = await this.runWithLoadedTree(this.iframesDoc); + chrome.automation.getDesktop(function(desktopNode) { + runTestIfIframeIsLoaded(rootNode); - desktopNode.addEventListener('loadComplete', function(evt) { - runTestIfIframeIsLoaded(rootNode); - }, true); - }); + desktopNode.addEventListener('loadComplete', function(evt) { + runTestIfIframeIsLoaded(rootNode); + }, true); }); }); /** Tests navigating into and out of iframes using nextObject */ TEST_F( 'ChromeVoxBackgroundTest', 'ForwardObjectNavigationThroughIframes', - function() { + async function() { const mockFeedback = this.createMockFeedback(); let running = false; @@ -751,18 +737,17 @@ mockFeedback.replay(); }.bind(this); - this.runWithLoadedTree(this.iframesDoc, function(rootNode) { - chrome.automation.getDesktop(function(desktopNode) { - runTestIfIframeIsLoaded(rootNode); + const rootNode = await this.runWithLoadedTree(this.iframesDoc); + chrome.automation.getDesktop(function(desktopNode) { + runTestIfIframeIsLoaded(rootNode); - desktopNode.addEventListener('loadComplete', function(evt) { - runTestIfIframeIsLoaded(rootNode); - }, true); - }); + desktopNode.addEventListener('loadComplete', function(evt) { + runTestIfIframeIsLoaded(rootNode); + }, true); }); }); -TEST_F('ChromeVoxBackgroundTest', 'SelectOptionSelected', function() { +TEST_F('ChromeVoxBackgroundTest', 'SelectOptionSelected', async function() { // Undoes the ChromeVoxNextE2E call setting this to true. The doDefault action // should always be read. BaseAutomationHandler.announceActions = false; @@ -775,28 +760,27 @@ <option>grapefruit </select> `; - this.runWithLoadedTree(site, function(root) { - const select = root.find({role: RoleType.POP_UP_BUTTON}); - const clickSelect = select.doDefault.bind(select); - const selectLastOption = () => { - const options = select.findAll({role: RoleType.LIST_BOX_OPTION}); - options[options.length - 1].doDefault(); - }; + const root = await this.runWithLoadedTree(site); + const select = root.find({role: RoleType.POP_UP_BUTTON}); + const clickSelect = select.doDefault.bind(select); + const selectLastOption = () => { + const options = select.findAll({role: RoleType.LIST_BOX_OPTION}); + options[options.length - 1].doDefault(); + }; - mockFeedback.call(doCmd('nextObject')) - .expectSpeech('Button', 'Press Search+Space to activate') - .call(clickSelect) - .expectSpeech('apple') - .expectSpeech('Button') - .expectSpeech('Expanded') - .call(selectLastOption) - .expectNextSpeechUtteranceIsNot('apple') - .expectSpeech('grapefruit') - .replay(); - }); + mockFeedback.call(doCmd('nextObject')) + .expectSpeech('Button', 'Press Search+Space to activate') + .call(clickSelect) + .expectSpeech('apple') + .expectSpeech('Button') + .expectSpeech('Expanded') + .call(selectLastOption) + .expectNextSpeechUtteranceIsNot('apple') + .expectSpeech('grapefruit') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'ToggleButton', function() { +TEST_F('ChromeVoxBackgroundTest', 'ToggleButton', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div aria-pressed="mixed" role="button">boldface</div> @@ -804,58 +788,55 @@ <div aria-pressed="false" role="button">cancel</div> <div aria-pressed role="button">close</div> `; - this.runWithLoadedTree(site, function(root) { - const b = ChromeVoxState.instance; - const move = doCmd('nextObject'); - mockFeedback.call(move) - .expectSpeech('boldface') - .expectSpeech('Toggle Button') - .expectSpeech('Partially pressed') + const root = await this.runWithLoadedTree(site); + const b = ChromeVoxState.instance; + const move = doCmd('nextObject'); + mockFeedback.call(move) + .expectSpeech('boldface') + .expectSpeech('Toggle Button') + .expectSpeech('Partially pressed') - .call(move) - .expectSpeech('ok') - .expectSpeech('Toggle Button') - .expectSpeech('Pressed') + .call(move) + .expectSpeech('ok') + .expectSpeech('Toggle Button') + .expectSpeech('Pressed') - .call(move) - .expectSpeech('cancel') - .expectSpeech('Toggle Button') - .expectSpeech('Not pressed') + .call(move) + .expectSpeech('cancel') + .expectSpeech('Toggle Button') + .expectSpeech('Not pressed') - .call(move) - .expectSpeech('close') - .expectSpeech('Button') + .call(move) + .expectSpeech('close') + .expectSpeech('Button') - .replay(); - }); + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'EditText', function() { +TEST_F('ChromeVoxBackgroundTest', 'EditText', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <input type="text"></input> <input role="combobox" type="text"></input> `; - this.runWithLoadedTree(site, function(root) { - const nextEditText = doCmd('nextEditText'); - const previousEditText = doCmd('previousEditText'); - mockFeedback.call(nextEditText) - .expectSpeech('Combo box') - .call(previousEditText) - .expectSpeech('Edit text') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + const nextEditText = doCmd('nextEditText'); + const previousEditText = doCmd('previousEditText'); + mockFeedback.call(nextEditText) + .expectSpeech('Combo box') + .call(previousEditText) + .expectSpeech('Edit text') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'ComboBox', function() { +TEST_F('ChromeVoxBackgroundTest', 'ComboBox', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.comboBoxDoc, function() { - mockFeedback.expectSpeech('Edit text', 'Choose an item', 'Combo box') - .replay(); - }); + await this.runWithLoadedTree(this.comboBoxDoc); + mockFeedback.expectSpeech('Edit text', 'Choose an item', 'Combo box') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'BackwardForwardSync', function() { +TEST_F('ChromeVoxBackgroundTest', 'BackwardForwardSync', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div aria-label="Group" role="group" tabindex=0> @@ -867,105 +848,101 @@ </li> </ul> `; - this.runWithLoadedTree(site, function(root) { - const listItem = root.find({role: RoleType.LIST_ITEM}); + const root = await this.runWithLoadedTree(site); + const listItem = root.find({role: RoleType.LIST_ITEM}); - mockFeedback.call(listItem.focus.bind(listItem)) - .expectSpeech('ok', 'List item') - .call(this.doCmd('nextObject')) - .expectSpeech('\u2022 ') // bullet - .call(this.doCmd('nextObject')) - .expectSpeech('Button') - .call(this.doCmd('previousObject')) - .expectSpeech('\u2022 ') // bullet - .call(this.doCmd('previousObject')) - .expectSpeech('List item') - .call(this.doCmd('previousObject')) - .expectSpeech('Edit text') - .call(this.doCmd('previousObject')) - .expectSpeech('Group') - .replay(); - }); + mockFeedback.call(listItem.focus.bind(listItem)) + .expectSpeech('ok', 'List item') + .call(this.doCmd('nextObject')) + .expectSpeech('\u2022 ') // bullet + .call(this.doCmd('nextObject')) + .expectSpeech('Button') + .call(this.doCmd('previousObject')) + .expectSpeech('\u2022 ') // bullet + .call(this.doCmd('previousObject')) + .expectSpeech('List item') + .call(this.doCmd('previousObject')) + .expectSpeech('Edit text') + .call(this.doCmd('previousObject')) + .expectSpeech('Group') + .replay(); }); /** Tests that navigation works when the current object disappears. */ -TEST_F('ChromeVoxBackgroundTest', 'DisappearingObject', function() { +TEST_F('ChromeVoxBackgroundTest', 'DisappearingObject', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.disappearingObjectDoc, function(rootNode) { - const deleteButton = - rootNode.find({role: RoleType.BUTTON, attributes: {name: 'Delete'}}); - const pressDelete = deleteButton.doDefault.bind(deleteButton); - mockFeedback.expectSpeech('start').expectBraille('start'); + const rootNode = await this.runWithLoadedTree(this.disappearingObjectDoc); + const deleteButton = + rootNode.find({role: RoleType.BUTTON, attributes: {name: 'Delete'}}); + const pressDelete = deleteButton.doDefault.bind(deleteButton); + mockFeedback.expectSpeech('start').expectBraille('start'); - mockFeedback.call(doCmd('nextObject')) - .expectSpeech('Before1') - .call(doCmd('nextObject')) - .expectSpeech('Before2') - .call(doCmd('nextObject')) - .expectSpeech('Before3') - .call(doCmd('nextObject')) - .expectSpeech('Disappearing') - .call(pressDelete) - .expectSpeech('Deleted') - .call(doCmd('nextObject')) - .expectSpeech('After1') - .call(doCmd('nextObject')) - .expectSpeech('After2') - .call(doCmd('previousObject')) - .expectSpeech('After1') - .call(doCmd('dumpTree')); + mockFeedback.call(doCmd('nextObject')) + .expectSpeech('Before1') + .call(doCmd('nextObject')) + .expectSpeech('Before2') + .call(doCmd('nextObject')) + .expectSpeech('Before3') + .call(doCmd('nextObject')) + .expectSpeech('Disappearing') + .call(pressDelete) + .expectSpeech('Deleted') + .call(doCmd('nextObject')) + .expectSpeech('After1') + .call(doCmd('nextObject')) + .expectSpeech('After2') + .call(doCmd('previousObject')) + .expectSpeech('After1') + .call(doCmd('dumpTree')); - /* - // This is broken by cl/1260523 making tree updating (more) - asynchronous. - // TODO(aboxhall/dtseng): Add a function to wait for next tree update? - mockFeedback - .call(doCmd('previousObject')) - .expectSpeech('Before3'); - */ + /* + // This is broken by cl/1260523 making tree updating (more) + asynchronous. + // TODO(aboxhall/dtseng): Add a function to wait for next tree update? + mockFeedback + .call(doCmd('previousObject')) + .expectSpeech('Before3'); + */ - mockFeedback.replay(); - }); + mockFeedback.replay(); }); /** Tests that focus jumps to details properly when indicated. */ -TEST_F('ChromeVoxBackgroundTest', 'JumpToDetails', function() { +TEST_F('ChromeVoxBackgroundTest', 'JumpToDetails', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.detailsDoc, function(rootNode) { - mockFeedback.call(doCmd('jumpToDetails')).expectSpeech('Details'); - mockFeedback.replay(); - }); + const rootNode = await this.runWithLoadedTree(this.detailsDoc); + mockFeedback.call(doCmd('jumpToDetails')).expectSpeech('Details'); + mockFeedback.replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'ButtonNameValueDescription', function() { - const mockFeedback = this.createMockFeedback(); - const site = '<input type="submit" aria-label="foo" value="foo"></input>'; - this.runWithLoadedTree(site, function(root) { - const btn = root.find({role: RoleType.BUTTON}); - mockFeedback.call(btn.focus.bind(btn)) - .expectSpeech('foo') - .expectSpeech('Button') - .replay(); - }); -}); +TEST_F( + 'ChromeVoxBackgroundTest', 'ButtonNameValueDescription', async function() { + const mockFeedback = this.createMockFeedback(); + const site = '<input type="submit" aria-label="foo" value="foo"></input>'; + const root = await this.runWithLoadedTree(site); + const btn = root.find({role: RoleType.BUTTON}); + mockFeedback.call(btn.focus.bind(btn)) + .expectSpeech('foo') + .expectSpeech('Button') + .replay(); + }); -TEST_F('ChromeVoxBackgroundTest', 'NameFromHeadingLink', function() { +TEST_F('ChromeVoxBackgroundTest', 'NameFromHeadingLink', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>before</p> <h1><a href="google.com">go</a><p>here</p></h1> `; - this.runWithLoadedTree(site, function(root) { - const link = root.find({role: RoleType.LINK}); - mockFeedback.call(link.focus.bind(link)) - .expectSpeech('go') - .expectSpeech('Link') - .expectSpeech('Heading 1') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + const link = root.find({role: RoleType.LINK}); + mockFeedback.call(link.focus.bind(link)) + .expectSpeech('go') + .expectSpeech('Link') + .expectSpeech('Heading 1') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'OptionChildIndexCount', function() { +TEST_F('ChromeVoxBackgroundTest', 'OptionChildIndexCount', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div role="listbox"> @@ -975,57 +952,56 @@ </div> `; - this.runWithLoadedTree(site, function(root) { - // Select first child of the list box, similar to what happens if navigated - // by Tab. - const firstChild = root.find({role: RoleType.PARAGRAPH}); - mockFeedback - .call( - () => ChromeVoxState.instance.setCurrentRange( - cursors.Range.fromNode(firstChild))) - .call(doCmd('nextObject')) - .expectSpeech('List box') - .expectSpeech('Fruits') - .call(doCmd('nextObject')) - .expectSpeech('apple') - .expectSpeech(' 1 of 2 ') - .call(doCmd('nextObject')) - .expectSpeech('banana') - .expectSpeech(' 2 of 2 ') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + // Select first child of the list box, similar to what happens if navigated + // by Tab. + const firstChild = root.find({role: RoleType.PARAGRAPH}); + mockFeedback + .call( + () => ChromeVoxState.instance.setCurrentRange( + cursors.Range.fromNode(firstChild))) + .call(doCmd('nextObject')) + .expectSpeech('List box') + .expectSpeech('Fruits') + .call(doCmd('nextObject')) + .expectSpeech('apple') + .expectSpeech(' 1 of 2 ') + .call(doCmd('nextObject')) + .expectSpeech('banana') + .expectSpeech(' 2 of 2 ') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'ListMarkerIsIgnored', function() { +TEST_F('ChromeVoxBackgroundTest', 'ListMarkerIsIgnored', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree('<ul><li>apple</ul>', function(root) { - mockFeedback.call(doCmd('nextObject')) - .expectNextSpeechUtteranceIsNot('listMarker') - .expectSpeech('\u2022 apple') // bullet apple - .replay(); - }); + const root = await this.runWithLoadedTree('<ul><li>apple</ul>'); + mockFeedback.call(doCmd('nextObject')) + .expectNextSpeechUtteranceIsNot('listMarker') + .expectSpeech('\u2022 apple') // bullet apple + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'SymetricComplexHeading', function() { +TEST_F('ChromeVoxBackgroundTest', 'SymetricComplexHeading', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <h4><p>NW</p><p>NE</p></h4> <h4><p>SW</p><p>SE</p></h4> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.call(doCmd('nextHeading')) - .expectNextSpeechUtteranceIsNot('NE') - .expectSpeech('NW') - .call(doCmd('previousHeading')) - .expectNextSpeechUtteranceIsNot('NE') - .expectSpeech('NW') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.call(doCmd('nextHeading')) + .expectNextSpeechUtteranceIsNot('NE') + .expectSpeech('NW') + .call(doCmd('previousHeading')) + .expectNextSpeechUtteranceIsNot('NE') + .expectSpeech('NW') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'ContentEditableJumpSyncsRange', function() { - const mockFeedback = this.createMockFeedback(); - const site = ` +TEST_F( + 'ChromeVoxBackgroundTest', 'ContentEditableJumpSyncsRange', + async function() { + const mockFeedback = this.createMockFeedback(); + const site = ` <p>start</p> <div contenteditable> <h1>Top News</h1> @@ -1033,61 +1009,59 @@ <h1>Sports</h1> </div> `; - this.runWithLoadedTree(site, function(root) { - const assertRangeHasText = function(text) { - return function() { - assertEquals( - text, ChromeVoxState.instance.getCurrentRange().start.node.name); + const root = await this.runWithLoadedTree(site); + const assertRangeHasText = function(text) { + return function() { + assertEquals( + text, ChromeVoxState.instance.getCurrentRange().start.node.name); + }; }; - }; - mockFeedback.call(doCmd('nextEditText')) - .expectSpeech('Top News Most Popular Sports') - .call(doCmd('nextHeading')) - .expectSpeech('Top News') - .call(assertRangeHasText('Top News')) - .call(doCmd('nextHeading')) - .expectSpeech('Most Popular') - .call(assertRangeHasText('Most Popular')) - .call(doCmd('nextHeading')) - .expectSpeech('Sports') - .call(assertRangeHasText('Sports')) - .replay(); - }); -}); + mockFeedback.call(doCmd('nextEditText')) + .expectSpeech('Top News Most Popular Sports') + .call(doCmd('nextHeading')) + .expectSpeech('Top News') + .call(assertRangeHasText('Top News')) + .call(doCmd('nextHeading')) + .expectSpeech('Most Popular') + .call(assertRangeHasText('Most Popular')) + .call(doCmd('nextHeading')) + .expectSpeech('Sports') + .call(assertRangeHasText('Sports')) + .replay(); + }); -TEST_F('ChromeVoxBackgroundTest', 'Selection', function() { +TEST_F('ChromeVoxBackgroundTest', 'Selection', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>simple</p> <p>doc</p> `; - this.runWithLoadedTree(site, function(root) { - // Fakes a toggleSelection command. - root.addEventListener(EventType.DOCUMENT_SELECTION_CHANGED, function() { - if (root.focusObject.name === 'simple' && root.focusOffset === 3) { - CommandHandlerInterface.instance.onCommand('toggleSelection'); - } - }, true); + const root = await this.runWithLoadedTree(site); + // Fakes a toggleSelection command. + root.addEventListener(EventType.DOCUMENT_SELECTION_CHANGED, function() { + if (root.focusObject.name === 'simple' && root.focusOffset === 3) { + CommandHandlerInterface.instance.onCommand('toggleSelection'); + } + }, true); - mockFeedback.call(doCmd('toggleSelection')) - .expectSpeech('simple', 'selected') - .call(doCmd('nextObject')) - .expectSpeech('doc', 'selected') - .call(doCmd('previousObject')) - .expectSpeech('doc', 'unselected') - .call(doCmd('nextCharacter')) - .expectSpeech('i', 'selected') - .call(doCmd('previousCharacter')) - .expectSpeech('i', 'unselected') - .call(doCmd('nextCharacter')) - .call(doCmd('nextCharacter')) - .expectSpeech('End selection', 'sim') - .replay(); - }); + mockFeedback.call(doCmd('toggleSelection')) + .expectSpeech('simple', 'selected') + .call(doCmd('nextObject')) + .expectSpeech('doc', 'selected') + .call(doCmd('previousObject')) + .expectSpeech('doc', 'unselected') + .call(doCmd('nextCharacter')) + .expectSpeech('i', 'selected') + .call(doCmd('previousCharacter')) + .expectSpeech('i', 'unselected') + .call(doCmd('nextCharacter')) + .call(doCmd('nextCharacter')) + .expectSpeech('End selection', 'sim') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'BasicTableCommands', function() { +TEST_F('ChromeVoxBackgroundTest', 'BasicTableCommands', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <table border=1> @@ -1095,70 +1069,69 @@ <tr><td>Dan</td><td>Mr</td><td>666 Elm Street</td><td>212 222 5555</td></tr> </table> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.call(doCmd('nextRow')) - .expectSpeech('Dan', 'row 2 column 1') - .call(doCmd('previousRow')) - .expectSpeech('name', 'row 1 column 1') - .call(doCmd('previousRow')) - .expectSpeech('No cell above') - .call(doCmd('nextCol')) - .expectSpeech('title', 'row 1 column 2') - .call(doCmd('nextRow')) - .expectSpeech('Mr', 'row 2 column 2') - .call(doCmd('previousRow')) - .expectSpeech('title', 'row 1 column 2') - .call(doCmd('nextCol')) - .expectSpeech('address', 'row 1 column 3') - .call(doCmd('nextCol')) - .expectSpeech('phone', 'row 1 column 4') - .call(doCmd('nextCol')) - .expectSpeech('No cell right') - .call(doCmd('previousRow')) - .expectSpeech('No cell above') - .call(doCmd('nextRow')) - .expectSpeech('212 222 5555', 'row 2 column 4') - .call(doCmd('nextRow')) - .expectSpeech('No cell below') - .call(doCmd('nextCol')) - .expectSpeech('No cell right') - .call(doCmd('previousCol')) - .expectSpeech('666 Elm Street', 'row 2 column 3') - .call(doCmd('previousCol')) - .expectSpeech('Mr', 'row 2 column 2') + const root = await this.runWithLoadedTree(site); + mockFeedback.call(doCmd('nextRow')) + .expectSpeech('Dan', 'row 2 column 1') + .call(doCmd('previousRow')) + .expectSpeech('name', 'row 1 column 1') + .call(doCmd('previousRow')) + .expectSpeech('No cell above') + .call(doCmd('nextCol')) + .expectSpeech('title', 'row 1 column 2') + .call(doCmd('nextRow')) + .expectSpeech('Mr', 'row 2 column 2') + .call(doCmd('previousRow')) + .expectSpeech('title', 'row 1 column 2') + .call(doCmd('nextCol')) + .expectSpeech('address', 'row 1 column 3') + .call(doCmd('nextCol')) + .expectSpeech('phone', 'row 1 column 4') + .call(doCmd('nextCol')) + .expectSpeech('No cell right') + .call(doCmd('previousRow')) + .expectSpeech('No cell above') + .call(doCmd('nextRow')) + .expectSpeech('212 222 5555', 'row 2 column 4') + .call(doCmd('nextRow')) + .expectSpeech('No cell below') + .call(doCmd('nextCol')) + .expectSpeech('No cell right') + .call(doCmd('previousCol')) + .expectSpeech('666 Elm Street', 'row 2 column 3') + .call(doCmd('previousCol')) + .expectSpeech('Mr', 'row 2 column 2') - .call(doCmd('goToRowLastCell')) - .expectSpeech('212 222 5555', 'row 2 column 4') - .call(doCmd('goToRowLastCell')) - .expectSpeech('212 222 5555') - .call(doCmd('goToRowFirstCell')) - .expectSpeech('Dan', 'row 2 column 1') - .call(doCmd('goToRowFirstCell')) - .expectSpeech('Dan') + .call(doCmd('goToRowLastCell')) + .expectSpeech('212 222 5555', 'row 2 column 4') + .call(doCmd('goToRowLastCell')) + .expectSpeech('212 222 5555') + .call(doCmd('goToRowFirstCell')) + .expectSpeech('Dan', 'row 2 column 1') + .call(doCmd('goToRowFirstCell')) + .expectSpeech('Dan') - .call(doCmd('goToColFirstCell')) - .expectSpeech('name', 'row 1 column 1') - .call(doCmd('goToColFirstCell')) - .expectSpeech('name') - .call(doCmd('goToColLastCell')) - .expectSpeech('Dan', 'row 2 column 1') - .call(doCmd('goToColLastCell')) - .expectSpeech('Dan') + .call(doCmd('goToColFirstCell')) + .expectSpeech('name', 'row 1 column 1') + .call(doCmd('goToColFirstCell')) + .expectSpeech('name') + .call(doCmd('goToColLastCell')) + .expectSpeech('Dan', 'row 2 column 1') + .call(doCmd('goToColLastCell')) + .expectSpeech('Dan') - .call(doCmd('goToLastCell')) - .expectSpeech('212 222 5555', 'row 2 column 4') - .call(doCmd('goToLastCell')) - .expectSpeech('212 222 5555') - .call(doCmd('goToFirstCell')) - .expectSpeech('name', 'row 1 column 1') - .call(doCmd('goToFirstCell')) - .expectSpeech('name') + .call(doCmd('goToLastCell')) + .expectSpeech('212 222 5555', 'row 2 column 4') + .call(doCmd('goToLastCell')) + .expectSpeech('212 222 5555') + .call(doCmd('goToFirstCell')) + .expectSpeech('name', 'row 1 column 1') + .call(doCmd('goToFirstCell')) + .expectSpeech('name') - .replay(); - }); + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'MissingTableCells', function() { +TEST_F('ChromeVoxBackgroundTest', 'MissingTableCells', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <table border=1> @@ -1167,141 +1140,133 @@ <tr><td>f</td></tr> </table> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.call(doCmd('goToRowLastCell')) - .expectSpeech('c', 'row 1 column 3') - .call(doCmd('goToRowLastCell')) - .expectSpeech('c') - .call(doCmd('goToRowFirstCell')) - .expectSpeech('a', 'row 1 column 1') - .call(doCmd('goToRowFirstCell')) - .expectSpeech('a') + const root = await this.runWithLoadedTree(site); + mockFeedback.call(doCmd('goToRowLastCell')) + .expectSpeech('c', 'row 1 column 3') + .call(doCmd('goToRowLastCell')) + .expectSpeech('c') + .call(doCmd('goToRowFirstCell')) + .expectSpeech('a', 'row 1 column 1') + .call(doCmd('goToRowFirstCell')) + .expectSpeech('a') - .call(doCmd('nextCol')) - .expectSpeech('b', 'row 1 column 2') + .call(doCmd('nextCol')) + .expectSpeech('b', 'row 1 column 2') - .call(doCmd('goToColLastCell')) - .expectSpeech('e', 'row 2 column 2') - .call(doCmd('goToColLastCell')) - .expectSpeech('e') - .call(doCmd('goToColFirstCell')) - .expectSpeech('b', 'row 1 column 2') - .call(doCmd('goToColFirstCell')) - .expectSpeech('b') + .call(doCmd('goToColLastCell')) + .expectSpeech('e', 'row 2 column 2') + .call(doCmd('goToColLastCell')) + .expectSpeech('e') + .call(doCmd('goToColFirstCell')) + .expectSpeech('b', 'row 1 column 2') + .call(doCmd('goToColFirstCell')) + .expectSpeech('b') - .call(doCmd('goToFirstCell')) - .expectSpeech('a', 'row 1 column 1') - .call(doCmd('goToFirstCell')) - .expectSpeech('a') - .call(doCmd('goToLastCell')) - .expectSpeech('f', 'row 3 column 1') - .call(doCmd('goToLastCell')) - .expectSpeech('f') - .replay(); - }); + .call(doCmd('goToFirstCell')) + .expectSpeech('a', 'row 1 column 1') + .call(doCmd('goToFirstCell')) + .expectSpeech('a') + .call(doCmd('goToLastCell')) + .expectSpeech('f', 'row 3 column 1') + .call(doCmd('goToLastCell')) + .expectSpeech('f') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'DisabledState', function() { +TEST_F('ChromeVoxBackgroundTest', 'DisabledState', async function() { const mockFeedback = this.createMockFeedback(); const site = '<button aria-disabled="true">ok</button>'; - this.runWithLoadedTree(site, function(root) { - mockFeedback.expectSpeech('ok', 'Disabled', 'Button').replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.expectSpeech('ok', 'Disabled', 'Button').replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'HeadingLevels', function() { +TEST_F('ChromeVoxBackgroundTest', 'HeadingLevels', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <h1>1</h1><h2>2</h2><h3>3</h3><h4>4</h4><h5>5</h5><h6>6</h6> `; - this.runWithLoadedTree(site, function(root) { - const makeLevelAssertions = function(level) { - mockFeedback.call(doCmd('nextHeading' + level)) - .expectSpeech('Heading ' + level) - .call(doCmd('nextHeading' + level)) - .expectEarcon('wrap') - .call(doCmd('previousHeading' + level)) - .expectEarcon('wrap'); - }; - for (let i = 1; i <= 6; i++) { - makeLevelAssertions(i); - } - mockFeedback.replay(); - }); + const root = await this.runWithLoadedTree(site); + const makeLevelAssertions = function(level) { + mockFeedback.call(doCmd('nextHeading' + level)) + .expectSpeech('Heading ' + level) + .call(doCmd('nextHeading' + level)) + .expectEarcon('wrap') + .call(doCmd('previousHeading' + level)) + .expectEarcon('wrap'); + }; + for (let i = 1; i <= 6; i++) { + makeLevelAssertions(i); + } + mockFeedback.replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'EditableNavigation', function() { +TEST_F('ChromeVoxBackgroundTest', 'EditableNavigation', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div contenteditable>this is a test</div> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.call(doCmd('nextObject')) - .expectSpeech('this is a test') - .call(doCmd('nextObject')) - .expectSpeech('this is a test') - .call(doCmd('nextWord')) - .expectSpeech('is') - .call(doCmd('nextWord')) - .expectSpeech('a') - .call(doCmd('nextWord')) - .expectSpeech('test') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.call(doCmd('nextObject')) + .expectSpeech('this is a test') + .call(doCmd('nextObject')) + .expectSpeech('this is a test') + .call(doCmd('nextWord')) + .expectSpeech('is') + .call(doCmd('nextWord')) + .expectSpeech('a') + .call(doCmd('nextWord')) + .expectSpeech('test') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'NavigationMovesFocus', function() { +TEST_F('ChromeVoxBackgroundTest', 'NavigationMovesFocus', async function() { const site = ` <p>start</p> <input type="text"></input> `; - this.runWithLoadedTree(site, function(root) { - this.listenOnce( - root.find({role: RoleType.TEXT_FIELD}), 'focus', function(e) { - const focus = ChromeVoxState.instance.currentRange.start.node; - assertEquals(RoleType.TEXT_FIELD, focus.role); - assertTrue(focus.state.focused); - }); - doCmd('nextEditText')(); + const root = await this.runWithLoadedTree(site); + this.listenOnce(root.find({role: RoleType.TEXT_FIELD}), 'focus', function(e) { + const focus = ChromeVoxState.instance.currentRange.start.node; + assertEquals(RoleType.TEXT_FIELD, focus.role); + assertTrue(focus.state.focused); }); + doCmd('nextEditText')(); }); -TEST_F('ChromeVoxBackgroundTest', 'BrailleCaretNavigation', function() { +TEST_F('ChromeVoxBackgroundTest', 'BrailleCaretNavigation', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>This is a<em>test</em> of inline braille<br>with a second line</p> `; - this.runWithLoadedTree(site, function(root) { - const text = 'This is a'; - mockFeedback.call(doCmd('nextCharacter')) - .expectBraille(text, {startIndex: 1, endIndex: 2}) // h - .call(doCmd('nextCharacter')) - .expectBraille(text, {startIndex: 2, endIndex: 3}) // i - .call(doCmd('nextWord')) - .expectBraille(text, {startIndex: 5, endIndex: 7}) // is - .call(doCmd('previousWord')) - .expectBraille(text, {startIndex: 0, endIndex: 4}) // This - .call(doCmd('nextLine')) - // Ensure nothing is selected when the range covers the entire line. - .expectBraille('with a second line', {startIndex: -1, endIndex: -1}) - .replay(); - }); + const root = await this.runWithLoadedTree(site); + const text = 'This is a'; + mockFeedback.call(doCmd('nextCharacter')) + .expectBraille(text, {startIndex: 1, endIndex: 2}) // h + .call(doCmd('nextCharacter')) + .expectBraille(text, {startIndex: 2, endIndex: 3}) // i + .call(doCmd('nextWord')) + .expectBraille(text, {startIndex: 5, endIndex: 7}) // is + .call(doCmd('previousWord')) + .expectBraille(text, {startIndex: 0, endIndex: 4}) // This + .call(doCmd('nextLine')) + // Ensure nothing is selected when the range covers the entire line. + .expectBraille('with a second line', {startIndex: -1, endIndex: -1}) + .replay(); }); // This tests ChromeVox's special support for following an in-page link // if you force-click on it. Compare with InPageLinks, below. -TEST_F('ChromeVoxBackgroundTest', 'ForceClickInPageLinks', function() { +TEST_F('ChromeVoxBackgroundTest', 'ForceClickInPageLinks', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <a href="#there">hi</a> <button id="there">there</button> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.expectSpeech('hi', 'Internal link') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech('there', 'Button') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.expectSpeech('hi', 'Internal link') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech('there', 'Button') + .replay(); }); // This tests ChromeVox's handling of the scrolledToAnchor event, which is @@ -1312,22 +1277,23 @@ // Note: this test needs the test server running because the browser // does not follow same-page links on data urls (because it modifies the // url fragment, and any change to the url is disallowed for a data url). -TEST_F('ChromeVoxBackgroundTestWithTestServer', 'InPageLinks', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(undefined, function(root) { - mockFeedback.call(doCmd('nextObject')) - .expectSpeech('Jump', 'Internal link') - .call(press(KeyCode.RETURN)) - .expectSpeech('Found It') - .call(doCmd('nextHeading')) - .expectSpeech('Continue Here', 'Heading 2') - .replay(); - }.bind(this), { - url: `${testRunnerParams.testServerBaseUrl}accessibility/in_page_links.html` - }); -}); +TEST_F( + 'ChromeVoxBackgroundTestWithTestServer', 'InPageLinks', async function() { + const mockFeedback = this.createMockFeedback(); + const root = await this.runWithLoadedTree(undefined, { + url: `${ + testRunnerParams.testServerBaseUrl}accessibility/in_page_links.html` + }); + mockFeedback.call(doCmd('nextObject')) + .expectSpeech('Jump', 'Internal link') + .call(press(KeyCode.RETURN)) + .expectSpeech('Found It') + .call(doCmd('nextHeading')) + .expectSpeech('Continue Here', 'Heading 2') + .replay(); + }); -TEST_F('ChromeVoxBackgroundTest', 'ListItem', function() { +TEST_F('ChromeVoxBackgroundTest', 'ListItem', async function() { this.resetContextualOutput(); const mockFeedback = this.createMockFeedback(); const site = ` @@ -1335,130 +1301,125 @@ <ul><li>apple<li>grape<li>banana</ul> <ol><li>pork<li>beef<li>chicken</ol> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.call(doCmd('nextLine')) - .expectSpeech('\u2022 apple', 'List item') - .expectBraille('\u2022 apple lstitm lst +3') - .call(doCmd('nextLine')) - .expectSpeech('\u2022 grape', 'List item') - .expectBraille('\u2022 grape lstitm') - .call(doCmd('nextLine')) - .expectSpeech('\u2022 banana', 'List item') - .expectBraille('\u2022 banana lstitm lst end') + const root = await this.runWithLoadedTree(site); + mockFeedback.call(doCmd('nextLine')) + .expectSpeech('\u2022 apple', 'List item') + .expectBraille('\u2022 apple lstitm lst +3') + .call(doCmd('nextLine')) + .expectSpeech('\u2022 grape', 'List item') + .expectBraille('\u2022 grape lstitm') + .call(doCmd('nextLine')) + .expectSpeech('\u2022 banana', 'List item') + .expectBraille('\u2022 banana lstitm lst end') - // Object nav should be the same. - .call(doCmd('nextObject')) - .expectSpeech('1. pork', 'List item') - .expectBraille('1. pork lstitm lst +3') - .call(doCmd('nextObject')) - .expectSpeech('2. beef', 'List item') - .expectBraille('2. beef lstitm') + // Object nav should be the same. + .call(doCmd('nextObject')) + .expectSpeech('1. pork', 'List item') + .expectBraille('1. pork lstitm lst +3') + .call(doCmd('nextObject')) + .expectSpeech('2. beef', 'List item') + .expectBraille('2. beef lstitm') - // Mixing with line nav. - .call(doCmd('nextLine')) - .expectSpeech('3. chicken', 'List item') - .expectBraille('3. chicken lstitm lst end') - .replay(); - }); + // Mixing with line nav. + .call(doCmd('nextLine')) + .expectSpeech('3. chicken', 'List item') + .expectBraille('3. chicken lstitm lst end') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'BusyHeading', function() { +TEST_F('ChromeVoxBackgroundTest', 'BusyHeading', async function() { this.resetContextualOutput(); const mockFeedback = this.createMockFeedback(); const site = ` <p>start</p> <h2><a href="#">Lots</a><a href="#">going</a><a href="#">here</a></h2> `; - this.runWithLoadedTree(site, function(root) { - // In the past, this would have inserted the 'heading 2' after the first - // link's output. Make sure it goes to the end. - mockFeedback.call(doCmd('nextLine')) - .expectSpeech( - 'Lots', 'Link', 'going', 'Link', 'here', 'Link', 'Heading 2') - .expectBraille('Lots lnk going lnk here lnk h2') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + // In the past, this would have inserted the 'heading 2' after the first + // link's output. Make sure it goes to the end. + mockFeedback.call(doCmd('nextLine')) + .expectSpeech( + 'Lots', 'Link', 'going', 'Link', 'here', 'Link', 'Heading 2') + .expectBraille('Lots lnk going lnk here lnk h2') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'NodeVsSubnode', function() { +TEST_F('ChromeVoxBackgroundTest', 'NodeVsSubnode', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree('<a href="#">test</a>', function(root) { - const link = root.find({role: RoleType.LINK}); - function outputLinkRange(start, end) { - return function() { - new Output() - .withSpeech(new cursors.Range( - new cursors.Cursor(link, start), new cursors.Cursor(link, end))) - .go(); - }; - } + const root = await this.runWithLoadedTree('<a href="#">test</a>'); + const link = root.find({role: RoleType.LINK}); + function outputLinkRange(start, end) { + return function() { + new Output() + .withSpeech(new cursors.Range( + new cursors.Cursor(link, start), new cursors.Cursor(link, end))) + .go(); + }; + } - mockFeedback.call(outputLinkRange(0, 0)) - .expectSpeech('test', 'Internal link') - .call(outputLinkRange(0, 1)) - .expectSpeech('t') - .call(outputLinkRange(1, 1)) - .expectSpeech('test', 'Internal link') - .call(outputLinkRange(1, 2)) - .expectSpeech('e') - .call(outputLinkRange(1, 3)) - .expectNextSpeechUtteranceIsNot('Internal link') - .expectSpeech('es') - .call(outputLinkRange(0, 4)) - .expectSpeech('test', 'Internal link') - .replay(); - }); + mockFeedback.call(outputLinkRange(0, 0)) + .expectSpeech('test', 'Internal link') + .call(outputLinkRange(0, 1)) + .expectSpeech('t') + .call(outputLinkRange(1, 1)) + .expectSpeech('test', 'Internal link') + .call(outputLinkRange(1, 2)) + .expectSpeech('e') + .call(outputLinkRange(1, 3)) + .expectNextSpeechUtteranceIsNot('Internal link') + .expectSpeech('es') + .call(outputLinkRange(0, 4)) + .expectSpeech('test', 'Internal link') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'NativeFind', function() { +TEST_F('ChromeVoxBackgroundTest', 'NativeFind', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <a href="#">grape</a> <a href="#">pineapple</a> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.call(press(KeyCode.F, {ctrl: true})) - .expectSpeech('Find', 'Edit text') - .call(press(KeyCode.G)) - .expectSpeech('grape', 'Link') - .call(press(KeyCode.BACK)) - .call(press(KeyCode.L)) - .expectSpeech('pineapple', 'Link') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.call(press(KeyCode.F, {ctrl: true})) + .expectSpeech('Find', 'Edit text') + .call(press(KeyCode.G)) + .expectSpeech('grape', 'Link') + .call(press(KeyCode.BACK)) + .call(press(KeyCode.L)) + .expectSpeech('pineapple', 'Link') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'EditableKeyCommand', function() { +TEST_F('ChromeVoxBackgroundTest', 'EditableKeyCommand', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <input type="text"></input> <textarea>test</textarea> <div role="textbox" contenteditable>test</div> `; - this.runWithLoadedTree(site, function(root) { - const assertCurNode = function(node) { - return function() { - assertEquals(node, ChromeVoxState.instance.currentRange.start.node); - }; + const root = await this.runWithLoadedTree(site); + const assertCurNode = function(node) { + return function() { + assertEquals(node, ChromeVoxState.instance.currentRange.start.node); }; + }; - const textField = root.firstChild; - const textArea = textField.nextSibling; - const contentEditable = textArea.nextSibling; + const textField = root.firstChild; + const textArea = textField.nextSibling; + const contentEditable = textArea.nextSibling; - mockFeedback.call(assertCurNode(textField)) - .call(doCmd('nextObject')) - .call(assertCurNode(textArea)) - .call(doCmd('nextObject')) - .call(assertCurNode(contentEditable)) - .call(doCmd('previousObject')) - .expectSpeech('Text area') - .call(assertCurNode(textArea)) - .call(doCmd('previousObject')) - .call(assertCurNode(textField)) + mockFeedback.call(assertCurNode(textField)) + .call(doCmd('nextObject')) + .call(assertCurNode(textArea)) + .call(doCmd('nextObject')) + .call(assertCurNode(contentEditable)) + .call(doCmd('previousObject')) + .expectSpeech('Text area') + .call(assertCurNode(textArea)) + .call(doCmd('previousObject')) + .call(assertCurNode(textField)) - .replay(); - }); + .replay(); }); // TODO(crbug.com/935678): Test times out flakily in MSAN builds. @@ -1470,11 +1431,11 @@ #define MAYBE_TextSelectionAndLiveRegion TextSelectionAndLiveRegion #endif `, - 'ChromeVoxBackgroundTest', 'MAYBE_TextSelectionAndLiveRegion', function() { + 'ChromeVoxBackgroundTest', 'MAYBE_TextSelectionAndLiveRegion', + async function() { BaseAutomationHandler.announceActions = true; const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <p>start</p> <div><input value="test" type="text"></input></div> <div id="live" aria-live="assertive"></div> @@ -1495,28 +1456,26 @@ } }); </script> - `, - function(root) { - const textField = root.find({role: RoleType.TEXT_FIELD}); - const div = textField.parent; - mockFeedback.call(textField.focus.bind(textField)) - .expectSpeech('Edit text') - .call(div.doDefault.bind(div)) - .expectSpeechWithQueueMode('go', QueueMode.CATEGORY_FLUSH) + `); + const textField = root.find({role: RoleType.TEXT_FIELD}); + const div = textField.parent; + mockFeedback.call(textField.focus.bind(textField)) + .expectSpeech('Edit text') + .call(div.doDefault.bind(div)) + .expectSpeechWithQueueMode('go', QueueMode.CATEGORY_FLUSH) - .call(div.doDefault.bind(div)) - .expectSpeechWithQueueMode('queued', QueueMode.QUEUE) - .expectSpeechWithQueueMode('e', QueueMode.CATEGORY_FLUSH) + .call(div.doDefault.bind(div)) + .expectSpeechWithQueueMode('queued', QueueMode.QUEUE) + .expectSpeechWithQueueMode('e', QueueMode.CATEGORY_FLUSH) - .call(div.doDefault.bind(div)) - .expectSpeechWithQueueMode('interrupted', QueueMode.QUEUE) - .expectSpeechWithQueueMode('s', QueueMode.CATEGORY_FLUSH) + .call(div.doDefault.bind(div)) + .expectSpeechWithQueueMode('interrupted', QueueMode.QUEUE) + .expectSpeechWithQueueMode('s', QueueMode.CATEGORY_FLUSH) - .replay(); - }); + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'TableColumnHeaders', function() { +TEST_F('ChromeVoxBackgroundTest', 'TableColumnHeaders', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div role="grid"> @@ -1541,23 +1500,22 @@ </div> </div> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.call(doCmd('nextRow')) - .expectSpeech('Mountain View', 'row 2 column 1') - .call(doCmd('nextRow')) - .expectNextSpeechUtteranceIsNot('city') - .expectSpeech('San Jose', 'row 3 column 1') - .call(doCmd('nextCol')) - .expectSpeech('CA', 'row 3 column 2', 'state') - .call(doCmd('previousRow')) - .expectSpeech('CA', 'row 2 column 2') - .call(doCmd('previousRow')) - .expectSpeech('state', 'row 1 column 2') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.call(doCmd('nextRow')) + .expectSpeech('Mountain View', 'row 2 column 1') + .call(doCmd('nextRow')) + .expectNextSpeechUtteranceIsNot('city') + .expectSpeech('San Jose', 'row 3 column 1') + .call(doCmd('nextCol')) + .expectSpeech('CA', 'row 3 column 2', 'state') + .call(doCmd('previousRow')) + .expectSpeech('CA', 'row 2 column 2') + .call(doCmd('previousRow')) + .expectSpeech('state', 'row 1 column 2') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'ActiveDescendantUpdates', function() { +TEST_F('ChromeVoxBackgroundTest', 'ActiveDescendantUpdates', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div aria-label="container" tabindex=0 role="group" id="active" @@ -1576,18 +1534,17 @@ }); </script> `; - this.runWithLoadedTree(site, function(root) { - const group = root.firstChild; - mockFeedback.call(group.focus.bind(group)) - .call(group.doDefault.bind(group)) - .expectSpeech('Tree item', ' 2 of 2 ') - .call(group.doDefault.bind(group)) - .expectSpeech('Tree item', 'Not selected', ' 1 of 2 ') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + const group = root.firstChild; + mockFeedback.call(group.focus.bind(group)) + .call(group.doDefault.bind(group)) + .expectSpeech('Tree item', ' 2 of 2 ') + .call(group.doDefault.bind(group)) + .expectSpeech('Tree item', 'Not selected', ' 1 of 2 ') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'NavigationEscapesEdit', function() { +TEST_F('ChromeVoxBackgroundTest', 'NavigationEscapesEdit', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>before content editable</p> @@ -1596,100 +1553,96 @@ <textarea style="word-spacing: 1000px">this is a test</textarea> <p>after text area</p> `; - this.runWithLoadedTree(site, function(root) { - const assertBeginning = function(expected) { - const textEditHandler = - DesktopAutomationInterface.instance.textEditHandler; - assertNotNullNorUndefined(textEditHandler); - assertEquals(expected, textEditHandler.isSelectionOnFirstLine()); - }; - const assertEnd = function(expected) { - const textEditHandler = - DesktopAutomationInterface.instance.textEditHandler; - assertNotNullNorUndefined(textEditHandler); - assertEquals(expected, textEditHandler.isSelectionOnLastLine()); - }; - const [contentEditable, textArea] = - root.findAll({role: RoleType.TEXT_FIELD}); + const root = await this.runWithLoadedTree(site); + const assertBeginning = function(expected) { + const textEditHandler = DesktopAutomationInterface.instance.textEditHandler; + assertNotNullNorUndefined(textEditHandler); + assertEquals(expected, textEditHandler.isSelectionOnFirstLine()); + }; + const assertEnd = function(expected) { + const textEditHandler = DesktopAutomationInterface.instance.textEditHandler; + assertNotNullNorUndefined(textEditHandler); + assertEquals(expected, textEditHandler.isSelectionOnLastLine()); + }; + const [contentEditable, textArea] = root.findAll({role: RoleType.TEXT_FIELD}); - this.listenOnce(contentEditable, EventType.FOCUS, function() { - mockFeedback.call(assertBeginning.bind(this, true)) - .call(assertEnd.bind(this, false)) + this.listenOnce(contentEditable, EventType.FOCUS, function() { + mockFeedback.call(assertBeginning.bind(this, true)) + .call(assertEnd.bind(this, false)) - .call(press(KeyCode.DOWN)) - .expectSpeech('is') - .call(assertBeginning.bind(this, false)) - .call(assertEnd.bind(this, false)) + .call(press(KeyCode.DOWN)) + .expectSpeech('is') + .call(assertBeginning.bind(this, false)) + .call(assertEnd.bind(this, false)) - .call(press(KeyCode.DOWN)) - .expectSpeech('a') - .call(assertBeginning.bind(this, false)) - .call(assertEnd.bind(this, false)) + .call(press(KeyCode.DOWN)) + .expectSpeech('a') + .call(assertBeginning.bind(this, false)) + .call(assertEnd.bind(this, false)) - .call(press(KeyCode.DOWN)) - .expectSpeech('test') - .call(assertBeginning.bind(this, false)) - .call(assertEnd.bind(this, true)) + .call(press(KeyCode.DOWN)) + .expectSpeech('test') + .call(assertBeginning.bind(this, false)) + .call(assertEnd.bind(this, true)) - .call(textArea.focus.bind(textArea)) - .expectSpeech('Text area') - .call(assertBeginning.bind(this, true)) - .call(assertEnd.bind(this, false)) + .call(textArea.focus.bind(textArea)) + .expectSpeech('Text area') + .call(assertBeginning.bind(this, true)) + .call(assertEnd.bind(this, false)) - .call(press(40 /* ArrowDown */)) - .expectSpeech('is') - .call(assertBeginning.bind(this, false)) - .call(assertEnd.bind(this, false)) + .call(press(40 /* ArrowDown */)) + .expectSpeech('is') + .call(assertBeginning.bind(this, false)) + .call(assertEnd.bind(this, false)) - .call(press(40 /* ArrowDown */)) - .expectSpeech('a') - .call(assertBeginning.bind(this, false)) - .call(assertEnd.bind(this, false)) + .call(press(40 /* ArrowDown */)) + .expectSpeech('a') + .call(assertBeginning.bind(this, false)) + .call(assertEnd.bind(this, false)) - .call(press(40 /* ArrowDown */)) - .expectSpeech('test') - .call(assertBeginning.bind(this, false)) - .call(assertEnd.bind(this, true)) + .call(press(40 /* ArrowDown */)) + .expectSpeech('test') + .call(assertBeginning.bind(this, false)) + .call(assertEnd.bind(this, true)) - .replay(); + .replay(); - // TODO: soft line breaks currently won't work in <textarea>. - }.bind(this)); - contentEditable.focus(); - }); + // TODO: soft line breaks currently won't work in <textarea>. + }.bind(this)); + contentEditable.focus(); }); -TEST_F('ChromeVoxBackgroundTest', 'SelectDoesNotSyncNavigation', function() { - const mockFeedback = this.createMockFeedback(); - const site = ` +TEST_F( + 'ChromeVoxBackgroundTest', 'SelectDoesNotSyncNavigation', async function() { + const mockFeedback = this.createMockFeedback(); + const site = ` <select> <option>apple</option> <option>grape</option> </select> `; - this.runWithLoadedTree(site, function(root) { - const select = root.find({role: RoleType.POP_UP_BUTTON}); - mockFeedback.expectSpeech('Button', 'has pop up', 'Collapsed') - .call(select.doDefault.bind(select)) - .expectSpeech('Expanded') - .call( - () => assertEquals( - select, ChromeVoxState.instance.currentRange.start.node)) - .call(press(KeyCode.DOWN)) - .expectSpeech('grape', 'List item', ' 2 of 2 ') - .call( - () => assertEquals( - select, ChromeVoxState.instance.currentRange.start.node)) - .call(press(KeyCode.UP)) - .expectSpeech('apple', 'List item', ' 1 of 2 ') - .call( - () => assertEquals( - select, ChromeVoxState.instance.currentRange.start.node)) - .replay(); - }); -}); + const root = await this.runWithLoadedTree(site); + const select = root.find({role: RoleType.POP_UP_BUTTON}); + mockFeedback.expectSpeech('Button', 'has pop up', 'Collapsed') + .call(select.doDefault.bind(select)) + .expectSpeech('Expanded') + .call( + () => assertEquals( + select, ChromeVoxState.instance.currentRange.start.node)) + .call(press(KeyCode.DOWN)) + .expectSpeech('grape', 'List item', ' 2 of 2 ') + .call( + () => assertEquals( + select, ChromeVoxState.instance.currentRange.start.node)) + .call(press(KeyCode.UP)) + .expectSpeech('apple', 'List item', ' 1 of 2 ') + .call( + () => assertEquals( + select, ChromeVoxState.instance.currentRange.start.node)) + .replay(); + }); -TEST_F('ChromeVoxBackgroundTest', 'NavigationIgnoresLabels', function() { +TEST_F('ChromeVoxBackgroundTest', 'NavigationIgnoresLabels', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>before</p> @@ -1699,70 +1652,70 @@ <p>after</p> <button aria-labelledby="label headingLabel"></button> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.expectSpeech('before') - .call(doCmd('nextObject')) - .expectSpeech('lebal', 'Link') - .call(doCmd('nextObject')) - .expectSpeech('headingLabel', 'Heading 2') - .call(doCmd('nextObject')) - .expectSpeech('after') - .call(doCmd('previousObject')) - .expectSpeech('headingLabel', 'Heading 2') - .call(doCmd('previousObject')) - .expectSpeech('lebal', 'Link') - .call(doCmd('previousObject')) - .expectSpeech('before') - .call(doCmd('nextObject')) - .expectSpeech('lebal', 'Link') - .call(doCmd('nextObject')) - .expectSpeech('headingLabel', 'Heading 2') - .call(doCmd('nextObject')) - .expectSpeech('after') - .call(doCmd('nextObject')) - .expectSpeech('label headingLabel', 'Button') - .call(doCmd('nextObject')) - .expectEarcon(Earcon.WRAP) - .call(doCmd('nextObject')) - .expectSpeech('before') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.expectSpeech('before') + .call(doCmd('nextObject')) + .expectSpeech('lebal', 'Link') + .call(doCmd('nextObject')) + .expectSpeech('headingLabel', 'Heading 2') + .call(doCmd('nextObject')) + .expectSpeech('after') + .call(doCmd('previousObject')) + .expectSpeech('headingLabel', 'Heading 2') + .call(doCmd('previousObject')) + .expectSpeech('lebal', 'Link') + .call(doCmd('previousObject')) + .expectSpeech('before') + .call(doCmd('nextObject')) + .expectSpeech('lebal', 'Link') + .call(doCmd('nextObject')) + .expectSpeech('headingLabel', 'Heading 2') + .call(doCmd('nextObject')) + .expectSpeech('after') + .call(doCmd('nextObject')) + .expectSpeech('label headingLabel', 'Button') + .call(doCmd('nextObject')) + .expectEarcon(Earcon.WRAP) + .call(doCmd('nextObject')) + .expectSpeech('before') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'NavigationIgnoresDescriptions', function() { - const mockFeedback = this.createMockFeedback(); - const site = ` +TEST_F( + 'ChromeVoxBackgroundTest', 'NavigationIgnoresDescriptions', + async function() { + const mockFeedback = this.createMockFeedback(); + const site = ` <p>before</p> <p id="desc">label</p> <a href="#next" id="csed">lebal</a> <p>after</p> <button aria-describedby="desc"></button> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.expectSpeech('before') - .call(doCmd('nextObject')) - .expectSpeech('lebal', 'Link') - .call(doCmd('nextObject')) - .expectSpeech('after') - .call(doCmd('previousObject')) - .expectSpeech('lebal', 'Link') - .call(doCmd('previousObject')) - .expectSpeech('before') - .call(doCmd('nextObject')) - .expectSpeech('lebal', 'Link') - .call(doCmd('nextObject')) - .expectSpeech('after') - .call(doCmd('nextObject')) - .expectSpeech('label', 'lebal', 'Button') - .call(doCmd('nextObject')) - .expectEarcon(Earcon.WRAP) - .call(doCmd('nextObject')) - .expectSpeech('before') - .replay(); - }); -}); + const root = await this.runWithLoadedTree(site); + mockFeedback.expectSpeech('before') + .call(doCmd('nextObject')) + .expectSpeech('lebal', 'Link') + .call(doCmd('nextObject')) + .expectSpeech('after') + .call(doCmd('previousObject')) + .expectSpeech('lebal', 'Link') + .call(doCmd('previousObject')) + .expectSpeech('before') + .call(doCmd('nextObject')) + .expectSpeech('lebal', 'Link') + .call(doCmd('nextObject')) + .expectSpeech('after') + .call(doCmd('nextObject')) + .expectSpeech('label', 'lebal', 'Button') + .call(doCmd('nextObject')) + .expectEarcon(Earcon.WRAP) + .call(doCmd('nextObject')) + .expectSpeech('before') + .replay(); + }); -TEST_F('ChromeVoxBackgroundTest', 'MathContentViaInnerHtml', function() { +TEST_F('ChromeVoxBackgroundTest', 'MathContentViaInnerHtml', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div role="math"> @@ -1797,15 +1750,14 @@ </semantics> </div> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.call(doCmd('nextObject')) - .expectSpeech('a ( y + m ) squared + b ( y + m ) + c = 0 .') - .expectSpeech('Press up, down, left, or right to explore math') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.call(doCmd('nextObject')) + .expectSpeech('a ( y + m ) squared + b ( y + m ) + c = 0 .') + .expectSpeech('Press up, down, left, or right to explore math') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'GestureGranularity', function() { +TEST_F('ChromeVoxBackgroundTest', 'GestureGranularity', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>This is a test</p> @@ -1816,65 +1768,64 @@ <a href="#">there</a> <button>world</button> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.call(doGesture(Gesture.SWIPE_LEFT3)) - .expectSpeech('Word') - .call(doGesture(Gesture.SWIPE_DOWN1)) - .expectSpeech('is') - .call(doGesture(Gesture.SWIPE_DOWN1)) - .expectSpeech('a') - .call(doGesture(Gesture.SWIPE_UP1)) - .expectSpeech('is') + const root = await this.runWithLoadedTree(site); + mockFeedback.call(doGesture(Gesture.SWIPE_LEFT3)) + .expectSpeech('Word') + .call(doGesture(Gesture.SWIPE_DOWN1)) + .expectSpeech('is') + .call(doGesture(Gesture.SWIPE_DOWN1)) + .expectSpeech('a') + .call(doGesture(Gesture.SWIPE_UP1)) + .expectSpeech('is') - .call(doGesture(Gesture.SWIPE_LEFT3)) - .expectSpeech('Character') - .call(doGesture(Gesture.SWIPE_DOWN1)) - .expectSpeech('s') - .call(doGesture(Gesture.SWIPE_UP1)) - .expectSpeech('i') + .call(doGesture(Gesture.SWIPE_LEFT3)) + .expectSpeech('Character') + .call(doGesture(Gesture.SWIPE_DOWN1)) + .expectSpeech('s') + .call(doGesture(Gesture.SWIPE_UP1)) + .expectSpeech('i') - .call(doGesture(Gesture.SWIPE_LEFT3)) - .expectSpeech('Form field control') - .call(doGesture(Gesture.SWIPE_DOWN1)) - .expectSpeech('and', 'Button') - .call(doGesture(Gesture.SWIPE_UP1)) - .expectSpeech('world', 'Button') + .call(doGesture(Gesture.SWIPE_LEFT3)) + .expectSpeech('Form field control') + .call(doGesture(Gesture.SWIPE_DOWN1)) + .expectSpeech('and', 'Button') + .call(doGesture(Gesture.SWIPE_UP1)) + .expectSpeech('world', 'Button') - .call(doGesture(Gesture.SWIPE_LEFT3)) - .expectSpeech('Link') - .call(doGesture(Gesture.SWIPE_DOWN1)) - .expectSpeech('greetings', 'Internal link') - .call(doGesture(Gesture.SWIPE_UP1)) - .expectSpeech('there', 'Internal link') + .call(doGesture(Gesture.SWIPE_LEFT3)) + .expectSpeech('Link') + .call(doGesture(Gesture.SWIPE_DOWN1)) + .expectSpeech('greetings', 'Internal link') + .call(doGesture(Gesture.SWIPE_UP1)) + .expectSpeech('there', 'Internal link') - .call(doGesture(Gesture.SWIPE_LEFT3)) - .expectSpeech('Heading') - .call(doGesture(Gesture.SWIPE_DOWN1)) - .expectSpeech('hello', 'Heading 2') - .call(doGesture(Gesture.SWIPE_UP1)) - .expectSpeech('here', 'Heading 2') - .call(doGesture(Gesture.SWIPE_UP1)) - .expectSpeech('hello', 'Heading 2') + .call(doGesture(Gesture.SWIPE_LEFT3)) + .expectSpeech('Heading') + .call(doGesture(Gesture.SWIPE_DOWN1)) + .expectSpeech('hello', 'Heading 2') + .call(doGesture(Gesture.SWIPE_UP1)) + .expectSpeech('here', 'Heading 2') + .call(doGesture(Gesture.SWIPE_UP1)) + .expectSpeech('hello', 'Heading 2') - .call(doGesture(Gesture.SWIPE_LEFT3)) - .expectSpeech('Line') - .call(doGesture(Gesture.SWIPE_UP1)) - .expectSpeech('This is a test') + .call(doGesture(Gesture.SWIPE_LEFT3)) + .expectSpeech('Line') + .call(doGesture(Gesture.SWIPE_UP1)) + .expectSpeech('This is a test') - .call(doGesture(Gesture.SWIPE_RIGHT3)) - .expectSpeech('Heading') - .call(doGesture(Gesture.SWIPE_RIGHT3)) - .expectSpeech('Internal link') - .call(doGesture(Gesture.SWIPE_RIGHT3)) - .expectSpeech('Form field control') - .call(doGesture(Gesture.SWIPE_RIGHT3)) - .expectSpeech('Character') + .call(doGesture(Gesture.SWIPE_RIGHT3)) + .expectSpeech('Heading') + .call(doGesture(Gesture.SWIPE_RIGHT3)) + .expectSpeech('Internal link') + .call(doGesture(Gesture.SWIPE_RIGHT3)) + .expectSpeech('Form field control') + .call(doGesture(Gesture.SWIPE_RIGHT3)) + .expectSpeech('Character') - .replay(); - }); + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'LinesFilterWhitespace', function() { +TEST_F('ChromeVoxBackgroundTest', 'LinesFilterWhitespace', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>start</p> @@ -1885,21 +1836,21 @@ </div> </div> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.expectSpeech('start') - .clearPendingOutput() - .call(doCmd('nextLine')) - .expectSpeech('Munich') - .expectNextSpeechUtteranceIsNot(' ') - .expectSpeech('London') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.expectSpeech('start') + .clearPendingOutput() + .call(doCmd('nextLine')) + .expectSpeech('Munich') + .expectNextSpeechUtteranceIsNot(' ') + .expectSpeech('London') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'TabSwitchAndRefreshRecovery', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree('<p>tab1</p>', function(root1) { - this.runWithLoadedTree('<p>tab2</p>', function(root2) { +TEST_F( + 'ChromeVoxBackgroundTest', 'TabSwitchAndRefreshRecovery', async function() { + const mockFeedback = this.createMockFeedback(); + const root1 = await this.runWithLoadedTree('<p>tab1</p>'); + const root2 = await this.runWithLoadedTree('<p>tab2</p>'); mockFeedback.expectSpeech('tab2') .clearPendingOutput() .call(press(KeyCode.TAB, {shift: true, ctrl: true})) @@ -1917,10 +1868,8 @@ }) .replay(); }); - }); -}); -TEST_F('ChromeVoxBackgroundTest', 'ListName', function() { +TEST_F('ChromeVoxBackgroundTest', 'ListName', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div id="_md-chips-wrapper-76" tabindex="-1" class="md-chips md-readonly" @@ -1932,27 +1881,25 @@ <div role="listitem">Football</div> </div> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.expectSpeech('Favorite Sports').replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.expectSpeech('Favorite Sports').replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'LayoutTable', function() { +TEST_F('ChromeVoxBackgroundTest', 'LayoutTable', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <table><tr><td>start</td></tr></table><p>end</p> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.expectSpeech('start') - .call(doCmd('nextObject')) - .expectNextSpeechUtteranceIsNot('row 1 column 1') - .expectNextSpeechUtteranceIsNot('Table , 1 by 1') - .expectSpeech('end') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.expectSpeech('start') + .call(doCmd('nextObject')) + .expectNextSpeechUtteranceIsNot('row 1 column 1') + .expectNextSpeechUtteranceIsNot('Table , 1 by 1') + .expectSpeech('end') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'ReinsertedNodeRecovery', function() { +TEST_F('ChromeVoxBackgroundTest', 'ReinsertedNodeRecovery', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div> @@ -1970,39 +1917,37 @@ }); </script> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.expectSpeech('start') - .clearPendingOutput() - .call(doCmd('nextObject')) - .call(doCmd('nextObject')) - .call(doCmd('nextObject')) - .expectSpeech('end', 'Button') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.expectSpeech('start') + .clearPendingOutput() + .call(doCmd('nextObject')) + .call(doCmd('nextObject')) + .call(doCmd('nextObject')) + .expectSpeech('end', 'Button') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'PointerTargetsLeafNode', function() { +TEST_F('ChromeVoxBackgroundTest', 'PointerTargetsLeafNode', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div role=button><p>Washington</p></div> <div role=button><p>Adams</p></div> <div role=button><p>Jefferson</p></div> `; - this.runWithLoadedTree(site, function(root) { - const button = - root.find({role: RoleType.BUTTON, attributes: {name: 'Jefferson'}}); - const buttonP = button.firstChild; - assertNotNullNorUndefined(buttonP); - const buttonText = buttonP.firstChild; - assertNotNullNorUndefined(buttonText); - mockFeedback.call(simulateHitTestResult(buttonText)) - .expectSpeech('Jefferson') - .expectSpeech('Button') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + const button = + root.find({role: RoleType.BUTTON, attributes: {name: 'Jefferson'}}); + const buttonP = button.firstChild; + assertNotNullNorUndefined(buttonP); + const buttonText = buttonP.firstChild; + assertNotNullNorUndefined(buttonText); + mockFeedback.call(simulateHitTestResult(buttonText)) + .expectSpeech('Jefferson') + .expectSpeech('Button') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'AriaSliderWithValueNow', function() { +TEST_F('ChromeVoxBackgroundTest', 'AriaSliderWithValueNow', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div id="slider" role="slider" tabindex="0" aria-valuemin="0" @@ -2015,16 +1960,13 @@ }); </script> `; - this.runWithLoadedTree(site, function(root) { - const slider = root.find({role: RoleType.SLIDER}); - assertNotNullNorUndefined(slider); - mockFeedback.call(slider.doDefault.bind(slider)) - .expectSpeech('51') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + const slider = root.find({role: RoleType.SLIDER}); + assertNotNullNorUndefined(slider); + mockFeedback.call(slider.doDefault.bind(slider)).expectSpeech('51').replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'AriaSliderWithValueText', function() { +TEST_F('ChromeVoxBackgroundTest', 'AriaSliderWithValueText', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div id="slider" role="slider" tabindex="0" aria-valuemin="0" @@ -2038,18 +1980,17 @@ }); </script> `; - this.runWithLoadedTree(site, function(root) { - const slider = root.find({role: RoleType.SLIDER}); - assertNotNullNorUndefined(slider); - mockFeedback.clearPendingOutput() - .call(slider.doDefault.bind(slider)) - .expectNextSpeechUtteranceIsNot('51') - .expectSpeech('large') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + const slider = root.find({role: RoleType.SLIDER}); + assertNotNullNorUndefined(slider); + mockFeedback.clearPendingOutput() + .call(slider.doDefault.bind(slider)) + .expectNextSpeechUtteranceIsNot('51') + .expectSpeech('large') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'SelectValidityOutput', function() { +TEST_F('ChromeVoxBackgroundTest', 'SelectValidityOutput', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>start</p> @@ -2065,92 +2006,89 @@ }); </script> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.expectSpeech('start') - .call(doCmd('nextObject')) - .expectSpeech('Name:') - .expectSpeech('Edit text') - .expectSpeech('Required') - .expectNextSpeechUtteranceIsNot('Alert') - .expectSpeech('Please enter name') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.expectSpeech('start') + .call(doCmd('nextObject')) + .expectSpeech('Name:') + .expectSpeech('Edit text') + .expectSpeech('Required') + .expectNextSpeechUtteranceIsNot('Alert') + .expectSpeech('Please enter name') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'EventFromAction', function() { +TEST_F('ChromeVoxBackgroundTest', 'EventFromAction', async function() { const site = '<button>ok</button><button>cancel</button>'; - this.runWithLoadedTree(site, function(root) { - const button = root.findAll({role: RoleType.BUTTON})[1]; - button.addEventListener(EventType.FOCUS, this.newCallback(function(evt) { - assertEquals(RoleType.BUTTON, evt.target.role); - assertEquals('action', evt.eventFrom); - assertEquals('cancel', evt.target.name); - assertEquals('focus', evt.eventFromAction); - })); - - button.focus(); - }); -}); - -TEST_F('ChromeVoxBackgroundTest', 'EventFromUser', function() { - const site = '<button>ok</button><button>cancel</button>'; - this.runWithLoadedTree(site, async function(root) { - const buttons = root.findAll({role: RoleType.BUTTON}); - const okButton = buttons[0]; - const cancelButton = buttons[1]; - - await new Promise(r => { - if (okButton.state.focused) { - r(); - } else { - okButton.addEventListener('focus', r); - } - }); - - press(KeyCode.TAB)(); - - const evt = - await new Promise(r => cancelButton.addEventListener('focus', r)); + const root = await this.runWithLoadedTree(site); + const button = root.findAll({role: RoleType.BUTTON})[1]; + button.addEventListener(EventType.FOCUS, this.newCallback(function(evt) { assertEquals(RoleType.BUTTON, evt.target.role); - assertEquals('user', evt.eventFrom); + assertEquals('action', evt.eventFrom); assertEquals('cancel', evt.target.name); - }); + assertEquals('focus', evt.eventFromAction); + })); + + button.focus(); }); -TEST_F('ChromeVoxBackgroundTest', 'ReadPhoneticPronunciationTest', function() { - const mockFeedback = this.createMockFeedback(); - const site = ` +TEST_F('ChromeVoxBackgroundTest', 'EventFromUser', async function() { + const site = '<button>ok</button><button>cancel</button>'; + const root = await this.runWithLoadedTree(site); + const buttons = root.findAll({role: RoleType.BUTTON}); + const okButton = buttons[0]; + const cancelButton = buttons[1]; + + await new Promise(r => { + if (okButton.state.focused) { + r(); + } else { + okButton.addEventListener('focus', r); + } + }); + + press(KeyCode.TAB)(); + + const evt = await new Promise(r => cancelButton.addEventListener('focus', r)); + assertEquals(RoleType.BUTTON, evt.target.role); + assertEquals('user', evt.eventFrom); + assertEquals('cancel', evt.target.name); +}); + +TEST_F( + 'ChromeVoxBackgroundTest', 'ReadPhoneticPronunciationTest', + async function() { + const mockFeedback = this.createMockFeedback(); + const site = ` <button>This is a button</button> <input type="text"></input> `; - this.runWithLoadedTree(site, function(root) { - root.find({role: RoleType.BUTTON}).focus(); - mockFeedback.call(doCmd('readPhoneticPronunciation')) - .expectSpeech( - 'T: tango, h: hotel, i: india, s: sierra, : , i: india, ' + - 's: sierra, : , a: alpha, : , b: bravo, u: uniform, t: tango, ' + - 't: tango, o: oscar, n: november') - .call(doCmd('nextWord')) - .call(doCmd('readPhoneticPronunciation')) - .expectSpeech('i: india, s: sierra') - .call(doCmd('previousWord')) - .call(doCmd('readPhoneticPronunciation')) - .expectSpeech('T: tango, h: hotel, i: india, s: sierra') - .call(doCmd('nextWord')) - .call(doCmd('nextWord')) - .call(doCmd('nextWord')) - .call(doCmd('readPhoneticPronunciation')) - .expectSpeech( - 'b: bravo, u: uniform, t: tango, t: tango, o: oscar, ' + - 'n: november') - .call(doCmd('nextEditText')) - .call(doCmd('readPhoneticPronunciation')) - .expectSpeech('No available text for this item'); - mockFeedback.replay(); - }); -}); + const root = await this.runWithLoadedTree(site); + root.find({role: RoleType.BUTTON}).focus(); + mockFeedback.call(doCmd('readPhoneticPronunciation')) + .expectSpeech( + 'T: tango, h: hotel, i: india, s: sierra, : , i: india, ' + + 's: sierra, : , a: alpha, : , b: bravo, u: uniform, t: tango, ' + + 't: tango, o: oscar, n: november') + .call(doCmd('nextWord')) + .call(doCmd('readPhoneticPronunciation')) + .expectSpeech('i: india, s: sierra') + .call(doCmd('previousWord')) + .call(doCmd('readPhoneticPronunciation')) + .expectSpeech('T: tango, h: hotel, i: india, s: sierra') + .call(doCmd('nextWord')) + .call(doCmd('nextWord')) + .call(doCmd('nextWord')) + .call(doCmd('readPhoneticPronunciation')) + .expectSpeech( + 'b: bravo, u: uniform, t: tango, t: tango, o: oscar, ' + + 'n: november') + .call(doCmd('nextEditText')) + .call(doCmd('readPhoneticPronunciation')) + .expectSpeech('No available text for this item'); + mockFeedback.replay(); + }); -TEST_F('ChromeVoxBackgroundTest', 'SimilarItemNavigation', function() { +TEST_F('ChromeVoxBackgroundTest', 'SimilarItemNavigation', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <h3><a href="#a">inner</a></h3> @@ -2159,27 +2097,26 @@ <a href="#b">outer1</a> <h3>outer2</h3> `; - this.runWithLoadedTree(site, function(root) { - assertEquals( - RoleType.LINK, ChromeVoxState.instance.currentRange.start.node.role); - assertEquals('inner', ChromeVoxState.instance.currentRange.start.node.name); - mockFeedback.call(doCmd('nextSimilarItem')) - .expectSpeech('outer1', 'Link') - .call(doCmd('nextSimilarItem')) - .expectSpeech('inner', 'Link') - .call(doCmd('nextSimilarItem')) - .call(doCmd('previousSimilarItem')) - .expectSpeech('inner', 'Link') - .call(doCmd('nextHeading')) - .expectSpeech('outer2', 'Heading 3') - .call(doCmd('previousSimilarItem')) - .expectSpeech('inner', 'Heading 3'); + const root = await this.runWithLoadedTree(site); + assertEquals( + RoleType.LINK, ChromeVoxState.instance.currentRange.start.node.role); + assertEquals('inner', ChromeVoxState.instance.currentRange.start.node.name); + mockFeedback.call(doCmd('nextSimilarItem')) + .expectSpeech('outer1', 'Link') + .call(doCmd('nextSimilarItem')) + .expectSpeech('inner', 'Link') + .call(doCmd('nextSimilarItem')) + .call(doCmd('previousSimilarItem')) + .expectSpeech('inner', 'Link') + .call(doCmd('nextHeading')) + .expectSpeech('outer2', 'Heading 3') + .call(doCmd('previousSimilarItem')) + .expectSpeech('inner', 'Heading 3'); - mockFeedback.replay(); - }); + mockFeedback.replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'InvalidItemNavigation', function() { +TEST_F('ChromeVoxBackgroundTest', 'InvalidItemNavigation', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <h3><a href="#a">inner</a></h3> @@ -2194,54 +2131,54 @@ <h3>outer2</h3> `; - this.runWithLoadedTree(site, function(root) { - assertEquals( - RoleType.LINK, ChromeVoxState.instance.currentRange.start.node.role); - assertEquals('inner', ChromeVoxState.instance.currentRange.start.node.name); - mockFeedback.call(doCmd('nextInvalidItem')) - .expectSpeech('txet', 'misspelled') - .call(doCmd('nextInvalidItem')) - .expectSpeech('some other reason') - .call(doCmd('nextInvalidItem')) - .expectSpeech('this are', 'grammar error') - .call(doCmd('nextInvalidItem')) - .expectSpeech('error is this') - // Ensure wrap. - .call(doCmd('nextInvalidItem')) - .expectSpeech('txet') - // Wrap backward. - .call(doCmd('previousInvalidItem')) - .expectSpeech('error is this') - .call(doCmd('previousInvalidItem')) - .expectSpeech('this are', 'grammar error'); + const root = await this.runWithLoadedTree(site); + assertEquals( + RoleType.LINK, ChromeVoxState.instance.currentRange.start.node.role); + assertEquals('inner', ChromeVoxState.instance.currentRange.start.node.name); + mockFeedback.call(doCmd('nextInvalidItem')) + .expectSpeech('txet', 'misspelled') + .call(doCmd('nextInvalidItem')) + .expectSpeech('some other reason') + .call(doCmd('nextInvalidItem')) + .expectSpeech('this are', 'grammar error') + .call(doCmd('nextInvalidItem')) + .expectSpeech('error is this') + // Ensure wrap. + .call(doCmd('nextInvalidItem')) + .expectSpeech('txet') + // Wrap backward. + .call(doCmd('previousInvalidItem')) + .expectSpeech('error is this') + .call(doCmd('previousInvalidItem')) + .expectSpeech('this are', 'grammar error'); - mockFeedback.replay(); - }); + mockFeedback.replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'InvalidItemNavigationNoItem', function() { - const mockFeedback = this.createMockFeedback(); - const site = ` +TEST_F( + 'ChromeVoxBackgroundTest', 'InvalidItemNavigationNoItem', async function() { + const mockFeedback = this.createMockFeedback(); + const site = ` <h3><a href="#a">inner</a></h3> <p>some text</p> <button>some other text</button> <a href="#b">outer1</a> <h3>outer2</h3> `; - this.runWithLoadedTree(site, function(root) { - assertEquals( - RoleType.LINK, ChromeVoxState.instance.currentRange.start.node.role); - assertEquals('inner', ChromeVoxState.instance.currentRange.start.node.name); - mockFeedback.call(doCmd('nextInvalidItem')) - .expectSpeech('No invalid item') - .call(doCmd('previousInvalidItem')) - .expectSpeech('No invalid item'); + const root = await this.runWithLoadedTree(site); + assertEquals( + RoleType.LINK, ChromeVoxState.instance.currentRange.start.node.role); + assertEquals( + 'inner', ChromeVoxState.instance.currentRange.start.node.name); + mockFeedback.call(doCmd('nextInvalidItem')) + .expectSpeech('No invalid item') + .call(doCmd('previousInvalidItem')) + .expectSpeech('No invalid item'); - mockFeedback.replay(); - }); -}); + mockFeedback.replay(); + }); -TEST_F('ChromeVoxBackgroundTest', 'TableWithAriaRowCol', function() { +TEST_F('ChromeVoxBackgroundTest', 'TableWithAriaRowCol', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div role="table"> @@ -2250,31 +2187,30 @@ </div> </div> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.call(doCmd('fullyDescribe')) - .expectSpeech('test', 'row 3 column 1', 'Table , 1 by 1') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.call(doCmd('fullyDescribe')) + .expectSpeech('test', 'row 3 column 1', 'Table , 1 by 1') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'NonModalDialogHeadingJump', function() { - const mockFeedback = this.createMockFeedback(); - const site = ` +TEST_F( + 'ChromeVoxBackgroundTest', 'NonModalDialogHeadingJump', async function() { + const mockFeedback = this.createMockFeedback(); + const site = ` <h2>Heading outside dialog</h2> <div role="dialog"> <h2>Heading inside dialog</h2> </div> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.call(doCmd('nextHeading')) - .expectSpeech('Heading inside dialog') - .call(doCmd('previousHeading')) - .expectSpeech('Heading outside dialog') - .replay(); - }); -}); + const root = await this.runWithLoadedTree(site); + mockFeedback.call(doCmd('nextHeading')) + .expectSpeech('Heading inside dialog') + .call(doCmd('previousHeading')) + .expectSpeech('Heading outside dialog') + .replay(); + }); -TEST_F('ChromeVoxBackgroundTest', 'LevelEndsForNestedLists', function() { +TEST_F('ChromeVoxBackgroundTest', 'LevelEndsForNestedLists', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div> @@ -2301,84 +2237,82 @@ </div> `; - this.runWithLoadedTree(site, function(root) { - const blueberries = root.find({attributes: {name: 'Blueberries'}}); - const grapefruits = root.find({attributes: {name: 'Grapefruits'}}); + const root = await this.runWithLoadedTree(site); + const blueberries = root.find({attributes: {name: 'Blueberries'}}); + const grapefruits = root.find({attributes: {name: 'Grapefruits'}}); - mockFeedback - .call(() => { - ChromeVoxState.instance.setCurrentRange( - cursors.Range.fromNode(blueberries)); - }) - .call(doCmd('nextObject')) - .expectSpeech( - '◦ Raspberries', 'List item', 'List end', 'nested level 2') - .call(() => { - ChromeVoxState.instance.setCurrentRange( - cursors.Range.fromNode(grapefruits)); - }) - .call(doCmd('nextObject')) - .expectSpeech('■ Mandarins', 'List item', 'List end', 'nested level 3') - .call(doCmd('nextObject')) - // Nested level is not mentioned for level 1. - .expectSpeech('• Bananas', 'List item', 'List end') - .replay(); - }); + mockFeedback + .call(() => { + ChromeVoxState.instance.setCurrentRange( + cursors.Range.fromNode(blueberries)); + }) + .call(doCmd('nextObject')) + .expectSpeech('◦ Raspberries', 'List item', 'List end', 'nested level 2') + .call(() => { + ChromeVoxState.instance.setCurrentRange( + cursors.Range.fromNode(grapefruits)); + }) + .call(doCmd('nextObject')) + .expectSpeech('■ Mandarins', 'List item', 'List end', 'nested level 3') + .call(doCmd('nextObject')) + // Nested level is not mentioned for level 1. + .expectSpeech('• Bananas', 'List item', 'List end') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'NestedListNavigationSimple', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.nestedListDoc, function(root) { - mockFeedback.expectSpeech('• Lemons', 'List item', 'List', 'with 4 items') - .call(doCmd('nextObject')) - .expectSpeech('• Oranges', 'List item') - .call(doCmd('nextObject')) - .expectSpeech('• ', 'Berries', 'List item') - .expectBraille('• Berries lstitm') - .call(doCmd('nextObject')) - .expectSpeech('◦ Strawberries', 'List item', 'List', 'with 2 items') - .call(doCmd('nextObject')) - .expectSpeech('◦ Raspberries', 'List item', 'List end') - .call(doCmd('nextObject')) - .expectSpeech('• Bananas', 'List item', 'List end') - .expectBraille('• Bananas lstitm lst end') - .replay(); - }); -}); +TEST_F( + 'ChromeVoxBackgroundTest', 'NestedListNavigationSimple', async function() { + const mockFeedback = this.createMockFeedback(); + const root = await this.runWithLoadedTree(this.nestedListDoc); + mockFeedback.expectSpeech('• Lemons', 'List item', 'List', 'with 4 items') + .call(doCmd('nextObject')) + .expectSpeech('• Oranges', 'List item') + .call(doCmd('nextObject')) + .expectSpeech('• ', 'Berries', 'List item') + .expectBraille('• Berries lstitm') + .call(doCmd('nextObject')) + .expectSpeech('◦ Strawberries', 'List item', 'List', 'with 2 items') + .call(doCmd('nextObject')) + .expectSpeech('◦ Raspberries', 'List item', 'List end') + .call(doCmd('nextObject')) + .expectSpeech('• Bananas', 'List item', 'List end') + .expectBraille('• Bananas lstitm lst end') + .replay(); + }); -TEST_F('ChromeVoxBackgroundTest', 'NestedListNavigationMixed', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.nestedListDoc, function(root) { - mockFeedback.expectSpeech('• Lemons', 'List item', 'List', 'with 4 items') - .call(doCmd('nextObject')) - .expectSpeech('• Oranges', 'List item') - .call(doCmd('nextLine')) - .expectSpeech('• ', 'Berries', 'List item') - .call(doCmd('nextLine')) - .expectSpeech('◦ Strawberries', 'List item', 'List', 'with 2 items') - .call(doCmd('previousLine')) - .expectSpeech('• ', 'Berries') - .call(doCmd('nextWord')) - .expectSpeech('◦ Strawberries') - .call(doCmd('nextWord')) - .expectSpeech('◦ Raspberries') - .call(doCmd('previousObject')) - .call(doCmd('previousObject')) - .expectSpeech('• ', 'Berries') - .call(doCmd('previousCharacter')) - .call(doCmd('previousCharacter')) - .call(doCmd('previousCharacter')) - .expectSpeech('g') // For Oranges - .call(doCmd('nextGroup')) - .expectSpeech('◦ Strawberries', '◦ Raspberries') - .clearPendingOutput() - .call(doCmd('previousGroup')) - .expectSpeech('• Oranges') - .replay(); - }); -}); +TEST_F( + 'ChromeVoxBackgroundTest', 'NestedListNavigationMixed', async function() { + const mockFeedback = this.createMockFeedback(); + const root = await this.runWithLoadedTree(this.nestedListDoc); + mockFeedback.expectSpeech('• Lemons', 'List item', 'List', 'with 4 items') + .call(doCmd('nextObject')) + .expectSpeech('• Oranges', 'List item') + .call(doCmd('nextLine')) + .expectSpeech('• ', 'Berries', 'List item') + .call(doCmd('nextLine')) + .expectSpeech('◦ Strawberries', 'List item', 'List', 'with 2 items') + .call(doCmd('previousLine')) + .expectSpeech('• ', 'Berries') + .call(doCmd('nextWord')) + .expectSpeech('◦ Strawberries') + .call(doCmd('nextWord')) + .expectSpeech('◦ Raspberries') + .call(doCmd('previousObject')) + .call(doCmd('previousObject')) + .expectSpeech('• ', 'Berries') + .call(doCmd('previousCharacter')) + .call(doCmd('previousCharacter')) + .call(doCmd('previousCharacter')) + .expectSpeech('g') // For Oranges + .call(doCmd('nextGroup')) + .expectSpeech('◦ Strawberries', '◦ Raspberries') + .clearPendingOutput() + .call(doCmd('previousGroup')) + .expectSpeech('• Oranges') + .replay(); + }); -TEST_F('ChromeVoxBackgroundTest', 'NavigationByList', function() { +TEST_F('ChromeVoxBackgroundTest', 'NavigationByList', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>Start here</p> @@ -2410,176 +2344,167 @@ <dd>Description for green</dd> </dl> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.call(doCmd('jumpToTop')) - .call(doCmd('nextList')) - .expectSpeech('Drinks', 'List', 'with 2 items') - .call(doCmd('nextList')) - .expectSpeech('List', 'with 0 items') - .call(doCmd('nextList')) - .expectSpeech('Lunch', 'List', 'with 3 items') - .call(doCmd('nextList')) - .expectSpeech('Nested list', 'List', 'with 1 item') - .call(doCmd('nextList')) - .expectSpeech('Colors', 'Description list', 'with 3 items') - .call(doCmd('nextList')) - // Ensure we wrap correctly. - .expectSpeech('Drinks', 'List', 'with 2 items') - .call(doCmd('nextObject')) - .call(doCmd('nextObject')) - .expectSpeech('\u2022 Coffee') - // Ensure we wrap correctly and go to previous list, not top of - // current list. - .call(doCmd('previousList')) - .expectSpeech('Colors') - .call(doCmd('previousObject')) - .expectSpeech('Another random paragraph') - // Ensure we dive into the nested list. - .call(doCmd('previousList')) - .expectSpeech('Nested list', 'List', 'with 1 item') - .call(doCmd('previousList')) - .expectSpeech('Lunch') - .call(doCmd('nextObject')) - .call(doCmd('nextObject')) - .expectSpeech('1. Burgers') - // Ensure we go to the previous list, not the top of the current - // list. - .call(doCmd('previousList')) - .expectSpeech('List', 'with 0 items') - .call(doCmd('previousObject')) - .expectSpeech('A random paragraph') - .call(doCmd('previousList')) - .expectSpeech('Drinks', 'List', 'with 2 items') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.call(doCmd('jumpToTop')) + .call(doCmd('nextList')) + .expectSpeech('Drinks', 'List', 'with 2 items') + .call(doCmd('nextList')) + .expectSpeech('List', 'with 0 items') + .call(doCmd('nextList')) + .expectSpeech('Lunch', 'List', 'with 3 items') + .call(doCmd('nextList')) + .expectSpeech('Nested list', 'List', 'with 1 item') + .call(doCmd('nextList')) + .expectSpeech('Colors', 'Description list', 'with 3 items') + .call(doCmd('nextList')) + // Ensure we wrap correctly. + .expectSpeech('Drinks', 'List', 'with 2 items') + .call(doCmd('nextObject')) + .call(doCmd('nextObject')) + .expectSpeech('\u2022 Coffee') + // Ensure we wrap correctly and go to previous list, not top of + // current list. + .call(doCmd('previousList')) + .expectSpeech('Colors') + .call(doCmd('previousObject')) + .expectSpeech('Another random paragraph') + // Ensure we dive into the nested list. + .call(doCmd('previousList')) + .expectSpeech('Nested list', 'List', 'with 1 item') + .call(doCmd('previousList')) + .expectSpeech('Lunch') + .call(doCmd('nextObject')) + .call(doCmd('nextObject')) + .expectSpeech('1. Burgers') + // Ensure we go to the previous list, not the top of the current + // list. + .call(doCmd('previousList')) + .expectSpeech('List', 'with 0 items') + .call(doCmd('previousObject')) + .expectSpeech('A random paragraph') + .call(doCmd('previousList')) + .expectSpeech('Drinks', 'List', 'with 2 items') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'NoListTest', function() { +TEST_F('ChromeVoxBackgroundTest', 'NoListTest', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree('<button>Click me</button>', function(root) { - mockFeedback.call(doCmd('nextList')) - .expectSpeech('No next list') - .call(doCmd('previousList')) - .expectSpeech('No previous list'); - mockFeedback.replay(); - }); + const root = await this.runWithLoadedTree('<button>Click me</button>'); + mockFeedback.call(doCmd('nextList')) + .expectSpeech('No next list') + .call(doCmd('previousList')) + .expectSpeech('No previous list'); + mockFeedback.replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'NavigateToLastHeading', function() { +TEST_F('ChromeVoxBackgroundTest', 'NavigateToLastHeading', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <h1>First</h1> <h1>Second</h1> <h1>Third</h1> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.call(doCmd('jumpToTop')) - .expectSpeech('First', 'Heading 1') - .call(doCmd('previousHeading')) - .expectSpeech('Third', 'Heading 1') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.call(doCmd('jumpToTop')) + .expectSpeech('First', 'Heading 1') + .call(doCmd('previousHeading')) + .expectSpeech('Third', 'Heading 1') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'ReadLinkURLTest', function() { +TEST_F('ChromeVoxBackgroundTest', 'ReadLinkURLTest', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <a href="https://www.google.com/">A popular link</a> <button>Not a link</button> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.call(doCmd('nextLink')) - .expectSpeech( - 'A popular link', 'Link', 'Press Search+Space to activate') - .call(doCmd('readLinkURL')) - .expectSpeech('Link URL: https://www.google.com/') - .call(doCmd('nextObject')) - .expectSpeech('Not a link', 'Button', 'Press Search+Space to activate') - .call(doCmd('readLinkURL')) - .expectSpeech('No URL found') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.call(doCmd('nextLink')) + .expectSpeech('A popular link', 'Link', 'Press Search+Space to activate') + .call(doCmd('readLinkURL')) + .expectSpeech('Link URL: https://www.google.com/') + .call(doCmd('nextObject')) + .expectSpeech('Not a link', 'Button', 'Press Search+Space to activate') + .call(doCmd('readLinkURL')) + .expectSpeech('No URL found') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'NoRepeatTitle', function() { +TEST_F('ChromeVoxBackgroundTest', 'NoRepeatTitle', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div role="button" aria-label="title" title="title"></div> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.expectSpeech('title') - .expectSpeech('Button') - .expectNextSpeechUtteranceIsNot('title') - .expectSpeech('Press Search+Space to activate') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.expectSpeech('title') + .expectSpeech('Button') + .expectNextSpeechUtteranceIsNot('title') + .expectSpeech('Press Search+Space to activate') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'PhoneticsAndCommands', function() { +TEST_F('ChromeVoxBackgroundTest', 'PhoneticsAndCommands', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>some sample text</p> <button>ok</button> <p>A</p> `; - this.runWithLoadedTree(site, function(root) { - const noPhonetics = {phoneticCharacters: undefined}; - const phonetics = {phoneticCharacters: true}; - mockFeedback.call(doCmd('nextObject')) - .expectSpeechWithProperties(noPhonetics, 'ok') - .call(doCmd('previousObject')) - .expectSpeechWithProperties(noPhonetics, 'some sample text') - .call(doCmd('nextWord')) - .expectSpeechWithProperties(noPhonetics, 'sample') - .call(doCmd('previousWord')) - .expectSpeechWithProperties(noPhonetics, 'some') - .call(doCmd('nextCharacter')) - .expectSpeechWithProperties(phonetics, 'o') - .call(doCmd('nextCharacter')) - .expectSpeechWithProperties(phonetics, 'm') - .call(doCmd('previousCharacter')) - .expectSpeechWithProperties(phonetics, 'o') - .call(doCmd('jumpToBottom')) - .expectSpeechWithProperties(noPhonetics, 'A'); - mockFeedback.replay(); - }); + const root = await this.runWithLoadedTree(site); + const noPhonetics = {phoneticCharacters: undefined}; + const phonetics = {phoneticCharacters: true}; + mockFeedback.call(doCmd('nextObject')) + .expectSpeechWithProperties(noPhonetics, 'ok') + .call(doCmd('previousObject')) + .expectSpeechWithProperties(noPhonetics, 'some sample text') + .call(doCmd('nextWord')) + .expectSpeechWithProperties(noPhonetics, 'sample') + .call(doCmd('previousWord')) + .expectSpeechWithProperties(noPhonetics, 'some') + .call(doCmd('nextCharacter')) + .expectSpeechWithProperties(phonetics, 'o') + .call(doCmd('nextCharacter')) + .expectSpeechWithProperties(phonetics, 'm') + .call(doCmd('previousCharacter')) + .expectSpeechWithProperties(phonetics, 'o') + .call(doCmd('jumpToBottom')) + .expectSpeechWithProperties(noPhonetics, 'A'); + mockFeedback.replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'ToggleScreen', function() { +TEST_F('ChromeVoxBackgroundTest', 'ToggleScreen', async function() { const mockFeedback = this.createMockFeedback(); // Pretend we've already accepted the confirmation dialog once. localStorage['acceptToggleScreen'] = 'true'; - this.runWithLoadedTree('<div>Unimportant web content</div>', function() { - mockFeedback.call(doCmd('toggleScreen')) - .expectSpeech('Screen off') - .call(doCmd('toggleScreen')) - .expectSpeech('Screen on') - .call(doCmd('toggleScreen')) - .expectSpeech('Screen off') - .replay(); - }); + await this.runWithLoadedTree('<div>Unimportant web content</div>'); + mockFeedback.call(doCmd('toggleScreen')) + .expectSpeech('Screen off') + .call(doCmd('toggleScreen')) + .expectSpeech('Screen on') + .call(doCmd('toggleScreen')) + .expectSpeech('Screen off') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'NoFocusTalkBackDisabled', function() { +TEST_F('ChromeVoxBackgroundTest', 'NoFocusTalkBackDisabled', async function() { // Fire onCustomSpokenFeedbackEnabled event to communicate that Talkback is // off for the current app. this.dispatchOnCustomSpokenFeedbackToggledEvent(false); const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree('<p>Test document</p>', function() { - ChromeVoxState.instance.setCurrentRange(null); - mockFeedback.call(doCmd('nextObject')) - .expectSpeech( - 'No current ChromeVox focus. Press Alt+Shift+L to go to the ' + - 'launcher.') - .call(doCmd('previousObject')) - .expectSpeech( - 'No current ChromeVox focus. Press Alt+Shift+L to go to the ' + - 'launcher.'); - mockFeedback.replay(); - }); + await this.runWithLoadedTree('<p>Test document</p>'); + ChromeVoxState.instance.setCurrentRange(null); + mockFeedback.call(doCmd('nextObject')) + .expectSpeech( + 'No current ChromeVox focus. Press Alt+Shift+L to go to the ' + + 'launcher.') + .call(doCmd('previousObject')) + .expectSpeech( + 'No current ChromeVox focus. Press Alt+Shift+L to go to the ' + + 'launcher.'); + mockFeedback.replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'NoFocusTalkBackEnabled', function() { +TEST_F('ChromeVoxBackgroundTest', 'NoFocusTalkBackEnabled', async function() { // Fire onCustomSpokenFeedbackEnabled event to communicate that Talkback is // on for the current app. We don't want to announce the no-focus hint message // when TalkBack is on because we expect ChromeVox to have no focus in that @@ -2587,23 +2512,22 @@ // try to speak at the same time. this.dispatchOnCustomSpokenFeedbackToggledEvent(true); const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree('<p>Start here</p>', function() { - ChromeVoxState.instance.setCurrentRange(null); - mockFeedback.call(doCmd('nextObject')) - .call( - () => assertFalse(mockFeedback.utteranceInQueue( - 'No current ChromeVox focus. ' + - 'Press Alt+Shift+L to go to the launcher.'))) - .call(doCmd('previousObject')) - .call( - () => assertFalse(mockFeedback.utteranceInQueue( - 'No current ChromeVox focus. ' + - 'Press Alt+Shift+L to go to the launcher.'))); - mockFeedback.replay(); - }); + await this.runWithLoadedTree('<p>Start here</p>'); + ChromeVoxState.instance.setCurrentRange(null); + mockFeedback.call(doCmd('nextObject')) + .call( + () => assertFalse(mockFeedback.utteranceInQueue( + 'No current ChromeVox focus. ' + + 'Press Alt+Shift+L to go to the launcher.'))) + .call(doCmd('previousObject')) + .call( + () => assertFalse(mockFeedback.utteranceInQueue( + 'No current ChromeVox focus. ' + + 'Press Alt+Shift+L to go to the launcher.'))); + mockFeedback.replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'NavigateOutOfMultiline', function() { +TEST_F('ChromeVoxBackgroundTest', 'NavigateOutOfMultiline', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>start</p> @@ -2613,33 +2537,32 @@ </div> <p>after</p> `; - this.runWithLoadedTree(site, function(root) { - const contentEditable = - root.find({attributes: {nonAtomicTextFieldRoot: true}}); - mockFeedback.call(contentEditable.focus.bind(contentEditable)) - .expectSpeech(/Testing testing\s+one two three/) - .call(doCmd('nextLine')) - .expectSpeech('one two three') - .call(doCmd('nextLine')) - .expectSpeech('after') + const root = await this.runWithLoadedTree(site); + const contentEditable = + root.find({attributes: {nonAtomicTextFieldRoot: true}}); + mockFeedback.call(contentEditable.focus.bind(contentEditable)) + .expectSpeech(/Testing testing\s+one two three/) + .call(doCmd('nextLine')) + .expectSpeech('one two three') + .call(doCmd('nextLine')) + .expectSpeech('after') - // In reverse (explicitly focus, instead of moving to previous - // line, because all subsequent commands require the content - // editable to be focused first): - .clearPendingOutput() - .call(contentEditable.focus.bind(contentEditable)) - .expectSpeech(/Testing testing\s+one two three/) - .call(doCmd('nextLine')) - .expectSpeech('one two three') - .call(doCmd('previousLine')) - .expectSpeech('Testing testing') - .call(doCmd('previousLine')) - .expectSpeech('before') - .replay(); - }); + // In reverse (explicitly focus, instead of moving to previous + // line, because all subsequent commands require the content + // editable to be focused first): + .clearPendingOutput() + .call(contentEditable.focus.bind(contentEditable)) + .expectSpeech(/Testing testing\s+one two three/) + .call(doCmd('nextLine')) + .expectSpeech('one two three') + .call(doCmd('previousLine')) + .expectSpeech('Testing testing') + .call(doCmd('previousLine')) + .expectSpeech('before') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'ReadWindowTitle', function() { +TEST_F('ChromeVoxBackgroundTest', 'ReadWindowTitle', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>start</p> @@ -2649,62 +2572,59 @@ button.addEventListener('click', _ => document.title = 'bar'); </script> `; - this.runWithLoadedTree(site, function(root) { - const clickButtonThenReadCurrentTitle = () => { - const desktop = root.parent.root; - desktop.addEventListener(EventType.TREE_CHANGED, (evt) => { - if (evt.target.role === RoleType.WINDOW && - /bar/.test(evt.target.name)) { - doCmd('readCurrentTitle')(); - } - }); - const button = root.find({role: RoleType.BUTTON}); - button.doDefault(); - }; + const root = await this.runWithLoadedTree(site); + const clickButtonThenReadCurrentTitle = () => { + const desktop = root.parent.root; + desktop.addEventListener(EventType.TREE_CHANGED, (evt) => { + if (evt.target.role === RoleType.WINDOW && /bar/.test(evt.target.name)) { + doCmd('readCurrentTitle')(); + } + }); + const button = root.find({role: RoleType.BUTTON}); + button.doDefault(); + }; - mockFeedback.clearPendingOutput() - .call(clickButtonThenReadCurrentTitle) + mockFeedback.clearPendingOutput() + .call(clickButtonThenReadCurrentTitle) - // This test may run against official builds, so match against - // utterances starting with 'bar'. This should exclude any other - // utterances that contain 'bar' e.g. data:...bar.. or the data url. - .expectSpeech(/^bar*/) - .replay(); - }); + // This test may run against official builds, so match against + // utterances starting with 'bar'. This should exclude any other + // utterances that contain 'bar' e.g. data:...bar.. or the data url. + .expectSpeech(/^bar*/) + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'OutputEmptyQueueMode', function() { +TEST_F('ChromeVoxBackgroundTest', 'OutputEmptyQueueMode', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree('<p>unused</p>', function(root) { - const output = new Output(); - Output.forceModeForNextSpeechUtterance(QueueMode.CATEGORY_FLUSH); - output.append_( - output.speechBuffer_, new Spannable(''), - {annotation: [new OutputAction()]}); - output.withString('test'); - mockFeedback.clearPendingOutput() - .call(output.go.bind(output)) - .expectSpeechWithQueueMode('', QueueMode.CATEGORY_FLUSH) - .expectSpeechWithQueueMode('test', QueueMode.CATEGORY_FLUSH) - .replay(); - }); + const root = await this.runWithLoadedTree('<p>unused</p>'); + const output = new Output(); + Output.forceModeForNextSpeechUtterance(QueueMode.CATEGORY_FLUSH); + output.append_( + output.speechBuffer_, new Spannable(''), + {annotation: [new OutputAction()]}); + output.withString('test'); + mockFeedback.clearPendingOutput() + .call(output.go.bind(output)) + .expectSpeechWithQueueMode('', QueueMode.CATEGORY_FLUSH) + .expectSpeechWithQueueMode('test', QueueMode.CATEGORY_FLUSH) + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'SetAccessibilityFocus', function() { - this.runWithLoadedTree('<p>Text.</p><button>Button</button>', function(root) { - const node = root.find({role: RoleType.BUTTON}); +TEST_F('ChromeVoxBackgroundTest', 'SetAccessibilityFocus', async function() { + const root = + await this.runWithLoadedTree('<p>Text.</p><button>Button</button>'); + const node = root.find({role: RoleType.BUTTON}); - node.addEventListener(EventType.FOCUS, this.newCallback(function() { - chrome.automation.getAccessibilityFocus((focusedNode) => { - assertEquals(node, focusedNode); - }); - })); + node.addEventListener(EventType.FOCUS, this.newCallback(function() { + chrome.automation.getAccessibilityFocus((focusedNode) => { + assertEquals(node, focusedNode); + }); + })); - node.focus(); - }); + node.focus(); }); -TEST_F('ChromeVoxBackgroundTest', 'MenuItemRadio', function() { +TEST_F('ChromeVoxBackgroundTest', 'MenuItemRadio', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <ul role="menu" tabindex="0" autofocus> @@ -2713,19 +2633,18 @@ <li role="menuitemradio" aria-checked="false">Large</li> </ul> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.expectSpeech('Menu', 'with 3 items') - .call(doCmd('nextObject')) - .expectSpeech('Small, menu item radio button selected', ' 1 of 3 ') - .call(doCmd('nextObject')) - .expectSpeech('Medium, menu item radio button unselected', ' 2 of 3 ') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.expectSpeech('Menu', 'with 3 items') + .call(doCmd('nextObject')) + .expectSpeech('Small, menu item radio button selected', ' 1 of 3 ') + .call(doCmd('nextObject')) + .expectSpeech('Medium, menu item radio button unselected', ' 2 of 3 ') + .replay(); }); TEST_F( 'ChromeVoxBackgroundTest', 'ButtonNavigationIgnoresRadioButtons', - function() { + async function() { const mockFeedback = this.createMockFeedback(); const site = ` <button>Action 1</button> @@ -2736,63 +2655,59 @@ <button>Action 2</button> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.call(doCmd('nextButton')) - .expectSpeech('Action 1', 'Button') - .call(doCmd('nextButton')) - .expectSpeech('Action 2', 'Button'); + const root = await this.runWithLoadedTree(site); + mockFeedback.call(doCmd('nextButton')) + .expectSpeech('Action 1', 'Button') + .call(doCmd('nextButton')) + .expectSpeech('Action 2', 'Button'); - mockFeedback.replay(); - }); + mockFeedback.replay(); }); TEST_F( - 'ChromeVoxBackgroundTest', 'FocusableNamedDivIsNotContainer', function() { + 'ChromeVoxBackgroundTest', 'FocusableNamedDivIsNotContainer', + async function() { const site = ` <div aria-label="hello world" tabindex="0">hello world</div> `; - this.runWithLoadedTree(site, function(root) { - const genericContainer = root.find({role: RoleType.GENERIC_CONTAINER}); - assertTrue(AutomationPredicate.object(genericContainer)); - assertFalse(AutomationPredicate.container(genericContainer)); - }); + const root = await this.runWithLoadedTree(site); + const genericContainer = root.find({role: RoleType.GENERIC_CONTAINER}); + assertTrue(AutomationPredicate.object(genericContainer)); + assertFalse(AutomationPredicate.container(genericContainer)); }); -TEST_F('ChromeVoxBackgroundTest', 'HitTestOnExoSurface', function() { +TEST_F('ChromeVoxBackgroundTest', 'HitTestOnExoSurface', async function() { const site = ` <button></button> <input type="text"</input> `; - this.runWithLoadedTree(site, function(root) { - const fakeWindow = root.find({role: RoleType.BUTTON}); - const realTextField = root.find({role: RoleType.TEXT_FIELD}); + const root = await this.runWithLoadedTree(site); + const fakeWindow = root.find({role: RoleType.BUTTON}); + const realTextField = root.find({role: RoleType.TEXT_FIELD}); - // Fake the role and className to imitate a ExoSurface. - Object.defineProperty(fakeWindow, 'role', {get: () => RoleType.WINDOW}); - Object.defineProperty( - fakeWindow, 'className', {get: () => 'ExoSurface-40'}); + // Fake the role and className to imitate a ExoSurface. + Object.defineProperty(fakeWindow, 'role', {get: () => RoleType.WINDOW}); + Object.defineProperty(fakeWindow, 'className', {get: () => 'ExoSurface-40'}); - // Mock and expect a call for the fake window. - chrome.accessibilityPrivate.sendSyntheticMouseEvent = - this.newCallback(evt => { - assertEquals(fakeWindow.location.left, evt.x); - assertEquals(fakeWindow.location.top, evt.y); - }); + // Mock and expect a call for the fake window. + chrome.accessibilityPrivate.sendSyntheticMouseEvent = + this.newCallback(evt => { + assertEquals(fakeWindow.location.left, evt.x); + assertEquals(fakeWindow.location.top, evt.y); + }); - // Fake a mouse explore event on the real text field. This should not - // trigger the above mouse path. - GestureCommandHandler.pointerHandler_.onMouseMove( - realTextField.location.left, realTextField.location.top); + // Fake a mouse explore event on the real text field. This should not + // trigger the above mouse path. + GestureCommandHandler.pointerHandler_.onMouseMove( + realTextField.location.left, realTextField.location.top); - // Fake a touch explore gesture event on the fake window which should - // trigger a mouse move. - GestureCommandHandler.onAccessibilityGesture_( - Gesture.TOUCH_EXPLORE, fakeWindow.location.left, - fakeWindow.location.top); - }); + // Fake a touch explore gesture event on the fake window which should + // trigger a mouse move. + GestureCommandHandler.onAccessibilityGesture_( + Gesture.TOUCH_EXPLORE, fakeWindow.location.left, fakeWindow.location.top); }); -TEST_F('ChromeVoxBackgroundTest', 'PointerSkipsContainers', function() { +TEST_F('ChromeVoxBackgroundTest', 'PointerSkipsContainers', async function() { PointerHandler.MIN_NO_POINTER_ANCHOR_SOUND_DELAY_MS = -1; const mockFeedback = this.createMockFeedback(); const site = ` @@ -2800,43 +2715,42 @@ <div role=button><p></p></div> </div> `; - this.runWithLoadedTree(site, function(root) { - ChromeVoxState.addObserver(new class { - onCurrentRangeChanged(range) { - if (!range) { - ChromeVox.tts.speak('range cleared!'); - } + const root = await this.runWithLoadedTree(site); + ChromeVoxState.addObserver(new class { + onCurrentRangeChanged(range) { + if (!range) { + ChromeVox.tts.speak('range cleared!'); } - }()); + } + }()); - const button = root.find({role: RoleType.BUTTON}); - assertNotNullNorUndefined(button); - const group = button.parent; - assertNotNullNorUndefined(group); - mockFeedback.call(simulateHitTestResult(button)) - .expectSpeech('Button') - .call(() => { - // Override the role to simulate panes which are only found in - // views. - Object.defineProperty(group, 'role', { - get() { - return chrome.automation.RoleType.PANE; - } - }); - }) - .call(simulateHitTestResult(group)) - .expectSpeech('range cleared!') - .expectEarcon(Earcon.NO_POINTER_ANCHOR) - .call(simulateHitTestResult(button)) - .expectSpeech('Button') - .call(simulateHitTestResult(group)) - .expectSpeech('range cleared!') - .expectEarcon(Earcon.NO_POINTER_ANCHOR) - .replay(); - }); + const button = root.find({role: RoleType.BUTTON}); + assertNotNullNorUndefined(button); + const group = button.parent; + assertNotNullNorUndefined(group); + mockFeedback.call(simulateHitTestResult(button)) + .expectSpeech('Button') + .call(() => { + // Override the role to simulate panes which are only found in + // views. + Object.defineProperty(group, 'role', { + get() { + return chrome.automation.RoleType.PANE; + } + }); + }) + .call(simulateHitTestResult(group)) + .expectSpeech('range cleared!') + .expectEarcon(Earcon.NO_POINTER_ANCHOR) + .call(simulateHitTestResult(button)) + .expectSpeech('Button') + .call(simulateHitTestResult(group)) + .expectSpeech('range cleared!') + .expectEarcon(Earcon.NO_POINTER_ANCHOR) + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'FocusOnUnknown', function() { +TEST_F('ChromeVoxBackgroundTest', 'FocusOnUnknown', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>start</p> @@ -2845,46 +2759,44 @@ </div> <div role="group" tabindex=0></div> `; - this.runWithLoadedTree(site, function(root) { - const [group1, group2] = root.findAll({role: RoleType.GROUP}); - assertNotNullNorUndefined(group1); - assertNotNullNorUndefined(group2); - Object.defineProperty(group1, 'role', { - get() { - return chrome.automation.RoleType.UNKNOWN; - } - }); - Object.defineProperty(group2, 'role', { - get() { - return chrome.automation.RoleType.UNKNOWN; - } - }); - - const evt2 = new CustomAutomationEvent(EventType.FOCUS, group2); - const currentRange = ChromeVoxState.instance.currentRange; - DesktopAutomationInterface.instance.onFocus(evt2); - assertEquals(currentRange, ChromeVoxState.instance.currentRange); - - const evt1 = new CustomAutomationEvent(EventType.FOCUS, group1); - mockFeedback - .call(DesktopAutomationInterface.instance.onFocus.bind( - DesktopAutomationInterface.instance, evt1)) - .expectSpeech('hello') - .replay(); + const root = await this.runWithLoadedTree(site); + const [group1, group2] = root.findAll({role: RoleType.GROUP}); + assertNotNullNorUndefined(group1); + assertNotNullNorUndefined(group2); + Object.defineProperty(group1, 'role', { + get() { + return chrome.automation.RoleType.UNKNOWN; + } }); + Object.defineProperty(group2, 'role', { + get() { + return chrome.automation.RoleType.UNKNOWN; + } + }); + + const evt2 = new CustomAutomationEvent(EventType.FOCUS, group2); + const currentRange = ChromeVoxState.instance.currentRange; + DesktopAutomationInterface.instance.onFocus(evt2); + assertEquals(currentRange, ChromeVoxState.instance.currentRange); + + const evt1 = new CustomAutomationEvent(EventType.FOCUS, group1); + mockFeedback + .call(DesktopAutomationInterface.instance.onFocus.bind( + DesktopAutomationInterface.instance, evt1)) + .expectSpeech('hello') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'TimeDateCommand', function() { +TEST_F('ChromeVoxBackgroundTest', 'TimeDateCommand', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree('<p></p>', function(root) { - mockFeedback.call(doCmd('speakTimeAndDate')) - .expectSpeech(/(AM|PM)*(2)/) - .expectBraille(/(AM|PM)*(2)/) - .replay(); - }); + const root = await this.runWithLoadedTree('<p></p>'); + mockFeedback.call(doCmd('speakTimeAndDate')) + .expectSpeech(/(AM|PM)*(2)/) + .expectBraille(/(AM|PM)*(2)/) + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'SwipeToScrollByPage', function() { +TEST_F('ChromeVoxBackgroundTest', 'SwipeToScrollByPage', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p style="font-size: 200pt">This is a test</p> @@ -2895,50 +2807,49 @@ <p style="font-size: 200pt">This is a test</p> <p style="font-size: 200pt">This is a test</p> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.call(doGesture(Gesture.SWIPE_UP3)) - .expectSpeech(/Page 2 of/) - .call(doGesture(Gesture.SWIPE_UP3)) - .expectSpeech(/Page 3 of/) - .call(doGesture(Gesture.SWIPE_DOWN3)) - .expectSpeech(/Page 2 of/) - .call(doGesture(Gesture.SWIPE_DOWN3)) - .expectSpeech(/Page 1 of/) - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.call(doGesture(Gesture.SWIPE_UP3)) + .expectSpeech(/Page 2 of/) + .call(doGesture(Gesture.SWIPE_UP3)) + .expectSpeech(/Page 3 of/) + .call(doGesture(Gesture.SWIPE_DOWN3)) + .expectSpeech(/Page 2 of/) + .call(doGesture(Gesture.SWIPE_DOWN3)) + .expectSpeech(/Page 1 of/) + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'PointerOnOffOnRepeatsNode', function() { - PointerHandler.MIN_NO_POINTER_ANCHOR_SOUND_DELAY_MS = -1; - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree('<button>hi</button>', function(root) { - ChromeVoxState.addObserver(new class { - onCurrentRangeChanged(range) { - if (!range) { - ChromeVox.tts.speak('range cleared!'); +TEST_F( + 'ChromeVoxBackgroundTest', 'PointerOnOffOnRepeatsNode', async function() { + PointerHandler.MIN_NO_POINTER_ANCHOR_SOUND_DELAY_MS = -1; + const mockFeedback = this.createMockFeedback(); + const root = await this.runWithLoadedTree('<button>hi</button>'); + ChromeVoxState.addObserver(new class { + onCurrentRangeChanged(range) { + if (!range) { + ChromeVox.tts.speak('range cleared!'); + } } - } - }()); + }()); - const button = root.find({role: RoleType.BUTTON}); - assertNotNullNorUndefined(button); - mockFeedback.call(simulateHitTestResult(button)) - .expectSpeech('hi', 'Button') + const button = root.find({role: RoleType.BUTTON}); + assertNotNullNorUndefined(button); + mockFeedback.call(simulateHitTestResult(button)) + .expectSpeech('hi', 'Button') - // Touch slightly off of the button. - .call(GestureCommandHandler.onAccessibilityGesture_.bind( - null, Gesture.TOUCH_EXPLORE, button.location.left, - button.location.top + 60)) - .expectSpeech('range cleared!') - .expectEarcon(Earcon.NO_POINTER_ANCHOR) - .clearPendingOutput() - .call(simulateHitTestResult(button)) - .expectSpeech('hi', 'Button') - .replay(); - }); -}); + // Touch slightly off of the button. + .call(GestureCommandHandler.onAccessibilityGesture_.bind( + null, Gesture.TOUCH_EXPLORE, button.location.left, + button.location.top + 60)) + .expectSpeech('range cleared!') + .expectEarcon(Earcon.NO_POINTER_ANCHOR) + .clearPendingOutput() + .call(simulateHitTestResult(button)) + .expectSpeech('hi', 'Button') + .replay(); + }); -TEST_F('ChromeVoxBackgroundTest', 'PopupButtonCollapsed', function() { +TEST_F('ChromeVoxBackgroundTest', 'PopupButtonCollapsed', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <select id="button"> @@ -2946,16 +2857,15 @@ <option value="Banana">Banana</option> </select> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.call(doCmd('jumpToTop')) - .expectSpeech( - 'Apple', 'Button', 'has pop up', 'Collapsed', - 'Press Search+Space to activate') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.call(doCmd('jumpToTop')) + .expectSpeech( + 'Apple', 'Button', 'has pop up', 'Collapsed', + 'Press Search+Space to activate') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'PopupButtonExpanded', function() { +TEST_F('ChromeVoxBackgroundTest', 'PopupButtonExpanded', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <button id="button" aria-haspopup="true" aria-expanded="true" @@ -2970,18 +2880,17 @@ <li role="menuitem">Item 3</li> </ul> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback - .call(doCmd('jumpToTop')) - // SetSize is only reported if popup button is expanded. - .expectSpeech( - 'Click me', 'Button', 'has pop up', 'with 3 items', 'Expanded', - 'Press Search+Space to activate') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback + .call(doCmd('jumpToTop')) + // SetSize is only reported if popup button is expanded. + .expectSpeech( + 'Click me', 'Button', 'has pop up', 'with 3 items', 'Expanded', + 'Press Search+Space to activate') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'SortDirection', function() { +TEST_F('ChromeVoxBackgroundTest', 'SortDirection', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <table border="1"> @@ -2999,130 +2908,125 @@ }); </script> `; - this.runWithLoadedTree(site, function(root) { - const sortButton = root.find({role: RoleType.BUTTON}); - mockFeedback.expectSpeech('Button', 'Ascending sort') - .call(sortButton.doDefault.bind(sortButton)) - .expectSpeech('Descending sort') - .call(sortButton.doDefault.bind(sortButton)) - .expectSpeech('Ascending sort') - .call(sortButton.doDefault.bind(sortButton)) - .expectSpeech('Descending sort') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + const sortButton = root.find({role: RoleType.BUTTON}); + mockFeedback.expectSpeech('Button', 'Ascending sort') + .call(sortButton.doDefault.bind(sortButton)) + .expectSpeech('Descending sort') + .call(sortButton.doDefault.bind(sortButton)) + .expectSpeech('Ascending sort') + .call(sortButton.doDefault.bind(sortButton)) + .expectSpeech('Descending sort') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'InlineLineNavigation', function() { +TEST_F('ChromeVoxBackgroundTest', 'InlineLineNavigation', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>start</p> <p><strong>This</strong><b>is</b>a <em>test</em></p> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.call(doCmd('nextLine')) - .expectSpeech('This', 'is', 'a ', 'test') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.call(doCmd('nextLine')) + .expectSpeech('This', 'is', 'a ', 'test') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'AudioVideo', function() { +TEST_F('ChromeVoxBackgroundTest', 'AudioVideo', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <button></button> <button></button> `; - this.runWithLoadedTree(site, function(root) { - const [audio, video] = root.findAll({role: RoleType.BUTTON}); + const root = await this.runWithLoadedTree(site); + const [audio, video] = root.findAll({role: RoleType.BUTTON}); - assertNotNullNorUndefined(audio); - assertNotNullNorUndefined(video); + assertNotNullNorUndefined(audio); + assertNotNullNorUndefined(video); - assertEquals(undefined, audio.name); - assertEquals(undefined, video.name); - assertEquals(undefined, audio.firstChild); - assertEquals(undefined, video.firstChild); + assertEquals(undefined, audio.name); + assertEquals(undefined, video.name); + assertEquals(undefined, audio.firstChild); + assertEquals(undefined, video.firstChild); - // Fake the roles. - Object.defineProperty(audio, 'role', { - get() { - return chrome.automation.RoleType.AUDIO; - } + // Fake the roles. + Object.defineProperty(audio, 'role', { + get() { + return chrome.automation.RoleType.AUDIO; + } + }); + + Object.defineProperty(video, 'role', { + get() { + return chrome.automation.RoleType.VIDEO; + } + }); + + mockFeedback.call(doCmd('nextObject')) + .expectSpeech('Video') + .call(doCmd('previousObject')) + .expectSpeech('Audio') + .replay(); +}); + +TEST_F('ChromeVoxBackgroundTest', 'AlertNoAnnouncement', async function() { + const mockFeedback = this.createMockFeedback(); + const root = await this.runWithLoadedTree('<button></button>'); + ChromeVoxState.addObserver(new class { + onCurrentRangeChanged(range) { + assertNotReached('Range was changed unexpectedly.'); + } + }()); + const button = root.find({role: RoleType.BUTTON}); + const alertEvt = new CustomAutomationEvent(EventType.ALERT, button); + mockFeedback + .call(DesktopAutomationInterface.instance.onAlert.bind( + DesktopAutomationInterface.instance, alertEvt)) + .call(() => assertFalse(mockFeedback.utteranceInQueue('Alert'))) + .replay(); +}); + +TEST_F('ChromeVoxBackgroundTest', 'AlertAnnouncement', async function() { + const mockFeedback = this.createMockFeedback(); + const root = await this.runWithLoadedTree('<button>hello world</button>'); + ChromeVoxState.addObserver(new class { + onCurrentRangeChanged(range) { + assertNotReached('Range was changed unexpectedly.'); + } + }()); + + const button = root.find({role: RoleType.BUTTON}); + const alertEvt = new CustomAutomationEvent(EventType.ALERT, button); + mockFeedback + .call(DesktopAutomationInterface.instance.onAlert.bind( + DesktopAutomationInterface.instance, alertEvt)) + .expectNextSpeechUtteranceIsNot('Alert') + .expectSpeech('hello world') + .replay(); +}); + +TEST_F( + 'ChromeVoxBackgroundTest', 'SwipeLeftRight4ByContainers', async function() { + const mockFeedback = this.createMockFeedback(); + const root = await this.runWithLoadedTree(`<p>test</p>`); + mockFeedback.call(doGesture(Gesture.SWIPE_RIGHT4)) + .expectSpeech('Launcher', 'Button', 'Shelf', 'Tool bar', ', window') + .call(doGesture(Gesture.SWIPE_RIGHT4)) + .expectSpeech('Shelf', 'Tool bar') + .call(doGesture(Gesture.SWIPE_RIGHT4)) + .expectSpeech(/Status tray*/) + .call(doGesture(Gesture.SWIPE_RIGHT4)) + .expectSpeech(/Address and search bar*/) + + .call(doGesture(Gesture.SWIPE_LEFT4)) + .expectSpeech(/Status tray*/) + .call(doGesture(Gesture.SWIPE_LEFT4)) + .expectSpeech('Shelf', 'Tool bar') + + .replay(); }); - Object.defineProperty(video, 'role', { - get() { - return chrome.automation.RoleType.VIDEO; - } - }); - - mockFeedback.call(doCmd('nextObject')) - .expectSpeech('Video') - .call(doCmd('previousObject')) - .expectSpeech('Audio') - .replay(); - }); -}); - -TEST_F('ChromeVoxBackgroundTest', 'AlertNoAnnouncement', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree('<button></button>', function(root) { - ChromeVoxState.addObserver(new class { - onCurrentRangeChanged(range) { - assertNotReached('Range was changed unexpectedly.'); - } - }()); - const button = root.find({role: RoleType.BUTTON}); - const alertEvt = new CustomAutomationEvent(EventType.ALERT, button); - mockFeedback - .call(DesktopAutomationInterface.instance.onAlert.bind( - DesktopAutomationInterface.instance, alertEvt)) - .call(() => assertFalse(mockFeedback.utteranceInQueue('Alert'))) - .replay(); - }); -}); - -TEST_F('ChromeVoxBackgroundTest', 'AlertAnnouncement', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree('<button>hello world</button>', function(root) { - ChromeVoxState.addObserver(new class { - onCurrentRangeChanged(range) { - assertNotReached('Range was changed unexpectedly.'); - } - }()); - - const button = root.find({role: RoleType.BUTTON}); - const alertEvt = new CustomAutomationEvent(EventType.ALERT, button); - mockFeedback - .call(DesktopAutomationInterface.instance.onAlert.bind( - DesktopAutomationInterface.instance, alertEvt)) - .expectNextSpeechUtteranceIsNot('Alert') - .expectSpeech('hello world') - .replay(); - }); -}); - -TEST_F('ChromeVoxBackgroundTest', 'SwipeLeftRight4ByContainers', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(`<p>test</p>`, function(root) { - mockFeedback.call(doGesture(Gesture.SWIPE_RIGHT4)) - .expectSpeech('Launcher', 'Button', 'Shelf', 'Tool bar', ', window') - .call(doGesture(Gesture.SWIPE_RIGHT4)) - .expectSpeech('Shelf', 'Tool bar') - .call(doGesture(Gesture.SWIPE_RIGHT4)) - .expectSpeech(/Status tray*/) - .call(doGesture(Gesture.SWIPE_RIGHT4)) - .expectSpeech(/Address and search bar*/) - - .call(doGesture(Gesture.SWIPE_LEFT4)) - .expectSpeech(/Status tray*/) - .call(doGesture(Gesture.SWIPE_LEFT4)) - .expectSpeech('Shelf', 'Tool bar') - - .replay(); - }); -}); - -TEST_F('ChromeVoxBackgroundTest', 'SwipeLeftRight2', function() { +TEST_F('ChromeVoxBackgroundTest', 'SwipeLeftRight2', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p id="live" aria-live="polite"</p> @@ -3132,19 +3036,20 @@ }); </script> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.call(doGesture(Gesture.SWIPE_RIGHT2)).expectSpeech('Enter'); - mockFeedback.call(doGesture(Gesture.SWIPE_LEFT2)) - .expectSpeech('Escape') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.call(doGesture(Gesture.SWIPE_RIGHT2)).expectSpeech('Enter'); + mockFeedback.call(doGesture(Gesture.SWIPE_LEFT2)) + .expectSpeech('Escape') + .replay(); }); // TODO(crbug.com/1228418) - Improve the generation of summaries across ChromeOS -TEST_F('ChromeVoxBackgroundTest', 'AlertDialogAutoSummaryTextContent', function() { - this.resetContextualOutput(); - const mockFeedback = this.createMockFeedback(); - const site = ` +TEST_F( + 'ChromeVoxBackgroundTest', 'AlertDialogAutoSummaryTextContent', + async function() { + this.resetContextualOutput(); + const mockFeedback = this.createMockFeedback(); + const site = ` <p>start</p> <div role="alertdialog" aria-label="Setup"> <h1>Welcome</h1> @@ -3154,118 +3059,115 @@ </div> <p>end</p> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.call(doCmd('nextObject')) - .expectSpeech('Setup') - .expectSpeech(`Welcome This is some introductory text Exit Let's go`) - .expectSpeech('Welcome', 'Heading 1') - .call(doCmd('nextObject')) - .expectSpeech('This is some introductory text') - .call(doCmd('nextObject')) - .expectSpeech('Exit', 'Button') - .call(doCmd('nextObject')) - .expectSpeech(`Let's go`, 'Button') - .call(doCmd('nextObject')) - .expectSpeech('end') + const root = await this.runWithLoadedTree(site); + mockFeedback.call(doCmd('nextObject')) + .expectSpeech('Setup') + .expectSpeech(`Welcome This is some introductory text Exit Let's go`) + .expectSpeech('Welcome', 'Heading 1') + .call(doCmd('nextObject')) + .expectSpeech('This is some introductory text') + .call(doCmd('nextObject')) + .expectSpeech('Exit', 'Button') + .call(doCmd('nextObject')) + .expectSpeech(`Let's go`, 'Button') + .call(doCmd('nextObject')) + .expectSpeech('end') - .call(doCmd('previousObject')) - .expectSpeech(`Let's go`, 'Button') - .expectSpeech('Setup') - .expectSpeech(`Welcome This is some introductory text Exit Let's go`) + .call(doCmd('previousObject')) + .expectSpeech(`Let's go`, 'Button') + .expectSpeech('Setup') + .expectSpeech(`Welcome This is some introductory text Exit Let's go`) - .replay(); - }); -}); + .replay(); + }); -TEST_F('ChromeVoxBackgroundTest', 'ImageAnnotations', function() { +TEST_F('ChromeVoxBackgroundTest', 'ImageAnnotations', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>start</p> <img alt="bar" src="data:image/png;base64,iVBORw0KGgoAAAANS"> <img src="data:image/png;base64,iVBORw0KGgoAAAANS"> `; - this.runWithLoadedTree(site, function(root) { - const [namedImg, unnamedImg] = root.findAll({role: RoleType.IMAGE}); + const root = await this.runWithLoadedTree(site); + const [namedImg, unnamedImg] = root.findAll({role: RoleType.IMAGE}); - assertNotNullNorUndefined(namedImg); - assertNotNullNorUndefined(unnamedImg); + assertNotNullNorUndefined(namedImg); + assertNotNullNorUndefined(unnamedImg); - assertEquals('bar', namedImg.name); - assertEquals(undefined, unnamedImg.name); + assertEquals('bar', namedImg.name); + assertEquals(undefined, unnamedImg.name); - // Fake the image annotation. - Object.defineProperty(namedImg, 'imageAnnotation', { - get() { - return 'foo'; - } - }); - Object.defineProperty(unnamedImg, 'imageAnnotation', { - get() { - return 'foo'; - } - }); - - mockFeedback.call(doCmd('nextObject')) - .expectSpeech('start') - .expectNextSpeechUtteranceIsNot('foo') - .expectSpeech('bar', 'Image') - .call(doCmd('nextObject')) - .expectNextSpeechUtteranceIsNot('bar') - .expectSpeech('foo', 'Image') - .replay(); + // Fake the image annotation. + Object.defineProperty(namedImg, 'imageAnnotation', { + get() { + return 'foo'; + } }); + Object.defineProperty(unnamedImg, 'imageAnnotation', { + get() { + return 'foo'; + } + }); + + mockFeedback.call(doCmd('nextObject')) + .expectSpeech('start') + .expectNextSpeechUtteranceIsNot('foo') + .expectSpeech('bar', 'Image') + .call(doCmd('nextObject')) + .expectNextSpeechUtteranceIsNot('bar') + .expectSpeech('foo', 'Image') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'VolumeChanges', function() { +TEST_F('ChromeVoxBackgroundTest', 'VolumeChanges', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree('<p>test</p>', function() { - const bounds = ChromeVoxState.instance.getFocusBounds(); - mockFeedback.call(press(KeyCode.VOLUME_UP)) - .expectSpeech('Volume', 'Slider', /\d+%/) - .call(() => { - // The bounds should not have changed. - assertEquals( - JSON.stringify(bounds), - JSON.stringify(ChromeVoxState.instance.getFocusBounds())); - }) - .replay(); - }); + await this.runWithLoadedTree('<p>test</p>'); + const bounds = ChromeVoxState.instance.getFocusBounds(); + mockFeedback.call(press(KeyCode.VOLUME_UP)) + .expectSpeech('Volume', 'Slider', /\d+%/) + .call(() => { + // The bounds should not have changed. + assertEquals( + JSON.stringify(bounds), + JSON.stringify(ChromeVoxState.instance.getFocusBounds())); + }) + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'WrapContentEditableAtEndOfDoc', function() { - const mockFeedback = this.createMockFeedback(); - const site = `<p>start</p> +TEST_F( + 'ChromeVoxBackgroundTest', 'WrapContentEditableAtEndOfDoc', + async function() { + const mockFeedback = this.createMockFeedback(); + const site = `<p>start</p> <div role="textbox" contenteditable aria-multiline="false"></div>`; - this.runWithLoadedTree(site, function() { - mockFeedback.call(doCmd('nextObject')) - .expectSpeech('Edit text') - .call(doCmd('nextObject')) - .expectEarcon(Earcon.WRAP) - .expectSpeech('Web Content') - .call(doCmd('nextObject')) - .expectSpeech('start') - .replay(); - }); -}); + await this.runWithLoadedTree(site); + mockFeedback.call(doCmd('nextObject')) + .expectSpeech('Edit text') + .call(doCmd('nextObject')) + .expectEarcon(Earcon.WRAP) + .expectSpeech('Web Content') + .call(doCmd('nextObject')) + .expectSpeech('start') + .replay(); + }); -TEST_F('ChromeVoxBackgroundTest', 'ReadFromHereBlankNodes', function() { +TEST_F('ChromeVoxBackgroundTest', 'ReadFromHereBlankNodes', async function() { const mockFeedback = this.createMockFeedback(); const site = `<a tabindex=0></a><p>start</p><a tabindex=0></a><p>end</p>`; - this.runWithLoadedTree(site, function(root) { - assertEquals( - RoleType.STATIC_TEXT, - ChromeVoxState.instance.currentRange.start.node.role); + const root = await this.runWithLoadedTree(site); + assertEquals( + RoleType.STATIC_TEXT, + ChromeVoxState.instance.currentRange.start.node.role); - // "start" is uttered twice, once for the initial focus as the page loads, - // and once during the 'read from here' command. - mockFeedback.expectSpeech('start') - .call(doCmd('readFromHere')) - .expectSpeech('start', 'end') - .replay(); - }); + // "start" is uttered twice, once for the initial focus as the page loads, + // and once during the 'read from here' command. + mockFeedback.expectSpeech('start') + .call(doCmd('readFromHere')) + .expectSpeech('start', 'end') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'ContainerButtons', function() { +TEST_F('ChromeVoxBackgroundTest', 'ContainerButtons', async function() { const mockFeedback = this.createMockFeedback(); // This pattern can be found in ARC++/YouTube. @@ -3275,25 +3177,25 @@ <div role="group">4 minutes, Cat Video</div> </div> `; - this.runWithLoadedTree(site, function(root) { - const group = root.find({role: RoleType.GROUP}); + const root = await this.runWithLoadedTree(site); + const group = root.find({role: RoleType.GROUP}); - Object.defineProperty(group, 'clickable', { - get() { - return true; - } - }); - - mockFeedback.call(doCmd('nextObject')) - .expectSpeech('Cat Video', 'Button') - .call(doCmd('nextObject')) - .expectSpeech('4 minutes, Cat Video') - .replay(); + Object.defineProperty(group, 'clickable', { + get() { + return true; + } }); + + mockFeedback.call(doCmd('nextObject')) + .expectSpeech('Cat Video', 'Button') + .call(doCmd('nextObject')) + .expectSpeech('4 minutes, Cat Video') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'FocusOnWebAreaIgnoresEvents', function() { - const site = ` +TEST_F( + 'ChromeVoxBackgroundTest', 'FocusOnWebAreaIgnoresEvents', async function() { + const site = ` <div role="application" tabindex=0 aria-label="container"> <select> <option>apple</option> @@ -3311,45 +3213,45 @@ }); </script> `; - this.runWithLoadedTree(site, async function(root) { - const application = root.find({role: RoleType.APPLICATION}); - const popUpButton = root.find({role: RoleType.POP_UP_BUTTON}); - const p = root.find({role: RoleType.PARAGRAPH}); + const root = await this.runWithLoadedTree(site); + const application = root.find({role: RoleType.APPLICATION}); + const popUpButton = root.find({role: RoleType.POP_UP_BUTTON}); + const p = root.find({role: RoleType.PARAGRAPH}); - // Move focus to the select, which honors value changes through - // FocusAutomationHandler. - popUpButton.focus(); - await TestUtils.waitForSpeech('apple'); + // Move focus to the select, which honors value changes through + // FocusAutomationHandler. + popUpButton.focus(); + await TestUtils.waitForSpeech('apple'); - // Clicking the paragraph programmatically changes the select value. - p.doDefault(); - await TestUtils.waitForSpeech('grape'); - assertEquals( - RoleType.POP_UP_BUTTON, - ChromeVoxState.instance.currentRange.start.node.role); + // Clicking the paragraph programmatically changes the select value. + p.doDefault(); + await TestUtils.waitForSpeech('grape'); + assertEquals( + RoleType.POP_UP_BUTTON, + ChromeVoxState.instance.currentRange.start.node.role); - // Now, move focus to the application which is a parent of the select. - application.focus(); - await TestUtils.waitForSpeech('container'); + // Now, move focus to the application which is a parent of the select. + application.focus(); + await TestUtils.waitForSpeech('container'); - // Hook into the speak call, to see what comes next. - let nextSpeech; - ChromeVox.tts.speak = textString => { - nextSpeech = textString; - }; + // Hook into the speak call, to see what comes next. + let nextSpeech; + ChromeVox.tts.speak = textString => { + nextSpeech = textString; + }; - // Trigger another value update for the select. - p.doDefault(); + // Trigger another value update for the select. + p.doDefault(); - // This comes when the <select>'s value changes. - await TestUtils.waitForEvent(application, EventType.SELECTED_VALUE_CHANGED); + // This comes when the <select>'s value changes. + await TestUtils.waitForEvent( + application, EventType.SELECTED_VALUE_CHANGED); - // Nothing should have been spoken. - assertEquals(undefined, nextSpeech); - }); -}); + // Nothing should have been spoken. + assertEquals(undefined, nextSpeech); + }); -TEST_F('ChromeVoxBackgroundTest', 'AriaLeaves', function() { +TEST_F('ChromeVoxBackgroundTest', 'AriaLeaves', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div role="radio"><p>PM</p></div> @@ -3360,34 +3262,33 @@ p.addEventListener('click', () => {}); </script> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.expectSpeech('PM, radio button unselected') - .call(doCmd('nextObject')) - .expectSpeech('PM') - .call( - () => assertEquals( - RoleType.STATIC_TEXT, - ChromeVoxState.instance.currentRange.start.node.role)) + const root = await this.runWithLoadedTree(site); + mockFeedback.expectSpeech('PM, radio button unselected') + .call(doCmd('nextObject')) + .expectSpeech('PM') + .call( + () => assertEquals( + RoleType.STATIC_TEXT, + ChromeVoxState.instance.currentRange.start.node.role)) - .call(doCmd('nextObject')) - .expectSpeech('Agree, switch off') - .call( - () => assertEquals( - RoleType.SWITCH, - ChromeVoxState.instance.currentRange.start.node.role)) + .call(doCmd('nextObject')) + .expectSpeech('Agree, switch off') + .call( + () => assertEquals( + RoleType.SWITCH, + ChromeVoxState.instance.currentRange.start.node.role)) - .call(doCmd('nextObject')) - .expectSpeech('Agree', 'Check box') - .call( - () => assertEquals( - RoleType.CHECK_BOX, - ChromeVoxState.instance.currentRange.start.node.role)) + .call(doCmd('nextObject')) + .expectSpeech('Agree', 'Check box') + .call( + () => assertEquals( + RoleType.CHECK_BOX, + ChromeVoxState.instance.currentRange.start.node.role)) - .replay(); - }); + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'MarkedContent', function() { +TEST_F('ChromeVoxBackgroundTest', 'MarkedContent', async function() { this.resetContextualOutput(); const mockFeedback = this.createMockFeedback(); const site = ` @@ -3403,48 +3304,49 @@ <span>This is </span><span role="suggestion"><span role="deletion">everyone's</span></span><span> text.</span> `; - this.runWithLoadedTree(site, function(rootNode) { - mockFeedback.expectSpeech('Start') - .call(doCmd('nextObject')) - .expectSpeech('This is ') - .call(doCmd('nextObject')) - .expectSpeech('Marked content', 'my', 'Marked content end') - .expectBraille('Marked content my Marked content end') - .call(doCmd('nextObject')) - .expectSpeech(' text.') - .expectBraille(' text.') - .call(doCmd('nextObject')) - .expectSpeech('This is ') - .call(doCmd('nextObject')) - .expectSpeech('Comment', 'your', 'Comment end') - .expectBraille('Comment your Comment end') - .call(doCmd('nextObject')) - .expectSpeech(' text.') - .expectBraille(' text.') - .call(doCmd('nextObject')) - .expectSpeech('This is ') - .call(doCmd('nextObject')) - .expectSpeech('Suggest', 'Insert', 'their', 'Insert end', 'Suggest end') - .expectBraille('Suggest Insert their Insert end Suggest end') - .call(doCmd('nextObject')) - .expectSpeech(' text.') - .expectBraille(' text.') - .call(doCmd('nextObject')) - .expectSpeech('This is ') - .call(doCmd('nextObject')) - .expectSpeech( - 'Suggest', 'Delete', `everyone's`, 'Delete end', 'Suggest end') - .expectBraille(`Suggest Delete everyone's Delete end Suggest end`) - .call(doCmd('nextObject')) - .expectSpeech(' text.') - .expectBraille(' text.') - .replay(); - }); + const rootNode = await this.runWithLoadedTree(site); + mockFeedback.expectSpeech('Start') + .call(doCmd('nextObject')) + .expectSpeech('This is ') + .call(doCmd('nextObject')) + .expectSpeech('Marked content', 'my', 'Marked content end') + .expectBraille('Marked content my Marked content end') + .call(doCmd('nextObject')) + .expectSpeech(' text.') + .expectBraille(' text.') + .call(doCmd('nextObject')) + .expectSpeech('This is ') + .call(doCmd('nextObject')) + .expectSpeech('Comment', 'your', 'Comment end') + .expectBraille('Comment your Comment end') + .call(doCmd('nextObject')) + .expectSpeech(' text.') + .expectBraille(' text.') + .call(doCmd('nextObject')) + .expectSpeech('This is ') + .call(doCmd('nextObject')) + .expectSpeech('Suggest', 'Insert', 'their', 'Insert end', 'Suggest end') + .expectBraille('Suggest Insert their Insert end Suggest end') + .call(doCmd('nextObject')) + .expectSpeech(' text.') + .expectBraille(' text.') + .call(doCmd('nextObject')) + .expectSpeech('This is ') + .call(doCmd('nextObject')) + .expectSpeech( + 'Suggest', 'Delete', `everyone's`, 'Delete end', 'Suggest end') + .expectBraille(`Suggest Delete everyone's Delete end Suggest end`) + .call(doCmd('nextObject')) + .expectSpeech(' text.') + .expectBraille(' text.') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'ClickAncestorAreNotActionable', function() { - const mockFeedback = this.createMockFeedback(); - const site = ` +TEST_F( + 'ChromeVoxBackgroundTest', 'ClickAncestorAreNotActionable', + async function() { + const mockFeedback = this.createMockFeedback(); + const site = ` <p>Start</p> <div id="button1" role="button" aria-label="OK"> <div role="group">OK</div> @@ -3458,63 +3360,62 @@ document.getElementById('button2').addEventListener('click', () => {}); </script> `; - this.runWithLoadedTree(site, function(rootNode) { - mockFeedback.expectSpeech('Start') - .call(doCmd('nextObject')) - .expectSpeech('OK') - .call(doCmd('nextObject')) - .expectSpeech('cancel') - .call(doCmd('nextObject')) - .expectSpeech('more info') - .call(doCmd('nextObject')) - .expectSpeech('end') - .replay(); - }); -}); + const rootNode = await this.runWithLoadedTree(site); + mockFeedback.expectSpeech('Start') + .call(doCmd('nextObject')) + .expectSpeech('OK') + .call(doCmd('nextObject')) + .expectSpeech('cancel') + .call(doCmd('nextObject')) + .expectSpeech('more info') + .call(doCmd('nextObject')) + .expectSpeech('end') + .replay(); + }); -TEST_F('ChromeVoxBackgroundTest', 'TouchEditingState', function() { +TEST_F('ChromeVoxBackgroundTest', 'TouchEditingState', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>Start</p> <input type="text"></input> `; - this.runWithLoadedTree(site, function(rootNode) { - const bounds = rootNode.find({role: RoleType.TEXT_FIELD}).location; - mockFeedback.expectSpeech('Start') - .call(doGesture( - chrome.accessibilityPrivate.Gesture.TOUCH_EXPLORE, bounds.left, - bounds.top)) - .expectSpeech('Edit text', 'Double tap to start editing') - .call(doGesture( - chrome.accessibilityPrivate.Gesture.CLICK, bounds.left, bounds.top)) - .expectSpeech('Edit text', 'is editing') - .replay(); - }); + const rootNode = await this.runWithLoadedTree(site); + const bounds = rootNode.find({role: RoleType.TEXT_FIELD}).location; + mockFeedback.expectSpeech('Start') + .call(doGesture( + chrome.accessibilityPrivate.Gesture.TOUCH_EXPLORE, bounds.left, + bounds.top)) + .expectSpeech('Edit text', 'Double tap to start editing') + .call(doGesture( + chrome.accessibilityPrivate.Gesture.CLICK, bounds.left, bounds.top)) + .expectSpeech('Edit text', 'is editing') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'TouchGesturesProducesEarcons', function() { - const mockFeedback = this.createMockFeedback(); - const site = ` +TEST_F( + 'ChromeVoxBackgroundTest', 'TouchGesturesProducesEarcons', + async function() { + const mockFeedback = this.createMockFeedback(); + const site = ` <p>Start</p> <button>ok</button> <a href="chromevox.com">cancel</a> `; - this.runWithLoadedTree(site, function(rootNode) { - mockFeedback.expectSpeech('Start') - .call(doGesture(chrome.accessibilityPrivate.Gesture.SWIPE_RIGHT1)) - .expectSpeech('ok', 'Button') - .expectEarcon(Earcon.BUTTON) - .call(doGesture(chrome.accessibilityPrivate.Gesture.SWIPE_RIGHT1)) - .expectSpeech('cancel', 'Link') - .expectEarcon(Earcon.LINK) - .call(doGesture(chrome.accessibilityPrivate.Gesture.SWIPE_LEFT1)) - .expectSpeech('ok', 'Button') - .expectEarcon(Earcon.BUTTON) - .replay(); - }); -}); + const rootNode = await this.runWithLoadedTree(site); + mockFeedback.expectSpeech('Start') + .call(doGesture(chrome.accessibilityPrivate.Gesture.SWIPE_RIGHT1)) + .expectSpeech('ok', 'Button') + .expectEarcon(Earcon.BUTTON) + .call(doGesture(chrome.accessibilityPrivate.Gesture.SWIPE_RIGHT1)) + .expectSpeech('cancel', 'Link') + .expectEarcon(Earcon.LINK) + .call(doGesture(chrome.accessibilityPrivate.Gesture.SWIPE_LEFT1)) + .expectSpeech('ok', 'Button') + .expectEarcon(Earcon.BUTTON) + .replay(); + }); -TEST_F('ChromeVoxBackgroundTest', 'Separator', function() { +TEST_F('ChromeVoxBackgroundTest', 'Separator', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>Start</p> @@ -3522,20 +3423,19 @@ <p><span role="separator">Separator content should be read</span></p> <p><span>World</span></p> `; - this.runWithLoadedTree(site, function(rootNode) { - mockFeedback.expectSpeech('Start') - .call(doCmd('nextObject')) - .expectSpeech('Hello') - .call(doCmd('nextObject')) - .expectSpeech('Separator content should be read', 'Separator') - .expectBraille('Separator content should be read seprtr') - .call(doCmd('nextObject')) - .expectSpeech('World') - .replay(); - }); + const rootNode = await this.runWithLoadedTree(site); + mockFeedback.expectSpeech('Start') + .call(doCmd('nextObject')) + .expectSpeech('Hello') + .call(doCmd('nextObject')) + .expectSpeech('Separator content should be read', 'Separator') + .expectBraille('Separator content should be read seprtr') + .call(doCmd('nextObject')) + .expectSpeech('World') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'FocusAfterClick', function() { +TEST_F('ChromeVoxBackgroundTest', 'FocusAfterClick', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>Start</p> @@ -3547,20 +3447,19 @@ }, false); </script> `; - this.runWithLoadedTree(site, function(root) { - BaseAutomationHandler.announceActions = false; - mockFeedback.expectSpeech('Start') - .call(doCmd('nextObject')) - .expectSpeech('Click me') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech('Focus me') - .call(() => { - assertEquals( - 'Focus me', - ChromeVoxState.instance.getCurrentRange().start.node.name); - }) - .replay(); - }); + const root = await this.runWithLoadedTree(site); + BaseAutomationHandler.announceActions = false; + mockFeedback.expectSpeech('Start') + .call(doCmd('nextObject')) + .expectSpeech('Click me') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech('Focus me') + .call(() => { + assertEquals( + 'Focus me', + ChromeVoxState.instance.getCurrentRange().start.node.name); + }) + .replay(); }); SYNC_TEST_F('ChromeVoxBackgroundTest', 'EarconPlayback', function() { @@ -3605,74 +3504,76 @@ assertEquals(0, Object.keys(engine.lastEarconSources_).length); }); -TEST_F('ChromeVoxBackgroundTest', 'MixedNavWithRangeInvalidation', function() { - const mockFeedback = this.createMockFeedback(); - const site = ` +TEST_F( + 'ChromeVoxBackgroundTest', 'MixedNavWithRangeInvalidation', + async function() { + const mockFeedback = this.createMockFeedback(); + const site = ` <p>Start</p> <button>apple</button> <a href="google.com">grape</a> <button>banana</button> `; - this.runWithLoadedTree(site, function(root) { - // Different ways to navigate to the next object. - const keyboardHandler = ChromeVoxState.instance.keyboardHandler_; - const nextObjectKeyboard = keyboardHandler.onKeyDown.bind(keyboardHandler, { - keyCode: KeyCode.RIGHT, - metaKey: true, - preventDefault: () => {}, - stopPropagation: () => {} + const root = await this.runWithLoadedTree(site); + // Different ways to navigate to the next object. + const keyboardHandler = ChromeVoxState.instance.keyboardHandler_; + const nextObjectKeyboard = + keyboardHandler.onKeyDown.bind(keyboardHandler, { + keyCode: KeyCode.RIGHT, + metaKey: true, + preventDefault: () => {}, + stopPropagation: () => {} + }); + const nextObjectBraille = BrailleCommandHandler.onBrailleKeyEvent.bind( + BrailleCommandHandler, {command: BrailleKeyCommand.PAN_RIGHT}); + const nextObjectGesture = + GestureCommandHandler.onAccessibilityGesture_.bind( + GestureCommandHandler, Gesture.SWIPE_RIGHT1); + const clearCurrentRange = ChromeVoxState.instance.setCurrentRange.bind( + ChromeVoxState.instance, null); + const toggleTalkBack = () => { + ChromeVoxState.instance.talkBackEnabled = + !ChromeVoxState.instance.talkBackEnabled; + }; + + mockFeedback + .expectSpeech('Start') + + .call(clearCurrentRange) + .call(nextObjectKeyboard) + .expectSpeech('apple', 'Button') + + .call(clearCurrentRange) + .call(nextObjectBraille) + .expectSpeech('grape', 'Link') + + .call(clearCurrentRange) + .call(nextObjectGesture) + .expectSpeech('banana', 'Button') + + .call(clearCurrentRange) + .call(nextObjectKeyboard) + .expectSpeech('Web Content') + + .call(clearCurrentRange) + .call(toggleTalkBack) + .call(nextObjectKeyboard) + .call(() => assertFalse(!!ChromeVoxState.instance.currentRange)) + + .call(nextObjectBraille) + .call(() => assertFalse(!!ChromeVoxState.instance.currentRange)) + + .call(nextObjectGesture) + .call(() => assertFalse(!!ChromeVoxState.instance.currentRange)) + + .call(toggleTalkBack) + .call(nextObjectKeyboard) + .call(() => assertTrue(!!ChromeVoxState.instance.currentRange)) + + .replay(); }); - const nextObjectBraille = BrailleCommandHandler.onBrailleKeyEvent.bind( - BrailleCommandHandler, {command: BrailleKeyCommand.PAN_RIGHT}); - const nextObjectGesture = - GestureCommandHandler.onAccessibilityGesture_.bind( - GestureCommandHandler, Gesture.SWIPE_RIGHT1); - const clearCurrentRange = ChromeVoxState.instance.setCurrentRange.bind( - ChromeVoxState.instance, null); - const toggleTalkBack = () => { - ChromeVoxState.instance.talkBackEnabled = - !ChromeVoxState.instance.talkBackEnabled; - }; - mockFeedback - .expectSpeech('Start') - - .call(clearCurrentRange) - .call(nextObjectKeyboard) - .expectSpeech('apple', 'Button') - - .call(clearCurrentRange) - .call(nextObjectBraille) - .expectSpeech('grape', 'Link') - - .call(clearCurrentRange) - .call(nextObjectGesture) - .expectSpeech('banana', 'Button') - - .call(clearCurrentRange) - .call(nextObjectKeyboard) - .expectSpeech('Web Content') - - .call(clearCurrentRange) - .call(toggleTalkBack) - .call(nextObjectKeyboard) - .call(() => assertFalse(!!ChromeVoxState.instance.currentRange)) - - .call(nextObjectBraille) - .call(() => assertFalse(!!ChromeVoxState.instance.currentRange)) - - .call(nextObjectGesture) - .call(() => assertFalse(!!ChromeVoxState.instance.currentRange)) - - .call(toggleTalkBack) - .call(nextObjectKeyboard) - .call(() => assertTrue(!!ChromeVoxState.instance.currentRange)) - - .replay(); - }); -}); - -TEST_F('ChromeVoxBackgroundTest', 'DetailsChanged', function() { +TEST_F('ChromeVoxBackgroundTest', 'DetailsChanged', async function() { const mockFeedback = this.createMockFeedback(); // Make sure we're not testing reading of the hint from the button's output @@ -3688,24 +3589,20 @@ }); </script> `; - this.runWithLoadedTree(site, function(root) { - const button = root.find({role: RoleType.BUTTON}); - mockFeedback.expectSpeech('ok') - .call(button.doDefault.bind(button)) - .expectSpeech('Press Search+A, J to jump to details') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + const button = root.find({role: RoleType.BUTTON}); + mockFeedback.expectSpeech('ok') + .call(button.doDefault.bind(button)) + .expectSpeech('Press Search+A, J to jump to details') + .replay(); }); -SYNC_TEST_F('ChromeVoxBackgroundTest', 'PageLoadEarcons', async function() { +SYNC_TEST_F('ChromeVoxBackgroundTest', 'PageLoadEarcons', function() { const sawEarcons = []; const fakeEarcons = {playEarcon: (earcon) => sawEarcons.push(earcon)}; Object.defineProperty(ChromeVox, 'earcons', {get: () => fakeEarcons}); AutomationUtil.getTopLevelRoot = (node) => node; - const module = await import('./page_load_sound_handler.js'); - const PageLoadSoundHandler = module.PageLoadSoundHandler; - // Use this specific object to control the load environment. const handler = new PageLoadSoundHandler(); @@ -3735,19 +3632,18 @@ [Earcon.PAGE_START_LOADING, Earcon.PAGE_FINISH_LOADING], sawEarcons); }); -TEST_F('ChromeVoxBackgroundTest', 'NewTabRead', function() { +TEST_F('ChromeVoxBackgroundTest', 'NewTabRead', async function() { const mockFeedback = this.createMockFeedback(); const site = `<p>start</p><p>end</p>`; - this.runWithLoadedTree(site, function(root) { - mockFeedback.call(doCmd('nextObject')) - .expectSpeech('end') - .call(press(KeyCode.T, {ctrl: true})) - .expectSpeech(/New Tab/) - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.call(doCmd('nextObject')) + .expectSpeech('end') + .call(press(KeyCode.T, {ctrl: true})) + .expectSpeech(/New Tab/) + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'NestedMenuHints', function() { +TEST_F('ChromeVoxBackgroundTest', 'NestedMenuHints', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div role="menu" aria-orientation="vertical"> @@ -3757,18 +3653,16 @@ </div> </div> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback - .expectSpeech( - 'Press left or right arrow to navigate; enter to activate') - .call( - () => assertFalse(mockFeedback.utteranceInQueue( - 'Press up or down arrow to navigate; enter to activate'))) - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback + .expectSpeech('Press left or right arrow to navigate; enter to activate') + .call( + () => assertFalse(mockFeedback.utteranceInQueue( + 'Press up or down arrow to navigate; enter to activate'))) + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'SkipLabelDescriptionFor', function() { +TEST_F('ChromeVoxBackgroundTest', 'SkipLabelDescriptionFor', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>start</p> @@ -3778,89 +3672,86 @@ </label> <p>end</p> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.expectSpeech('start') - .call(doCmd('nextObject')) - .expectSpeech('Enable speech logging', 'Check box') - .call(doCmd('nextObject')) - .expectSpeech('end') - .call(doCmd('previousObject')) - .expectSpeech('Enable speech logging', 'Check box') - .call(doCmd('previousObject')) - .expectSpeech('start') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + mockFeedback.expectSpeech('start') + .call(doCmd('nextObject')) + .expectSpeech('Enable speech logging', 'Check box') + .call(doCmd('nextObject')) + .expectSpeech('end') + .call(doCmd('previousObject')) + .expectSpeech('Enable speech logging', 'Check box') + .call(doCmd('previousObject')) + .expectSpeech('start') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'Abbreviation', function() { +TEST_F('ChromeVoxBackgroundTest', 'Abbreviation', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <abbr title="uniform resource locator">URL</abbr> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.expectSpeech('URL', 'uniform resource locator', 'Abbreviation') - .replay(); - }); + await this.runWithLoadedTree(site); + mockFeedback.expectSpeech('URL', 'uniform resource locator', 'Abbreviation') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'EndOfText', function() { +TEST_F('ChromeVoxBackgroundTest', 'EndOfText', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>start</p> <div tabindex=0 role="textbox" contenteditable><p>abc</p><p>123</p></div> `; - this.runWithLoadedTree(site, function(root) { - const contentEditable = root.find({role: RoleType.TEXT_FIELD}); + const root = await this.runWithLoadedTree(site); + const contentEditable = root.find({role: RoleType.TEXT_FIELD}); - this.listenOnce(contentEditable, EventType.FOCUS, function() { - mockFeedback.call(press(KeyCode.RIGHT)) - .expectSpeech('b') - .call(press(KeyCode.RIGHT)) - .expectSpeech('c') - .call(press(KeyCode.RIGHT)) - .expectSpeech('\n') - .call(press(KeyCode.RIGHT)) - .expectSpeech('1') - .call(press(KeyCode.RIGHT)) - .expectSpeech('2') - .call(press(KeyCode.RIGHT)) - .expectSpeech('3') - .call(press(KeyCode.RIGHT)) - .expectSpeech('End of text') + this.listenOnce(contentEditable, EventType.FOCUS, function() { + mockFeedback.call(press(KeyCode.RIGHT)) + .expectSpeech('b') + .call(press(KeyCode.RIGHT)) + .expectSpeech('c') + .call(press(KeyCode.RIGHT)) + .expectSpeech('\n') + .call(press(KeyCode.RIGHT)) + .expectSpeech('1') + .call(press(KeyCode.RIGHT)) + .expectSpeech('2') + .call(press(KeyCode.RIGHT)) + .expectSpeech('3') + .call(press(KeyCode.RIGHT)) + .expectSpeech('End of text') - .call(press(KeyCode.LEFT)) - .expectSpeech('3') - .call(press(KeyCode.LEFT)) - .expectSpeech('2') - .call(press(KeyCode.LEFT)) - .expectSpeech('1') - .call(press(KeyCode.LEFT)) - .expectSpeech('\n') - .call(press(KeyCode.LEFT)) - .expectSpeech('c') - .call(press(KeyCode.LEFT)) - .expectSpeech('b') - .call(press(KeyCode.LEFT)) - .expectSpeech('a') + .call(press(KeyCode.LEFT)) + .expectSpeech('3') + .call(press(KeyCode.LEFT)) + .expectSpeech('2') + .call(press(KeyCode.LEFT)) + .expectSpeech('1') + .call(press(KeyCode.LEFT)) + .expectSpeech('\n') + .call(press(KeyCode.LEFT)) + .expectSpeech('c') + .call(press(KeyCode.LEFT)) + .expectSpeech('b') + .call(press(KeyCode.LEFT)) + .expectSpeech('a') - .replay(); - }.bind(this)); - contentEditable.focus(); - }); + .replay(); + }.bind(this)); + contentEditable.focus(); }); -TEST_F('ChromeVoxBackgroundTest', 'ShowContextMenuOnViewsTab', function() { - const mockFeedback = this.createMockFeedback(); - const site = `<p>test</p>`; - this.runWithLoadedTree(site, function(root) { - const tabs = root.findAll({Role: RoleType.TAB}); - assertTrue(tabs.length > 0); - tabs[0].showContextMenu(); - mockFeedback.expectSpeech(/menu opened/).replay(); - }); -}); +TEST_F( + 'ChromeVoxBackgroundTest', 'ShowContextMenuOnViewsTab', async function() { + const mockFeedback = this.createMockFeedback(); + const site = `<p>test</p>`; + const root = await this.runWithLoadedTree(site); + const tabs = root.findAll({Role: RoleType.TAB}); + assertTrue(tabs.length > 0); + tabs[0].showContextMenu(); + mockFeedback.expectSpeech(/menu opened/).replay(); + }); -TEST_F('ChromeVoxBackgroundTest', 'SelectWithOptGroup', function() { +TEST_F('ChromeVoxBackgroundTest', 'SelectWithOptGroup', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <select> @@ -3871,40 +3762,38 @@ </optgroup> </select> `; - this.runWithLoadedTree(site, function() { - mockFeedback.expectSpeech('Tyrannosaurus', 'has pop up', 'Collapsed') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech('Tyrannosaurus') - .call(press(KeyCode.DOWN)) - .expectSpeech('Velociraptor') - .call(press(KeyCode.DOWN)) - .expectSpeech('Deinonychus') - .call(press(KeyCode.UP)) - .expectSpeech('Velociraptor') - .replay(); - }); + await this.runWithLoadedTree(site); + mockFeedback.expectSpeech('Tyrannosaurus', 'has pop up', 'Collapsed') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech('Tyrannosaurus') + .call(press(KeyCode.DOWN)) + .expectSpeech('Velociraptor') + .call(press(KeyCode.DOWN)) + .expectSpeech('Deinonychus') + .call(press(KeyCode.UP)) + .expectSpeech('Velociraptor') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'GroupNavigation', function() { +TEST_F('ChromeVoxBackgroundTest', 'GroupNavigation', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p><span>hello</span><a href="a.com">hi</a><a href="a.com">hey</a></p> <p><span>goodbye</span><a href="a.com">bye</a><a href="a.com">chow</a></p> `; - this.runWithLoadedTree(site, function() { - mockFeedback.call(doCmd('nextGroup')) - .expectSpeech('goodbye', 'bye', 'Link', 'chow', 'Link') - .call(doCmd('nextObject')) - .expectSpeech('goodbye') - .call(doCmd('nextObject')) - .expectSpeech('bye', 'Link') - .call(doCmd('previousGroup')) - .expectSpeech('hello', 'hi', 'Link', 'hey', 'Link') - .replay(); - }); + await this.runWithLoadedTree(site); + mockFeedback.call(doCmd('nextGroup')) + .expectSpeech('goodbye', 'bye', 'Link', 'chow', 'Link') + .call(doCmd('nextObject')) + .expectSpeech('goodbye') + .call(doCmd('nextObject')) + .expectSpeech('bye', 'Link') + .call(doCmd('previousGroup')) + .expectSpeech('hello', 'hi', 'Link', 'hey', 'Link') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'AllowIframeToBeFocused', function(root) { +TEST_F('ChromeVoxBackgroundTest', 'AllowIframeToBeFocused', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>hello</p> @@ -3918,13 +3807,12 @@ }); </script> `; - this.runWithLoadedTree(site, function(root) { - const button = root.find({role: RoleType.BUTTON}); - mockFeedback.expectSpeech('hello') - .call(button.doDefault.bind(button)) - .expectSpeech('test title') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + const button = root.find({role: RoleType.BUTTON}); + mockFeedback.expectSpeech('hello') + .call(button.doDefault.bind(button)) + .expectSpeech('test title') + .replay(); }); TEST_F('ChromeVoxBackgroundTest', 'NewWindowWebSpeech', function() { @@ -3966,7 +3854,7 @@ })(); }); -TEST_F('ChromeVoxBackgroundTest', 'MultipleListBoxes', function() { +TEST_F('ChromeVoxBackgroundTest', 'MultipleListBoxes', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>start</p> @@ -4021,126 +3909,120 @@ </div> </div> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.call(press(KeyCode.TAB)) - .expectSpeech( - 'Listbox item 1', ' 1 of 3 ', 'Configuration 1', 'List box') - .call(press(KeyCode.TAB)) - .expectSpeech( - 'Listbox item 2', ' 2 of 3 ', 'Configuration 2', 'List box') - .call(press(KeyCode.TAB)) - .expectSpeech( - 'Listbox item 3', ' 3 of 3 ', 'Configuration 3', 'List box') - .replay(); - }); + await this.runWithLoadedTree(site); + mockFeedback.call(press(KeyCode.TAB)) + .expectSpeech('Listbox item 1', ' 1 of 3 ', 'Configuration 1', 'List box') + .call(press(KeyCode.TAB)) + .expectSpeech('Listbox item 2', ' 2 of 3 ', 'Configuration 2', 'List box') + .call(press(KeyCode.TAB)) + .expectSpeech('Listbox item 3', ' 3 of 3 ', 'Configuration 3', 'List box') + .replay(); }); // Make sure linear navigation does not go inside ListBox's options. -TEST_F('ChromeVoxBackgroundTest', 'ListBoxLinearNavigation', function() { +TEST_F('ChromeVoxBackgroundTest', 'ListBoxLinearNavigation', async function() { const mockFeedback = this.createMockFeedback(); - const site = - - this.runWithLoadedTree(this.listBoxDoc, function(root) { - mockFeedback.call(doCmd('nextObject')) - .expectSpeech('Select an item', 'List box') - .call(doCmd('nextObject')) - .expectSpeech('Click', 'Button') - .call(doCmd('previousObject')) - .expectSpeech('Select an item', 'List box') - .replay(); - }); + await this.runWithLoadedTree(this.listBoxDoc); + mockFeedback.call(doCmd('nextObject')) + .expectSpeech('Select an item', 'List box') + .call(doCmd('nextObject')) + .expectSpeech('Click', 'Button') + .call(doCmd('previousObject')) + .expectSpeech('Select an item', 'List box') + .replay(); }); // Make sure navigation with Tab to ListBox lands on options. -TEST_F('ChromeVoxBackgroundTest', 'ListBoxItemsNavigation', function() { +TEST_F('ChromeVoxBackgroundTest', 'ListBoxItemsNavigation', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.listBoxDoc, function(root) { - mockFeedback.call(press(KeyCode.TAB)) - .expectSpeech( - 'Listbox item one', ' 1 of 3 ', 'Select an item', 'List box') - .call(doCmd('nextObject')) - .expectSpeech('Listbox item two', ' 2 of 3 ') - .call(doCmd('nextObject')) - .expectSpeech('Listbox item three', ' 3 of 3 ') - .replay(); - }); + await this.runWithLoadedTree(this.listBoxDoc); + mockFeedback.call(press(KeyCode.TAB)) + .expectSpeech( + 'Listbox item one', ' 1 of 3 ', 'Select an item', 'List box') + .call(doCmd('nextObject')) + .expectSpeech('Listbox item two', ' 2 of 3 ') + .call(doCmd('nextObject')) + .expectSpeech('Listbox item three', ' 3 of 3 ') + .replay(); }); -TEST_F('ChromeVoxBackgroundTest', 'CrossWindowNextPreviousFocus', function() { - const mockFeedback = this.createMockFeedback(); - const site = ` +TEST_F( + 'ChromeVoxBackgroundTest', 'CrossWindowNextPreviousFocus', + async function() { + const mockFeedback = this.createMockFeedback(); + const site = ` <div aria-label="first"><button>second</button><button>third</button></div> <div aria-label="fourth"><button>fifth</button><button>sixth</button></div> `; - this.runWithLoadedTree(site, function(root) { - // Fake out the divs to be windows. - const window1 = root.children[0]; - const window2 = root.children[1]; - Object.defineProperty(window1, 'role', {get: () => RoleType.WINDOW}); - Object.defineProperty(window2, 'role', {get: () => RoleType.WINDOW}); + const root = await this.runWithLoadedTree(site); + // Fake out the divs to be windows. + const window1 = root.children[0]; + const window2 = root.children[1]; + Object.defineProperty(window1, 'role', {get: () => RoleType.WINDOW}); + Object.defineProperty(window2, 'role', {get: () => RoleType.WINDOW}); - // Linear nav should just wrap inside the first window. - mockFeedback.call(doCmd('nextObject')) - .expectSpeech('third', 'Button') + // Linear nav should just wrap inside the first window. + mockFeedback.call(doCmd('nextObject')) + .expectSpeech('third', 'Button') - // Wrap. - .call(doCmd('nextObject')) - .expectSpeech('second', 'Button', 'first, window') + // Wrap. + .call(doCmd('nextObject')) + .expectSpeech('second', 'Button', 'first, window') - // Wrap. - .call(doCmd('previousObject')) - .expectSpeech('third', 'Button') + // Wrap. + .call(doCmd('previousObject')) + .expectSpeech('third', 'Button') - .call(() => { - // Link the two "windows" with next/previous focus. - Object.defineProperty(window1, 'nextFocus', {get: () => window2}); - Object.defineProperty(window2, 'previousFocus', {get: () => window1}); - }) + .call(() => { + // Link the two "windows" with next/previous focus. + Object.defineProperty(window1, 'nextFocus', {get: () => window2}); + Object.defineProperty( + window2, 'previousFocus', {get: () => window1}); + }) - // window1 -> window2. - .call(doCmd('nextObject')) - .expectSpeech('fifth', 'Button', 'fourth, window') + // window1 -> window2. + .call(doCmd('nextObject')) + .expectSpeech('fifth', 'Button', 'fourth, window') - // window2 -> window1. - .call(doCmd('previousObject')) - .expectSpeech('third', 'Button', 'first, window') + // window2 -> window1. + .call(doCmd('previousObject')) + .expectSpeech('third', 'Button', 'first, window') - .call(() => { - // Link the two "windows" with next/previous focus in a slightly - // different way. - Object.defineProperty(window1, 'previousFocus', {get: () => window2}); - Object.defineProperty(window2, 'nextFocus', {get: () => window1}); - }) + .call(() => { + // Link the two "windows" with next/previous focus in a slightly + // different way. + Object.defineProperty( + window1, 'previousFocus', {get: () => window2}); + Object.defineProperty(window2, 'nextFocus', {get: () => window1}); + }) - .call(doCmd('previousObject')) - .expectSpeech('second', 'Button') + .call(doCmd('previousObject')) + .expectSpeech('second', 'Button') - // window1 -> window2. - .call(doCmd('previousObject')) - .expectSpeech('sixth', 'Button', 'fourth, window') + // window1 -> window2. + .call(doCmd('previousObject')) + .expectSpeech('sixth', 'Button', 'fourth, window') - // window2 -> window1. - .call(doCmd('nextObject')) - .expectSpeech('second', 'Button', 'first, window') + // window2 -> window1. + .call(doCmd('nextObject')) + .expectSpeech('second', 'Button', 'first, window') - .replay(); - }); -}); + .replay(); + }); -TEST_F('ChromeVoxBackgroundTest', 'GestureOnPopUpButton', function() { +TEST_F('ChromeVoxBackgroundTest', 'GestureOnPopUpButton', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <select><option>apple</option><option>banana</option></select> `; - this.runWithLoadedTree(site, function(root) { - mockFeedback.expectSpeech('Button', 'has pop up') - .call(doGesture(Gesture.CLICK)) - .expectSpeech('Button', 'has pop up', 'Expanded') - .call(doGesture(Gesture.SWIPE_DOWN1)) - .expectSpeech('banana') - .call(doGesture(Gesture.SWIPE_UP1)) - .expectSpeech('apple') - .replay(); - }); + await this.runWithLoadedTree(site); + mockFeedback.expectSpeech('Button', 'has pop up') + .call(doGesture(Gesture.CLICK)) + .expectSpeech('Button', 'has pop up', 'Expanded') + .call(doGesture(Gesture.SWIPE_DOWN1)) + .expectSpeech('banana') + .call(doGesture(Gesture.SWIPE_UP1)) + .expectSpeech('apple') + .replay(); });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/classic_background.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/classic_background.js index c4addc6..c62c6cd 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/classic_background.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/classic_background.js
@@ -6,6 +6,7 @@ * @fileoverview Script that runs on the background page. */ +import {AbstractTts} from '../common/abstract_tts.js'; import {CompositeTts} from '../common/composite_tts.js'; import {ChromeVoxEditableTextBase, TypingEcho} from '../common/editable_text_base.js'; import {TtsBackground} from '../common/tts_background.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js index 122cbd37..1bf5c5b 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
@@ -5,6 +5,8 @@ /** * @fileoverview ChromeVox commands. */ +import {AbstractTts} from '../common/abstract_tts.js'; +import {CommandStore} from '../common/command_store.js'; import {TypingEcho} from '../common/editable_text_base.js'; import {ChromeVoxKbHandler} from '../common/keyboard_handler.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler_test.js index cb817d8..9afcc050 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler_test.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler_test.js
@@ -42,49 +42,46 @@ TEST_F( 'ChromeVoxDesktopAutomationHandlerTest', 'OnValueChangedSlider', - function() { + async function() { const mockFeedback = this.createMockFeedback(); const site = `<input type="range"></input>`; - this.runWithLoadedTree(site, function(root) { - const slider = root.find({role: RoleType.SLIDER}); - assertTrue(!!slider); + const root = await this.runWithLoadedTree(site); + const slider = root.find({role: RoleType.SLIDER}); + assertTrue(!!slider); - let sliderValue = '50%'; - Object.defineProperty(slider, 'value', {get: () => sliderValue}); + let sliderValue = '50%'; + Object.defineProperty(slider, 'value', {get: () => sliderValue}); - const event = - new CustomAutomationEvent(EventType.VALUE_CHANGED, slider); - mockFeedback.call(() => this.handler_.onValueChanged(event)) - .expectSpeech('Slider', '50%') + const event = new CustomAutomationEvent(EventType.VALUE_CHANGED, slider); + mockFeedback.call(() => this.handler_.onValueChanged(event)) + .expectSpeech('Slider', '50%') - // Override the min time to observe value changes so that even super - // fast updates triggers speech. - .call(() => DesktopAutomationHandler.MIN_VALUE_CHANGE_DELAY_MS = -1) - .call(() => sliderValue = '60%') - .call(() => this.handler_.onValueChanged(event)) + // Override the min time to observe value changes so that even super + // fast updates triggers speech. + .call(() => DesktopAutomationHandler.MIN_VALUE_CHANGE_DELAY_MS = -1) + .call(() => sliderValue = '60%') + .call(() => this.handler_.onValueChanged(event)) - // The range stays on the slider, so subsequent value changes only - // report the value. - .expectNextSpeechUtteranceIsNot('Slider') - .expectSpeech('60%') + // The range stays on the slider, so subsequent value changes only + // report the value. + .expectNextSpeechUtteranceIsNot('Slider') + .expectSpeech('60%') - // Set the min time and send a value change which should be ignored. - .call( - () => DesktopAutomationHandler.MIN_VALUE_CHANGE_DELAY_MS = - 10000) - .call(() => sliderValue = '70%') - .call(() => this.handler_.onValueChanged(event)) + // Set the min time and send a value change which should be ignored. + .call( + () => DesktopAutomationHandler.MIN_VALUE_CHANGE_DELAY_MS = 10000) + .call(() => sliderValue = '70%') + .call(() => this.handler_.onValueChanged(event)) - // Send one more that is processed. - .call(() => DesktopAutomationHandler.MIN_VALUE_CHANGE_DELAY_MS = -1) - .call(() => sliderValue = '80%') - .call(() => this.handler_.onValueChanged(event)) + // Send one more that is processed. + .call(() => DesktopAutomationHandler.MIN_VALUE_CHANGE_DELAY_MS = -1) + .call(() => sliderValue = '80%') + .call(() => this.handler_.onValueChanged(event)) - .expectNextSpeechUtteranceIsNot('70%') - .expectSpeech('80%') + .expectNextSpeechUtteranceIsNot('70%') + .expectSpeech('80%') - .replay(); - }); + .replay(); }); TEST_F( @@ -113,63 +110,65 @@ }); // Ensures behavior when IME candidates are selected. -TEST_F('ChromeVoxDesktopAutomationHandlerTest', 'ImeCandidate', function() { - const mockFeedback = this.createMockFeedback(); - const site = `<button>First</button><button>Second</button>`; - this.runWithLoadedTree(site, function(root) { - const candidates = root.findAll({role: RoleType.BUTTON}); - const first = candidates[0]; - const second = candidates[1]; - assertNotNullNorUndefined(first); - assertNotNullNorUndefined(second); - // Fake roles to imitate IME candidates. - Object.defineProperty(first, 'role', {get: () => RoleType.IME_CANDIDATE}); - Object.defineProperty(second, 'role', {get: () => RoleType.IME_CANDIDATE}); - const selectFirst = new CustomAutomationEvent(EventType.SELECTION, first); - const selectSecond = new CustomAutomationEvent(EventType.SELECTION, second); - mockFeedback.call(() => this.handler_.onSelection(selectFirst)) - .expectSpeech('First') - .expectSpeech('F: foxtrot, i: india, r: romeo, s: sierra, t: tango') - .call(() => this.handler_.onSelection(selectSecond)) - .expectSpeech('Second') - .expectSpeech( - 'S: sierra, e: echo, c: charlie, o: oscar, n: november, d: delta') - .call(() => this.handler_.onSelection(selectFirst)) - .expectSpeech('First') - .expectSpeech(/foxtrot/) - .replay(); - }); -}); - TEST_F( - 'ChromeVoxDesktopAutomationHandlerTest', 'IgnoreRepeatedAlerts', - function() { + 'ChromeVoxDesktopAutomationHandlerTest', 'ImeCandidate', async function() { const mockFeedback = this.createMockFeedback(); - const site = `<button>Hello world</button>`; - this.runWithLoadedTree(site, function(root) { - const button = root.find({role: RoleType.BUTTON}); - assertTrue(!!button); - const event = new CustomAutomationEvent(EventType.ALERT, button); - mockFeedback - .call(() => { - DesktopAutomationHandler.MIN_ALERT_DELAY_MS = 20 * 1000; - this.handler_.onAlert(event); - }) - .expectSpeech('Hello world') - .clearPendingOutput() - .call(() => { - // Repeated alerts should be ignored. - this.handler_.onAlert(event); - assertFalse(mockFeedback.utteranceInQueue('Hello world')); - this.handler_.onAlert(event); - assertFalse(mockFeedback.utteranceInQueue('Hello world')); - }) - .replay(); - }); + const site = `<button>First</button><button>Second</button>`; + const root = await this.runWithLoadedTree(site); + const candidates = root.findAll({role: RoleType.BUTTON}); + const first = candidates[0]; + const second = candidates[1]; + assertNotNullNorUndefined(first); + assertNotNullNorUndefined(second); + // Fake roles to imitate IME candidates. + Object.defineProperty(first, 'role', {get: () => RoleType.IME_CANDIDATE}); + Object.defineProperty( + second, 'role', {get: () => RoleType.IME_CANDIDATE}); + const selectFirst = new CustomAutomationEvent(EventType.SELECTION, first); + const selectSecond = + new CustomAutomationEvent(EventType.SELECTION, second); + mockFeedback.call(() => this.handler_.onSelection(selectFirst)) + .expectSpeech('First') + .expectSpeech('F: foxtrot, i: india, r: romeo, s: sierra, t: tango') + .call(() => this.handler_.onSelection(selectSecond)) + .expectSpeech('Second') + .expectSpeech( + 'S: sierra, e: echo, c: charlie, o: oscar, n: november, d: delta') + .call(() => this.handler_.onSelection(selectFirst)) + .expectSpeech('First') + .expectSpeech(/foxtrot/) + .replay(); }); TEST_F( - 'ChromeVoxDesktopAutomationHandlerTest', 'DatalistSelection', function() { + 'ChromeVoxDesktopAutomationHandlerTest', 'IgnoreRepeatedAlerts', + async function() { + const mockFeedback = this.createMockFeedback(); + const site = `<button>Hello world</button>`; + const root = await this.runWithLoadedTree(site); + const button = root.find({role: RoleType.BUTTON}); + assertTrue(!!button); + const event = new CustomAutomationEvent(EventType.ALERT, button); + mockFeedback + .call(() => { + DesktopAutomationHandler.MIN_ALERT_DELAY_MS = 20 * 1000; + this.handler_.onAlert(event); + }) + .expectSpeech('Hello world') + .clearPendingOutput() + .call(() => { + // Repeated alerts should be ignored. + this.handler_.onAlert(event); + assertFalse(mockFeedback.utteranceInQueue('Hello world')); + this.handler_.onAlert(event); + assertFalse(mockFeedback.utteranceInQueue('Hello world')); + }) + .replay(); + }); + +TEST_F( + 'ChromeVoxDesktopAutomationHandlerTest', 'DatalistSelection', + async function() { const mockFeedback = this.createMockFeedback(); const site = ` <input aria-label="Choose one" list="list"> @@ -178,25 +177,24 @@ <option>bar</option> </datalist> `; - this.runWithLoadedTree(site, async function(root) { - const combobox = root.find({ - role: RoleType.TEXT_FIELD_WITH_COMBO_BOX, - attributes: {name: 'Choose one'} - }); - assertTrue(!!combobox); - combobox.focus(); - await new Promise(r => combobox.addEventListener(EventType.FOCUS, r)); - - // The combobox is now actually focused, safe to send arrows. - mockFeedback.call(press(KeyCode.DOWN)) - .expectSpeech('foo', 'List item', ' 1 of 2 ') - .expectBraille('foo lstitm 1/2 (x)') - .call(press(KeyCode.DOWN)) - .expectSpeech('bar', 'List item', ' 2 of 2 ') - .expectBraille('bar lstitm 2/2 (x)') - .call(press(KeyCode.UP)) - .expectSpeech('foo', 'List item', ' 1 of 2 ') - .expectBraille('foo lstitm 1/2 (x)') - .replay(); + const root = await this.runWithLoadedTree(site); + const combobox = root.find({ + role: RoleType.TEXT_FIELD_WITH_COMBO_BOX, + attributes: {name: 'Choose one'} }); + assertTrue(!!combobox); + combobox.focus(); + await new Promise(r => combobox.addEventListener(EventType.FOCUS, r)); + + // The combobox is now actually focused, safe to send arrows. + mockFeedback.call(press(KeyCode.DOWN)) + .expectSpeech('foo', 'List item', ' 1 of 2 ') + .expectBraille('foo lstitm 1/2 (x)') + .call(press(KeyCode.DOWN)) + .expectSpeech('bar', 'List item', ' 2 of 2 ') + .expectBraille('bar lstitm 2/2 (x)') + .call(press(KeyCode.UP)) + .expectSpeech('foo', 'List item', ' 1 of 2 ') + .expectBraille('foo lstitm 1/2 (x)') + .replay(); });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing.js index c72373d..9489df6 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing.js
@@ -6,6 +6,7 @@ * @fileoverview Processes events related to editing text and emits the * appropriate spoken and braille feedback. */ +import {AbstractTts} from '../../common/abstract_tts.js'; import {ChromeVoxEditableTextBase, TextChangeEvent} from '../../common/editable_text_base.js'; import {BrailleBackground} from '../braille_background.js'; import {Color} from '../color.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing_test.js index bf80b15c..4ebbc483 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing_test.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing_test.js
@@ -66,55 +66,50 @@ </textarea> `; -TEST_F('ChromeVoxEditingTest', 'Focus', function() { +TEST_F('ChromeVoxEditingTest', 'Focus', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(doc, function(root) { - const singleLine = root.find( - {role: RoleType.TEXT_FIELD, attributes: {name: 'singleLine'}}); - const textarea = - root.find({role: RoleType.TEXT_FIELD, attributes: {name: 'textArea'}}); - singleLine.focus(); - mockFeedback.expectSpeech('singleLine', 'Single line field', 'Edit text') - .expectBraille( - 'singleLine Single line field ed', {startIndex: 11, endIndex: 11}) - .call(textarea.focus.bind(textarea)) - .expectSpeech('textArea', 'Line 1\nline 2\nline 3', 'Text area') - .expectBraille( - 'textArea Line 1\nline 2\nline 3 mled', - {startIndex: 9, endIndex: 9}); + const root = await this.runWithLoadedTree(doc); + const singleLine = + root.find({role: RoleType.TEXT_FIELD, attributes: {name: 'singleLine'}}); + const textarea = + root.find({role: RoleType.TEXT_FIELD, attributes: {name: 'textArea'}}); + singleLine.focus(); + mockFeedback.expectSpeech('singleLine', 'Single line field', 'Edit text') + .expectBraille( + 'singleLine Single line field ed', {startIndex: 11, endIndex: 11}) + .call(textarea.focus.bind(textarea)) + .expectSpeech('textArea', 'Line 1\nline 2\nline 3', 'Text area') + .expectBraille( + 'textArea Line 1\nline 2\nline 3 mled', {startIndex: 9, endIndex: 9}); - mockFeedback.replay(); - }); + mockFeedback.replay(); }); -TEST_F('ChromeVoxEditingTest', 'Multiline', function() { +TEST_F('ChromeVoxEditingTest', 'Multiline', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(doc, function(root) { - const textarea = - root.find({role: RoleType.TEXT_FIELD, attributes: {name: 'textArea'}}); - textarea.focus(); - mockFeedback.expectSpeech('textArea', 'Line 1\nline 2\nline 3', 'Text area') - .expectBraille( - 'textArea Line 1\nline 2\nline 3 mled', - {startIndex: 9, endIndex: 9}) - .call(textarea.setSelection.bind(textarea, 1, 1)) - .expectSpeech('i') - .expectBraille('Line 1\nmled', {startIndex: 1, endIndex: 1}) - .call(textarea.setSelection.bind(textarea, 7, 7)) - .expectSpeech('line 2') - .expectBraille('line 2\n', {startIndex: 0, endIndex: 0}) - .call(textarea.setSelection.bind(textarea, 7, 13)) - .expectSpeech('line 2', 'selected') - .expectBraille('line 2\n', {startIndex: 0, endIndex: 6}); + const root = await this.runWithLoadedTree(doc); + const textarea = + root.find({role: RoleType.TEXT_FIELD, attributes: {name: 'textArea'}}); + textarea.focus(); + mockFeedback.expectSpeech('textArea', 'Line 1\nline 2\nline 3', 'Text area') + .expectBraille( + 'textArea Line 1\nline 2\nline 3 mled', {startIndex: 9, endIndex: 9}) + .call(textarea.setSelection.bind(textarea, 1, 1)) + .expectSpeech('i') + .expectBraille('Line 1\nmled', {startIndex: 1, endIndex: 1}) + .call(textarea.setSelection.bind(textarea, 7, 7)) + .expectSpeech('line 2') + .expectBraille('line 2\n', {startIndex: 0, endIndex: 0}) + .call(textarea.setSelection.bind(textarea, 7, 13)) + .expectSpeech('line 2', 'selected') + .expectBraille('line 2\n', {startIndex: 0, endIndex: 6}); - mockFeedback.replay(); - }); + mockFeedback.replay(); }); -TEST_F('ChromeVoxEditingTest', 'TextButNoSelectionChange', function() { +TEST_F('ChromeVoxEditingTest', 'TextButNoSelectionChange', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <h1>Test doc</h1> <input type='text' id='input' value='text1'> <!-- We don't seem to get an event in js when the automation @@ -133,26 +128,23 @@ } timer = window.setInterval(poll, 200); </script> - `, - function(root) { - const input = root.find({role: RoleType.TEXT_FIELD}); - input.focus(); - mockFeedback.expectSpeech('text1', 'Edit text') - .expectBraille('text1 ed', {startIndex: 0, endIndex: 0}) - .call(input.setSelection.bind(input, 5, 5)) - .expectBraille('text2 ed', {startIndex: 5, endIndex: 5}); + `); + const input = root.find({role: RoleType.TEXT_FIELD}); + input.focus(); + mockFeedback.expectSpeech('text1', 'Edit text') + .expectBraille('text1 ed', {startIndex: 0, endIndex: 0}) + .call(input.setSelection.bind(input, 5, 5)) + .expectBraille('text2 ed', {startIndex: 5, endIndex: 5}); - mockFeedback.replay(); - }); + mockFeedback.replay(); }); -TEST_F('ChromeVoxEditingTest', 'RichTextMoveByLine', function() { +TEST_F('ChromeVoxEditingTest', 'RichTextMoveByLine', async function() { // Turn on rich text output settings. localStorage['announceRichTextAttributes'] = 'true'; const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <div role="textbox" contenteditable> <h2>hello</h2> <div><br></div> @@ -179,35 +171,32 @@ } }, true); </script> - `, - async function(root) { - await this.focusFirstTextField(root); + `); + await this.focusFirstTextField(root); - const go = root.find({role: RoleType.BUTTON}); - const moveByLine = go.doDefault.bind(go); - mockFeedback.call(moveByLine) - .expectSpeech('\n') - .expectBraille('\n') - .call(moveByLine) - .expectSpeech('This is a ', 'test', 'Link', ' of rich text') - .expectBraille('This is a test of rich text') - .call(moveByLine) - .expectSpeech('\n') - .expectBraille('\n') - .call(moveByLine) - .expectSpeech('hello', 'Heading 2') - .expectBraille('hello h2 mled') - .replay(); - }); + const go = root.find({role: RoleType.BUTTON}); + const moveByLine = go.doDefault.bind(go); + mockFeedback.call(moveByLine) + .expectSpeech('\n') + .expectBraille('\n') + .call(moveByLine) + .expectSpeech('This is a ', 'test', 'Link', ' of rich text') + .expectBraille('This is a test of rich text') + .call(moveByLine) + .expectSpeech('\n') + .expectBraille('\n') + .call(moveByLine) + .expectSpeech('hello', 'Heading 2') + .expectBraille('hello h2 mled') + .replay(); }); -TEST_F('ChromeVoxEditingTest', 'RichTextMoveByCharacter', function() { +TEST_F('ChromeVoxEditingTest', 'RichTextMoveByCharacter', async function() { // Turn on rich text output settings. localStorage['announceRichTextAttributes'] = 'true'; const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <div role="textbox" contenteditable>This <b>is</b> a test.</div> <button id="go">Go</button> @@ -231,61 +220,59 @@ } }, true); </script> - `, - async function(root) { - await this.focusFirstTextField(root); + `); + await this.focusFirstTextField(root); - const go = root.find({role: RoleType.BUTTON}); - const moveByChar = go.doDefault.bind(go); - const lineText = 'This is a test. mled'; + const go = root.find({role: RoleType.BUTTON}); + const moveByChar = go.doDefault.bind(go); + const lineText = 'This is a test. mled'; - mockFeedback.call(moveByChar) - .expectSpeech('h') - .expectBraille(lineText, {startIndex: 1, endIndex: 1}) - .call(moveByChar) - .expectSpeech('i') - .expectBraille(lineText, {startIndex: 2, endIndex: 2}) - .call(moveByChar) - .expectSpeech('s') - .expectBraille(lineText, {startIndex: 3, endIndex: 3}) - .call(moveByChar) - .expectSpeech(' ') - .expectBraille(lineText, {startIndex: 4, endIndex: 4}) + mockFeedback.call(moveByChar) + .expectSpeech('h') + .expectBraille(lineText, {startIndex: 1, endIndex: 1}) + .call(moveByChar) + .expectSpeech('i') + .expectBraille(lineText, {startIndex: 2, endIndex: 2}) + .call(moveByChar) + .expectSpeech('s') + .expectBraille(lineText, {startIndex: 3, endIndex: 3}) + .call(moveByChar) + .expectSpeech(' ') + .expectBraille(lineText, {startIndex: 4, endIndex: 4}) - .call(moveByChar) - .expectSpeech('i') - .expectSpeech('Bold') - .expectBraille(lineText, {startIndex: 5, endIndex: 5}) + .call(moveByChar) + .expectSpeech('i') + .expectSpeech('Bold') + .expectBraille(lineText, {startIndex: 5, endIndex: 5}) - .call(moveByChar) - .expectSpeech('s') - .expectBraille(lineText, {startIndex: 6, endIndex: 6}) + .call(moveByChar) + .expectSpeech('s') + .expectBraille(lineText, {startIndex: 6, endIndex: 6}) - .call(moveByChar) - .expectSpeech(' ') - .expectSpeech('Not bold') - .expectBraille(lineText, {startIndex: 7, endIndex: 7}) + .call(moveByChar) + .expectSpeech(' ') + .expectSpeech('Not bold') + .expectBraille(lineText, {startIndex: 7, endIndex: 7}) - .call(moveByChar) - .expectSpeech('a') - .expectBraille(lineText, {startIndex: 8, endIndex: 8}) + .call(moveByChar) + .expectSpeech('a') + .expectBraille(lineText, {startIndex: 8, endIndex: 8}) - .call(moveByChar) - .expectSpeech(' ') - .expectBraille(lineText, {startIndex: 9, endIndex: 9}) + .call(moveByChar) + .expectSpeech(' ') + .expectBraille(lineText, {startIndex: 9, endIndex: 9}) - .replay(); - }); + .replay(); }); TEST_F( - 'ChromeVoxEditingTest', 'RichTextMoveByCharacterAllAttributes', function() { + 'ChromeVoxEditingTest', 'RichTextMoveByCharacterAllAttributes', + async function() { // Turn on rich text output settings. localStorage['announceRichTextAttributes'] = 'true'; const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <div role="textbox" contenteditable> <p style="font-size:20px; font-family:times"> <b style="color:#ff0000">Move</b> @@ -303,157 +290,153 @@ sel.modify('move', 'forward', 'character'); }, true); </script> - `, - async function(root) { - await this.focusFirstTextField(root); + `); + await this.focusFirstTextField(root); - const go = root.find({role: RoleType.BUTTON}); - const moveByChar = go.doDefault.bind(go); - const lineText = 'Move through text by character test! mled'; - const lineOnLinkText = - 'Move through text by character test lnk ! mled'; + const go = root.find({role: RoleType.BUTTON}); + const moveByChar = go.doDefault.bind(go); + const lineText = 'Move through text by character test! mled'; + const lineOnLinkText = 'Move through text by character test lnk ! mled'; - mockFeedback.call(moveByChar) - .expectSpeech('o') - .expectSpeech('Size 20') - .expectSpeech('Red, 100% opacity.') - .expectSpeech('Bold') - .expectSpeech('Font Tinos') - .expectBraille(lineText, {startIndex: 1, endIndex: 1}) - .call(moveByChar) - .expectSpeech('v') - .expectBraille(lineText, {startIndex: 2, endIndex: 2}) - .call(moveByChar) - .expectSpeech('e') - .expectBraille(lineText, {startIndex: 3, endIndex: 3}) - .call(moveByChar) - .expectSpeech(' ') - .expectSpeech('Black, 100% opacity.') - .expectSpeech('Not bold') - .expectBraille(lineText, {startIndex: 4, endIndex: 4}) - .call(moveByChar) - .expectSpeech('t') - .expectSpeech('Italic') - .expectBraille(lineText, {startIndex: 5, endIndex: 5}) - .call(moveByChar) - .expectSpeech('h') - .expectBraille(lineText, {startIndex: 6, endIndex: 6}) - .call(moveByChar) - .expectSpeech('r') - .expectBraille(lineText, {startIndex: 7, endIndex: 7}) - .call(moveByChar) - .expectSpeech('o') - .expectBraille(lineText, {startIndex: 8, endIndex: 8}) - .call(moveByChar) - .expectSpeech('u') - .expectBraille(lineText, {startIndex: 9, endIndex: 9}) - .call(moveByChar) - .expectSpeech('g') - .expectBraille(lineText, {startIndex: 10, endIndex: 10}) - .call(moveByChar) - .expectSpeech('h') - .expectBraille(lineText, {startIndex: 11, endIndex: 11}) - .call(moveByChar) - .expectSpeech(' ') - .expectSpeech('Not italic') - .expectBraille(lineText, {startIndex: 12, endIndex: 12}) - .call(moveByChar) - .expectSpeech('t') - .expectSpeech('Underline') - .expectSpeech('Font Gelasio') - .expectBraille(lineText, {startIndex: 13, endIndex: 13}) - .call(moveByChar) - .expectSpeech('e') - .expectBraille(lineText, {startIndex: 14, endIndex: 14}) - .call(moveByChar) - .expectSpeech('x') - .expectBraille(lineText, {startIndex: 15, endIndex: 15}) - .call(moveByChar) - .expectSpeech('t') - .expectBraille(lineText, {startIndex: 16, endIndex: 16}) - .call(moveByChar) - .expectSpeech(' ') - .expectSpeech('Not underline') - .expectSpeech('Font Tinos') - .expectBraille(lineText, {startIndex: 17, endIndex: 17}) - .call(moveByChar) - .expectSpeech('b') - .expectBraille(lineText, {startIndex: 18, endIndex: 18}) - .call(moveByChar) - .expectSpeech('y') - .expectBraille(lineText, {startIndex: 19, endIndex: 19}) - .call(moveByChar) - .expectSpeech(' ') - .expectBraille(lineText, {startIndex: 20, endIndex: 20}) - .call(moveByChar) - .expectSpeech('c') - .expectSpeech('Size 12') - .expectSpeech('Blue, 100% opacity.') - .expectSpeech('Line through') - .expectBraille(lineText, {startIndex: 21, endIndex: 21}) - .call(moveByChar) - .expectSpeech('h') - .expectBraille(lineText, {startIndex: 22, endIndex: 22}) - .call(moveByChar) - .expectSpeech('a') - .expectBraille(lineText, {startIndex: 23, endIndex: 23}) - .call(moveByChar) - .expectSpeech('r') - .expectBraille(lineText, {startIndex: 24, endIndex: 24}) - .call(moveByChar) - .expectSpeech('a') - .expectBraille(lineText, {startIndex: 25, endIndex: 25}) - .call(moveByChar) - .expectSpeech('c') - .expectBraille(lineText, {startIndex: 26, endIndex: 26}) - .call(moveByChar) - .expectSpeech('t') - .expectBraille(lineText, {startIndex: 27, endIndex: 27}) - .call(moveByChar) - .expectSpeech('e') - .expectBraille(lineText, {startIndex: 28, endIndex: 28}) - .call(moveByChar) - .expectSpeech('r') - .expectBraille(lineText, {startIndex: 29, endIndex: 29}) - .call(moveByChar) - .expectSpeech(' ') - .expectSpeech('Size 20') - .expectSpeech('Black, 100% opacity.') - .expectSpeech('Not line through') - .expectBraille(lineText, {startIndex: 30, endIndex: 30}) - .call(moveByChar) - .expectSpeech('t') - .expectSpeech('Blue, 100% opacity.') - .expectSpeech('Link') - .expectSpeech('Underline') - .expectBraille(lineOnLinkText, {startIndex: 31, endIndex: 31}) - .call(moveByChar) - .expectSpeech('e') - .expectBraille(lineOnLinkText, {startIndex: 32, endIndex: 32}) - .call(moveByChar) - .expectSpeech('s') - .expectBraille(lineOnLinkText, {startIndex: 33, endIndex: 33}) - .call(moveByChar) - .expectSpeech('t') - .expectBraille(lineOnLinkText, {startIndex: 34, endIndex: 34}) - .call(moveByChar) - .expectSpeech('!') - .expectSpeech('Black, 100% opacity.') - .expectSpeech('Not link') - .expectSpeech('Not underline') - .expectBraille(lineText, {startIndex: 35, endIndex: 35}) + mockFeedback.call(moveByChar) + .expectSpeech('o') + .expectSpeech('Size 20') + .expectSpeech('Red, 100% opacity.') + .expectSpeech('Bold') + .expectSpeech('Font Tinos') + .expectBraille(lineText, {startIndex: 1, endIndex: 1}) + .call(moveByChar) + .expectSpeech('v') + .expectBraille(lineText, {startIndex: 2, endIndex: 2}) + .call(moveByChar) + .expectSpeech('e') + .expectBraille(lineText, {startIndex: 3, endIndex: 3}) + .call(moveByChar) + .expectSpeech(' ') + .expectSpeech('Black, 100% opacity.') + .expectSpeech('Not bold') + .expectBraille(lineText, {startIndex: 4, endIndex: 4}) + .call(moveByChar) + .expectSpeech('t') + .expectSpeech('Italic') + .expectBraille(lineText, {startIndex: 5, endIndex: 5}) + .call(moveByChar) + .expectSpeech('h') + .expectBraille(lineText, {startIndex: 6, endIndex: 6}) + .call(moveByChar) + .expectSpeech('r') + .expectBraille(lineText, {startIndex: 7, endIndex: 7}) + .call(moveByChar) + .expectSpeech('o') + .expectBraille(lineText, {startIndex: 8, endIndex: 8}) + .call(moveByChar) + .expectSpeech('u') + .expectBraille(lineText, {startIndex: 9, endIndex: 9}) + .call(moveByChar) + .expectSpeech('g') + .expectBraille(lineText, {startIndex: 10, endIndex: 10}) + .call(moveByChar) + .expectSpeech('h') + .expectBraille(lineText, {startIndex: 11, endIndex: 11}) + .call(moveByChar) + .expectSpeech(' ') + .expectSpeech('Not italic') + .expectBraille(lineText, {startIndex: 12, endIndex: 12}) + .call(moveByChar) + .expectSpeech('t') + .expectSpeech('Underline') + .expectSpeech('Font Gelasio') + .expectBraille(lineText, {startIndex: 13, endIndex: 13}) + .call(moveByChar) + .expectSpeech('e') + .expectBraille(lineText, {startIndex: 14, endIndex: 14}) + .call(moveByChar) + .expectSpeech('x') + .expectBraille(lineText, {startIndex: 15, endIndex: 15}) + .call(moveByChar) + .expectSpeech('t') + .expectBraille(lineText, {startIndex: 16, endIndex: 16}) + .call(moveByChar) + .expectSpeech(' ') + .expectSpeech('Not underline') + .expectSpeech('Font Tinos') + .expectBraille(lineText, {startIndex: 17, endIndex: 17}) + .call(moveByChar) + .expectSpeech('b') + .expectBraille(lineText, {startIndex: 18, endIndex: 18}) + .call(moveByChar) + .expectSpeech('y') + .expectBraille(lineText, {startIndex: 19, endIndex: 19}) + .call(moveByChar) + .expectSpeech(' ') + .expectBraille(lineText, {startIndex: 20, endIndex: 20}) + .call(moveByChar) + .expectSpeech('c') + .expectSpeech('Size 12') + .expectSpeech('Blue, 100% opacity.') + .expectSpeech('Line through') + .expectBraille(lineText, {startIndex: 21, endIndex: 21}) + .call(moveByChar) + .expectSpeech('h') + .expectBraille(lineText, {startIndex: 22, endIndex: 22}) + .call(moveByChar) + .expectSpeech('a') + .expectBraille(lineText, {startIndex: 23, endIndex: 23}) + .call(moveByChar) + .expectSpeech('r') + .expectBraille(lineText, {startIndex: 24, endIndex: 24}) + .call(moveByChar) + .expectSpeech('a') + .expectBraille(lineText, {startIndex: 25, endIndex: 25}) + .call(moveByChar) + .expectSpeech('c') + .expectBraille(lineText, {startIndex: 26, endIndex: 26}) + .call(moveByChar) + .expectSpeech('t') + .expectBraille(lineText, {startIndex: 27, endIndex: 27}) + .call(moveByChar) + .expectSpeech('e') + .expectBraille(lineText, {startIndex: 28, endIndex: 28}) + .call(moveByChar) + .expectSpeech('r') + .expectBraille(lineText, {startIndex: 29, endIndex: 29}) + .call(moveByChar) + .expectSpeech(' ') + .expectSpeech('Size 20') + .expectSpeech('Black, 100% opacity.') + .expectSpeech('Not line through') + .expectBraille(lineText, {startIndex: 30, endIndex: 30}) + .call(moveByChar) + .expectSpeech('t') + .expectSpeech('Blue, 100% opacity.') + .expectSpeech('Link') + .expectSpeech('Underline') + .expectBraille(lineOnLinkText, {startIndex: 31, endIndex: 31}) + .call(moveByChar) + .expectSpeech('e') + .expectBraille(lineOnLinkText, {startIndex: 32, endIndex: 32}) + .call(moveByChar) + .expectSpeech('s') + .expectBraille(lineOnLinkText, {startIndex: 33, endIndex: 33}) + .call(moveByChar) + .expectSpeech('t') + .expectBraille(lineOnLinkText, {startIndex: 34, endIndex: 34}) + .call(moveByChar) + .expectSpeech('!') + .expectSpeech('Black, 100% opacity.') + .expectSpeech('Not link') + .expectSpeech('Not underline') + .expectBraille(lineText, {startIndex: 35, endIndex: 35}) - .replay(); - }); + .replay(); }); // Tests specifically for cursor workarounds. TEST_F( 'ChromeVoxEditingTest', 'RichTextMoveByCharacterNodeWorkaround', - function() { + async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <div role="textbox" contenteditable>hello <b>world</b></div> <button id="go">Go</button> @@ -463,40 +446,39 @@ sel.modify('move', 'forward', 'character'); }, true); </script> - `, - async function(root) { - await this.focusFirstTextField(root); + `); + await this.focusFirstTextField(root); - const go = root.find({role: RoleType.BUTTON}); - const moveByChar = go.doDefault.bind(go); - const lineText = 'hello world mled'; + const go = root.find({role: RoleType.BUTTON}); + const moveByChar = go.doDefault.bind(go); + const lineText = 'hello world mled'; - mockFeedback.call(moveByChar) - .expectSpeech('e') - .expectBraille(lineText, {startIndex: 1, endIndex: 1}) - .call(moveByChar) - .expectSpeech('l') - .expectBraille(lineText, {startIndex: 2, endIndex: 2}) - .call(moveByChar) - .expectSpeech('l') - .expectBraille(lineText, {startIndex: 3, endIndex: 3}) - .call(moveByChar) - .expectSpeech('o') - .expectBraille(lineText, {startIndex: 4, endIndex: 4}) - .call(moveByChar) - .expectSpeech(' ') - .expectBraille(lineText, {startIndex: 5, endIndex: 5}) - .call(moveByChar) - .expectSpeech('w') - .expectBraille(lineText, {startIndex: 6, endIndex: 6}) - .replay(); - }); + mockFeedback.call(moveByChar) + .expectSpeech('e') + .expectBraille(lineText, {startIndex: 1, endIndex: 1}) + .call(moveByChar) + .expectSpeech('l') + .expectBraille(lineText, {startIndex: 2, endIndex: 2}) + .call(moveByChar) + .expectSpeech('l') + .expectBraille(lineText, {startIndex: 3, endIndex: 3}) + .call(moveByChar) + .expectSpeech('o') + .expectBraille(lineText, {startIndex: 4, endIndex: 4}) + .call(moveByChar) + .expectSpeech(' ') + .expectBraille(lineText, {startIndex: 5, endIndex: 5}) + .call(moveByChar) + .expectSpeech('w') + .expectBraille(lineText, {startIndex: 6, endIndex: 6}) + .replay(); }); -TEST_F('ChromeVoxEditingTest', 'RichTextMoveByCharacterEndOfLine', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` +TEST_F( + 'ChromeVoxEditingTest', 'RichTextMoveByCharacterEndOfLine', + async function() { + const mockFeedback = this.createMockFeedback(); + const root = await this.runWithLoadedTree(` <div role="textbox" contenteditable>Test</div> <button id="go">Go</button> @@ -506,38 +488,35 @@ sel.modify('move', 'forward', 'character'); }, true); </script> - `, - async function(root) { - await this.focusFirstTextField(root); + `); + await this.focusFirstTextField(root); - const go = root.find({role: RoleType.BUTTON}); - const moveByChar = go.doDefault.bind(go); - const lineText = 'Test mled'; + const go = root.find({role: RoleType.BUTTON}); + const moveByChar = go.doDefault.bind(go); + const lineText = 'Test mled'; - mockFeedback.call(moveByChar) - .expectSpeech('e') - .expectBraille(lineText, {startIndex: 1, endIndex: 1}) - .call(moveByChar) - .expectSpeech('s') - .expectBraille(lineText, {startIndex: 2, endIndex: 2}) - .call(moveByChar) - .expectSpeech('t') - .expectBraille(lineText, {startIndex: 3, endIndex: 3}) - .call(moveByChar) - .expectSpeech('End of text') - .expectBraille(lineText, {startIndex: 4, endIndex: 4}) + mockFeedback.call(moveByChar) + .expectSpeech('e') + .expectBraille(lineText, {startIndex: 1, endIndex: 1}) + .call(moveByChar) + .expectSpeech('s') + .expectBraille(lineText, {startIndex: 2, endIndex: 2}) + .call(moveByChar) + .expectSpeech('t') + .expectBraille(lineText, {startIndex: 3, endIndex: 3}) + .call(moveByChar) + .expectSpeech('End of text') + .expectBraille(lineText, {startIndex: 4, endIndex: 4}) - .replay(); - }); -}); + .replay(); + }); -TEST_F('ChromeVoxEditingTest', 'RichTextLinkOutput', function() { +TEST_F('ChromeVoxEditingTest', 'RichTextLinkOutput', async function() { // Turn on rich text output settings. localStorage['announceRichTextAttributes'] = 'true'; const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <div role="textbox" contenteditable>a <a href="#">test</a></div> <button id="go">Go</button> <script> @@ -546,42 +525,39 @@ sel.modify('move', 'forward', 'character'); }, true); </script> - `, - async function(root) { - await this.focusFirstTextField(root); + `); + await this.focusFirstTextField(root); - const go = root.find({role: RoleType.BUTTON}); - const moveByChar = go.doDefault.bind(go); - const lineText = 'a test mled'; - const lineOnLinkText = 'a test lnk mled'; + const go = root.find({role: RoleType.BUTTON}); + const moveByChar = go.doDefault.bind(go); + const lineText = 'a test mled'; + const lineOnLinkText = 'a test lnk mled'; - mockFeedback.call(moveByChar) - .expectSpeech(' ') - .expectBraille(lineText, {startIndex: 1, endIndex: 1}) - .call(moveByChar) - .expectSpeech('t') - .expectSpeech('Blue, 100% opacity.') - .expectSpeech('Link') - .expectSpeech('Underline') - .expectBraille(lineOnLinkText, {startIndex: 2, endIndex: 2}) - .call(moveByChar) - .expectSpeech('e') - .expectBraille(lineOnLinkText, {startIndex: 3, endIndex: 3}) - .call(moveByChar) - .expectSpeech('s') - .expectBraille(lineOnLinkText, {startIndex: 4, endIndex: 4}) - .call(moveByChar) - .expectSpeech('t') - .expectBraille(lineOnLinkText, {startIndex: 5, endIndex: 5}) + mockFeedback.call(moveByChar) + .expectSpeech(' ') + .expectBraille(lineText, {startIndex: 1, endIndex: 1}) + .call(moveByChar) + .expectSpeech('t') + .expectSpeech('Blue, 100% opacity.') + .expectSpeech('Link') + .expectSpeech('Underline') + .expectBraille(lineOnLinkText, {startIndex: 2, endIndex: 2}) + .call(moveByChar) + .expectSpeech('e') + .expectBraille(lineOnLinkText, {startIndex: 3, endIndex: 3}) + .call(moveByChar) + .expectSpeech('s') + .expectBraille(lineOnLinkText, {startIndex: 4, endIndex: 4}) + .call(moveByChar) + .expectSpeech('t') + .expectBraille(lineOnLinkText, {startIndex: 5, endIndex: 5}) - .replay(); - }); + .replay(); }); -TEST_F('ChromeVoxEditingTest', 'RichTextExtendByCharacter', function() { +TEST_F('ChromeVoxEditingTest', 'RichTextExtendByCharacter', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <div role="textbox" contenteditable>Te<br>st</div> <button id="go">Go</button> @@ -591,32 +567,29 @@ sel.modify('extend', 'forward', 'character'); }, true); </script> - `, - async function(root) { - await this.focusFirstTextField(root); + `); + await this.focusFirstTextField(root); - const go = root.find({role: RoleType.BUTTON}); - const moveByChar = go.doDefault.bind(go); + const go = root.find({role: RoleType.BUTTON}); + const moveByChar = go.doDefault.bind(go); - mockFeedback.call(moveByChar) - .expectSpeech('T', 'selected') - .call(moveByChar) - .expectSpeech('e', 'selected') - .call(moveByChar) - .expectSpeech('selected') - .call(moveByChar) - .expectSpeech('s', 'selected') - .call(moveByChar) - .expectSpeech('t', 'selected') + mockFeedback.call(moveByChar) + .expectSpeech('T', 'selected') + .call(moveByChar) + .expectSpeech('e', 'selected') + .call(moveByChar) + .expectSpeech('selected') + .call(moveByChar) + .expectSpeech('s', 'selected') + .call(moveByChar) + .expectSpeech('t', 'selected') - .replay(); - }); + .replay(); }); -TEST_F('ChromeVoxEditingTest', 'RichTextImageByCharacter', function() { +TEST_F('ChromeVoxEditingTest', 'RichTextImageByCharacter', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <p contenteditable> <img alt="dog"> is a <img alt="cat"> test </p> @@ -635,60 +608,56 @@ sel.modify('move', dir, 'character'); }, true); </script> - `, - async function(root) { - await this.focusFirstTextField(root, {role: RoleType.PARAGRAPH}); + `); + await this.focusFirstTextField(root, {role: RoleType.PARAGRAPH}); - const go = root.find({role: RoleType.BUTTON}); - const moveByChar = go.doDefault.bind(go); - const lineText = 'dog is a cat test mled'; - const lineOnCatText = 'dog is a cat img test mled'; + const go = root.find({role: RoleType.BUTTON}); + const moveByChar = go.doDefault.bind(go); + const lineText = 'dog is a cat test mled'; + const lineOnCatText = 'dog is a cat img test mled'; - // This is initial output from focusing the contenteditable (which has - // no role). - mockFeedback.expectSpeech( - 'dog', 'Image', ' is a ', 'cat', 'Image', ' test'); - mockFeedback.expectBraille('dog img is a cat img test'); + // This is initial output from focusing the contenteditable (which has + // no role). + mockFeedback.expectSpeech('dog', 'Image', ' is a ', 'cat', 'Image', ' test'); + mockFeedback.expectBraille('dog img is a cat img test'); - const moves = [ - {speech: [' '], braille: [lineText, {startIndex: 3, endIndex: 3}]}, - {speech: ['i'], braille: [lineText, {startIndex: 4, endIndex: 4}]}, - {speech: ['s'], braille: [lineText, {startIndex: 5, endIndex: 5}]}, - {speech: [' '], braille: [lineText, {startIndex: 6, endIndex: 6}]}, - {speech: ['a'], braille: [lineText, {startIndex: 7, endIndex: 7}]}, - {speech: [' '], braille: [lineText, {startIndex: 8, endIndex: 8}]}, { - speech: ['cat', 'Image'], - braille: [lineOnCatText, {startIndex: 9, endIndex: 9}] - }, - {speech: [' '], braille: [lineText, {startIndex: 12, endIndex: 12}]} - ]; + const moves = [ + {speech: [' '], braille: [lineText, {startIndex: 3, endIndex: 3}]}, + {speech: ['i'], braille: [lineText, {startIndex: 4, endIndex: 4}]}, + {speech: ['s'], braille: [lineText, {startIndex: 5, endIndex: 5}]}, + {speech: [' '], braille: [lineText, {startIndex: 6, endIndex: 6}]}, + {speech: ['a'], braille: [lineText, {startIndex: 7, endIndex: 7}]}, + {speech: [' '], braille: [lineText, {startIndex: 8, endIndex: 8}]}, { + speech: ['cat', 'Image'], + braille: [lineOnCatText, {startIndex: 9, endIndex: 9}] + }, + {speech: [' '], braille: [lineText, {startIndex: 12, endIndex: 12}]} + ]; - for (const item of moves) { - mockFeedback.call(moveByChar); - mockFeedback.expectSpeech.apply(mockFeedback, item.speech); - mockFeedback.expectBraille.apply(mockFeedback, item.braille); - } + for (const item of moves) { + mockFeedback.call(moveByChar); + mockFeedback.expectSpeech.apply(mockFeedback, item.speech); + mockFeedback.expectBraille.apply(mockFeedback, item.braille); + } - const backMoves = moves.reverse(); - backMoves.shift(); - for (const backItem of backMoves) { - mockFeedback.call(moveByChar); - mockFeedback.expectSpeech.apply(mockFeedback, backItem.speech); - mockFeedback.expectBraille.apply(mockFeedback, backItem.braille); - } + const backMoves = moves.reverse(); + backMoves.shift(); + for (const backItem of backMoves) { + mockFeedback.call(moveByChar); + mockFeedback.expectSpeech.apply(mockFeedback, backItem.speech); + mockFeedback.expectBraille.apply(mockFeedback, backItem.braille); + } - mockFeedback.replay(); - }); + mockFeedback.replay(); }); -TEST_F('ChromeVoxEditingTest', 'RichTextSelectByLine', function() { +TEST_F('ChromeVoxEditingTest', 'RichTextSelectByLine', async function() { const mockFeedback = this.createMockFeedback(); // Use digit strings like "11111" and "22222" because the character widths // of digits are always the same. This means the test can move down one line // middle of "11111" and reliably hit a given character position in "22222", // regardless of font configuration. https://crbug.com/898213 - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <div> <button id="go">Go</button> </div> @@ -724,80 +693,78 @@ sel.modify.apply(sel, commands.shift()); }, true); </script> - `, - async function(root) { - await this.focusFirstTextField(root, {role: RoleType.PARAGRAPH}); + `); + await this.focusFirstTextField(root, {role: RoleType.PARAGRAPH}); - const go = root.find({role: RoleType.BUTTON}); - const move = go.doDefault.bind(go); + const go = root.find({role: RoleType.BUTTON}); + const move = go.doDefault.bind(go); - // By character. - mockFeedback.call(move) - .expectSpeech('1', 'selected') - .expectBraille('11111 line\nmled', {startIndex: 0, endIndex: 1}) - .call(move) - .expectSpeech('1', 'selected') - .expectBraille('11111 line\nmled', {startIndex: 0, endIndex: 2}) + // By character. + mockFeedback.call(move) + .expectSpeech('1', 'selected') + .expectBraille('11111 line\nmled', {startIndex: 0, endIndex: 1}) + .call(move) + .expectSpeech('1', 'selected') + .expectBraille('11111 line\nmled', {startIndex: 0, endIndex: 2}) - // Forward selection by line (notice the partial selections from the - // first and second lines). - .call(move) - .expectSpeech('111 line', '22', 'selected') - .expectBraille('22222 line\n', {startIndex: 0, endIndex: 2}) + // Forward selection by line (notice the partial selections from the + // first and second lines). + .call(move) + .expectSpeech('111 line', '22', 'selected') + .expectBraille('22222 line\n', {startIndex: 0, endIndex: 2}) - .call(move) - .expectSpeech('222 line', '33', 'selected') - .expectBraille('33333 line\n', {startIndex: 0, endIndex: 2}) + .call(move) + .expectSpeech('222 line', '33', 'selected') + .expectBraille('33333 line\n', {startIndex: 0, endIndex: 2}) - // Backward selection by line. - .call(move) - .expectSpeech('222 line', '33', 'unselected') - .expectBraille('22222 line\n', {startIndex: 0, endIndex: 2}) + // Backward selection by line. + .call(move) + .expectSpeech('222 line', '33', 'unselected') + .expectBraille('22222 line\n', {startIndex: 0, endIndex: 2}) - .call(move) - .expectSpeech('111 line', '22', 'unselected') - .expectBraille('11111 line\nmled', {startIndex: 0, endIndex: 2}) + .call(move) + .expectSpeech('111 line', '22', 'unselected') + .expectBraille('11111 line\nmled', {startIndex: 0, endIndex: 2}) - // Document boundary. - .call(move) - .expectSpeech('111 line', '22222 line', '33333 line', 'selected') - .expectBraille('33333 line\n', {startIndex: 0, endIndex: 10}) + // Document boundary. + .call(move) + .expectSpeech('111 line', '22222 line', '33333 line', 'selected') + .expectBraille('33333 line\n', {startIndex: 0, endIndex: 10}) - // The script repositions the caret to the 'n' of the third line. - .call(move) - .expectSpeech('33333 line') - .expectBraille('33333 line\n', {startIndex: 10, endIndex: 10}) - .call(move) - .expectSpeech('e') - .expectBraille('33333 line\n', {startIndex: 9, endIndex: 9}) - .call(move) - .expectSpeech('n') - .expectBraille('33333 line\n', {startIndex: 8, endIndex: 8}) + // The script repositions the caret to the 'n' of the third line. + .call(move) + .expectSpeech('33333 line') + .expectBraille('33333 line\n', {startIndex: 10, endIndex: 10}) + .call(move) + .expectSpeech('e') + .expectBraille('33333 line\n', {startIndex: 9, endIndex: 9}) + .call(move) + .expectSpeech('n') + .expectBraille('33333 line\n', {startIndex: 8, endIndex: 8}) - // Backward selection. + // Backward selection. - // Growing. - .call(move) - .expectSpeech('ne', '33333 li', 'selected') - .expectBraille('22222 line\n', {startIndex: 8, endIndex: 11}) + // Growing. + .call(move) + .expectSpeech('ne', '33333 li', 'selected') + .expectBraille('22222 line\n', {startIndex: 8, endIndex: 11}) - .call(move) - .expectSpeech('ne', '22222 li', 'selected') - .expectBraille('11111 line\n', {startIndex: 8, endIndex: 11}) + .call(move) + .expectSpeech('ne', '22222 li', 'selected') + .expectBraille('11111 line\n', {startIndex: 8, endIndex: 11}) - // Shrinking. - .call(move) - .expectSpeech('ne', '22222 li', 'unselected') - .expectBraille('22222 line\n', {startIndex: 8, endIndex: 11}) + // Shrinking. + .call(move) + .expectSpeech('ne', '22222 li', 'unselected') + .expectBraille('22222 line\n', {startIndex: 8, endIndex: 11}) - .replay(); - }); + .replay(); }); -TEST_F('ChromeVoxEditingTest', 'RichTextSelectComplexStructure', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` +TEST_F( + 'ChromeVoxEditingTest', 'RichTextSelectComplexStructure', async function() { + const mockFeedback = this.createMockFeedback(); + const root = await this.runWithLoadedTree(` <div> <button id="go">Go</button> </div> @@ -833,420 +800,396 @@ sel.modify.apply(sel, commands.shift()); }, true); </script> - `, - async function(root) { - await this.focusFirstTextField(root, {role: RoleType.TEXT_FIELD}); + `); + await this.focusFirstTextField(root, {role: RoleType.TEXT_FIELD}); - const go = root.find({role: RoleType.BUTTON}); - const move = go.doDefault.bind(go); + const go = root.find({role: RoleType.BUTTON}); + const move = go.doDefault.bind(go); - // By character. - mockFeedback.call(move) - .expectSpeech('1', 'Heading 1', 'selected') - .expectBraille('11111 line h1 mled', {startIndex: 0, endIndex: 1}) - .call(move) - .expectSpeech('1', 'Heading 1', 'selected') - .expectBraille('11111 line h1 mled', {startIndex: 0, endIndex: 2}) + // By character. + mockFeedback.call(move) + .expectSpeech('1', 'Heading 1', 'selected') + .expectBraille('11111 line h1 mled', {startIndex: 0, endIndex: 1}) + .call(move) + .expectSpeech('1', 'Heading 1', 'selected') + .expectBraille('11111 line h1 mled', {startIndex: 0, endIndex: 2}) - // Forward selection by line (notice the partial selections from the - // first and second lines). - .call(move) - .expectSpeech('111 line', 'Heading 1', '222', 'Link', 'selected') - .expectBraille('22222 line lnk', {startIndex: 0, endIndex: 3}) + // Forward selection by line (notice the partial selections from the + // first and second lines). + .call(move) + .expectSpeech('111 line', 'Heading 1', '222', 'Link', 'selected') + .expectBraille('22222 line lnk', {startIndex: 0, endIndex: 3}) - .call(move) - .expectSpeech('22 line', 'Link', 'selected') - .expectBraille( - '33333 line 1. lstitm lst +1', {startIndex: 0, endIndex: 0}) + .call(move) + .expectSpeech('22 line', 'Link', 'selected') + .expectBraille( + '33333 line 1. lstitm lst +1', {startIndex: 0, endIndex: 0}) - // Shrinking. - .call(move) - .expectSpeech('22 line', 'Link', 'unselected') - .expectBraille('22222 line lnk', {startIndex: 0, endIndex: 3}) + // Shrinking. + .call(move) + .expectSpeech('22 line', 'Link', 'unselected') + .expectBraille('22222 line lnk', {startIndex: 0, endIndex: 3}) - .call(move) - .expectSpeech('111 line', 'Heading 1', '222', 'Link', 'unselected') - .expectBraille('11111 line h1 mled', {startIndex: 0, endIndex: 2}) + .call(move) + .expectSpeech('111 line', 'Heading 1', '222', 'Link', 'unselected') + .expectBraille('11111 line h1 mled', {startIndex: 0, endIndex: 2}) - // Document boundary. - .call(move) - .expectSpeech( - '111 line', 'Heading 1', '22222 line', 'Link', '33333 line', - 'List item', 'selected') - .expectBraille( - '33333 line 1. lstitm lst +1', {startIndex: 0, endIndex: 10}) + // Document boundary. + .call(move) + .expectSpeech( + '111 line', 'Heading 1', '22222 line', 'Link', '33333 line', + 'List item', 'selected') + .expectBraille( + '33333 line 1. lstitm lst +1', {startIndex: 0, endIndex: 10}) - // The script repositions the caret to the end of the last line. - .call(move) - .expectSpeech('End of text') - .expectBraille( - '33333 line 1. lstitm lst +1', {startIndex: 10, endIndex: 10}) - .call(move) - .expectSpeech('e') - .expectBraille( - '33333 line 1. lstitm lst +1', {startIndex: 9, endIndex: 9}) - .call(move) - .expectSpeech('n') - .expectBraille( - '33333 line 1. lstitm lst +1', {startIndex: 8, endIndex: 8}) + // The script repositions the caret to the end of the last line. + .call(move) + .expectSpeech('End of text') + .expectBraille( + '33333 line 1. lstitm lst +1', {startIndex: 10, endIndex: 10}) + .call(move) + .expectSpeech('e') + .expectBraille( + '33333 line 1. lstitm lst +1', {startIndex: 9, endIndex: 9}) + .call(move) + .expectSpeech('n') + .expectBraille( + '33333 line 1. lstitm lst +1', {startIndex: 8, endIndex: 8}) - // Backward selection. - // Some bugs exist in Blink where we don't get all selection events - // in this complex structure via extending selection, so we do it - // twice. - .call(move) - .call(move) - .expectSpeech('ine', 'Link') - .expectSpeech('33333 li', 'List item', 'selected') - .expectBraille('11111 line h1', {startIndex: 7, endIndex: 10}) + // Backward selection. + // Some bugs exist in Blink where we don't get all selection events + // in this complex structure via extending selection, so we do it + // twice. + .call(move) + .call(move) + .expectSpeech('ine', 'Link') + .expectSpeech('33333 li', 'List item', 'selected') + .expectBraille('11111 line h1', {startIndex: 7, endIndex: 10}) - .replay(); - }); -}); + .replay(); + }); -TEST_F('ChromeVoxEditingTest', 'EditableLineOneStaticText', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxEditingTest', 'EditableLineOneStaticText', async function() { + const root = await this.runWithLoadedTree(` <p contenteditable style="word-spacing:100000px">this is a test</p> - `, - function(root) { - const staticText = root.find({role: RoleType.STATIC_TEXT}); + `); + const staticText = root.find({role: RoleType.STATIC_TEXT}); - let e = new EditableLine(staticText, 0, staticText, 0); - assertEquals('this ', e.text); + let e = new EditableLine(staticText, 0, staticText, 0); + assertEquals('this ', e.text); - assertEquals(0, e.startOffset); - assertEquals(0, e.endOffset); - assertEquals(0, e.localStartOffset); - assertEquals(0, e.localEndOffset); + assertEquals(0, e.startOffset); + assertEquals(0, e.endOffset); + assertEquals(0, e.localStartOffset); + assertEquals(0, e.localEndOffset); - assertEquals(0, e.containerStartOffset); - assertEquals(4, e.containerEndOffset); + assertEquals(0, e.containerStartOffset); + assertEquals(4, e.containerEndOffset); - e = new EditableLine(staticText, 1, staticText, 1); - assertEquals('this ', e.text); + e = new EditableLine(staticText, 1, staticText, 1); + assertEquals('this ', e.text); - assertEquals(1, e.startOffset); - assertEquals(1, e.endOffset); - assertEquals(1, e.localStartOffset); - assertEquals(1, e.localEndOffset); + assertEquals(1, e.startOffset); + assertEquals(1, e.endOffset); + assertEquals(1, e.localStartOffset); + assertEquals(1, e.localEndOffset); - assertEquals(0, e.containerStartOffset); - assertEquals(4, e.containerEndOffset); + assertEquals(0, e.containerStartOffset); + assertEquals(4, e.containerEndOffset); - e = new EditableLine(staticText, 5, staticText, 5); - assertEquals('is ', e.text); + e = new EditableLine(staticText, 5, staticText, 5); + assertEquals('is ', e.text); - assertEquals(0, e.startOffset); - assertEquals(0, e.endOffset); - assertEquals(5, e.localStartOffset); - assertEquals(5, e.localEndOffset); + assertEquals(0, e.startOffset); + assertEquals(0, e.endOffset); + assertEquals(5, e.localStartOffset); + assertEquals(5, e.localEndOffset); - assertEquals(0, e.containerStartOffset); - assertEquals(2, e.containerEndOffset); + assertEquals(0, e.containerStartOffset); + assertEquals(2, e.containerEndOffset); - e = new EditableLine(staticText, 7, staticText, 7); - assertEquals('is ', e.text); + e = new EditableLine(staticText, 7, staticText, 7); + assertEquals('is ', e.text); - assertEquals(2, e.startOffset); - assertEquals(2, e.endOffset); - assertEquals(7, e.localStartOffset); - assertEquals(7, e.localEndOffset); + assertEquals(2, e.startOffset); + assertEquals(2, e.endOffset); + assertEquals(7, e.localStartOffset); + assertEquals(7, e.localEndOffset); - assertEquals(0, e.containerStartOffset); - assertEquals(2, e.containerEndOffset); - }); + assertEquals(0, e.containerStartOffset); + assertEquals(2, e.containerEndOffset); }); -TEST_F('ChromeVoxEditingTest', 'EditableLineTwoStaticTexts', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxEditingTest', 'EditableLineTwoStaticTexts', async function() { + const root = await this.runWithLoadedTree(` <p contenteditable>hello <b>world</b></p> - `, - function(root) { - const text = root.find({role: RoleType.STATIC_TEXT}); - const bold = text.nextSibling; + `); + const text = root.find({role: RoleType.STATIC_TEXT}); + const bold = text.nextSibling; - let e = new EditableLine(text, 0, text, 0); - assertEquals('hello world', e.text); + let e = new EditableLine(text, 0, text, 0); + assertEquals('hello world', e.text); - assertEquals(0, e.startOffset); - assertEquals(0, e.endOffset); - assertEquals(0, e.localStartOffset); - assertEquals(0, e.localEndOffset); + assertEquals(0, e.startOffset); + assertEquals(0, e.endOffset); + assertEquals(0, e.localStartOffset); + assertEquals(0, e.localEndOffset); - assertEquals(0, e.containerStartOffset); - assertEquals(5, e.containerEndOffset); + assertEquals(0, e.containerStartOffset); + assertEquals(5, e.containerEndOffset); - e = new EditableLine(text, 5, text, 5); - assertEquals('hello world', e.text); + e = new EditableLine(text, 5, text, 5); + assertEquals('hello world', e.text); - assertEquals(5, e.startOffset); - assertEquals(5, e.endOffset); - assertEquals(5, e.localStartOffset); - assertEquals(5, e.localEndOffset); + assertEquals(5, e.startOffset); + assertEquals(5, e.endOffset); + assertEquals(5, e.localStartOffset); + assertEquals(5, e.localEndOffset); - assertEquals(0, e.containerStartOffset); - assertEquals(5, e.containerEndOffset); + assertEquals(0, e.containerStartOffset); + assertEquals(5, e.containerEndOffset); - e = new EditableLine(bold, 0, bold, 0); - assertEquals('hello world', e.text); + e = new EditableLine(bold, 0, bold, 0); + assertEquals('hello world', e.text); - assertEquals(6, e.startOffset); - assertEquals(6, e.endOffset); - assertEquals(0, e.localStartOffset); - assertEquals(0, e.localEndOffset); + assertEquals(6, e.startOffset); + assertEquals(6, e.endOffset); + assertEquals(0, e.localStartOffset); + assertEquals(0, e.localEndOffset); - assertEquals(6, e.containerStartOffset); - assertEquals(10, e.containerEndOffset); + assertEquals(6, e.containerStartOffset); + assertEquals(10, e.containerEndOffset); - e = new EditableLine(bold, 4, bold, 4); - assertEquals('hello world', e.text); + e = new EditableLine(bold, 4, bold, 4); + assertEquals('hello world', e.text); - assertEquals(10, e.startOffset); - assertEquals(10, e.endOffset); - assertEquals(4, e.localStartOffset); - assertEquals(4, e.localEndOffset); + assertEquals(10, e.startOffset); + assertEquals(10, e.endOffset); + assertEquals(4, e.localStartOffset); + assertEquals(4, e.localEndOffset); - assertEquals(6, e.containerStartOffset); - assertEquals(10, e.containerEndOffset); - }); + assertEquals(6, e.containerStartOffset); + assertEquals(10, e.containerEndOffset); }); -TEST_F('ChromeVoxEditingTest', 'EditableLineEquality', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxEditingTest', 'EditableLineEquality', async function() { + const root = await this.runWithLoadedTree(` <div contenteditable role="textbox"> <p style="word-spacing:100000px">this is a test</p> <p>hello <b>world</b></p> </div> - `, - function(root) { - const thisIsATest = - root.findAll({role: RoleType.PARAGRAPH})[0].firstChild; - const hello = root.findAll({role: RoleType.PARAGRAPH})[1].firstChild; - const world = root.findAll({role: RoleType.PARAGRAPH})[1].lastChild; + `); + const thisIsATest = root.findAll({role: RoleType.PARAGRAPH})[0].firstChild; + const hello = root.findAll({role: RoleType.PARAGRAPH})[1].firstChild; + const world = root.findAll({role: RoleType.PARAGRAPH})[1].lastChild; - // The same position -- sanity check. - let e1 = new EditableLine(thisIsATest, 0, thisIsATest, 0); - assertEquals('this ', e1.text); - assertTrue(e1.isSameLine(e1)); + // The same position -- sanity check. + let e1 = new EditableLine(thisIsATest, 0, thisIsATest, 0); + assertEquals('this ', e1.text); + assertTrue(e1.isSameLine(e1)); - // Offset into the same soft line. - let e2 = new EditableLine(thisIsATest, 1, thisIsATest, 1); - assertTrue(e1.isSameLine(e2)); + // Offset into the same soft line. + let e2 = new EditableLine(thisIsATest, 1, thisIsATest, 1); + assertTrue(e1.isSameLine(e2)); - // Boundary. - e2 = new EditableLine(thisIsATest, 4, thisIsATest, 4); - assertTrue(e1.isSameLine(e2)); + // Boundary. + e2 = new EditableLine(thisIsATest, 4, thisIsATest, 4); + assertTrue(e1.isSameLine(e2)); - // Offsets into different soft lines. - e2 = new EditableLine(thisIsATest, 5, thisIsATest, 5); - assertEquals('is ', e2.text); - assertFalse(e1.isSameLine(e2)); + // Offsets into different soft lines. + e2 = new EditableLine(thisIsATest, 5, thisIsATest, 5); + assertEquals('is ', e2.text); + assertFalse(e1.isSameLine(e2)); - // Sanity check; second soft line. - assertTrue(e2.isSameLine(e2)); + // Different offsets into second soft line. + e1 = new EditableLine(thisIsATest, 6, thisIsATest, 6); + assertTrue(e1.isSameLine(e2)); - // Different offsets into second soft line. - e1 = new EditableLine(thisIsATest, 6, thisIsATest, 6); - assertTrue(e1.isSameLine(e2)); + // Boundary. + e1 = new EditableLine(thisIsATest, 7, thisIsATest, 7); + assertTrue(e1.isSameLine(e2)); - // Boundary. - e1 = new EditableLine(thisIsATest, 7, thisIsATest, 7); - assertTrue(e1.isSameLine(e2)); + // Third line. + e1 = new EditableLine(thisIsATest, 8, thisIsATest, 8); + assertEquals('a ', e1.text); + assertFalse(e1.isSameLine(e2)); - // Third line. - e1 = new EditableLine(thisIsATest, 8, thisIsATest, 8); - assertEquals('a ', e1.text); - assertFalse(e1.isSameLine(e2)); + // Last line. + e2 = new EditableLine(thisIsATest, 10, thisIsATest, 10); + assertEquals('test', e2.text); + assertFalse(e1.isSameLine(e2)); - // Last line. - e2 = new EditableLine(thisIsATest, 10, thisIsATest, 10); - assertEquals('test', e2.text); - assertFalse(e1.isSameLine(e2)); + // Boundary. + e1 = new EditableLine(thisIsATest, 13, thisIsATest, 13); + assertTrue(e1.isSameLine(e2)); - // Boundary. - e1 = new EditableLine(thisIsATest, 13, thisIsATest, 13); - assertTrue(e1.isSameLine(e2)); + // Cross into new paragraph. + e2 = new EditableLine(hello, 0, hello, 0); + assertEquals('hello world', e2.text); + assertFalse(e1.isSameLine(e2)); - // Cross into new paragraph. - e2 = new EditableLine(hello, 0, hello, 0); - assertEquals('hello world', e2.text); - assertFalse(e1.isSameLine(e2)); + // On same node, with multi-static text line. + e1 = new EditableLine(hello, 1, hello, 1); + assertTrue(e1.isSameLine(e2)); - // On same node, with multi-static text line. - e1 = new EditableLine(hello, 1, hello, 1); - assertTrue(e1.isSameLine(e2)); + // On same node, with multi-static text line; boundary. + e1 = new EditableLine(hello, 5, hello, 5); + assertTrue(e1.isSameLine(e2)); - // On same node, with multi-static text line; boundary. - e1 = new EditableLine(hello, 5, hello, 5); - assertTrue(e1.isSameLine(e2)); + // On different node, with multi-static text line. + e1 = new EditableLine(world, 1, world, 1); + assertTrue(e1.isSameLine(e2)); - // On different node, with multi-static text line. - e1 = new EditableLine(world, 1, world, 1); - assertTrue(e1.isSameLine(e2)); - - // Another mix of lines. - e2 = new EditableLine(thisIsATest, 9, thisIsATest, 9); - assertFalse(e1.isSameLine(e2)); - }); + // Another mix of lines. + e2 = new EditableLine(thisIsATest, 9, thisIsATest, 9); + assertFalse(e1.isSameLine(e2)); }); -TEST_F('ChromeVoxEditingTest', 'EditableLineStrictEquality', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxEditingTest', 'EditableLineStrictEquality', async function() { + const root = await this.runWithLoadedTree(` <div contenteditable role="textbox"> <p style="word-spacing:100000px">this is a test</p> <p>hello <b>world</b></p> </div> - `, - function(root) { - const thisIsATest = - root.findAll({role: RoleType.PARAGRAPH})[0].firstChild; - const hello = root.findAll({role: RoleType.PARAGRAPH})[1].firstChild; - const world = root.findAll({role: RoleType.PARAGRAPH})[1].lastChild; + `); + const thisIsATest = root.findAll({role: RoleType.PARAGRAPH})[0].firstChild; + const hello = root.findAll({role: RoleType.PARAGRAPH})[1].firstChild; + const world = root.findAll({role: RoleType.PARAGRAPH})[1].lastChild; - // The same position -- sanity check. - let e1 = new EditableLine(thisIsATest, 0, thisIsATest, 0); - assertEquals('this ', e1.text); - assertTrue(e1.isSameLineAndSelection(e1)); + // The same position -- sanity check. + let e1 = new EditableLine(thisIsATest, 0, thisIsATest, 0); + assertEquals('this ', e1.text); + assertTrue(e1.isSameLineAndSelection(e1)); - // Offset into the same soft line. - let e2 = new EditableLine(thisIsATest, 1, thisIsATest, 1); - assertFalse(e1.isSameLineAndSelection(e2)); + // Offset into the same soft line. + let e2 = new EditableLine(thisIsATest, 1, thisIsATest, 1); + assertFalse(e1.isSameLineAndSelection(e2)); - // Boundary. - e2 = new EditableLine(thisIsATest, 4, thisIsATest, 4); - assertFalse(e1.isSameLineAndSelection(e2)); + // Boundary. + e2 = new EditableLine(thisIsATest, 4, thisIsATest, 4); + assertFalse(e1.isSameLineAndSelection(e2)); - // Offsets into different soft lines. - e2 = new EditableLine(thisIsATest, 5, thisIsATest, 5); - assertEquals('is ', e2.text); - assertFalse(e1.isSameLineAndSelection(e2)); + // Offsets into different soft lines. + e2 = new EditableLine(thisIsATest, 5, thisIsATest, 5); + assertEquals('is ', e2.text); + assertFalse(e1.isSameLineAndSelection(e2)); - // Sanity check; second soft line. - assertTrue(e2.isSameLineAndSelection(e2)); + // Sanity check; second soft line. + assertTrue(e2.isSameLineAndSelection(e2)); - // Different offsets into second soft line. - e1 = new EditableLine(thisIsATest, 6, thisIsATest, 6); - assertFalse(e1.isSameLineAndSelection(e2)); + // Different offsets into second soft line. + e1 = new EditableLine(thisIsATest, 6, thisIsATest, 6); + assertFalse(e1.isSameLineAndSelection(e2)); - // Boundary. - e1 = new EditableLine(thisIsATest, 7, thisIsATest, 7); - assertFalse(e1.isSameLineAndSelection(e2)); + // Boundary. + e1 = new EditableLine(thisIsATest, 7, thisIsATest, 7); + assertFalse(e1.isSameLineAndSelection(e2)); - // Cross into new paragraph. - e2 = new EditableLine(hello, 0, hello, 0); - assertEquals('hello world', e2.text); - assertFalse(e1.isSameLineAndSelection(e2)); + // Cross into new paragraph. + e2 = new EditableLine(hello, 0, hello, 0); + assertEquals('hello world', e2.text); + assertFalse(e1.isSameLineAndSelection(e2)); - // On same node, with multi-static text line. - e1 = new EditableLine(hello, 1, hello, 1); - assertFalse(e1.isSameLineAndSelection(e2)); + // On same node, with multi-static text line. + e1 = new EditableLine(hello, 1, hello, 1); + assertFalse(e1.isSameLineAndSelection(e2)); - // On same node, with multi-static text line; boundary. - e1 = new EditableLine(hello, 5, hello, 5); - assertFalse(e1.isSameLineAndSelection(e2)); - }); + // On same node, with multi-static text line; boundary. + e1 = new EditableLine(hello, 5, hello, 5); + assertFalse(e1.isSameLineAndSelection(e2)); }); -TEST_F('ChromeVoxEditingTest', 'EditableLineBaseLineAnchorOrFocus', function() { - this.runWithLoadedTree( - ` +TEST_F( + 'ChromeVoxEditingTest', 'EditableLineBaseLineAnchorOrFocus', + async function() { + const root = await this.runWithLoadedTree(` <div contenteditable role="textbox"> <p style="word-spacing:100000px">this is a test</p> <p>hello <b>world</b></p> </div> - `, - function(root) { - const thisIsATest = - root.findAll({role: RoleType.PARAGRAPH})[0].firstChild; - const hello = root.findAll({role: RoleType.PARAGRAPH})[1].firstChild; - const world = root.findAll({role: RoleType.PARAGRAPH})[1].lastChild; + `); + const thisIsATest = + root.findAll({role: RoleType.PARAGRAPH})[0].firstChild; + const hello = root.findAll({role: RoleType.PARAGRAPH})[1].firstChild; + const world = root.findAll({role: RoleType.PARAGRAPH})[1].lastChild; - // The same position -- sanity check. - let e1 = new EditableLine(thisIsATest, 0, thisIsATest, 0, true); - assertEquals('this ', e1.text); + // The same position -- sanity check. + let e1 = new EditableLine(thisIsATest, 0, thisIsATest, 0, true); + assertEquals('this ', e1.text); - // Offsets into different soft lines; base on focus (default). - e1 = new EditableLine(thisIsATest, 0, thisIsATest, 6); - assertEquals('is ', e1.text); - // Notice that the offset is truncated at the beginning of the line. - assertEquals(0, e1.startOffset); - // Notice that the end offset is properly retained. - assertEquals(1, e1.endOffset); + // Offsets into different soft lines; base on focus (default). + e1 = new EditableLine(thisIsATest, 0, thisIsATest, 6); + assertEquals('is ', e1.text); + // Notice that the offset is truncated at the beginning of the line. + assertEquals(0, e1.startOffset); + // Notice that the end offset is properly retained. + assertEquals(1, e1.endOffset); - // Offsets into different soft lines; base on anchor. - e1 = new EditableLine(thisIsATest, 0, thisIsATest, 6, true); - assertEquals('this ', e1.text); - assertEquals(0, e1.startOffset); - // Notice that the end offset is truncated up to the end of - // line. - assertEquals(5, e1.endOffset); + // Offsets into different soft lines; base on anchor. + e1 = new EditableLine(thisIsATest, 0, thisIsATest, 6, true); + assertEquals('this ', e1.text); + assertEquals(0, e1.startOffset); + // Notice that the end offset is truncated up to the end of + // line. + assertEquals(5, e1.endOffset); - // Across paragraph selection with base line on focus. - e1 = new EditableLine(thisIsATest, 5, hello, 2); - assertEquals('hello world', e1.text); - assertEquals(0, e1.startOffset); - assertEquals(2, e1.endOffset); + // Across paragraph selection with base line on focus. + e1 = new EditableLine(thisIsATest, 5, hello, 2); + assertEquals('hello world', e1.text); + assertEquals(0, e1.startOffset); + assertEquals(2, e1.endOffset); - // Across paragraph selection with base line on anchor. - e1 = new EditableLine(thisIsATest, 5, hello, 2, true); - assertEquals('is ', e1.text); - assertEquals(0, e1.startOffset); - assertEquals(3, e1.endOffset); - }); -}); + // Across paragraph selection with base line on anchor. + e1 = new EditableLine(thisIsATest, 5, hello, 2, true); + assertEquals('is ', e1.text); + assertEquals(0, e1.startOffset); + assertEquals(3, e1.endOffset); + }); -TEST_F('ChromeVoxEditingTest', 'IsValidLine', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxEditingTest', 'IsValidLine', async function() { + const root = await this.runWithLoadedTree(` <div contenteditable role="textbox"> <p style="word-spacing:100000px">this is a test</p> <p>end</p> </div> - `, - function(root) { - // Each word is on its own line, but parented by a static text. - const [text, endText] = root.findAll({role: RoleType.STATIC_TEXT}); + `); + // Each word is on its own line, but parented by a static text. + const [text, endText] = root.findAll({role: RoleType.STATIC_TEXT}); - // The EditableLine object automatically adjusts to surround the line no - // matter what the input is. - const line = new EditableLine(text, 0, text, 0); - assertTrue(line.isValidLine()); + // The EditableLine object automatically adjusts to surround the line no + // matter what the input is. + const line = new EditableLine(text, 0, text, 0); + assertTrue(line.isValidLine()); - // During the course of editing operations, this line may become - // invalidted. For example, if a user starts typing into the line, the - // bounding nodes might change. - // Simulate that here by modifying private state. + // During the course of editing operations, this line may become + // invalidted. For example, if a user starts typing into the line, the + // bounding nodes might change. + // Simulate that here by modifying private state. - // This puts the line at offset 8 (|this is a|). - line.localLineStartContainerOffset_ = 0; - line.localLineEndContainerOffset_ = 8; - assertFalse(line.isValidLine()); + // This puts the line at offset 8 (|this is a|). + line.localLineStartContainerOffset_ = 0; + line.localLineEndContainerOffset_ = 8; + assertFalse(line.isValidLine()); - // This puts us in the first line. - line.localLineStartContainerOffset_ = 0; - line.localLineEndContainerOffset_ = 4; - assertTrue(line.isValidLine()); + // This puts us in the first line. + line.localLineStartContainerOffset_ = 0; + line.localLineEndContainerOffset_ = 4; + assertTrue(line.isValidLine()); - // This is still fine (for our purposes) because the line is still - // intact. - line.localLineStartContainerOffset_ = 0; - line.localLineEndContainerOffset_ = 2; - assertTrue(line.isValidLine()); + // This is still fine (for our purposes) because the line is still + // intact. + line.localLineStartContainerOffset_ = 0; + line.localLineEndContainerOffset_ = 2; + assertTrue(line.isValidLine()); - // The line has changed. The end has been moved for some reason. - line.lineEndContainer_ = endText; - assertFalse(line.isValidLine()); - }); + // The line has changed. The end has been moved for some reason. + line.lineEndContainer_ = endText; + assertFalse(line.isValidLine()); }); -TEST_F('ChromeVoxEditingTest', 'TelTrimsWhitespace', function() { +TEST_F('ChromeVoxEditingTest', 'TelTrimsWhitespace', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <div id="go"></div> <input id="input" type="tel"></input> <script> @@ -1266,63 +1209,58 @@ input.selectionEnd = index; }, true); </script> - `, - async function(root) { - await this.focusFirstTextField(root); + `); + await this.focusFirstTextField(root); - const go = root.find({role: RoleType.GENERIC_CONTAINER}); - const enterKey = go.doDefault.bind(go); + const go = root.find({role: RoleType.GENERIC_CONTAINER}); + const enterKey = go.doDefault.bind(go); - mockFeedback.call(enterKey) - .expectSpeech('6') - .call(enterKey) - .expectSpeech('0') - .call(enterKey) - .expectSpeech('1') + mockFeedback.call(enterKey) + .expectSpeech('6') + .call(enterKey) + .expectSpeech('0') + .call(enterKey) + .expectSpeech('1') - // Deletion. - .call(enterKey) - .expectSpeech('1') - .replay(); - }); + // Deletion. + .call(enterKey) + .expectSpeech('1') + .replay(); }); -TEST_F('ChromeVoxEditingTest', 'BackwardWordDelete', function() { +TEST_F('ChromeVoxEditingTest', 'BackwardWordDelete', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <div style='max-width: 5px; overflow-wrap: normal' contenteditable> this is a test </div> - `, - async function(root) { - await this.focusFirstTextField( - root, {attributes: {nonAtomicTextFieldRoot: true}}); + `); + await this.focusFirstTextField( + root, {attributes: {nonAtomicTextFieldRoot: true}}); - mockFeedback.call(this.press(KeyCode.END, {ctrl: true})) - .expectSpeech('test') - .call(this.press(KeyCode.BACK, {ctrl: true})) - .expectSpeech('test, deleted') - .expectBraille('a\u00a0', {startIndex: 2, endIndex: 2}) - .call(this.press(KeyCode.BACK, {ctrl: true})) - .expectSpeech('a , deleted') - .expectBraille('is\u00a0', {startIndex: 3, endIndex: 3}) - .call(this.press(KeyCode.BACK, {ctrl: true})) - .expectSpeech('is , deleted') - .expectBraille('this\u00a0mled', {startIndex: 5, endIndex: 5}) - .call(this.press(KeyCode.BACK, {ctrl: true})) - .expectBraille(' mled', {startIndex: 0, endIndex: 0}) - .replay(); - }); + mockFeedback.call(this.press(KeyCode.END, {ctrl: true})) + .expectSpeech('test') + .call(this.press(KeyCode.BACK, {ctrl: true})) + .expectSpeech('test, deleted') + .expectBraille('a\u00a0', {startIndex: 2, endIndex: 2}) + .call(this.press(KeyCode.BACK, {ctrl: true})) + .expectSpeech('a , deleted') + .expectBraille('is\u00a0', {startIndex: 3, endIndex: 3}) + .call(this.press(KeyCode.BACK, {ctrl: true})) + .expectSpeech('is , deleted') + .expectBraille('this\u00a0mled', {startIndex: 5, endIndex: 5}) + .call(this.press(KeyCode.BACK, {ctrl: true})) + .expectBraille(' mled', {startIndex: 0, endIndex: 0}) + .replay(); }); TEST_F( - 'ChromeVoxEditingTest', 'BackwardWordDeleteAcrossParagraphs', function() { + 'ChromeVoxEditingTest', 'BackwardWordDeleteAcrossParagraphs', + async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <div style='max-width: 5px; overflow-wrap: normal' contenteditable @@ -1330,30 +1268,27 @@ <p>first line</p> <p>second line</p> </div> - `, - async function(root) { - await this.focusFirstTextField(root); + `); + await this.focusFirstTextField(root); - mockFeedback.call(this.press(KeyCode.END, {ctrl: true})) - .expectSpeech('line') - .call(this.press(KeyCode.BACK, {ctrl: true})) - .expectSpeech('line, deleted') - .call(this.press(KeyCode.BACK, {ctrl: true})) - .expectSpeech('second , deleted') - .call(this.press(KeyCode.BACK, {ctrl: true})) - .expectSpeech('line') - .call(this.press(KeyCode.BACK, {ctrl: true})) - .expectSpeech('line, deleted') - .call(this.press(KeyCode.BACK, {ctrl: true})) - .expectSpeech('first , deleted') - .replay(); - }); + mockFeedback.call(this.press(KeyCode.END, {ctrl: true})) + .expectSpeech('line') + .call(this.press(KeyCode.BACK, {ctrl: true})) + .expectSpeech('line, deleted') + .call(this.press(KeyCode.BACK, {ctrl: true})) + .expectSpeech('second , deleted') + .call(this.press(KeyCode.BACK, {ctrl: true})) + .expectSpeech('line') + .call(this.press(KeyCode.BACK, {ctrl: true})) + .expectSpeech('line, deleted') + .call(this.press(KeyCode.BACK, {ctrl: true})) + .expectSpeech('first , deleted') + .replay(); }); -TEST_F('ChromeVoxEditingTest', 'GrammarErrors', function() { +TEST_F('ChromeVoxEditingTest', 'GrammarErrors', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <div contenteditable="true" role="textbox"> This <span aria-invalid="grammar">are</span> a test </div> @@ -1365,269 +1300,247 @@ sel.modify('move', 'forward', 'character'); }, true); </script> - `, - async function(root) { - await this.focusFirstTextField(root); + `); + await this.focusFirstTextField(root); - const go = root.find({role: RoleType.BUTTON}); - const moveByChar = go.doDefault.bind(go); + const go = root.find({role: RoleType.BUTTON}); + const moveByChar = go.doDefault.bind(go); - mockFeedback.call(moveByChar) - .expectSpeech('h') - .call(moveByChar) - .expectSpeech('i') - .call(moveByChar) - .expectSpeech('s') - .call(moveByChar) - .expectSpeech(' ') + mockFeedback.call(moveByChar) + .expectSpeech('h') + .call(moveByChar) + .expectSpeech('i') + .call(moveByChar) + .expectSpeech('s') + .call(moveByChar) + .expectSpeech(' ') - .call(moveByChar) - .expectSpeech('a', 'Grammar error') - .call(moveByChar) - .expectSpeech('r') - .call(moveByChar) - .expectSpeech('e') - .call(moveByChar) - .expectSpeech(' ', 'Leaving grammar error') + .call(moveByChar) + .expectSpeech('a', 'Grammar error') + .call(moveByChar) + .expectSpeech('r') + .call(moveByChar) + .expectSpeech('e') + .call(moveByChar) + .expectSpeech(' ', 'Leaving grammar error') - .replay(); - }); + .replay(); }); // Flaky test, crbug.com/1098642. TEST_F( - 'ChromeVoxEditingTest', 'DISABLED_CharacterTypedAfterNewLine', function() { + 'ChromeVoxEditingTest', 'DISABLED_CharacterTypedAfterNewLine', + async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <p>start</p> <div contenteditable role="textbox"> <p>hello</p> </div> - `, - async function(root) { - await this.focusFirstTextField(root); + `); + await this.focusFirstTextField(root); - mockFeedback.call(this.press(KeyCode.END, {ctrl: true})) - .expectSpeech('hello') - .call(this.press(KeyCode.RETURN)) - .expectSpeech('\n') - .call(this.press(KeyCode.A)) - .expectSpeech('a') - .replay(); - }); + mockFeedback.call(this.press(KeyCode.END, {ctrl: true})) + .expectSpeech('hello') + .call(this.press(KeyCode.RETURN)) + .expectSpeech('\n') + .call(this.press(KeyCode.A)) + .expectSpeech('a') + .replay(); }); -TEST_F('ChromeVoxEditingTest', 'SelectAll', function() { +TEST_F('ChromeVoxEditingTest', 'SelectAll', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <div contenteditable role="textbox"> <p>first line</p> <p>second line</p> <p>third line</p> </div> - `, - async function(root) { - await this.focusFirstTextField(root); + `); + await this.focusFirstTextField(root); - mockFeedback.call(this.press(KeyCode.END, {ctrl: true})) - .expectSpeech('third line') - .call(this.press(KeyCode.A, {ctrl: true})) - .expectSpeech('first line', 'second line', 'third line', 'selected') - .call(this.press(KeyCode.UP)) - .expectSpeech('second line') - .call(this.press(KeyCode.A, {ctrl: true})) - .expectSpeech('first line', 'second line', 'third line', 'selected') - .call(this.press(KeyCode.HOME, {ctrl: true})) - .expectSpeech('first line') - .call(this.press(KeyCode.A, {ctrl: true})) - .expectSpeech('first line', 'second line', 'third line', 'selected') - .replay(); - }); + mockFeedback.call(this.press(KeyCode.END, {ctrl: true})) + .expectSpeech('third line') + .call(this.press(KeyCode.A, {ctrl: true})) + .expectSpeech('first line', 'second line', 'third line', 'selected') + .call(this.press(KeyCode.UP)) + .expectSpeech('second line') + .call(this.press(KeyCode.A, {ctrl: true})) + .expectSpeech('first line', 'second line', 'third line', 'selected') + .call(this.press(KeyCode.HOME, {ctrl: true})) + .expectSpeech('first line') + .call(this.press(KeyCode.A, {ctrl: true})) + .expectSpeech('first line', 'second line', 'third line', 'selected') + .replay(); }); -TEST_F('ChromeVoxEditingTest', 'TextAreaBrailleEmptyLine', function() { +TEST_F('ChromeVoxEditingTest', 'TextAreaBrailleEmptyLine', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - '<textarea></textarea>', async function(root) { - const textarea = await this.focusFirstTextField(root); - textarea.setValue('test\n\none\ntwo\n\nthree'); - await new Promise( - resolve => - this.listenOnce(textarea, 'valueInTextFieldChanged', resolve)); - mockFeedback.call(this.press(KeyCode.UP)).expectBraille('\n'); - mockFeedback.call(this.press(KeyCode.UP)).expectBraille('two\n'); - mockFeedback.call(this.press(KeyCode.UP)).expectBraille('one\n'); - mockFeedback.call(this.press(KeyCode.UP)).expectBraille('\n'); - mockFeedback.call(this.press(KeyCode.UP)) - .expectBraille('test\nmled') - .replay(); - }); + const root = await this.runWithLoadedTree('<textarea></textarea>'); + const textarea = await this.focusFirstTextField(root); + textarea.setValue('test\n\none\ntwo\n\nthree'); + await new Promise( + resolve => this.listenOnce(textarea, 'valueInTextFieldChanged', resolve)); + mockFeedback.call(this.press(KeyCode.UP)).expectBraille('\n'); + mockFeedback.call(this.press(KeyCode.UP)).expectBraille('two\n'); + mockFeedback.call(this.press(KeyCode.UP)).expectBraille('one\n'); + mockFeedback.call(this.press(KeyCode.UP)).expectBraille('\n'); + mockFeedback.call(this.press(KeyCode.UP)) + .expectBraille('test\nmled') + .replay(); }); -TEST_F('ChromeVoxEditingTest', 'MoveByCharacterIntent', function() { +TEST_F('ChromeVoxEditingTest', 'MoveByCharacterIntent', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <div contenteditable role="textbox"> <p>123</p> <p>456</p> </div> - `, - async function(root) { - await this.focusFirstTextField(root); + `); + await this.focusFirstTextField(root); - mockFeedback.call(this.press(KeyCode.RIGHT)) - .expectSpeech('2') - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('3') - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('\n') - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('4') - .call(this.press(KeyCode.LEFT)) - .expectSpeech('\n') - .call(this.press(KeyCode.LEFT)) - .expectSpeech('3') - .replay(); - }); + mockFeedback.call(this.press(KeyCode.RIGHT)) + .expectSpeech('2') + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('3') + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('\n') + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('4') + .call(this.press(KeyCode.LEFT)) + .expectSpeech('\n') + .call(this.press(KeyCode.LEFT)) + .expectSpeech('3') + .replay(); }); -TEST_F('ChromeVoxEditingTest', 'MoveByLineIntent', function() { +TEST_F('ChromeVoxEditingTest', 'MoveByLineIntent', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <div contenteditable role="textbox"> <p>123</p> <p>456</p> <p>789</p> </div> - `, - async function(root) { - await this.focusFirstTextField(root); + `); + await this.focusFirstTextField(root); - mockFeedback.call(this.press(KeyCode.DOWN)) - .expectSpeech('456') - .call(this.press(KeyCode.DOWN)) - .expectSpeech('789') - .call(this.press(KeyCode.UP)) - .expectSpeech('456') - .call(this.press(KeyCode.UP)) - .expectSpeech('123') - .replay(); - }); + mockFeedback.call(this.press(KeyCode.DOWN)) + .expectSpeech('456') + .call(this.press(KeyCode.DOWN)) + .expectSpeech('789') + .call(this.press(KeyCode.UP)) + .expectSpeech('456') + .call(this.press(KeyCode.UP)) + .expectSpeech('123') + .replay(); }); -TEST_F('ChromeVoxEditingTest', 'SelectAllBareTextContent', function() { +TEST_F('ChromeVoxEditingTest', 'SelectAllBareTextContent', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <div contenteditable role="textbox">unread</div> - `, - async function(root) { - await this.focusFirstTextField(root); + `); + await this.focusFirstTextField(root); - mockFeedback.call(this.press(KeyCode.END, {ctrl: true})) - .expectSpeech('unread') - .call(this.press(KeyCode.A, {ctrl: true})) - .expectSpeech('unread', 'selected') - .replay(); - }); + mockFeedback.call(this.press(KeyCode.END, {ctrl: true})) + .expectSpeech('unread') + .call(this.press(KeyCode.A, {ctrl: true})) + .expectSpeech('unread', 'selected') + .replay(); }); -TEST_F('ChromeVoxEditingTest', 'InputEvents', function() { +TEST_F('ChromeVoxEditingTest', 'InputEvents', async function() { const site = `<input type="text"></input>`; - this.runWithLoadedTree(site, async function(root) { - const input = await this.focusFirstTextField(root); + const root = await this.runWithLoadedTree(site); + const input = await this.focusFirstTextField(root); - // EventType.TEXT_SELECTION_CHANGED fires on focus as well. - // - // TODO(nektar): Deprecate and remove TEXT_SELECTION_CHANGED. - event = await this.waitForEditableEvent(); - assertEquals(EventType.TEXT_SELECTION_CHANGED, event.type); - assertEquals(input, event.target); - assertEquals('', input.value); + // EventType.TEXT_SELECTION_CHANGED fires on focus as well. + // + // TODO(nektar): Deprecate and remove TEXT_SELECTION_CHANGED. + event = await this.waitForEditableEvent(); + assertEquals(EventType.TEXT_SELECTION_CHANGED, event.type); + assertEquals(input, event.target); + assertEquals('', input.value); - this.press(KeyCode.A)(); + this.press(KeyCode.A)(); - event = await this.waitForEditableEvent(); - assertEquals(EventType.VALUE_IN_TEXT_FIELD_CHANGED, event.type); - assertEquals(input, event.target); - assertEquals('a', input.value); + event = await this.waitForEditableEvent(); + assertEquals(EventType.VALUE_IN_TEXT_FIELD_CHANGED, event.type); + assertEquals(input, event.target); + assertEquals('a', input.value); - // We deliberately used EventType.TEXT_SELECTION_CHANGED instead of - // EventType.DOCUMENT_SELECTION_CHANGED for text fields. - event = await this.waitForEditableEvent(); - assertEquals(EventType.TEXT_SELECTION_CHANGED, event.type); - assertEquals(input, event.target); - assertEquals('a', input.value); + // We deliberately used EventType.TEXT_SELECTION_CHANGED instead of + // EventType.DOCUMENT_SELECTION_CHANGED for text fields. + event = await this.waitForEditableEvent(); + assertEquals(EventType.TEXT_SELECTION_CHANGED, event.type); + assertEquals(input, event.target); + assertEquals('a', input.value); - this.press(KeyCode.B)(); + this.press(KeyCode.B)(); - event = await this.waitForEditableEvent(); - assertEquals(EventType.VALUE_IN_TEXT_FIELD_CHANGED, event.type); - assertEquals(input, event.target); - assertEquals('ab', input.value); + event = await this.waitForEditableEvent(); + assertEquals(EventType.VALUE_IN_TEXT_FIELD_CHANGED, event.type); + assertEquals(input, event.target); + assertEquals('ab', input.value); - event = await this.waitForEditableEvent(); - assertEquals(EventType.TEXT_SELECTION_CHANGED, event.type); - assertEquals(input, event.target); - assertEquals('ab', input.value); - }); + event = await this.waitForEditableEvent(); + assertEquals(EventType.TEXT_SELECTION_CHANGED, event.type); + assertEquals(input, event.target); + assertEquals('ab', input.value); }); -TEST_F('ChromeVoxEditingTest', 'TextAreaEvents', function() { +TEST_F('ChromeVoxEditingTest', 'TextAreaEvents', async function() { const site = `<textarea></textarea>`; - this.runWithLoadedTree(site, async function(root) { - const textArea = await this.focusFirstTextField(root); - let event = await this.waitForEditableEvent(); - assertEquals(EventType.DOCUMENT_SELECTION_CHANGED, event.type); - assertEquals(textArea, event.target); - assertEquals('', textArea.value); + const root = await this.runWithLoadedTree(site); + const textArea = await this.focusFirstTextField(root); + let event = await this.waitForEditableEvent(); + assertEquals(EventType.DOCUMENT_SELECTION_CHANGED, event.type); + assertEquals(textArea, event.target); + assertEquals('', textArea.value); - this.press(KeyCode.A)(); + this.press(KeyCode.A)(); - event = await this.waitForEditableEvent(); - assertEquals(EventType.DOCUMENT_SELECTION_CHANGED, event.type); - assertEquals(textArea, event.target); - assertEquals('a', textArea.value); + event = await this.waitForEditableEvent(); + assertEquals(EventType.DOCUMENT_SELECTION_CHANGED, event.type); + assertEquals(textArea, event.target); + assertEquals('a', textArea.value); - this.press(KeyCode.B)(); + this.press(KeyCode.B)(); - event = await this.waitForEditableEvent(); - assertEquals(EventType.DOCUMENT_SELECTION_CHANGED, event.type); - assertEquals(textArea, event.target); - assertEquals('ab', textArea.value); - }); + event = await this.waitForEditableEvent(); + assertEquals(EventType.DOCUMENT_SELECTION_CHANGED, event.type); + assertEquals(textArea, event.target); + assertEquals('ab', textArea.value); }); -TEST_F('ChromeVoxEditingTest', 'ContentEditableEvents', function() { +TEST_F('ChromeVoxEditingTest', 'ContentEditableEvents', async function() { const site = `<div role="textbox" contenteditable></div>`; - this.runWithLoadedTree(site, async function(root) { - const contentEditable = await this.focusFirstTextField(root); - let event = await this.waitForEditableEvent(); - assertEquals(EventType.DOCUMENT_SELECTION_CHANGED, event.type); - assertEquals(contentEditable, event.target); - assertEquals('', contentEditable.value); + const root = await this.runWithLoadedTree(site); + const contentEditable = await this.focusFirstTextField(root); + let event = await this.waitForEditableEvent(); + assertEquals(EventType.DOCUMENT_SELECTION_CHANGED, event.type); + assertEquals(contentEditable, event.target); + assertEquals('', contentEditable.value); - this.press(KeyCode.A)(); + this.press(KeyCode.A)(); - event = await this.waitForEditableEvent(); - assertEquals(EventType.DOCUMENT_SELECTION_CHANGED, event.type); - assertEquals(contentEditable, event.target); - assertEquals('a', contentEditable.value); + event = await this.waitForEditableEvent(); + assertEquals(EventType.DOCUMENT_SELECTION_CHANGED, event.type); + assertEquals(contentEditable, event.target); + assertEquals('a', contentEditable.value); - this.press(KeyCode.B)(); + this.press(KeyCode.B)(); - event = await this.waitForEditableEvent(); - assertEquals(EventType.DOCUMENT_SELECTION_CHANGED, event.type); - assertEquals(contentEditable, event.target); - assertEquals('ab', contentEditable.value); - }); + event = await this.waitForEditableEvent(); + assertEquals(EventType.DOCUMENT_SELECTION_CHANGED, event.type); + assertEquals(contentEditable, event.target); + assertEquals('ab', contentEditable.value); }); -TEST_F('ChromeVoxEditingTest', 'MarkedContent', function() { +TEST_F('ChromeVoxEditingTest', 'MarkedContent', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div contenteditable role="textbox"> @@ -1644,27 +1557,26 @@ role="deletion">everyone's</span></span><span> text.</span> </div> `; - this.runWithLoadedTree(site, async function(root) { - await this.focusFirstTextField(root); + const root = await this.runWithLoadedTree(site); + await this.focusFirstTextField(root); - mockFeedback.call(this.press(KeyCode.DOWN)) - .expectSpeech('This is ') - .expectSpeech('Marked content', 'my', 'Marked content end') - .call(this.press(KeyCode.DOWN)) - .expectSpeech('This is ') - .expectSpeech('Comment', 'your', 'Comment end') - .call(this.press(KeyCode.DOWN)) - .expectSpeech('This is ') - .expectSpeech('Suggest', 'Insert', 'their', 'Insert end', 'Suggest end') - .call(this.press(KeyCode.DOWN)) - .expectSpeech('This is ') - .expectSpeech( - 'Suggest', 'Delete', `everyone's`, 'Delete end', 'Suggest end') - .replay(); - }); + mockFeedback.call(this.press(KeyCode.DOWN)) + .expectSpeech('This is ') + .expectSpeech('Marked content', 'my', 'Marked content end') + .call(this.press(KeyCode.DOWN)) + .expectSpeech('This is ') + .expectSpeech('Comment', 'your', 'Comment end') + .call(this.press(KeyCode.DOWN)) + .expectSpeech('This is ') + .expectSpeech('Suggest', 'Insert', 'their', 'Insert end', 'Suggest end') + .call(this.press(KeyCode.DOWN)) + .expectSpeech('This is ') + .expectSpeech( + 'Suggest', 'Delete', `everyone's`, 'Delete end', 'Suggest end') + .replay(); }); -TEST_F('ChromeVoxEditingTest', 'NestedInsertionDeletion', function() { +TEST_F('ChromeVoxEditingTest', 'NestedInsertionDeletion', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div contenteditable role="textbox"> @@ -1676,20 +1588,19 @@ <p>End</p> </div> `; - this.runWithLoadedTree(site, async function(root) { - await this.focusFirstTextField(root); + const root = await this.runWithLoadedTree(site); + await this.focusFirstTextField(root); - mockFeedback.call(this.press(KeyCode.DOWN)) - .expectSpeech( - 'I ', 'Suggest', 'Username', 'Insert', 'was', 'Insert end', - 'Delete', 'am', 'Delete end', 'Suggest end', ' typing') - .call(this.press(KeyCode.DOWN)) - .expectSpeech('End') - .replay(); - }); + mockFeedback.call(this.press(KeyCode.DOWN)) + .expectSpeech( + 'I ', 'Suggest', 'Username', 'Insert', 'was', 'Insert end', 'Delete', + 'am', 'Delete end', 'Suggest end', ' typing') + .call(this.press(KeyCode.DOWN)) + .expectSpeech('End') + .replay(); }); -TEST_F('ChromeVoxEditingTest', 'MoveByCharSuggestions', function() { +TEST_F('ChromeVoxEditingTest', 'MoveByCharSuggestions', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div contenteditable="true" role="textbox"> @@ -1701,44 +1612,43 @@ <p>End</p> </div> `; - this.runWithLoadedTree(site, async function(root) { - await this.focusFirstTextField(root); + const root = await this.runWithLoadedTree(site); + await this.focusFirstTextField(root); - mockFeedback.call(this.press(KeyCode.DOWN)) - .expectSpeech('I ') - // Move forward through line. - .call(this.press(KeyCode.RIGHT)) - .expectSpeech(' ') - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('Suggest', 'Username', 'Insert', 'w') - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('a') - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('s') - .expectSpeech('Insert end') - .call(this.press(KeyCode.RIGHT)) - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('Delete', 'a') - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('m') - .expectSpeech('Delete end', 'Suggest end') - // Move backward through the same line. - .call(this.press(KeyCode.LEFT)) - .expectSpeech('Delete', 'a') - .call(this.press(KeyCode.LEFT)) - .call(this.press(KeyCode.LEFT)) - .expectSpeech('s', 'Insert end') - .call(this.press(KeyCode.LEFT)) - .expectSpeech('a') - .call(this.press(KeyCode.LEFT)) - .expectSpeech('Suggest', 'Insert', 'w') - .call(this.press(KeyCode.DOWN)) - .expectSpeech('End') - .replay(); - }); + mockFeedback.call(this.press(KeyCode.DOWN)) + .expectSpeech('I ') + // Move forward through line. + .call(this.press(KeyCode.RIGHT)) + .expectSpeech(' ') + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('Suggest', 'Username', 'Insert', 'w') + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('a') + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('s') + .expectSpeech('Insert end') + .call(this.press(KeyCode.RIGHT)) + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('Delete', 'a') + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('m') + .expectSpeech('Delete end', 'Suggest end') + // Move backward through the same line. + .call(this.press(KeyCode.LEFT)) + .expectSpeech('Delete', 'a') + .call(this.press(KeyCode.LEFT)) + .call(this.press(KeyCode.LEFT)) + .expectSpeech('s', 'Insert end') + .call(this.press(KeyCode.LEFT)) + .expectSpeech('a') + .call(this.press(KeyCode.LEFT)) + .expectSpeech('Suggest', 'Insert', 'w') + .call(this.press(KeyCode.DOWN)) + .expectSpeech('End') + .replay(); }); -TEST_F('ChromeVoxEditingTest', 'MoveByWordSuggestions', function() { +TEST_F('ChromeVoxEditingTest', 'MoveByWordSuggestions', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div contenteditable="true" role="textbox"> @@ -1750,34 +1660,34 @@ <p>End</p> </div> `; - this.runWithLoadedTree(site, async function(root) { - await this.focusFirstTextField(root); + const root = await this.runWithLoadedTree(site); + await this.focusFirstTextField(root); - mockFeedback.call(this.press(KeyCode.DOWN)) - .expectSpeech('I ') - // Move forward through line. - .call(this.press(KeyCode.RIGHT, {ctrl: true})) - .expectSpeech('I') - .call(this.press(KeyCode.RIGHT, {ctrl: true})) - .expectSpeech('Suggest', 'Username', 'Insert', 'was', 'Insert end') - .call(this.press(KeyCode.RIGHT, {ctrl: true})) - .expectSpeech('Delete', 'am', 'Delete end', 'Suggest end') - // Move backward through line. - .call(this.press(KeyCode.LEFT, {ctrl: true})) - .expectSpeech('Delete', 'am', 'Delete end', 'Suggest end') - .call(this.press(KeyCode.LEFT, {ctrl: true})) - .expectSpeech('Suggest', 'Username', 'Insert', 'was') - .call(this.press(KeyCode.LEFT, {ctrl: true})) - .expectSpeech('I') - .call(this.press(KeyCode.DOWN)) - .expectSpeech('End') - .replay(); - }); + mockFeedback.call(this.press(KeyCode.DOWN)) + .expectSpeech('I ') + // Move forward through line. + .call(this.press(KeyCode.RIGHT, {ctrl: true})) + .expectSpeech('I') + .call(this.press(KeyCode.RIGHT, {ctrl: true})) + .expectSpeech('Suggest', 'Username', 'Insert', 'was', 'Insert end') + .call(this.press(KeyCode.RIGHT, {ctrl: true})) + .expectSpeech('Delete', 'am', 'Delete end', 'Suggest end') + // Move backward through line. + .call(this.press(KeyCode.LEFT, {ctrl: true})) + .expectSpeech('Delete', 'am', 'Delete end', 'Suggest end') + .call(this.press(KeyCode.LEFT, {ctrl: true})) + .expectSpeech('Suggest', 'Username', 'Insert', 'was') + .call(this.press(KeyCode.LEFT, {ctrl: true})) + .expectSpeech('I') + .call(this.press(KeyCode.DOWN)) + .expectSpeech('End') + .replay(); }); -TEST_F('ChromeVoxEditingTest', 'MoveByWordSuggestionsNoIntents', function() { - const mockFeedback = this.createMockFeedback(); - const site = ` +TEST_F( + 'ChromeVoxEditingTest', 'MoveByWordSuggestionsNoIntents', async function() { + const mockFeedback = this.createMockFeedback(); + const site = ` <div contenteditable="true" role="textbox" id="textbox"> <p>Start</p> <span>I </span> @@ -1813,30 +1723,29 @@ }); </script> `; - this.runWithLoadedTree(site, async function(root) { - await this.focusFirstTextField(root); + const root = await this.runWithLoadedTree(site); + await this.focusFirstTextField(root); - mockFeedback.call(this.press(KeyCode.DOWN)) - .expectSpeech('I ') - // Move forward through line. + mockFeedback.call(this.press(KeyCode.DOWN)) + .expectSpeech('I ') + // Move forward through line. - // This first right arrow is allowed to be processed by the content - // editable. - .call(this.press(KeyCode.RIGHT, {ctrl: true})) - .expectSpeech('I') + // This first right arrow is allowed to be processed by the content + // editable. + .call(this.press(KeyCode.RIGHT, {ctrl: true})) + .expectSpeech('I') - // This next right is swallowed by the content editable mimicking custom - // rich editors. It manually moves selection (and looses intent data). - // We infer it by getting a command mapped for a raw control right arrow - // key. - .call(doCmd('nativeNextWord')) - .call(this.press(KeyCode.RIGHT, {ctrl: true})) - .expectSpeech('Suggest', 'Username', 'Insert', 'was', 'Insert end') - .replay(); - }); -}); + // This next right is swallowed by the content editable mimicking + // custom rich editors. It manually moves selection (and looses intent + // data). We infer it by getting a command mapped for a raw control + // right arrow key. + .call(doCmd('nativeNextWord')) + .call(this.press(KeyCode.RIGHT, {ctrl: true})) + .expectSpeech('Suggest', 'Username', 'Insert', 'was', 'Insert end') + .replay(); + }); -TEST_F('ChromeVoxEditingTest', 'Separator', function() { +TEST_F('ChromeVoxEditingTest', 'Separator', async function() { // In the past, an ARIA leaf role would cause subtree content to be removed. // However, the new decision is to not remove any content the user might // interact with. @@ -1849,32 +1758,31 @@ <p><span>World</span></p> </div> `; - this.runWithLoadedTree(site, async function(root) { - await this.focusFirstTextField(root); + const root = await this.runWithLoadedTree(site); + await this.focusFirstTextField(root); - mockFeedback.call(this.press(KeyCode.DOWN)) - .expectSpeech('Hello') - .call(this.press(KeyCode.DOWN)) - .expectSpeech('-', 'Separator') - .call(this.press(KeyCode.DOWN)) - .expectSpeech('World') + mockFeedback.call(this.press(KeyCode.DOWN)) + .expectSpeech('Hello') + .call(this.press(KeyCode.DOWN)) + .expectSpeech('-', 'Separator') + .call(this.press(KeyCode.DOWN)) + .expectSpeech('World') - .call(this.press(KeyCode.LEFT)) - .expectNextSpeechUtteranceIsNot('\n') - // This reads the entire line (just one character). - .expectSpeech('-', 'Separator') + .call(this.press(KeyCode.LEFT)) + .expectNextSpeechUtteranceIsNot('\n') + // This reads the entire line (just one character). + .expectSpeech('-', 'Separator') - .call(this.press(KeyCode.LEFT)) - // This reads the single character. - .expectSpeech('-') + .call(this.press(KeyCode.LEFT)) + // This reads the single character. + .expectSpeech('-') - .call(this.press(KeyCode.LEFT)) - // Notice this reads the entire line which is generally undesirable - // except for special cases like this. - .expectSpeech('Hello') + .call(this.press(KeyCode.LEFT)) + // Notice this reads the entire line which is generally undesirable + // except for special cases like this. + .expectSpeech('Hello') - .replay(); - }); + .replay(); }); // Test for the issue in crbug.com/1203840. This case was causing an infinite @@ -1882,7 +1790,8 @@ // workaround potential infinite loops correctly, and should be removed once the // proper fix is implemented in blink. TEST_F( - 'ChromeVoxEditingTest', 'EditableLineInfiniteLoopWorkaround', function() { + 'ChromeVoxEditingTest', 'EditableLineInfiniteLoopWorkaround', + async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div contenteditable="true" role="textbox"> @@ -1900,171 +1809,165 @@ <span>End</span> </div> `; - this.runWithLoadedTree(site, async function(root) { - await this.focusFirstTextField(root); + const root = await this.runWithLoadedTree(site); + await this.focusFirstTextField(root); - mockFeedback.call(this.press(KeyCode.DOWN)) - .expectSpeech('This is a test') - .call(this.press(KeyCode.DOWN)) - .expectSpeech('End') - .replay(); - }); + mockFeedback.call(this.press(KeyCode.DOWN)) + .expectSpeech('This is a test') + .call(this.press(KeyCode.DOWN)) + .expectSpeech('End') + .replay(); }); TEST_F( 'ChromeVoxEditingTest', 'TextEditHandlerCreatesAutomationEditable', - function() { + async function() { const site = ` <input type="text"></input> `; - this.runWithLoadedTree(site, async function(root) { - const input = await this.focusFirstTextField(root); + const root = await this.runWithLoadedTree(site); + const input = await this.focusFirstTextField(root); - // The initial real input is a simple non-rich text field. - assertEquals( - 'AutomationEditableText', - DesktopAutomationInterface.instance.textEditHandler.editableText_ - .constructor.name, - 'Real text field was not a non-rich text.'); + // The initial real input is a simple non-rich text field. + assertEquals( + 'AutomationEditableText', + DesktopAutomationInterface.instance.textEditHandler.editableText_ + .constructor.name, + 'Real text field was not a non-rich text.'); - // Now, we will override some properties directly to - // ensure we don't depend on Blink's behaviors which can change based - // on style. We want to work directly with only the automation api - // itself to ensure we have full coverage. - let htmlAttributes = {}; - let htmlTag = ''; - let state = {}; - Object.defineProperty( - input, 'htmlAttributes', {get: () => htmlAttributes}); - Object.defineProperty(input, 'htmlTag', {get: () => htmlTag}); - Object.defineProperty(input, 'state', {get: () => state}); + // Now, we will override some properties directly to + // ensure we don't depend on Blink's behaviors which can change based + // on style. We want to work directly with only the automation api + // itself to ensure we have full coverage. + let htmlAttributes = {}; + let htmlTag = ''; + let state = {}; + Object.defineProperty( + input, 'htmlAttributes', {get: () => htmlAttributes}); + Object.defineProperty(input, 'htmlTag', {get: () => htmlTag}); + Object.defineProperty(input, 'state', {get: () => state}); - // An invalid editable. - let didThrow = false; - let handler; - try { - handler = new TextEditHandler(input); - } catch (e) { - didThrow = true; - } - assertTrue(didThrow, 'Non-editable created editable handler.'); - - // A simple editable. - htmlAttributes = {}; - htmlTag = ''; - state = {editable: true}; + // An invalid editable. + let didThrow = false; + let handler; + try { handler = new TextEditHandler(input); - assertEquals( - 'AutomationEditableText', handler.editableText_.constructor.name, - 'Incorrect backing object for simple editable.'); + } catch (e) { + didThrow = true; + } + assertTrue(didThrow, 'Non-editable created editable handler.'); - // A non-rich editable via multiline. - htmlAttributes = {}; - htmlTag = ''; - state = {editable: true, multiline: true}; - handler = new TextEditHandler(input); - assertEquals( - 'AutomationEditableText', handler.editableText_.constructor.name, - 'Incorrect object for multiline editable.'); + // A simple editable. + htmlAttributes = {}; + htmlTag = ''; + state = {editable: true}; + handler = new TextEditHandler(input); + assertEquals( + 'AutomationEditableText', handler.editableText_.constructor.name, + 'Incorrect backing object for simple editable.'); - // A rich editable via textarea tag. - htmlAttributes = {}; - htmlTag = 'textarea'; - state = {editable: true}; - handler = new TextEditHandler(input); - assertEquals( - 'AutomationRichEditableText', - handler.editableText_.constructor.name, - 'Incorrect object for textarea html tag.'); + // A non-rich editable via multiline. + htmlAttributes = {}; + htmlTag = ''; + state = {editable: true, multiline: true}; + handler = new TextEditHandler(input); + assertEquals( + 'AutomationEditableText', handler.editableText_.constructor.name, + 'Incorrect object for multiline editable.'); - // A rich editable via state. - htmlAttributes = {}; - htmlTag = ''; - state = {editable: true, richlyEditable: true}; - handler = new TextEditHandler(input); - assertEquals( - 'AutomationRichEditableText', - handler.editableText_.constructor.name, - 'Incorrect object for richly editable state.'); + // A rich editable via textarea tag. + htmlAttributes = {}; + htmlTag = 'textarea'; + state = {editable: true}; + handler = new TextEditHandler(input); + assertEquals( + 'AutomationRichEditableText', handler.editableText_.constructor.name, + 'Incorrect object for textarea html tag.'); - // A rich editable via contenteditable. (aka <div contenteditable>). - htmlAttributes = {contenteditable: ''}; - htmlTag = ''; - state = {editable: true}; - handler = new TextEditHandler(input); - assertEquals( - 'AutomationRichEditableText', - handler.editableText_.constructor.name, - 'Incorrect object for content editable.'); + // A rich editable via state. + htmlAttributes = {}; + htmlTag = ''; + state = {editable: true, richlyEditable: true}; + handler = new TextEditHandler(input); + assertEquals( + 'AutomationRichEditableText', handler.editableText_.constructor.name, + 'Incorrect object for richly editable state.'); - // A rich editable via contenteditable. (aka <div - // contenteditable=true>). - htmlAttributes = {contenteditable: 'true'}; - htmlTag = ''; - state = {editable: true}; - handler = new TextEditHandler(input); - assertEquals( - 'AutomationRichEditableText', - handler.editableText_.constructor.name, - 'Incorrect object for content editable true.'); + // A rich editable via contenteditable. (aka <div contenteditable>). + htmlAttributes = {contenteditable: ''}; + htmlTag = ''; + state = {editable: true}; + handler = new TextEditHandler(input); + assertEquals( + 'AutomationRichEditableText', handler.editableText_.constructor.name, + 'Incorrect object for content editable.'); - // Note that it is not possible to have <div - // contenteditable="someInvalidValue"> or <div contenteditable=false> - // and still have the div expose editable state, so we never check - // that. - }); + // A rich editable via contenteditable. (aka <div + // contenteditable=true>). + htmlAttributes = {contenteditable: 'true'}; + htmlTag = ''; + state = {editable: true}; + handler = new TextEditHandler(input); + assertEquals( + 'AutomationRichEditableText', handler.editableText_.constructor.name, + 'Incorrect object for content editable true.'); + + // Note that it is not possible to have <div + // contenteditable="someInvalidValue"> or <div contenteditable=false> + // and still have the div expose editable state, so we never check + // that. }); // TODO(https://crbug.com/1254742): flakes due to underlying bug with // accessibility intents. -TEST_F('ChromeVoxEditingTest', 'DISABLED_ParagraphNavigation', function() { - const mockFeedback = this.createMockFeedback(); - const site = ` +TEST_F( + 'ChromeVoxEditingTest', 'DISABLED_ParagraphNavigation', async function() { + const mockFeedback = this.createMockFeedback(); + const site = ` <div contenteditable role="textbox" style='max-width: 5px; overflow-wrap: normal'> <p>This is paragraph number one.</p> <p>Another paragraph, number two.</p> </div> `; - this.runWithLoadedTree(site, async function(root) { - await this.focusFirstTextField(root); + const root = await this.runWithLoadedTree(site); + await this.focusFirstTextField(root); - // We bind specific callbacks to send keys here because EventGenerator - // (which sends key down and up) does not seem to work with these - // shortcuts. - const ctrlDown = () => chrome.accessibilityPrivate.sendSyntheticKeyEvent({ - type: chrome.accessibilityPrivate.SyntheticKeyboardEventType.KEYDOWN, - keyCode: KeyCode.DOWN, - modifiers: {ctrl: true} - }); - const ctrlUp = () => chrome.accessibilityPrivate.sendSyntheticKeyEvent({ - type: chrome.accessibilityPrivate.SyntheticKeyboardEventType.KEYDOWN, - keyCode: KeyCode.UP, - modifiers: {ctrl: true} - }); + // We bind specific callbacks to send keys here because EventGenerator + // (which sends key down and up) does not seem to work with these + // shortcuts. + const ctrlDown = () => chrome.accessibilityPrivate.sendSyntheticKeyEvent({ + type: chrome.accessibilityPrivate.SyntheticKeyboardEventType.KEYDOWN, + keyCode: KeyCode.DOWN, + modifiers: {ctrl: true} + }); + const ctrlUp = () => chrome.accessibilityPrivate.sendSyntheticKeyEvent({ + type: chrome.accessibilityPrivate.SyntheticKeyboardEventType.KEYDOWN, + keyCode: KeyCode.UP, + modifiers: {ctrl: true} + }); - mockFeedback.expectSpeech('Text area') - .call(ctrlDown) - .expectSpeech('Another paragraph, number two.') - .call(this.press(KeyCode.DOWN)) - .expectSpeech('paragraph, ') - .call(ctrlUp) - .expectSpeech('This is paragraph number one.') - .call(this.press(KeyCode.UP)) - .expectSpeech('number ') - .call(this.press(KeyCode.UP)) - .expectSpeech('paragraph ') - .call(ctrlDown) - .expectSpeech('Another paragraph, number two.') - .call(this.press(KeyCode.DOWN)) - .expectSpeech('paragraph, ') - .replay(); - }); -}); + mockFeedback.expectSpeech('Text area') + .call(ctrlDown) + .expectSpeech('Another paragraph, number two.') + .call(this.press(KeyCode.DOWN)) + .expectSpeech('paragraph, ') + .call(ctrlUp) + .expectSpeech('This is paragraph number one.') + .call(this.press(KeyCode.UP)) + .expectSpeech('number ') + .call(this.press(KeyCode.UP)) + .expectSpeech('paragraph ') + .call(ctrlDown) + .expectSpeech('Another paragraph, number two.') + .call(this.press(KeyCode.DOWN)) + .expectSpeech('paragraph, ') + .replay(); + }); TEST_F( 'ChromeVoxEditingTest', 'StartAndEndOfOutputStopAtEditableRoot', - function() { + async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div role="article"> @@ -2073,20 +1976,19 @@ </div> </div> `; - this.runWithLoadedTree(site, async function(root) { - await this.focusFirstTextField(root); - mockFeedback.expectSpeech('Text area') - .call(this.press(KeyCode.DOWN)) - .expectSpeech('world') - .call(this.press(KeyCode.UP)) - .expectNextSpeechUtteranceIsNot('Article') - .expectNextSpeechUtteranceIsNot('Article end') - .expectSpeech('hello') - .replay(); - }); + const root = await this.runWithLoadedTree(site); + await this.focusFirstTextField(root); + mockFeedback.expectSpeech('Text area') + .call(this.press(KeyCode.DOWN)) + .expectSpeech('world') + .call(this.press(KeyCode.UP)) + .expectNextSpeechUtteranceIsNot('Article') + .expectNextSpeechUtteranceIsNot('Article end') + .expectSpeech('hello') + .replay(); }); -TEST_F('ChromeVoxEditingTest', 'TableNavigation', function() { +TEST_F('ChromeVoxEditingTest', 'TableNavigation', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div contenteditable role="textbox" tabindex=0> @@ -2096,161 +1998,174 @@ </table> </div> `; - this.runWithLoadedTree(site, async function(root) { - await this.focusFirstTextField(root); + const root = await this.runWithLoadedTree(site); + await this.focusFirstTextField(root); - mockFeedback.expectSpeech('Text area') - .call(this.press(KeyCode.RIGHT)) - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('e') - .call(doCmd('nextCol')) - .expectSpeech('goodbye') - .expectSpeech('row 1 column 2') - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('o') - .call(doCmd('previousCol')) - .expectSpeech('hello', 'world') - .expectSpeech('row 1 column 1') - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('e') - .replay(); - }); + mockFeedback.expectSpeech('Text area') + .call(this.press(KeyCode.RIGHT)) + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('e') + .call(doCmd('nextCol')) + .expectSpeech('goodbye') + .expectSpeech('row 1 column 2') + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('o') + .call(doCmd('previousCol')) + .expectSpeech('hello', 'world') + .expectSpeech('row 1 column 1') + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('e') + .replay(); }); TEST_F( - 'ChromeVoxEditingTest', 'InputTextBrailleContractions', function() { + 'ChromeVoxEditingTest', 'InputTextBrailleContractions', async function() { const site = ` <input type=text value="about that"></input> `; - this.runWithLoadedTree(site, async function(root) { - await this.focusFirstTextField(root); + const root = await this.runWithLoadedTree(site); + await this.focusFirstTextField(root); - // In case LibLouis takes a while to load. - if (!ChromeVox.braille.displayManager_.translatorManager_.liblouis_ - .isLoaded()) { - await new Promise(r => { - ChromeVox.braille.displayManager_.translatorManager_.liblouis_ - .onInstanceLoad_ = r; - }); - } + // In case LibLouis takes a while to load. + if (!ChromeVox.braille.displayManager_.translatorManager_.liblouis_ + .isLoaded()) { + await new Promise(r => { + ChromeVox.braille.displayManager_.translatorManager_.liblouis_ + .onInstanceLoad_ = r; + }); + } - // Fake an available display. - ChromeVox.braille.displayManager_.refreshDisplayState_( - {available: true, textRowCount: 1, textColumnCount: 40}); + // Fake an available display. + ChromeVox.braille.displayManager_.refreshDisplayState_( + {available: true, textRowCount: 1, textColumnCount: 40}); - // Set braille to use 6-dot braille (which is defaulted to UEB grade 2 - // contracted braille). - localStorage['brailleTable'] = 'en-ueb-g2'; + // Set braille to use 6-dot braille (which is defaulted to UEB grade 2 + // contracted braille). + localStorage['brailleTable'] = 'en-ueb-g2'; - // Wait for it to be fully refreshed (liblouis loads the new tables, our - // translators are re-created). - await BrailleBackground.getInstance() + // Wait for it to be fully refreshed (liblouis loads the new tables, our + // translators are re-created). + await BrailleBackground.getInstance() + .getTranslatorManager() + .loadTablesForTest(); + + // Fake an available display. + ChromeVox.braille.displayManager_.refreshDisplayState_( + {available: true, textRowCount: 1, textColumnCount: 40}); + + // Set braille to use 6-dot braille (which is defaulted to UEB grade 2 + // contracted braille). + localStorage['brailleTable'] = 'en-ueb-g2'; + BrailleBackground.getInstance().getTranslatorManager().refresh( + localStorage['brailleTable']); + // Wait for it to be fully refreshed (liblouis loads the new tables, our + // translators are re-created). + await new Promise(r => { + BrailleBackground.getInstance() .getTranslatorManager() - .loadTablesForTest(); - - async function waitForBrailleDots(expectedDots) { - return new Promise(r => { - chrome.brailleDisplayPrivate.writeDots = (dotsBuffer) => { - const view = new Uint8Array(dotsBuffer); - const dots = new Array(view.length); - view.forEach((item, index) => dots[index] = item.toString(2)); - if (expectedDots.toString() === dots.toString()) { - r(); - } - }; - }); - } - - this.press(KeyCode.END)(); - - // This test intentionally leaves the raw binary encoding for braille. - // Dots are read from right to left. - await waitForBrailleDots([ - // 'ab' is 'about' in UEB Grade 2. - 1 /* a */, 11 /* b */, - - 0 /* space */, - - 11110 /* t */, 10011 /* h */, 1 /* a */, 11110 /* t */, - - 11000000 /* cursor _ */, - - 101011 /* ed contraction */ - ]); - - this.press(KeyCode.HOME)(); - await waitForBrailleDots([ - 11000001 /* a with a cursor _*/, 11 /* b */, 10101 /* o */, - 100101 /* u */, 11110 /* t */, - - 0 /* space */, - - // 't' by itself is contracted as 'that'. - 11110 /* t */, - - 0 /* space */, - - 101011 /* ed contraction */ - ]); + .addChangeListener(r); }); + + async function waitForBrailleDots(expectedDots) { + return new Promise(r => { + chrome.brailleDisplayPrivate.writeDots = (dotsBuffer) => { + const view = new Uint8Array(dotsBuffer); + const dots = new Array(view.length); + view.forEach((item, index) => dots[index] = item.toString(2)); + if (expectedDots.toString() === dots.toString()) { + r(); + } + }; + }); + } + + this.press(KeyCode.END)(); + + // This test intentionally leaves the raw binary encoding for braille. + // Dots are read from right to left. + await waitForBrailleDots([ + // 'ab' is 'about' in UEB Grade 2. + 1 /* a */, 11 /* b */, + + 0 /* space */, + + 11110 /* t */, 10011 /* h */, 1 /* a */, 11110 /* t */, + + 11000000 /* cursor _ */, + + 101011 /* ed contraction */ + ]); + + this.press(KeyCode.HOME)(); + await waitForBrailleDots([ + 11000001 /* a with a cursor _*/, 11 /* b */, 10101 /* o */, + 100101 /* u */, 11110 /* t */, + + 0 /* space */, + + // 't' by itself is contracted as 'that'. + 11110 /* t */, + + 0 /* space */, + + 101011 /* ed contraction */ + ]); }); -TEST_F('ChromeVoxEditingTest', 'ContextMenus', function() { +TEST_F('ChromeVoxEditingTest', 'ContextMenus', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <textarea>abc</textarea> `; - this.runWithLoadedTree(site, async function(root) { - await this.focusFirstTextField(root); + const root = await this.runWithLoadedTree(site); + await this.focusFirstTextField(root); - const textField = root.find({role: RoleType.TEXT_FIELD}); - mockFeedback.expectSpeech('Text area') - .call(() => { - textField.setSelection(0, 2); - }) - .expectSpeech('ab', 'selected') - .call(doCmd('contextMenu')) - .expectSpeech(' menu opened') - .call(this.press(KeyCode.ESCAPE)) - .expectSpeech('ab', 'selected') - .replay(); - }); + const textField = root.find({role: RoleType.TEXT_FIELD}); + mockFeedback.expectSpeech('Text area') + .call(() => { + textField.setSelection(0, 2); + }) + .expectSpeech('ab', 'selected') + .call(doCmd('contextMenu')) + .expectSpeech(' menu opened') + .call(this.press(KeyCode.ESCAPE)) + .expectSpeech('ab', 'selected') + .replay(); }); -TEST_F('ChromeVoxEditingTest', 'NativeCharWordCommands', function() { +TEST_F('ChromeVoxEditingTest', 'NativeCharWordCommands', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>start</p> <div role="textbox" contenteditable>This is a test</div> `; - this.runWithLoadedTree(site, async function(root) { - await this.focusFirstTextField(root); + const root = await this.runWithLoadedTree(site); + await this.focusFirstTextField(root); - const textField = root.find({role: RoleType.TEXT_FIELD}); - mockFeedback.expectSpeech('Text area') - .call(this.press(KeyCode.HOME, {ctrl: true})) - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('h') - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('i') - .call(this.press(KeyCode.LEFT)) - .expectSpeech('h') + const textField = root.find({role: RoleType.TEXT_FIELD}); + mockFeedback.expectSpeech('Text area') + .call(this.press(KeyCode.HOME, {ctrl: true})) + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('h') + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('i') + .call(this.press(KeyCode.LEFT)) + .expectSpeech('h') - .call(this.press(KeyCode.RIGHT, {ctrl: true})) - .expectSpeech('This') - .call(this.press(KeyCode.RIGHT, {ctrl: true})) - .expectSpeech('is') - .call(this.press(KeyCode.LEFT, {ctrl: true})) - .expectSpeech('is') - .call(this.press(KeyCode.LEFT, {ctrl: true})) - .expectSpeech('This') + .call(this.press(KeyCode.RIGHT, {ctrl: true})) + .expectSpeech('This') + .call(this.press(KeyCode.RIGHT, {ctrl: true})) + .expectSpeech('is') + .call(this.press(KeyCode.LEFT, {ctrl: true})) + .expectSpeech('is') + .call(this.press(KeyCode.LEFT, {ctrl: true})) + .expectSpeech('This') - .replay(); - }); + .replay(); }); -TEST_F('ChromeVoxEditingTest', 'TablesWithEmptyCells', function() { +TEST_F('ChromeVoxEditingTest', 'TablesWithEmptyCells', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div contenteditable="true" role="textbox"> @@ -2270,40 +2185,40 @@ </table> </div><div></div></div> `; - this.runWithLoadedTree(site, async function(root) { - await this.focusFirstTextField(root); + const root = await this.runWithLoadedTree(site); + await this.focusFirstTextField(root); - const textField = root.find({role: RoleType.TEXT_FIELD}); - mockFeedback.expectSpeech('Text area') - .call(this.press(KeyCode.HOME, {ctrl: true})) - .call(this.press(KeyCode.RIGHT)) - .call(this.press(KeyCode.RIGHT)) - .call(this.press(KeyCode.RIGHT)) - // This first cell is on a new line. - .expectSpeech('\n', 'row 1 column 1') - .call(this.press(KeyCode.RIGHT)) - // Non-breaking spaces (\u00a0) get preprocessed later by TtsBackground - // to ' '. This comes as part of speak line output in - // AutomationRichEditableText. - .expectSpeech('\u00a0') - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('\u00a0', 'row 1 column 2') - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('\u00a0') - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('\u00a0', 'row 2 column 1') - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('\u00a0') - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('\u00a0', 'row 2 column 2') + const textField = root.find({role: RoleType.TEXT_FIELD}); + mockFeedback.expectSpeech('Text area') + .call(this.press(KeyCode.HOME, {ctrl: true})) + .call(this.press(KeyCode.RIGHT)) + .call(this.press(KeyCode.RIGHT)) + .call(this.press(KeyCode.RIGHT)) + // This first cell is on a new line. + .expectSpeech('\n', 'row 1 column 1') + .call(this.press(KeyCode.RIGHT)) + // Non-breaking spaces (\u00a0) get preprocessed later by TtsBackground + // to ' '. This comes as part of speak line output in + // AutomationRichEditableText. + .expectSpeech('\u00a0') + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('\u00a0', 'row 1 column 2') + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('\u00a0') + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('\u00a0', 'row 2 column 1') + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('\u00a0') + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('\u00a0', 'row 2 column 2') - .replay(); - }); + .replay(); }); -TEST_F('ChromeVoxEditingTest', 'NonbreakingSpaceNewLineOrSpace', function() { - const mockFeedback = this.createMockFeedback(); - const site = ` +TEST_F( + 'ChromeVoxEditingTest', 'NonbreakingSpaceNewLineOrSpace', async function() { + const mockFeedback = this.createMockFeedback(); + const site = ` <div contenteditable="true" role="textbox"> <p>first line</p> <div><span> </span></div> @@ -2312,68 +2227,67 @@ <p>last line</p> </div> `; - this.runWithLoadedTree(site, async function(root) { - await this.focusFirstTextField(root); + const root = await this.runWithLoadedTree(site); + await this.focusFirstTextField(root); - const textField = root.find({role: RoleType.TEXT_FIELD}); - mockFeedback.expectSpeech('Text area') - .call(this.press(KeyCode.HOME, {ctrl: true})) - .call(this.press(KeyCode.DOWN)) - .expectSpeech('\n') - .call(this.press(KeyCode.DOWN)) - .expectSpeech('\n') - .call(this.press(KeyCode.DOWN)) - .expectSpeech('\n') - .call(this.press(KeyCode.DOWN)) - .expectSpeech('last line') + const textField = root.find({role: RoleType.TEXT_FIELD}); + mockFeedback.expectSpeech('Text area') + .call(this.press(KeyCode.HOME, {ctrl: true})) + .call(this.press(KeyCode.DOWN)) + .expectSpeech('\n') + .call(this.press(KeyCode.DOWN)) + .expectSpeech('\n') + .call(this.press(KeyCode.DOWN)) + .expectSpeech('\n') + .call(this.press(KeyCode.DOWN)) + .expectSpeech('last line') - .call(this.press(KeyCode.UP)) - .expectSpeech('\n') - .call(this.press(KeyCode.UP)) - .expectSpeech('\n') - .call(this.press(KeyCode.UP)) - .expectSpeech('\n') - .call(this.press(KeyCode.UP)) - .expectSpeech('first line') + .call(this.press(KeyCode.UP)) + .expectSpeech('\n') + .call(this.press(KeyCode.UP)) + .expectSpeech('\n') + .call(this.press(KeyCode.UP)) + .expectSpeech('\n') + .call(this.press(KeyCode.UP)) + .expectSpeech('first line') - .call(this.press(KeyCode.DOWN)) - .expectSpeech('\n') + .call(this.press(KeyCode.DOWN)) + .expectSpeech('\n') - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('\u00a0') - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('\u00a0') - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('\u00a0') - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('\u00a0') - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('\u00a0') - .call(this.press(KeyCode.RIGHT)) - .expectSpeech('l') + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('\u00a0') + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('\u00a0') + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('\u00a0') + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('\u00a0') + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('\u00a0') + .call(this.press(KeyCode.RIGHT)) + .expectSpeech('l') - .call(this.press(KeyCode.LEFT)) - .expectSpeech('\u00a0') - .call(this.press(KeyCode.LEFT)) - .expectSpeech('\u00a0') - .call(this.press(KeyCode.LEFT)) - .expectSpeech('\u00a0') - .call(this.press(KeyCode.LEFT)) - .expectSpeech('\u00a0') - .call(this.press(KeyCode.LEFT)) - .expectSpeech('\u00a0') - .call(this.press(KeyCode.LEFT)) - .expectSpeech('\u00a0') - .call(this.press(KeyCode.LEFT)) - .expectSpeech('\n') - .call(this.press(KeyCode.LEFT)) - .expectSpeech('e') + .call(this.press(KeyCode.LEFT)) + .expectSpeech('\u00a0') + .call(this.press(KeyCode.LEFT)) + .expectSpeech('\u00a0') + .call(this.press(KeyCode.LEFT)) + .expectSpeech('\u00a0') + .call(this.press(KeyCode.LEFT)) + .expectSpeech('\u00a0') + .call(this.press(KeyCode.LEFT)) + .expectSpeech('\u00a0') + .call(this.press(KeyCode.LEFT)) + .expectSpeech('\u00a0') + .call(this.press(KeyCode.LEFT)) + .expectSpeech('\n') + .call(this.press(KeyCode.LEFT)) + .expectSpeech('e') - .replay(); - }); -}); + .replay(); + }); -TEST_F('ChromeVoxEditingTest', 'JumpCommandsSyncSelection', function() { +TEST_F('ChromeVoxEditingTest', 'JumpCommandsSyncSelection', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <div contenteditable="true" role="textbox"> @@ -2383,28 +2297,27 @@ <table border=1><r><td>fifth</td></tr></table> </div> `; - this.runWithLoadedTree(site, async function(root) { - await this.focusFirstTextField(root); + const root = await this.runWithLoadedTree(site); + await this.focusFirstTextField(root); - const textField = root.find({role: RoleType.TEXT_FIELD}); - mockFeedback.expectSpeech('Text area') - .call(doCmd('nextTable')) - .expectSpeech('fifth', 'row 1 column 1', 'Table , 1 by 1') + const textField = root.find({role: RoleType.TEXT_FIELD}); + mockFeedback.expectSpeech('Text area') + .call(doCmd('nextTable')) + .expectSpeech('fifth', 'row 1 column 1', 'Table , 1 by 1') - // Verifies selection is where we expect. - .call(this.press(KeyCode.RIGHT, {shift: true, ctrl: true})) - .expectSpeech('fifth', 'row 1 column 1', 'Table , 1 by 1', 'selected') + // Verifies selection is where we expect. + .call(this.press(KeyCode.RIGHT, {shift: true, ctrl: true})) + .expectSpeech('fifth', 'row 1 column 1', 'Table , 1 by 1', 'selected') - .call(doCmd('previousHeading')) - .expectSpeech('second', 'Heading 1') - .call(this.press(KeyCode.RIGHT, {shift: true, ctrl: true})) - .expectSpeech('second', 'Heading 1', 'selected') + .call(doCmd('previousHeading')) + .expectSpeech('second', 'Heading 1') + .call(this.press(KeyCode.RIGHT, {shift: true, ctrl: true})) + .expectSpeech('second', 'Heading 1', 'selected') - .call(doCmd('nextLink')) - .expectSpeech('fourth', 'Internal link') - .call(this.press(KeyCode.RIGHT, {shift: true, ctrl: true})) - .expectSpeech('fourth', 'Link', 'selected') + .call(doCmd('nextLink')) + .expectSpeech('fourth', 'Internal link') + .call(this.press(KeyCode.RIGHT, {shift: true, ctrl: true})) + .expectSpeech('fourth', 'Link', 'selected') - .replay(); - }); + .replay(); });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/keyboard_handler_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/keyboard_handler_test.js index 5df64e9..110ae7ef 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/keyboard_handler_test.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/keyboard_handler_test.js
@@ -22,236 +22,240 @@ TEST_F( 'ChromeVoxBackgroundKeyboardHandlerTest', 'SearchGetsPassedThrough', - function() { - this.runWithLoadedTree('<p>test</p>', function() { - // A Search keydown gets eaten. - const searchDown = {}; - searchDown.preventDefault = this.newCallback(); - searchDown.stopPropagation = this.newCallback(); - searchDown.metaKey = true; - keyboardHandler.onKeyDown(searchDown); - assertEquals(1, keyboardHandler.eatenKeyDowns_.size); + async function() { + await this.runWithLoadedTree('<p>test</p>'); + // A Search keydown gets eaten. + const searchDown = {}; + searchDown.preventDefault = this.newCallback(); + searchDown.stopPropagation = this.newCallback(); + searchDown.metaKey = true; + keyboardHandler.onKeyDown(searchDown); + assertEquals(1, keyboardHandler.eatenKeyDowns_.size); - // A Search keydown does not get eaten when there's no range and there - // was no previous range. TalkBack is handled elsewhere. - ChromeVoxState.instance.setCurrentRange(null); - ChromeVoxState.instance.previousRange_ = null; - const searchDown2 = {}; - searchDown2.metaKey = true; - keyboardHandler.onKeyDown(searchDown2); - assertEquals(1, keyboardHandler.eatenKeyDowns_.size); - }); + // A Search keydown does not get eaten when there's no range and there + // was no previous range. TalkBack is handled elsewhere. + ChromeVoxState.instance.setCurrentRange(null); + ChromeVoxState.instance.previousRange_ = null; + const searchDown2 = {}; + searchDown2.metaKey = true; + keyboardHandler.onKeyDown(searchDown2); + assertEquals(1, keyboardHandler.eatenKeyDowns_.size); }); -TEST_F('ChromeVoxBackgroundKeyboardHandlerTest', 'PassThroughMode', function() { - this.runWithLoadedTree('<p>test</p>', function() { - assertUndefined(ChromeVox.passThroughMode); - assertEquals('no_pass_through', keyboardHandler.passThroughState_); - assertEquals(0, keyboardHandler.eatenKeyDowns_.size); - assertEquals(0, keyboardHandler.passedThroughKeyDowns_.size); +TEST_F( + 'ChromeVoxBackgroundKeyboardHandlerTest', 'PassThroughMode', + async function() { + await this.runWithLoadedTree('<p>test</p>'); + assertUndefined(ChromeVox.passThroughMode); + assertEquals('no_pass_through', keyboardHandler.passThroughState_); + assertEquals(0, keyboardHandler.eatenKeyDowns_.size); + assertEquals(0, keyboardHandler.passedThroughKeyDowns_.size); - // Send the pass through command: Search+Shift+Escape. - const search = - TestUtils.createMockKeyEvent(KeyCode.SEARCH, {metaKey: true}); - keyboardHandler.onKeyDown(search); - assertEquals(1, keyboardHandler.eatenKeyDowns_.size); - assertEquals(0, keyboardHandler.passedThroughKeyDowns_.size); - assertEquals('no_pass_through', keyboardHandler.passThroughState_); - assertUndefined(ChromeVox.passThroughMode); + // Send the pass through command: Search+Shift+Escape. + const search = + TestUtils.createMockKeyEvent(KeyCode.SEARCH, {metaKey: true}); + keyboardHandler.onKeyDown(search); + assertEquals(1, keyboardHandler.eatenKeyDowns_.size); + assertEquals(0, keyboardHandler.passedThroughKeyDowns_.size); + assertEquals('no_pass_through', keyboardHandler.passThroughState_); + assertUndefined(ChromeVox.passThroughMode); - const searchShift = TestUtils.createMockKeyEvent( - KeyCode.SHIFT, {metaKey: true, shiftKey: true}); - keyboardHandler.onKeyDown(searchShift); - assertEquals(2, keyboardHandler.eatenKeyDowns_.size); - assertEquals(0, keyboardHandler.passedThroughKeyDowns_.size); - assertEquals('no_pass_through', keyboardHandler.passThroughState_); - assertUndefined(ChromeVox.passThroughMode); + const searchShift = TestUtils.createMockKeyEvent( + KeyCode.SHIFT, {metaKey: true, shiftKey: true}); + keyboardHandler.onKeyDown(searchShift); + assertEquals(2, keyboardHandler.eatenKeyDowns_.size); + assertEquals(0, keyboardHandler.passedThroughKeyDowns_.size); + assertEquals('no_pass_through', keyboardHandler.passThroughState_); + assertUndefined(ChromeVox.passThroughMode); - const searchShiftEsc = TestUtils.createMockKeyEvent( - KeyCode.ESCAPE, {metaKey: true, shiftKey: true}); - keyboardHandler.onKeyDown(searchShiftEsc); - assertEquals(3, keyboardHandler.eatenKeyDowns_.size); - assertEquals(0, keyboardHandler.passedThroughKeyDowns_.size); - assertEquals( - 'pending_pass_through_keyups', keyboardHandler.passThroughState_); - assertTrue(ChromeVox.passThroughMode); + const searchShiftEsc = TestUtils.createMockKeyEvent( + KeyCode.ESCAPE, {metaKey: true, shiftKey: true}); + keyboardHandler.onKeyDown(searchShiftEsc); + assertEquals(3, keyboardHandler.eatenKeyDowns_.size); + assertEquals(0, keyboardHandler.passedThroughKeyDowns_.size); + assertEquals( + 'pending_pass_through_keyups', keyboardHandler.passThroughState_); + assertTrue(ChromeVox.passThroughMode); - keyboardHandler.onKeyUp(searchShiftEsc); - assertEquals(2, keyboardHandler.eatenKeyDowns_.size); - assertEquals(0, keyboardHandler.passedThroughKeyDowns_.size); - assertEquals( - 'pending_pass_through_keyups', keyboardHandler.passThroughState_); - assertTrue(ChromeVox.passThroughMode); + keyboardHandler.onKeyUp(searchShiftEsc); + assertEquals(2, keyboardHandler.eatenKeyDowns_.size); + assertEquals(0, keyboardHandler.passedThroughKeyDowns_.size); + assertEquals( + 'pending_pass_through_keyups', keyboardHandler.passThroughState_); + assertTrue(ChromeVox.passThroughMode); - keyboardHandler.onKeyUp(searchShift); - assertEquals(1, keyboardHandler.eatenKeyDowns_.size); - assertEquals(0, keyboardHandler.passedThroughKeyDowns_.size); - assertEquals( - 'pending_pass_through_keyups', keyboardHandler.passThroughState_); - assertTrue(ChromeVox.passThroughMode); + keyboardHandler.onKeyUp(searchShift); + assertEquals(1, keyboardHandler.eatenKeyDowns_.size); + assertEquals(0, keyboardHandler.passedThroughKeyDowns_.size); + assertEquals( + 'pending_pass_through_keyups', keyboardHandler.passThroughState_); + assertTrue(ChromeVox.passThroughMode); - keyboardHandler.onKeyUp(search); - assertEquals(0, keyboardHandler.eatenKeyDowns_.size); - assertEquals(0, keyboardHandler.passedThroughKeyDowns_.size); - assertEquals('pending_shortcut_keyups', keyboardHandler.passThroughState_); - assertTrue(ChromeVox.passThroughMode); + keyboardHandler.onKeyUp(search); + assertEquals(0, keyboardHandler.eatenKeyDowns_.size); + assertEquals(0, keyboardHandler.passedThroughKeyDowns_.size); + assertEquals( + 'pending_shortcut_keyups', keyboardHandler.passThroughState_); + assertTrue(ChromeVox.passThroughMode); - // Now, the next series of key downs should be passed through. - // Try Search+Ctrl+M. - keyboardHandler.onKeyDown(search); - assertEquals(0, keyboardHandler.eatenKeyDowns_.size); - assertEquals(1, keyboardHandler.passedThroughKeyDowns_.size); - assertEquals('pending_shortcut_keyups', keyboardHandler.passThroughState_); - assertTrue(ChromeVox.passThroughMode); + // Now, the next series of key downs should be passed through. + // Try Search+Ctrl+M. + keyboardHandler.onKeyDown(search); + assertEquals(0, keyboardHandler.eatenKeyDowns_.size); + assertEquals(1, keyboardHandler.passedThroughKeyDowns_.size); + assertEquals( + 'pending_shortcut_keyups', keyboardHandler.passThroughState_); + assertTrue(ChromeVox.passThroughMode); - const searchCtrl = TestUtils.createMockKeyEvent( - KeyCode.CONTROL, {metaKey: true, ctrlKey: true}); - keyboardHandler.onKeyDown(searchCtrl); - assertEquals(0, keyboardHandler.eatenKeyDowns_.size); - assertEquals(2, keyboardHandler.passedThroughKeyDowns_.size); - assertEquals('pending_shortcut_keyups', keyboardHandler.passThroughState_); - assertTrue(ChromeVox.passThroughMode); + const searchCtrl = TestUtils.createMockKeyEvent( + KeyCode.CONTROL, {metaKey: true, ctrlKey: true}); + keyboardHandler.onKeyDown(searchCtrl); + assertEquals(0, keyboardHandler.eatenKeyDowns_.size); + assertEquals(2, keyboardHandler.passedThroughKeyDowns_.size); + assertEquals( + 'pending_shortcut_keyups', keyboardHandler.passThroughState_); + assertTrue(ChromeVox.passThroughMode); - const searchCtrlM = - TestUtils.createMockKeyEvent(KeyCode.M, {metaKey: true, ctrlKey: true}); - keyboardHandler.onKeyDown(searchCtrlM); - assertEquals(0, keyboardHandler.eatenKeyDowns_.size); - assertEquals(3, keyboardHandler.passedThroughKeyDowns_.size); - assertEquals('pending_shortcut_keyups', keyboardHandler.passThroughState_); - assertTrue(ChromeVox.passThroughMode); + const searchCtrlM = TestUtils.createMockKeyEvent( + KeyCode.M, {metaKey: true, ctrlKey: true}); + keyboardHandler.onKeyDown(searchCtrlM); + assertEquals(0, keyboardHandler.eatenKeyDowns_.size); + assertEquals(3, keyboardHandler.passedThroughKeyDowns_.size); + assertEquals( + 'pending_shortcut_keyups', keyboardHandler.passThroughState_); + assertTrue(ChromeVox.passThroughMode); - keyboardHandler.onKeyUp(searchCtrlM); - assertEquals(0, keyboardHandler.eatenKeyDowns_.size); - assertEquals(2, keyboardHandler.passedThroughKeyDowns_.size); - assertEquals('pending_shortcut_keyups', keyboardHandler.passThroughState_); - assertTrue(ChromeVox.passThroughMode); + keyboardHandler.onKeyUp(searchCtrlM); + assertEquals(0, keyboardHandler.eatenKeyDowns_.size); + assertEquals(2, keyboardHandler.passedThroughKeyDowns_.size); + assertEquals( + 'pending_shortcut_keyups', keyboardHandler.passThroughState_); + assertTrue(ChromeVox.passThroughMode); - keyboardHandler.onKeyUp(searchCtrl); - assertEquals(0, keyboardHandler.eatenKeyDowns_.size); - assertEquals(1, keyboardHandler.passedThroughKeyDowns_.size); - assertEquals('pending_shortcut_keyups', keyboardHandler.passThroughState_); - assertTrue(ChromeVox.passThroughMode); + keyboardHandler.onKeyUp(searchCtrl); + assertEquals(0, keyboardHandler.eatenKeyDowns_.size); + assertEquals(1, keyboardHandler.passedThroughKeyDowns_.size); + assertEquals( + 'pending_shortcut_keyups', keyboardHandler.passThroughState_); + assertTrue(ChromeVox.passThroughMode); - keyboardHandler.onKeyUp(search); - assertEquals(0, keyboardHandler.eatenKeyDowns_.size); - assertEquals(0, keyboardHandler.passedThroughKeyDowns_.size); - assertEquals('no_pass_through', keyboardHandler.passThroughState_); - assertFalse(ChromeVox.passThroughMode); - }); -}); + keyboardHandler.onKeyUp(search); + assertEquals(0, keyboardHandler.eatenKeyDowns_.size); + assertEquals(0, keyboardHandler.passedThroughKeyDowns_.size); + assertEquals('no_pass_through', keyboardHandler.passThroughState_); + assertFalse(ChromeVox.passThroughMode); + }); TEST_F( - 'ChromeVoxBackgroundKeyboardHandlerTest', 'PassThroughModeOff', function() { - this.runWithLoadedTree('<p>test</p>', function() { - function assertNoPassThrough() { - assertUndefined(ChromeVox.passThroughMode); - assertEquals('no_pass_through', keyboardHandler.passThroughState_); - assertEquals(0, keyboardHandler.passedThroughKeyDowns_.size); - } + 'ChromeVoxBackgroundKeyboardHandlerTest', 'PassThroughModeOff', + async function() { + await this.runWithLoadedTree('<p>test</p>'); + function assertNoPassThrough() { + assertUndefined(ChromeVox.passThroughMode); + assertEquals('no_pass_through', keyboardHandler.passThroughState_); + assertEquals(0, keyboardHandler.passedThroughKeyDowns_.size); + } - // Send some random keys; ensure the pass through state variables never - // change. - const search = - TestUtils.createMockKeyEvent(KeyCode.SEARCH, {metaKey: true}); - keyboardHandler.onKeyDown(search); - assertNoPassThrough(); + // Send some random keys; ensure the pass through state variables never + // change. + const search = + TestUtils.createMockKeyEvent(KeyCode.SEARCH, {metaKey: true}); + keyboardHandler.onKeyDown(search); + assertNoPassThrough(); - const searchShift = TestUtils.createMockKeyEvent( - KeyCode.SHIFT, {metaKey: true, shiftKey: true}); - keyboardHandler.onKeyDown(searchShift); - assertNoPassThrough(); + const searchShift = TestUtils.createMockKeyEvent( + KeyCode.SHIFT, {metaKey: true, shiftKey: true}); + keyboardHandler.onKeyDown(searchShift); + assertNoPassThrough(); - const searchShiftM = TestUtils.createMockKeyEvent( - KeyCode.M, {metaKey: true, shiftKey: true}); - keyboardHandler.onKeyDown(searchShiftM); - assertNoPassThrough(); + const searchShiftM = TestUtils.createMockKeyEvent( + KeyCode.M, {metaKey: true, shiftKey: true}); + keyboardHandler.onKeyDown(searchShiftM); + assertNoPassThrough(); - keyboardHandler.onKeyUp(searchShiftM); - assertNoPassThrough(); + keyboardHandler.onKeyUp(searchShiftM); + assertNoPassThrough(); - keyboardHandler.onKeyUp(searchShift); - assertNoPassThrough(); + keyboardHandler.onKeyUp(searchShift); + assertNoPassThrough(); - keyboardHandler.onKeyUp(search); - assertNoPassThrough(); + keyboardHandler.onKeyUp(search); + assertNoPassThrough(); - keyboardHandler.onKeyDown(TestUtils.createMockKeyEvent(KeyCode.A)); - assertNoPassThrough(); + keyboardHandler.onKeyDown(TestUtils.createMockKeyEvent(KeyCode.A)); + assertNoPassThrough(); - keyboardHandler.onKeyDown( - TestUtils.createMockKeyEvent(KeyCode.A, {altKey: true})); - assertNoPassThrough(); + keyboardHandler.onKeyDown( + TestUtils.createMockKeyEvent(KeyCode.A, {altKey: true})); + assertNoPassThrough(); - keyboardHandler.onKeyUp( - TestUtils.createMockKeyEvent(KeyCode.A, {altKey: true})); - assertNoPassThrough(); - }); + keyboardHandler.onKeyUp( + TestUtils.createMockKeyEvent(KeyCode.A, {altKey: true})); + assertNoPassThrough(); }); TEST_F( 'ChromeVoxBackgroundKeyboardHandlerTest', 'UnexpectedKeyDownUpPairs', - function() { - this.runWithLoadedTree('<p>test</p>', function() { - // Send a few key downs. - const search = - TestUtils.createMockKeyEvent(KeyCode.SEARCH, {metaKey: true}); - keyboardHandler.onKeyDown(search); - assertEquals(1, keyboardHandler.eatenKeyDowns_.size); + async function() { + await this.runWithLoadedTree('<p>test</p>'); + // Send a few key downs. + const search = + TestUtils.createMockKeyEvent(KeyCode.SEARCH, {metaKey: true}); + keyboardHandler.onKeyDown(search); + assertEquals(1, keyboardHandler.eatenKeyDowns_.size); - const searchShift = TestUtils.createMockKeyEvent( - KeyCode.SHIFT, {metaKey: true, shiftKey: true}); - keyboardHandler.onKeyDown(searchShift); - assertEquals(2, keyboardHandler.eatenKeyDowns_.size); + const searchShift = TestUtils.createMockKeyEvent( + KeyCode.SHIFT, {metaKey: true, shiftKey: true}); + keyboardHandler.onKeyDown(searchShift); + assertEquals(2, keyboardHandler.eatenKeyDowns_.size); - const searchShiftM = TestUtils.createMockKeyEvent( - KeyCode.M, {metaKey: true, shiftKey: true}); - keyboardHandler.onKeyDown(searchShiftM); - assertEquals(3, keyboardHandler.eatenKeyDowns_.size); + const searchShiftM = TestUtils.createMockKeyEvent( + KeyCode.M, {metaKey: true, shiftKey: true}); + keyboardHandler.onKeyDown(searchShiftM); + assertEquals(3, keyboardHandler.eatenKeyDowns_.size); - // Now, send a key down, but no modifiers set, which is impossible to - // actually press. This key is not eaten. - const m = TestUtils.createMockKeyEvent(KeyCode.M, {}); - keyboardHandler.onKeyDown(m); - assertEquals(0, keyboardHandler.eatenKeyDowns_.size); + // Now, send a key down, but no modifiers set, which is impossible to + // actually press. This key is not eaten. + const m = TestUtils.createMockKeyEvent(KeyCode.M, {}); + keyboardHandler.onKeyDown(m); + assertEquals(0, keyboardHandler.eatenKeyDowns_.size); - // To demonstrate eaten keys still work, send Search by itself, which is - // always eaten. - keyboardHandler.onKeyDown(search); - assertEquals(1, keyboardHandler.eatenKeyDowns_.size); - }); + // To demonstrate eaten keys still work, send Search by itself, which is + // always eaten. + keyboardHandler.onKeyDown(search); + assertEquals(1, keyboardHandler.eatenKeyDowns_.size); }); TEST_F( 'ChromeVoxBackgroundKeyboardHandlerTest', - 'UnexpectedKeyDownUpPairsPassThrough', function() { - this.runWithLoadedTree('<p>test</p>', function() { - // Force pass through mode. - ChromeVox.passThroughMode = true; + 'UnexpectedKeyDownUpPairsPassThrough', async function() { + await this.runWithLoadedTree('<p>test</p>'); + // Force pass through mode. + ChromeVox.passThroughMode = true; - // Send a few key downs (which are passed through). - const search = - TestUtils.createMockKeyEvent(KeyCode.SEARCH, {metaKey: true}); - keyboardHandler.onKeyDown(search); - assertEquals(0, keyboardHandler.eatenKeyDowns_.size); - assertEquals(1, keyboardHandler.passedThroughKeyDowns_.size); + // Send a few key downs (which are passed through). + const search = + TestUtils.createMockKeyEvent(KeyCode.SEARCH, {metaKey: true}); + keyboardHandler.onKeyDown(search); + assertEquals(0, keyboardHandler.eatenKeyDowns_.size); + assertEquals(1, keyboardHandler.passedThroughKeyDowns_.size); - const searchShift = TestUtils.createMockKeyEvent( - KeyCode.SHIFT, {metaKey: true, shiftKey: true}); - keyboardHandler.onKeyDown(searchShift); - assertEquals(0, keyboardHandler.eatenKeyDowns_.size); - assertEquals(2, keyboardHandler.passedThroughKeyDowns_.size); + const searchShift = TestUtils.createMockKeyEvent( + KeyCode.SHIFT, {metaKey: true, shiftKey: true}); + keyboardHandler.onKeyDown(searchShift); + assertEquals(0, keyboardHandler.eatenKeyDowns_.size); + assertEquals(2, keyboardHandler.passedThroughKeyDowns_.size); - const searchShiftM = TestUtils.createMockKeyEvent( - KeyCode.M, {metaKey: true, shiftKey: true}); - keyboardHandler.onKeyDown(searchShiftM); - assertEquals(0, keyboardHandler.eatenKeyDowns_.size); - assertEquals(3, keyboardHandler.passedThroughKeyDowns_.size); + const searchShiftM = TestUtils.createMockKeyEvent( + KeyCode.M, {metaKey: true, shiftKey: true}); + keyboardHandler.onKeyDown(searchShiftM); + assertEquals(0, keyboardHandler.eatenKeyDowns_.size); + assertEquals(3, keyboardHandler.passedThroughKeyDowns_.size); - // Now, send a key down, but no modifiers set, which is impossible to - // actually press. This is passed through, so the count resets to 1. - const m = TestUtils.createMockKeyEvent(KeyCode.M, {}); - keyboardHandler.onKeyDown(m); - assertEquals(0, keyboardHandler.eatenKeyDowns_.size); - assertEquals(1, keyboardHandler.passedThroughKeyDowns_.size); - }); + // Now, send a key down, but no modifiers set, which is impossible to + // actually press. This is passed through, so the count resets to 1. + const m = TestUtils.createMockKeyEvent(KeyCode.M, {}); + keyboardHandler.onKeyDown(m); + assertEquals(0, keyboardHandler.eatenKeyDowns_.size); + assertEquals(1, keyboardHandler.passedThroughKeyDowns_.size); });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/keymaps/key_map.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/keymaps/key_map.js index 59acae8..c10847fd 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/keymaps/key_map.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/keymaps/key_map.js
@@ -20,14 +20,9 @@ * UserCommands. */ -goog.provide('KeyMap'); +// TODO(dtseng): KeyUtil only needed for sticky mode. -goog.require('KeyCode'); - -// TODO(dtseng): Only needed for sticky mode. -goog.require('KeyUtil'); - -KeyMap = class { +export class KeyMap { /** * @param {Array<Object<{command: string, sequence: KeySequence}>>} * commandsAndKeySequences An array of pairs - KeySequences and commands. @@ -212,7 +207,7 @@ this.commandToKey_[binding.command] = binding.sequence; } } -}; +} // This is intentionally not type-checked, as it is a serialized set of // KeySequence objects.
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions_test.js index 703f9fd..591d9845 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions_test.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions_test.js
@@ -28,10 +28,9 @@ }; -TEST_F('ChromeVoxLiveRegionsTest', 'LiveRegionAddElement', function() { +TEST_F('ChromeVoxLiveRegionsTest', 'LiveRegionAddElement', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const rootNode = await this.runWithLoadedTree(` <h1>Document with live region</h1> <p id="live" aria-live="assertive"></p> <button id="go">Go</button> @@ -40,19 +39,16 @@ document.getElementById('live').innerHTML = 'Hello, world'; }, false); </script> - `, - function(rootNode) { - const go = rootNode.find({role: RoleType.BUTTON}); - mockFeedback.call(go.doDefault.bind(go)) - .expectCategoryFlushSpeech('Hello, world'); - mockFeedback.replay(); - }); + `); + const go = rootNode.find({role: RoleType.BUTTON}); + mockFeedback.call(go.doDefault.bind(go)) + .expectCategoryFlushSpeech('Hello, world'); + mockFeedback.replay(); }); -TEST_F('ChromeVoxLiveRegionsTest', 'LiveRegionRemoveElement', function() { +TEST_F('ChromeVoxLiveRegionsTest', 'LiveRegionRemoveElement', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const rootNode = await this.runWithLoadedTree(` <h1>Document with live region</h1> <p id="live" aria-live="assertive" aria-relevant="removals">Hello, world</p> <button id="go">Go</button> @@ -61,21 +57,18 @@ document.getElementById('live').innerHTML = ''; }, false); </script> - `, - function(rootNode) { - const go = rootNode.find({role: RoleType.BUTTON}); - go.doDefault(); - mockFeedback.expectCategoryFlushSpeech('removed:') - .expectQueuedSpeech('Hello, world'); - mockFeedback.replay(); - }); + `); + const go = rootNode.find({role: RoleType.BUTTON}); + go.doDefault(); + mockFeedback.expectCategoryFlushSpeech('removed:') + .expectQueuedSpeech('Hello, world'); + mockFeedback.replay(); }); -TEST_F('ChromeVoxLiveRegionsTest', 'LiveRegionChangeAtomic', function() { +TEST_F('ChromeVoxLiveRegionsTest', 'LiveRegionChangeAtomic', async function() { LiveRegions.LIVE_REGION_QUEUE_TIME_MS = 0; const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const rootNode = await this.runWithLoadedTree(` <div id="live" aria-live="assertive" aria-atomic="true"> <div></div><div>Bravo</div><div></div> </div> @@ -86,20 +79,18 @@ document.querySelectorAll('div div')[0].textContent = 'Alpha'; }, false); </script> - `, - function(rootNode) { - const go = rootNode.find({role: RoleType.BUTTON}); - mockFeedback.call(go.doDefault.bind(go)) - .expectCategoryFlushSpeech('Alpha Bravo Charlie'); - mockFeedback.replay(); - }); + `); + const go = rootNode.find({role: RoleType.BUTTON}); + mockFeedback.call(go.doDefault.bind(go)) + .expectCategoryFlushSpeech('Alpha Bravo Charlie'); + mockFeedback.replay(); }); -TEST_F('ChromeVoxLiveRegionsTest', 'LiveRegionChangeAtomicText', function() { - LiveRegions.LIVE_REGION_QUEUE_TIME_MS = 0; - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` +TEST_F( + 'ChromeVoxLiveRegionsTest', 'LiveRegionChangeAtomicText', async function() { + LiveRegions.LIVE_REGION_QUEUE_TIME_MS = 0; + const mockFeedback = this.createMockFeedback(); + const rootNode = await this.runWithLoadedTree(` <h1 aria-atomic="true" id="live"aria-live="assertive">foo</h1> <button id="go">go</button> <script> @@ -107,25 +98,23 @@ document.getElementById('live').innerText = 'bar'; }); </script> - `, - function(rootNode) { - const go = rootNode.find({role: RoleType.BUTTON}); - mockFeedback.call(go.doDefault.bind(go)) - .expectCategoryFlushSpeech('bar', 'Heading 1'); - mockFeedback.replay(); - }); -}); + `); + const go = rootNode.find({role: RoleType.BUTTON}); + mockFeedback.call(go.doDefault.bind(go)) + .expectCategoryFlushSpeech('bar', 'Heading 1'); + mockFeedback.replay(); + }); -TEST_F('ChromeVoxLiveRegionsTest', 'LiveRegionChangeImageAlt', function() { - // Note that there is a live region outputted as a result of page load; the - // test expects a live region announcement after a click on the button, but - // the LiveRegions module has a half second filter for live region - // announcements on the same node. Set that timeout to 0 to prevent - // flakeyness. - LiveRegions.LIVE_REGION_QUEUE_TIME_MS = 0; - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` +TEST_F( + 'ChromeVoxLiveRegionsTest', 'LiveRegionChangeImageAlt', async function() { + // Note that there is a live region outputted as a result of page load; + // the test expects a live region announcement after a click on the + // button, but the LiveRegions module has a half second filter for live + // region announcements on the same node. Set that timeout to 0 to prevent + // flakeyness. + LiveRegions.LIVE_REGION_QUEUE_TIME_MS = 0; + const mockFeedback = this.createMockFeedback(); + const rootNode = await this.runWithLoadedTree(` <div id="live" aria-live="assertive"> <img id="img" src="#" alt="Before"> </div> @@ -135,19 +124,16 @@ document.getElementById('img').setAttribute('alt', 'After'); }, false); </script> - `, - function(rootNode) { - const go = rootNode.find({role: RoleType.BUTTON}); - mockFeedback.call(go.doDefault.bind(go)) - .expectCategoryFlushSpeech('After'); - mockFeedback.replay(); - }); -}); + `); + const go = rootNode.find({role: RoleType.BUTTON}); + mockFeedback.call(go.doDefault.bind(go)) + .expectCategoryFlushSpeech('After'); + mockFeedback.replay(); + }); -TEST_F('ChromeVoxLiveRegionsTest', 'LiveRegionThenFocus', function() { +TEST_F('ChromeVoxLiveRegionsTest', 'LiveRegionThenFocus', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const rootNode = await this.runWithLoadedTree(` <div id="live" aria-live="assertive"></div> <button id="go">Go</button> <button id="focus">Focus</button> @@ -159,38 +145,35 @@ }, 50); }, false); </script> - `, - function(rootNode) { - // Due to the above timing component, the live region can come either - // before or after the focus output. This depends on the EventBundle to - // which we get the live region. It can either be in its own bundle or - // be part of the bundle with the focus change. In either case, the - // first event should be flushed; the second should either be queued (in - // the case of the focus) or category flushed for the live region. - let sawFocus = false; - let sawLive = false; - const focusOrLive = function(candidate) { - sawFocus = candidate.text === 'Focus' || sawFocus; - sawLive = candidate.text === 'Live' || sawLive; - if (sawFocus && sawLive) { - return candidate.queueMode !== QueueMode.FLUSH; - } else if (sawFocus || sawLive) { - return candidate.queueMode === QueueMode.FLUSH; - } - }; - const go = rootNode.find({role: RoleType.BUTTON}); - mockFeedback.call(this.simulateUserInteraction.bind(this)) - .call(go.doDefault.bind(go)) - .expectSpeech(focusOrLive) - .expectSpeech(focusOrLive); - mockFeedback.replay(); - }); + `); + // Due to the above timing component, the live region can come either + // before or after the focus output. This depends on the EventBundle to + // which we get the live region. It can either be in its own bundle or + // be part of the bundle with the focus change. In either case, the + // first event should be flushed; the second should either be queued (in + // the case of the focus) or category flushed for the live region. + let sawFocus = false; + let sawLive = false; + const focusOrLive = function(candidate) { + sawFocus = candidate.text === 'Focus' || sawFocus; + sawLive = candidate.text === 'Live' || sawLive; + if (sawFocus && sawLive) { + return candidate.queueMode !== QueueMode.FLUSH; + } else if (sawFocus || sawLive) { + return candidate.queueMode === QueueMode.FLUSH; + } + }; + const go = rootNode.find({role: RoleType.BUTTON}); + mockFeedback.call(this.simulateUserInteraction.bind(this)) + .call(go.doDefault.bind(go)) + .expectSpeech(focusOrLive) + .expectSpeech(focusOrLive); + mockFeedback.replay(); }); -TEST_F('ChromeVoxLiveRegionsTest', 'FocusThenLiveRegion', function() { +TEST_F('ChromeVoxLiveRegionsTest', 'FocusThenLiveRegion', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const rootNode = await this.runWithLoadedTree(` <div id="live" aria-live="assertive"></div> <button id="go">Go</button> <button id="focus">Focus</button> @@ -202,28 +185,25 @@ }, 200); }, false); </script> - `, - function(rootNode) { - const go = rootNode.find({role: RoleType.BUTTON}); - mockFeedback.call(this.simulateUserInteraction.bind(this)) - .call(go.doDefault.bind(go)) - .expectSpeech('Focus') - .expectSpeech((candidate) => { - return candidate.text === 'Live' && - (candidate.queueMode === QueueMode.CATEGORY_FLUSH || - candidate.queueMode === QueueMode.QUEUE); - }); - mockFeedback.replay(); + `); + const go = rootNode.find({role: RoleType.BUTTON}); + mockFeedback.call(this.simulateUserInteraction.bind(this)) + .call(go.doDefault.bind(go)) + .expectSpeech('Focus') + .expectSpeech((candidate) => { + return candidate.text === 'Live' && + (candidate.queueMode === QueueMode.CATEGORY_FLUSH || + candidate.queueMode === QueueMode.QUEUE); }); + mockFeedback.replay(); }); -TEST_F('ChromeVoxLiveRegionsTest', 'LiveRegionCategoryFlush', function() { +TEST_F('ChromeVoxLiveRegionsTest', 'LiveRegionCategoryFlush', async function() { // Adjust the live region queue time to be shorter (i.e. flushes happen for // live regions coming 1 ms in time). Also, can help with flakeyness. LiveRegions.LIVE_REGION_QUEUE_TIME_MS = 1; const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const rootNode = await this.runWithLoadedTree(` <div id="live1" aria-live="assertive"></div> <div id="live2" aria-live="assertive"></div> <button id="go">Go</button> @@ -236,20 +216,17 @@ }, 1000); }, false); </script> - `, - function(rootNode) { - const go = rootNode.find({role: RoleType.BUTTON}); - mockFeedback.call(go.doDefault.bind(go)) - .expectCategoryFlushSpeech('Live1') - .expectCategoryFlushSpeech('Live2'); - mockFeedback.replay(); - }); + `); + const go = rootNode.find({role: RoleType.BUTTON}); + mockFeedback.call(go.doDefault.bind(go)) + .expectCategoryFlushSpeech('Live1') + .expectCategoryFlushSpeech('Live2'); + mockFeedback.replay(); }); -TEST_F('ChromeVoxLiveRegionsTest', 'SilentOnNodeChange', function() { +TEST_F('ChromeVoxLiveRegionsTest', 'SilentOnNodeChange', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const rootNode = await this.runWithLoadedTree(` <p>start</p> <button>first</button> <div role="button" id="live" aria-live="assertive"> @@ -263,62 +240,53 @@ pressed = !pressed; }, 50); </script> - `, - function(root) { - const focusAfterNodeChange = window.setTimeout.bind(window, function() { - root.firstChild.nextSibling.focus(); - }, 1000); - mockFeedback.call(focusAfterNodeChange) - .expectSpeech('hello!') - .expectNextSpeechUtteranceIsNot('hello!') - .expectNextSpeechUtteranceIsNot('hello!'); - mockFeedback.replay(); - }); + `); + const focusAfterNodeChange = window.setTimeout.bind(window, function() { + root.firstChild.nextSibling.focus(); + }, 1000); + mockFeedback.call(focusAfterNodeChange) + .expectSpeech('hello!') + .expectNextSpeechUtteranceIsNot('hello!') + .expectNextSpeechUtteranceIsNot('hello!'); + mockFeedback.replay(); }); -TEST_F('ChromeVoxLiveRegionsTest', 'SimulateTreeChanges', function() { +TEST_F('ChromeVoxLiveRegionsTest', 'SimulateTreeChanges', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <button></button> <div aria-live="assertive"> <p>hello</p><p>there</p> </div> - `, - function(root) { - const live = new LiveRegions(ChromeVoxState.instance); - const [t1, t2] = root.findAll({role: RoleType.STATIC_TEXT}); - mockFeedback.expectSpeech('hello there') - .clearPendingOutput() - .call(function() { - live.onTreeChange( - {type: TreeChangeType.TEXT_CHANGED, target: t2}); - live.onTreeChange( - {type: TreeChangeType.SUBTREE_UPDATE_END, target: t2}); - }) - .expectNextSpeechUtteranceIsNot('hello') - .expectSpeech('there') - .clearPendingOutput(); - mockFeedback - .call(function() { - live.onTreeChange( - {type: TreeChangeType.TEXT_CHANGED, target: t1}); - live.onTreeChange( - {type: TreeChangeType.TEXT_CHANGED, target: t2}); - live.onTreeChange( - {type: TreeChangeType.SUBTREE_UPDATE_END, target: t2}); - }) - .expectSpeech('hello') - .expectSpeech('there'); - mockFeedback.replay(); - }); + `); + const live = new LiveRegions(ChromeVoxState.instance); + const [t1, t2] = root.findAll({role: RoleType.STATIC_TEXT}); + mockFeedback.expectSpeech('hello there') + .clearPendingOutput() + .call(function() { + live.onTreeChange({type: TreeChangeType.TEXT_CHANGED, target: t2}); + live.onTreeChange( + {type: TreeChangeType.SUBTREE_UPDATE_END, target: t2}); + }) + .expectNextSpeechUtteranceIsNot('hello') + .expectSpeech('there') + .clearPendingOutput(); + mockFeedback + .call(function() { + live.onTreeChange({type: TreeChangeType.TEXT_CHANGED, target: t1}); + live.onTreeChange({type: TreeChangeType.TEXT_CHANGED, target: t2}); + live.onTreeChange( + {type: TreeChangeType.SUBTREE_UPDATE_END, target: t2}); + }) + .expectSpeech('hello') + .expectSpeech('there'); + mockFeedback.replay(); }); // Flaky: https://crbug.com/945199 -TEST_F('ChromeVoxLiveRegionsTest', 'DISABLED_LiveStatusOff', function() { +TEST_F('ChromeVoxLiveRegionsTest', 'DISABLED_LiveStatusOff', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const rootNode = await this.runWithLoadedTree(` <div><input aria-live="off" type="text"></input></div> <script> let input = document.querySelector('input'); @@ -337,25 +305,22 @@ } }); </script> - `, - function(root) { - const input = root.find({role: RoleType.TEXT_FIELD}); - const clickInput = input.parent.doDefault.bind(input.parent); - mockFeedback.call(input.focus.bind(input)) - .call(clickInput) - .expectSpeech('bb') - .clearPendingOutput() - .call(clickInput) - .expectNextSpeechUtteranceIsNot('bba') - .expectSpeech('a') - .replay(); - }); + `); + const input = root.find({role: RoleType.TEXT_FIELD}); + const clickInput = input.parent.doDefault.bind(input.parent); + mockFeedback.call(input.focus.bind(input)) + .call(clickInput) + .expectSpeech('bb') + .clearPendingOutput() + .call(clickInput) + .expectNextSpeechUtteranceIsNot('bba') + .expectSpeech('a') + .replay(); }); -TEST_F('ChromeVoxLiveRegionsTest', 'TreeChangeOnIgnoredNode', function() { +TEST_F('ChromeVoxLiveRegionsTest', 'TreeChangeOnIgnoredNode', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <button></button> <script> const button = document.body.children[0]; @@ -369,14 +334,13 @@ document.body.appendChild(ignored); }); </script> - `, - function(root) { - const button = root.find({role: chrome.automation.RoleType.BUTTON}); - mockFeedback.call(button.doDefault.bind(button)) - .expectSpeech('Alert', 'hi') - .replay(); - }); + `); + const button = root.find({role: chrome.automation.RoleType.BUTTON}); + mockFeedback.call(button.doDefault.bind(button)) + .expectSpeech('Alert', 'hi') + .replay(); }); + SYNC_TEST_F('ChromeVoxLiveRegionsTest', 'ShouldIgnoreLiveRegion', function() { const liveRegions = new LiveRegions(ChromeVoxState.instance);
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/loader.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/loader.js index 77fa89c0..ef8e326 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/loader.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/loader.js
@@ -23,7 +23,6 @@ goog.require('ChromeVoxState'); goog.require('ChromeVoxStateObserver'); goog.require('CommandHandlerInterface'); -goog.require('CommandStore'); goog.require('ConsoleTts'); goog.require('EventGenerator'); goog.require('EventSourceState'); @@ -31,7 +30,6 @@ goog.require('ExtensionBridge'); goog.require('JaPhoneticMap'); goog.require('KeyCode'); -goog.require('KeyMap'); goog.require('KeySequence'); goog.require('KeyUtil'); goog.require('LibLouis.FormType');
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/locale_output_helper_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/locale_output_helper_test.js index a75533e..168a0d0 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/locale_output_helper_test.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/locale_output_helper_test.js
@@ -178,402 +178,393 @@ TEST_F( 'ChromeVoxLocaleOutputHelperTest', 'MultipleLanguagesLabeledDocTest', - function() { + async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.multipleLanguagesLabeledDoc, function() { - localStorage['languageSwitching'] = 'true'; - this.setAvailableVoices(); - mockFeedback.call(doCmd('jumpToTop')) - .expectSpeechWithLocale('es', 'español: Hola.'); - mockFeedback.call(doCmd('nextLine')) - .expectSpeechWithLocale('en', 'English: Hello.'); - mockFeedback.call(doCmd('nextLine')) - .expectSpeechWithLocale('fr', 'français: Salut.'); - mockFeedback.call(doCmd('nextLine')) - .expectSpeechWithLocale('it', 'italiano: Ciao amico.'); - mockFeedback.replay(); - }); + await this.runWithLoadedTree(this.multipleLanguagesLabeledDoc); + localStorage['languageSwitching'] = 'true'; + this.setAvailableVoices(); + mockFeedback.call(doCmd('jumpToTop')) + .expectSpeechWithLocale('es', 'español: Hola.'); + mockFeedback.call(doCmd('nextLine')) + .expectSpeechWithLocale('en', 'English: Hello.'); + mockFeedback.call(doCmd('nextLine')) + .expectSpeechWithLocale('fr', 'français: Salut.'); + mockFeedback.call(doCmd('nextLine')) + .expectSpeechWithLocale('it', 'italiano: Ciao amico.'); + mockFeedback.replay(); }); TEST_F( 'ChromeVoxLocaleOutputHelperTest', 'NestedLanguagesLabeledDocTest', - function() { + async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.nestedLanguagesLabeledDoc, function() { - localStorage['languageSwitching'] = 'true'; - this.setAvailableVoices(); - mockFeedback.call(doCmd('jumpToTop')) - .expectSpeechWithLocale( - 'en', 'In the morning, I sometimes eat breakfast.'); - mockFeedback.call(doCmd('nextLine')) - .expectSpeechWithLocale( - 'fr', 'français: Dans l\'apres-midi, je dejeune.'); - mockFeedback.call(doCmd('nextLine')) - .expectSpeechWithLocale( - 'en', 'English: Hello it\'s a pleasure to meet you. '); - mockFeedback.call(doCmd('nextLine')) - .expectSpeechWithLocale('fr', 'français: Comment ca va?'); - mockFeedback.call(doCmd('nextLine')) - .expectSpeechWithLocale( - 'en', 'English: Switching back to English. '); - mockFeedback.call(doCmd('nextLine')) - .expectSpeechWithLocale('es', 'español: Hola.'); - mockFeedback.call(doCmd('nextLine')) - .expectSpeechWithLocale('en', 'English: Goodbye.'); - mockFeedback.replay(); - }); + await this.runWithLoadedTree(this.nestedLanguagesLabeledDoc); + localStorage['languageSwitching'] = 'true'; + this.setAvailableVoices(); + mockFeedback.call(doCmd('jumpToTop')) + .expectSpeechWithLocale( + 'en', 'In the morning, I sometimes eat breakfast.'); + mockFeedback.call(doCmd('nextLine')) + .expectSpeechWithLocale( + 'fr', 'français: Dans l\'apres-midi, je dejeune.'); + mockFeedback.call(doCmd('nextLine')) + .expectSpeechWithLocale( + 'en', 'English: Hello it\'s a pleasure to meet you. '); + mockFeedback.call(doCmd('nextLine')) + .expectSpeechWithLocale('fr', 'français: Comment ca va?'); + mockFeedback.call(doCmd('nextLine')) + .expectSpeechWithLocale('en', 'English: Switching back to English. '); + mockFeedback.call(doCmd('nextLine')) + .expectSpeechWithLocale('es', 'español: Hola.'); + mockFeedback.call(doCmd('nextLine')) + .expectSpeechWithLocale('en', 'English: Goodbye.'); + mockFeedback.replay(); }); -TEST_F('ChromeVoxLocaleOutputHelperTest', 'ButtonAndLinkDocTest', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.buttonAndLinkDoc, function(root) { - localStorage['languageSwitching'] = 'true'; - this.setAvailableVoices(); - mockFeedback - .call(doCmd('jumpToTop')) - // Use the author-provided language of 'es'. - .expectSpeechWithLocale( - 'es', 'español: This is a paragraph, written in English.') - .call(doCmd('nextObject')) - .expectSpeechWithLocale('es', 'This is a button, written in English.') - .expectSpeechWithLocale( - undefined, 'Button', 'Press Search+Space to activate') - .call(doCmd('nextObject')) - .expectSpeechWithLocale('es', 'Este es un enlace.') - .expectSpeechWithLocale(undefined, 'Link'); - mockFeedback.replay(); - }); -}); - +TEST_F( + 'ChromeVoxLocaleOutputHelperTest', 'ButtonAndLinkDocTest', + async function() { + const mockFeedback = this.createMockFeedback(); + const root = await this.runWithLoadedTree(this.buttonAndLinkDoc); + localStorage['languageSwitching'] = 'true'; + this.setAvailableVoices(); + mockFeedback + .call(doCmd('jumpToTop')) + // Use the author-provided language of 'es'. + .expectSpeechWithLocale( + 'es', 'español: This is a paragraph, written in English.') + .call(doCmd('nextObject')) + .expectSpeechWithLocale('es', 'This is a button, written in English.') + .expectSpeechWithLocale( + undefined, 'Button', 'Press Search+Space to activate') + .call(doCmd('nextObject')) + .expectSpeechWithLocale('es', 'Este es un enlace.') + .expectSpeechWithLocale(undefined, 'Link'); + mockFeedback.replay(); + }); TEST_F( 'ChromeVoxLocaleOutputHelperTest', 'JapaneseAndEnglishUnlabeledDocTest', - function() { + async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - this.japaneseAndEnglishUnlabeledDoc, function(root) { - localStorage['languageSwitching'] = 'true'; - this.setAvailableVoices(); - mockFeedback - .call(doCmd('jumpToTop')) - // Expect the node's contents to be read in one language - // (English). - // Language detection does not run on small runs of text, like - // the one in this test, we are falling back on the UI language - // of the browser, which is en-US. Please see testGenPreamble - // for more details. - .expectSpeechWithLocale( - 'en-us', - 'Hello, my name is 太田あきひろ. It\'s a pleasure to meet' + - ' you. どうぞよろしくお願いします.'); - mockFeedback.replay(); - }); + const root = + await this.runWithLoadedTree(this.japaneseAndEnglishUnlabeledDoc); + localStorage['languageSwitching'] = 'true'; + this.setAvailableVoices(); + mockFeedback + .call(doCmd('jumpToTop')) + // Expect the node's contents to be read in one language + // (English). + // Language detection does not run on small runs of text, like + // the one in this test, we are falling back on the UI language + // of the browser, which is en-US. Please see testGenPreamble + // for more details. + .expectSpeechWithLocale( + 'en-us', + 'Hello, my name is 太田あきひろ. It\'s a pleasure to meet' + + ' you. どうぞよろしくお願いします.'); + mockFeedback.replay(); }); - TEST_F( 'ChromeVoxLocaleOutputHelperTest', 'EnglishAndKoreanUnlabeledDocTest', - function() { + async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.englishAndKoreanUnlabeledDoc, function(root) { - localStorage['languageSwitching'] = 'true'; - this.setAvailableVoices(); - mockFeedback.call(doCmd('jumpToTop')) - .expectSpeechWithLocale( - 'en-us', - 'This text is written in English. 차에 한하여 중임할 수.' + - ' This text is also written in English.'); - mockFeedback.replay(); - }); + const root = + await this.runWithLoadedTree(this.englishAndKoreanUnlabeledDoc); + localStorage['languageSwitching'] = 'true'; + this.setAvailableVoices(); + mockFeedback.call(doCmd('jumpToTop')) + .expectSpeechWithLocale( + 'en-us', + 'This text is written in English. 차에 한하여 중임할 수.' + + ' This text is also written in English.'); + mockFeedback.replay(); }); TEST_F( 'ChromeVoxLocaleOutputHelperTest', 'EnglishAndFrenchUnlabeledDocTest', - function() { + async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.englishAndFrenchUnlabeledDoc, function(root) { - localStorage['languageSwitching'] = 'true'; - this.setAvailableVoices(); - mockFeedback.call(doCmd('jumpToTop')) - .expectSpeechWithLocale( - 'en', - 'This entire object should be read in English, even' + - ' the following French passage: ' + - 'salut mon ami! Ca va? Bien, et toi? It\'s hard to' + - ' differentiate between latin-based languages.'); - mockFeedback.replay(); - }); + const root = + await this.runWithLoadedTree(this.englishAndFrenchUnlabeledDoc); + localStorage['languageSwitching'] = 'true'; + this.setAvailableVoices(); + mockFeedback.call(doCmd('jumpToTop')) + .expectSpeechWithLocale( + 'en', + 'This entire object should be read in English, even' + + ' the following French passage: ' + + 'salut mon ami! Ca va? Bien, et toi? It\'s hard to' + + ' differentiate between latin-based languages.'); + mockFeedback.replay(); }); TEST_F( 'ChromeVoxLocaleOutputHelperTest', 'JapaneseCharacterUnlabeledDocTest', - function() { + async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - this.japaneseCharacterUnlabeledDoc, function(root) { - localStorage['languageSwitching'] = 'true'; - this.setAvailableVoices(); - mockFeedback.call(doCmd('jumpToTop')) - .expectSpeechWithLocale('en-us', 'ど'); - mockFeedback.replay(); - }); + const root = + await this.runWithLoadedTree(this.japaneseCharacterUnlabeledDoc); + localStorage['languageSwitching'] = 'true'; + this.setAvailableVoices(); + mockFeedback.call(doCmd('jumpToTop')) + .expectSpeechWithLocale('en-us', 'ど'); + mockFeedback.replay(); }); TEST_F( 'ChromeVoxLocaleOutputHelperTest', 'JapaneseAndChineseUnlabeledDocTest', - function() { + async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.japaneseAndChineseUnlabeledDoc, function(root) { - localStorage['languageSwitching'] = 'true'; - this.setAvailableVoices(); - mockFeedback.call(doCmd('jumpToTop')) - .expectSpeechWithLocale( - 'en-us', - '天気はいいですね. 右万諭全中結社原済権人点掲年難出面者会追'); - mockFeedback.replay(); - }); + const root = + await this.runWithLoadedTree(this.japaneseAndChineseUnlabeledDoc); + localStorage['languageSwitching'] = 'true'; + this.setAvailableVoices(); + mockFeedback.call(doCmd('jumpToTop')) + .expectSpeechWithLocale( + 'en-us', + '天気はいいですね. 右万諭全中結社原済権人点掲年難出面者会追'); + mockFeedback.replay(); }); TEST_F( 'ChromeVoxLocaleOutputHelperTest', 'JapaneseAndChineseLabeledDocTest', - function() { + async function() { const mockFeedback = this.createMockFeedback(); // Only difference between doc used in this test and // this.japaneseAndChineseUnlabeledDoc is the lang="zh" attribute. - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <meta charset="utf-8"> <p lang="zh"> 天気はいいですね. 右万諭全中結社原済権人点掲年難出面者会追 </p> - `, - function(root) { - localStorage['languageSwitching'] = 'true'; - this.setAvailableVoices(); - mockFeedback.call(doCmd('jumpToTop')) - .expectSpeechWithLocale( - 'zh', - '中文: 天気はいいですね. 右万諭全中結社原済権人点掲年難出面者会追'); - mockFeedback.replay(); - }); + `); + localStorage['languageSwitching'] = 'true'; + this.setAvailableVoices(); + mockFeedback.call(doCmd('jumpToTop')) + .expectSpeechWithLocale( + 'zh', + '中文: 天気はいいですね. 右万諭全中結社原済権人点掲年難出面者会追'); + mockFeedback.replay(); }); TEST_F( 'ChromeVoxLocaleOutputHelperTest', 'JapaneseAndKoreanUnlabeledDocTest', - function() { + async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.japaneseAndKoreanUnlabeledDoc, function(root) { - localStorage['languageSwitching'] = 'true'; - this.setAvailableVoices(); - // Language detection runs and assigns language of 'ko' to the node. - mockFeedback.call(doCmd('jumpToTop')) - .expectSpeechWithLocale( - 'ko', - '한국어: 私は. 법률이 정하는 바에 의하여 대법관이 아닌 법관을 둘 수' + - ' 있다'); - mockFeedback.replay(); - }); + const root = + await this.runWithLoadedTree(this.japaneseAndKoreanUnlabeledDoc); + localStorage['languageSwitching'] = 'true'; + this.setAvailableVoices(); + // Language detection runs and assigns language of 'ko' to the node. + mockFeedback.call(doCmd('jumpToTop')) + .expectSpeechWithLocale( + 'ko', + '한국어: 私は. 법률이 정하는 바에 의하여 대법관이 아닌 법관을 둘 수' + + ' 있다'); + mockFeedback.replay(); }); TEST_F( 'ChromeVoxLocaleOutputHelperTest', 'AsturianAndJapaneseDocTest', - function() { + async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.asturianAndJapaneseDoc, function(root) { - localStorage['languageSwitching'] = 'true'; - this.setAvailableVoices(); - mockFeedback.call(doCmd('jumpToTop')) - .expectSpeechWithLocale('ja', '日本語: ど') - .call(doCmd('nextObject')) - .expectSpeechWithLocale( - 'ast', - 'asturianu: Pretend that this text is Asturian. Testing' + - ' three-letter language code logic.'); - mockFeedback.replay(); - }); + const root = await this.runWithLoadedTree(this.asturianAndJapaneseDoc); + localStorage['languageSwitching'] = 'true'; + this.setAvailableVoices(); + mockFeedback.call(doCmd('jumpToTop')) + .expectSpeechWithLocale('ja', '日本語: ど') + .call(doCmd('nextObject')) + .expectSpeechWithLocale( + 'ast', + 'asturianu: Pretend that this text is Asturian. Testing' + + ' three-letter language code logic.'); + mockFeedback.replay(); }); TEST_F( - 'ChromeVoxLocaleOutputHelperTest', 'LanguageSwitchingOffTest', function() { + 'ChromeVoxLocaleOutputHelperTest', 'LanguageSwitchingOffTest', + async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.multipleLanguagesLabeledDoc, function(root) { - localStorage['languageSwitching'] = 'false'; - this.setAvailableVoices(); - // Locale should not be set if the language switching feature is off. - mockFeedback.call(doCmd('jumpToTop')) - .expectSpeechWithLocale(undefined, 'Hola.') - .call(doCmd('nextObject')) - .expectSpeechWithLocale(undefined, 'Hello.') - .call(doCmd('nextObject')) - .expectSpeechWithLocale(undefined, 'Salut.') - .call(doCmd('nextObject')) - .expectSpeechWithLocale(undefined, 'Ciao amico.'); - mockFeedback.replay(); - }); + const root = + await this.runWithLoadedTree(this.multipleLanguagesLabeledDoc); + localStorage['languageSwitching'] = 'false'; + this.setAvailableVoices(); + // Locale should not be set if the language switching feature is off. + mockFeedback.call(doCmd('jumpToTop')) + .expectSpeechWithLocale(undefined, 'Hola.') + .call(doCmd('nextObject')) + .expectSpeechWithLocale(undefined, 'Hello.') + .call(doCmd('nextObject')) + .expectSpeechWithLocale(undefined, 'Salut.') + .call(doCmd('nextObject')) + .expectSpeechWithLocale(undefined, 'Ciao amico.'); + mockFeedback.replay(); }); -TEST_F('ChromeVoxLocaleOutputHelperTest', 'DefaultToUILocaleTest', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - this.japaneseAndInvalidLanguagesLabeledDoc, function(root) { - localStorage['languageSwitching'] = 'true'; - this.setAvailableVoices(); - mockFeedback.call(doCmd('jumpToTop')) - .expectSpeechWithLocale('ja', '日本語: どうぞよろしくお願いします') - .call(doCmd('nextObject')) - .expectSpeechWithLocale('en-us', 'English (United States): Test') - .call(doCmd('nextObject')) - .expectSpeechWithLocale('en-us', 'Yikes'); - mockFeedback.replay(); - }); -}); - -TEST_F('ChromeVoxLocaleOutputHelperTest', 'NoAvailableVoicesTest', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.vietnameseAndUrduLabeledDoc, function(root) { - localStorage['languageSwitching'] = 'true'; - this.setAvailableVoices(); - mockFeedback.call(doCmd('jumpToTop')) - .expectSpeechWithLocale( - 'en-us', 'No voice available for language: Vietnamese') - .call(doCmd('nextObject')) - .expectSpeechWithLocale( - 'en-us', 'No voice available for language: Urdu'); - mockFeedback.replay(); - }); -}); - -TEST_F('ChromeVoxLocaleOutputHelperTest', 'WordNavigationTest', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.nestedLanguagesLabeledDoc, function() { - localStorage['languageSwitching'] = 'true'; - this.setAvailableVoices(); - mockFeedback.call(doCmd('jumpToTop')) - .expectSpeechWithLocale( - 'en', 'In the morning, I sometimes eat breakfast.') - .call(doCmd('nextLine')) - .expectSpeechWithLocale( - 'fr', 'français: Dans l\'apres-midi, je dejeune.') - .call(doCmd('nextWord')) - .expectSpeechWithLocale('fr', `l'apres`) - .call(doCmd('nextWord')) - .expectSpeechWithLocale('fr', `-`) - .call(doCmd('nextWord')) - .expectSpeechWithLocale('fr', `midi`) - .call(doCmd('nextWord')) - .expectSpeechWithLocale('fr', `,`) - .call(doCmd('nextWord')) - .expectSpeechWithLocale('fr', `je`) - .call(doCmd('nextWord')) - .expectSpeechWithLocale('fr', `dejeune`) - .call(doCmd('nextWord')) - .expectSpeechWithLocale('fr', `.`) - .call(doCmd('nextWord')) - .expectSpeechWithLocale('en', `English: Hello`) - .call(doCmd('nextWord')) - .expectSpeechWithLocale('en', `it's`) - .call(doCmd('nextWord')) - .expectSpeechWithLocale('en', `a`) - .call(doCmd('nextWord')) - .expectSpeechWithLocale('en', `pleasure`) - .call(doCmd('nextWord')) - .expectSpeechWithLocale('en', `to`) - .call(doCmd('nextWord')) - .expectSpeechWithLocale('en', `meet`) - .call(doCmd('nextWord')) - .expectSpeechWithLocale('en', `you`) - .call(doCmd('nextWord')) - .expectSpeechWithLocale('en', `.`) - .call(doCmd('nextWord')) - .expectSpeechWithLocale('fr', `français: Comment`) - .call(doCmd('previousWord')) - .expectSpeechWithLocale('en', `English: .`) - .call(doCmd('previousWord')) - .expectSpeechWithLocale('en', `you`) - .replay(); - }); -}); +TEST_F( + 'ChromeVoxLocaleOutputHelperTest', 'DefaultToUILocaleTest', + async function() { + const mockFeedback = this.createMockFeedback(); + const root = await this.runWithLoadedTree( + this.japaneseAndInvalidLanguagesLabeledDoc); + localStorage['languageSwitching'] = 'true'; + this.setAvailableVoices(); + mockFeedback.call(doCmd('jumpToTop')) + .expectSpeechWithLocale('ja', '日本語: どうぞよろしくお願いします') + .call(doCmd('nextObject')) + .expectSpeechWithLocale('en-us', 'English (United States): Test') + .call(doCmd('nextObject')) + .expectSpeechWithLocale('en-us', 'Yikes'); + mockFeedback.replay(); + }); TEST_F( - 'ChromeVoxLocaleOutputHelperTest', 'CharacterNavigationTest', function() { + 'ChromeVoxLocaleOutputHelperTest', 'NoAvailableVoicesTest', + async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.nestedLanguagesLabeledDoc, function() { - localStorage['languageSwitching'] = 'true'; - this.setAvailableVoices(); - mockFeedback.call(doCmd('jumpToTop')) - .expectSpeechWithLocale( - 'en', 'In the morning, I sometimes eat breakfast.') - .call(doCmd('nextLine')) - .expectSpeechWithLocale( - 'fr', 'français: Dans l\'apres-midi, je dejeune.') - .call(doCmd('nextCharacter')) - .expectSpeechWithLocale('fr', `a`) - .call(doCmd('nextCharacter')) - .expectSpeechWithLocale('fr', `n`) - .call(doCmd('nextCharacter')) - .expectSpeechWithLocale('fr', `s`) - .call(doCmd('nextCharacter')) - .expectSpeechWithLocale('fr', ` `) - .call(doCmd('nextCharacter')) - .expectSpeechWithLocale('fr', `l`) - .call(doCmd('nextLine')) - .expectSpeechWithLocale( - 'en', `English: Hello it's a pleasure to meet you. `) - .call(doCmd('nextCharacter')) - .expectSpeechWithLocale('en', `e`) - .call(doCmd('previousCharacter')) - .expectSpeechWithLocale('en', `H`) - .call(doCmd('previousCharacter')) - .expectSpeechWithLocale('fr', `français: .`) - .call(doCmd('previousCharacter')) - .expectSpeechWithLocale('fr', `e`) - .call(doCmd('previousCharacter')) - .expectSpeechWithLocale('fr', `n`) - .call(doCmd('previousCharacter')) - .expectSpeechWithLocale('fr', `u`) - .call(doCmd('previousCharacter')) - .expectSpeechWithLocale('fr', `e`) - .call(doCmd('previousCharacter')) - .expectSpeechWithLocale('fr', `j`) - .replay(); - }); + const root = + await this.runWithLoadedTree(this.vietnameseAndUrduLabeledDoc); + localStorage['languageSwitching'] = 'true'; + this.setAvailableVoices(); + mockFeedback.call(doCmd('jumpToTop')) + .expectSpeechWithLocale( + 'en-us', 'No voice available for language: Vietnamese') + .call(doCmd('nextObject')) + .expectSpeechWithLocale( + 'en-us', 'No voice available for language: Urdu'); + mockFeedback.replay(); + }); + +TEST_F( + 'ChromeVoxLocaleOutputHelperTest', 'WordNavigationTest', async function() { + const mockFeedback = this.createMockFeedback(); + await this.runWithLoadedTree(this.nestedLanguagesLabeledDoc); + localStorage['languageSwitching'] = 'true'; + this.setAvailableVoices(); + mockFeedback.call(doCmd('jumpToTop')) + .expectSpeechWithLocale( + 'en', 'In the morning, I sometimes eat breakfast.') + .call(doCmd('nextLine')) + .expectSpeechWithLocale( + 'fr', 'français: Dans l\'apres-midi, je dejeune.') + .call(doCmd('nextWord')) + .expectSpeechWithLocale('fr', `l'apres`) + .call(doCmd('nextWord')) + .expectSpeechWithLocale('fr', `-`) + .call(doCmd('nextWord')) + .expectSpeechWithLocale('fr', `midi`) + .call(doCmd('nextWord')) + .expectSpeechWithLocale('fr', `,`) + .call(doCmd('nextWord')) + .expectSpeechWithLocale('fr', `je`) + .call(doCmd('nextWord')) + .expectSpeechWithLocale('fr', `dejeune`) + .call(doCmd('nextWord')) + .expectSpeechWithLocale('fr', `.`) + .call(doCmd('nextWord')) + .expectSpeechWithLocale('en', `English: Hello`) + .call(doCmd('nextWord')) + .expectSpeechWithLocale('en', `it's`) + .call(doCmd('nextWord')) + .expectSpeechWithLocale('en', `a`) + .call(doCmd('nextWord')) + .expectSpeechWithLocale('en', `pleasure`) + .call(doCmd('nextWord')) + .expectSpeechWithLocale('en', `to`) + .call(doCmd('nextWord')) + .expectSpeechWithLocale('en', `meet`) + .call(doCmd('nextWord')) + .expectSpeechWithLocale('en', `you`) + .call(doCmd('nextWord')) + .expectSpeechWithLocale('en', `.`) + .call(doCmd('nextWord')) + .expectSpeechWithLocale('fr', `français: Comment`) + .call(doCmd('previousWord')) + .expectSpeechWithLocale('en', `English: .`) + .call(doCmd('previousWord')) + .expectSpeechWithLocale('en', `you`) + .replay(); + }); + +TEST_F( + 'ChromeVoxLocaleOutputHelperTest', 'CharacterNavigationTest', + async function() { + const mockFeedback = this.createMockFeedback(); + await this.runWithLoadedTree(this.nestedLanguagesLabeledDoc); + localStorage['languageSwitching'] = 'true'; + this.setAvailableVoices(); + mockFeedback.call(doCmd('jumpToTop')) + .expectSpeechWithLocale( + 'en', 'In the morning, I sometimes eat breakfast.') + .call(doCmd('nextLine')) + .expectSpeechWithLocale( + 'fr', 'français: Dans l\'apres-midi, je dejeune.') + .call(doCmd('nextCharacter')) + .expectSpeechWithLocale('fr', `a`) + .call(doCmd('nextCharacter')) + .expectSpeechWithLocale('fr', `n`) + .call(doCmd('nextCharacter')) + .expectSpeechWithLocale('fr', `s`) + .call(doCmd('nextCharacter')) + .expectSpeechWithLocale('fr', ` `) + .call(doCmd('nextCharacter')) + .expectSpeechWithLocale('fr', `l`) + .call(doCmd('nextLine')) + .expectSpeechWithLocale( + 'en', `English: Hello it's a pleasure to meet you. `) + .call(doCmd('nextCharacter')) + .expectSpeechWithLocale('en', `e`) + .call(doCmd('previousCharacter')) + .expectSpeechWithLocale('en', `H`) + .call(doCmd('previousCharacter')) + .expectSpeechWithLocale('fr', `français: .`) + .call(doCmd('previousCharacter')) + .expectSpeechWithLocale('fr', `e`) + .call(doCmd('previousCharacter')) + .expectSpeechWithLocale('fr', `n`) + .call(doCmd('previousCharacter')) + .expectSpeechWithLocale('fr', `u`) + .call(doCmd('previousCharacter')) + .expectSpeechWithLocale('fr', `e`) + .call(doCmd('previousCharacter')) + .expectSpeechWithLocale('fr', `j`) + .replay(); }); TEST_F( 'ChromeVoxLocaleOutputHelperTest', 'SwitchBetweenChineseDialectsTest', - function() { + async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.chineseDoc, function() { - localStorage['languageSwitching'] = 'true'; - this.setAvailableVoices(); - mockFeedback.call(doCmd('jumpToTop')) - .expectSpeechWithLocale('en-us', 'United States') - .call(doCmd('nextLine')) - .expectSpeechWithLocale( - 'zh-hans', '中文(简体): Simplified Chinese') - .call(doCmd('nextLine')) - .expectSpeechWithLocale( - 'zh-hant', '中文(繁體): Traditional Chinese'); - mockFeedback.replay(); - }); + await this.runWithLoadedTree(this.chineseDoc); + localStorage['languageSwitching'] = 'true'; + this.setAvailableVoices(); + mockFeedback.call(doCmd('jumpToTop')) + .expectSpeechWithLocale('en-us', 'United States') + .call(doCmd('nextLine')) + .expectSpeechWithLocale('zh-hans', '中文(简体): Simplified Chinese') + .call(doCmd('nextLine')) + .expectSpeechWithLocale( + 'zh-hant', '中文(繁體): Traditional Chinese'); + mockFeedback.replay(); }); TEST_F( 'ChromeVoxLocaleOutputHelperTest', 'SwitchBetweenPortugueseDialectsTest', - function() { + async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.portugueseDoc, function() { - localStorage['languageSwitching'] = 'true'; - this.setAvailableVoices(); - mockFeedback.call(doCmd('jumpToTop')) - .expectSpeechWithLocale('en-us', 'United States') - .call(doCmd('nextLine')) - .expectSpeechWithLocale('pt-br', 'português (Brasil): Brazil') - .call(doCmd('nextLine')) - .expectSpeechWithLocale('pt-pt', 'português (Portugal): Portugal'); - mockFeedback.replay(); - }); + await this.runWithLoadedTree(this.portugueseDoc); + localStorage['languageSwitching'] = 'true'; + this.setAvailableVoices(); + mockFeedback.call(doCmd('jumpToTop')) + .expectSpeechWithLocale('en-us', 'United States') + .call(doCmd('nextLine')) + .expectSpeechWithLocale('pt-br', 'português (Brasil): Brazil') + .call(doCmd('nextLine')) + .expectSpeechWithLocale('pt-pt', 'português (Portugal): Portugal'); + mockFeedback.replay(); }); // Tests logic in shouldAnnounceLocale_(). We only announce the locale once when @@ -581,26 +572,24 @@ // less specific locales, e.g. 'en-us' -> 'en' should not be announced. Finally, // subsequent transitions to the same locale, e.g. 'en' -> 'en-us' should not be // announced. -TEST_F('ChromeVoxLocaleOutputHelperTest', 'MaybeAnnounceLocale', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` +TEST_F( + 'ChromeVoxLocaleOutputHelperTest', 'MaybeAnnounceLocale', async function() { + const mockFeedback = this.createMockFeedback(); + await this.runWithLoadedTree(` <p lang="en">Start</p> <p lang="en-ca">Middle</p> <p lang="en">Penultimate</p> <p lang="en-ca">End</p> - `, - function() { - localStorage['languageSwitching'] = 'true'; - this.setAvailableVoices(); - mockFeedback.call(doCmd('jumpToTop')) - .expectSpeechWithLocale('en', 'Start') - .call(doCmd('nextObject')) - .expectSpeechWithLocale('en-ca', 'English (Canada): Middle') - .call(doCmd('nextObject')) - .expectSpeechWithLocale('en', 'Penultimate') - .call(doCmd('nextObject')) - .expectSpeechWithLocale('en-ca', 'End') - .replay(); - }); -}); + `); + localStorage['languageSwitching'] = 'true'; + this.setAvailableVoices(); + mockFeedback.call(doCmd('jumpToTop')) + .expectSpeechWithLocale('en', 'Start') + .call(doCmd('nextObject')) + .expectSpeechWithLocale('en-ca', 'English (Canada): Middle') + .call(doCmd('nextObject')) + .expectSpeechWithLocale('en', 'Penultimate') + .call(doCmd('nextObject')) + .expectSpeechWithLocale('en-ca', 'End') + .replay(); + });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_test.js index 4acd24f1..ad2414f 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_test.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_test.js
@@ -106,394 +106,371 @@ }; -TEST_F('ChromeVoxOutputE2ETest', 'Links', function() { - this.runWithLoadedTree('<a href="#">Click here</a>', function(root) { - const el = root.firstChild.firstChild; - const range = cursors.Range.fromNode(el); - const o = new Output().withSpeechAndBraille(range, null, 'navigate'); - assertEqualsJSON( - { - string_: 'Click here|Internal link|Press Search+Space to activate', - 'spans_': [ - // Attributes. - {value: 'name', start: 0, end: 10}, +TEST_F('ChromeVoxOutputE2ETest', 'Links', async function() { + const root = await this.runWithLoadedTree('<a href="#">Click here</a>'); + const el = root.firstChild.firstChild; + const range = cursors.Range.fromNode(el); + const o = new Output().withSpeechAndBraille(range, null, 'navigate'); + assertEqualsJSON( + { + string_: 'Click here|Internal link|Press Search+Space to activate', + 'spans_': [ + // Attributes. + {value: 'name', start: 0, end: 10}, - // Link earcon (based on the name). - {value: {earconId: 'LINK'}, start: 0, end: 10}, + // Link earcon (based on the name). + {value: {earconId: 'LINK'}, start: 0, end: 10}, - {value: {'delay': true}, start: 25, end: 55} - ] - }, - o.speechOutputForTest); - checkBrailleOutput( - 'Click here intlnk', - [{value: new OutputNodeSpan(el), start: 0, end: 17}], o); - }); + {value: {'delay': true}, start: 25, end: 55} + ] + }, + o.speechOutputForTest); + checkBrailleOutput( + 'Click here intlnk', [{value: new OutputNodeSpan(el), start: 0, end: 17}], + o); }); -TEST_F('ChromeVoxOutputE2ETest', 'Checkbox', function() { - this.runWithLoadedTree('<input type="checkbox">', function(root) { - const el = root.firstChild.firstChild; - const range = cursors.Range.fromNode(el); - const o = new Output().withSpeechAndBraille(range, null, 'navigate'); - checkSpeechOutput( - '|Check box|Not checked|Press Search+Space to toggle', - [ - {value: new OutputEarconAction('CHECK_OFF'), start: 0, end: 0}, - {value: 'role', start: 1, end: 10}, - {value: {'delay': true}, start: 23, end: 51} - ], - o); - checkBrailleOutput( - 'chk ( )', [{value: new OutputNodeSpan(el), start: 0, end: 7}], o); - }); +TEST_F('ChromeVoxOutputE2ETest', 'Checkbox', async function() { + const root = await this.runWithLoadedTree('<input type="checkbox">'); + const el = root.firstChild.firstChild; + const range = cursors.Range.fromNode(el); + const o = new Output().withSpeechAndBraille(range, null, 'navigate'); + checkSpeechOutput( + '|Check box|Not checked|Press Search+Space to toggle', + [ + {value: new OutputEarconAction('CHECK_OFF'), start: 0, end: 0}, + {value: 'role', start: 1, end: 10}, + {value: {'delay': true}, start: 23, end: 51} + ], + o); + checkBrailleOutput( + 'chk ( )', [{value: new OutputNodeSpan(el), start: 0, end: 7}], o); }); -TEST_F('ChromeVoxOutputE2ETest', 'InLineTextBoxValueGetsIgnored', function() { - this.runWithLoadedTree('<p>OK', function(root) { - let el = root.firstChild.firstChild.firstChild; - assertEquals('inlineTextBox', el.role); - let range = cursors.Range.fromNode(el); - let o = new Output().withSpeechAndBraille(range, null, 'navigate'); - assertEqualsJSON( - { - string_: 'OK', - 'spans_': [ - // Attributes. - {value: 'name', start: 0, end: 2} - ] - }, - o.speechOutputForTest); - checkBrailleOutput( - 'OK', [{value: new OutputNodeSpan(el), start: 0, end: 2}], o); +TEST_F( + 'ChromeVoxOutputE2ETest', 'InLineTextBoxValueGetsIgnored', + async function() { + const root = await this.runWithLoadedTree('<p>OK'); + let el = root.firstChild.firstChild.firstChild; + assertEquals('inlineTextBox', el.role); + let range = cursors.Range.fromNode(el); + let o = new Output().withSpeechAndBraille(range, null, 'navigate'); + assertEqualsJSON( + { + string_: 'OK', + 'spans_': [ + // Attributes. + {value: 'name', start: 0, end: 2} + ] + }, + o.speechOutputForTest); + checkBrailleOutput( + 'OK', [{value: new OutputNodeSpan(el), start: 0, end: 2}], o); - el = root.firstChild.firstChild; - assertEquals('staticText', el.role); - range = cursors.Range.fromNode(el); - o = new Output().withSpeechAndBraille(range, null, 'navigate'); - assertEqualsJSON( - { - string_: 'OK', - 'spans_': [ - // Attributes. - {value: 'name', start: 0, end: 2} - ] - }, - o.speechOutputForTest); - checkBrailleOutput( - 'OK', [{value: new OutputNodeSpan(el), start: 0, end: 2}], o); - }); -}); + el = root.firstChild.firstChild; + assertEquals('staticText', el.role); + range = cursors.Range.fromNode(el); + o = new Output().withSpeechAndBraille(range, null, 'navigate'); + assertEqualsJSON( + { + string_: 'OK', + 'spans_': [ + // Attributes. + {value: 'name', start: 0, end: 2} + ] + }, + o.speechOutputForTest); + checkBrailleOutput( + 'OK', [{value: new OutputNodeSpan(el), start: 0, end: 2}], o); + }); -TEST_F('ChromeVoxOutputE2ETest', 'Headings', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxOutputE2ETest', 'Headings', async function() { + const root = await this.runWithLoadedTree(` <h1>a</h1><h2>b</h2><h3>c</h3><h4>d</h4><h5>e</h5><h6>f</h6> - <h1><a href="a.com">b</a></h1> `, - function(root) { - let el = root.firstChild; - for (let i = 1; i <= 6; ++i) { - const range = cursors.Range.fromNode(el); - const o = new Output().withSpeechAndBraille(range, null, 'navigate'); - const letter = String.fromCharCode('a'.charCodeAt(0) + i - 1); - assertEqualsJSON( - { - string_: letter + '|Heading ' + i, - 'spans_': [ - // Attributes. - {value: 'nameOrDescendants', start: 0, end: 1} - ] - }, - o.speechOutputForTest); - checkBrailleOutput( - letter + ' h' + i, - [{value: new OutputNodeSpan(el), start: 0, end: 4}], o); - el = el.nextSibling; - } + <h1><a href="a.com">b</a></h1> `); + let el = root.firstChild; + for (let i = 1; i <= 6; ++i) { + const range = cursors.Range.fromNode(el); + const o = new Output().withSpeechAndBraille(range, null, 'navigate'); + const letter = String.fromCharCode('a'.charCodeAt(0) + i - 1); + assertEqualsJSON( + { + string_: letter + '|Heading ' + i, + 'spans_': [ + // Attributes. + {value: 'nameOrDescendants', start: 0, end: 1} + ] + }, + o.speechOutputForTest); + checkBrailleOutput( + letter + ' h' + i, [{value: new OutputNodeSpan(el), start: 0, end: 4}], + o); + el = el.nextSibling; + } - range = cursors.Range.fromNode(el); - o = new Output().withSpeechAndBraille(range, null, 'navigate'); - assertEqualsJSON( - { - string_: 'b|Link|Heading 1', - 'spans_': [ - {value: 'name', start: 0, end: 1}, - {value: new OutputEarconAction('LINK'), start: 0, end: 1}, - {value: 'role', start: 2, end: 6} - ] - }, - o.speechOutputForTest); - checkBrailleOutput( - 'b lnk h1', - [ - { - value: new OutputNodeSpan(el.firstChild.firstChild), - start: 0, - end: 1 - }, - {value: new OutputNodeSpan(el), start: 0, end: 8}, - {value: new OutputNodeSpan(el.firstChild), start: 2, end: 5} - ], - o); - }); + range = cursors.Range.fromNode(el); + o = new Output().withSpeechAndBraille(range, null, 'navigate'); + assertEqualsJSON( + { + string_: 'b|Link|Heading 1', + 'spans_': [ + {value: 'name', start: 0, end: 1}, + {value: new OutputEarconAction('LINK'), start: 0, end: 1}, + {value: 'role', start: 2, end: 6} + ] + }, + o.speechOutputForTest); + checkBrailleOutput( + 'b lnk h1', + [ + {value: new OutputNodeSpan(el.firstChild.firstChild), start: 0, end: 1}, + {value: new OutputNodeSpan(el), start: 0, end: 8}, + {value: new OutputNodeSpan(el.firstChild), start: 2, end: 5} + ], + o); }); // TODO(crbug.com/901725): test is flaky. -TEST_F('ChromeVoxOutputE2ETest', 'DISABLED_Audio', function() { - this.runWithLoadedTree( - '<audio src="foo.mp3" controls></audio>', function(root) { - let el = root.find({role: RoleType.BUTTON}); - let range = cursors.Range.fromNode(el); - let o = new Output().withoutHints().withSpeechAndBraille( - range, null, 'navigate'); +TEST_F('ChromeVoxOutputE2ETest', 'DISABLED_Audio', async function() { + const root = + await this.runWithLoadedTree('<audio src="foo.mp3" controls></audio>'); + let el = root.find({role: RoleType.BUTTON}); + let range = cursors.Range.fromNode(el); + let o = + new Output().withoutHints().withSpeechAndBraille(range, null, 'navigate'); - checkSpeechOutput( - 'play|Disabled|Button|audio|Tool bar', - [ - {value: new OutputEarconAction('BUTTON'), start: 0, end: 4}, - {value: 'name', start: 21, end: 26}, - {value: 'role', start: 27, end: 35} - ], - o); + checkSpeechOutput( + 'play|Disabled|Button|audio|Tool bar', + [ + {value: new OutputEarconAction('BUTTON'), start: 0, end: 4}, + {value: 'name', start: 21, end: 26}, {value: 'role', start: 27, end: 35} + ], + o); - checkBrailleOutput( - 'play xx btn audio tlbar', - [ - {value: new OutputNodeSpan(el), start: 0, end: 11}, - {value: new OutputNodeSpan(el.parent), start: 12, end: 23} - ], - o); + checkBrailleOutput( + 'play xx btn audio tlbar', + [ + {value: new OutputNodeSpan(el), start: 0, end: 11}, + {value: new OutputNodeSpan(el.parent), start: 12, end: 23} + ], + o); - // TODO(dtseng): Replace with a query. - el = el.nextSibling.nextSibling.nextSibling; - const prevRange = range; - range = cursors.Range.fromNode(el); - o = new Output().withoutHints().withSpeechAndBraille( - range, prevRange, 'navigate'); - checkSpeechOutput( - '|audio time scrubber|Slider|0:00|Min 0|Max 0', - [ - {value: 'name', start: 0, end: 0}, - {value: new OutputEarconAction('SLIDER'), start: 0, end: 0}, - {value: 'description', start: 1, end: 20}, - {value: 'role', start: 21, end: 27}, - {value: 'value', start: 28, end: 32} - ], - o); - checkBrailleOutput( - 'audio time scrubber sldr 0:00 min:0 max:0', - [{value: new OutputNodeSpan(el), start: 0, end: 41}], o); - }); + // TODO(dtseng): Replace with a query. + el = el.nextSibling.nextSibling.nextSibling; + const prevRange = range; + range = cursors.Range.fromNode(el); + o = new Output().withoutHints().withSpeechAndBraille( + range, prevRange, 'navigate'); + checkSpeechOutput( + '|audio time scrubber|Slider|0:00|Min 0|Max 0', + [ + {value: 'name', start: 0, end: 0}, + {value: new OutputEarconAction('SLIDER'), start: 0, end: 0}, + {value: 'description', start: 1, end: 20}, + {value: 'role', start: 21, end: 27}, + {value: 'value', start: 28, end: 32} + ], + o); + checkBrailleOutput( + 'audio time scrubber sldr 0:00 min:0 max:0', + [{value: new OutputNodeSpan(el), start: 0, end: 41}], o); }); -TEST_F('ChromeVoxOutputE2ETest', 'Input', function() { - this.runWithLoadedTree( +TEST_F('ChromeVoxOutputE2ETest', 'Input', async function() { + const root = await this.runWithLoadedTree( '<input type="text"></input>' + - '<input type="email"></input>' + - '<input type="password"></input>' + - '<input type="tel"></input>' + - '<input type="number"></input>' + - '<input type="time"></input>' + - '<input type="date"></input>' + - '<input type="file"</input>' + - '<input type="search"</input>' + - '<input type="invalidType"</input>', - function(root) { - const expectedSpansNonSearchBox = [ - {value: 'name', start: 0, end: 0}, - {value: new OutputEarconAction('EDITABLE_TEXT'), start: 0, end: 0}, - {value: new OutputSelectionSpan(0, 0, 0), start: 1, end: 1}, - {value: 'value', start: 1, end: 1}, {value: 'inputType', start: 2} - ]; - const expectedSpansForSearchBox = [ - {value: 'name', start: 0, end: 0}, - {value: new OutputEarconAction('EDITABLE_TEXT'), start: 0, end: 0}, - {value: new OutputSelectionSpan(0, 0, 0), start: 1, end: 1}, - {value: 'value', start: 1, end: 1}, {value: 'role', start: 2, end: 8} - ]; + '<input type="email"></input>' + + '<input type="password"></input>' + + '<input type="tel"></input>' + + '<input type="number"></input>' + + '<input type="time"></input>' + + '<input type="date"></input>' + + '<input type="file"</input>' + + '<input type="search"</input>' + + '<input type="invalidType"</input>'); + const expectedSpansNonSearchBox = [ + {value: 'name', start: 0, end: 0}, + {value: new OutputEarconAction('EDITABLE_TEXT'), start: 0, end: 0}, + {value: new OutputSelectionSpan(0, 0, 0), start: 1, end: 1}, + {value: 'value', start: 1, end: 1}, {value: 'inputType', start: 2} + ]; + const expectedSpansForSearchBox = [ + {value: 'name', start: 0, end: 0}, + {value: new OutputEarconAction('EDITABLE_TEXT'), start: 0, end: 0}, + {value: new OutputSelectionSpan(0, 0, 0), start: 1, end: 1}, + {value: 'value', start: 1, end: 1}, {value: 'role', start: 2, end: 8} + ]; - const expectedSpeechValues = [ - '||Edit text', '||Edit text, email entry', '||Password edit text', - '||Edit text numeric only', + const expectedSpeechValues = [ + '||Edit text', '||Edit text, email entry', '||Password edit text', + '||Edit text numeric only', + [ + '|Spin button', + [ + {value: 'name', start: 0, end: 0}, + {value: new OutputEarconAction('LISTBOX'), start: 0, end: 0}, + {value: 'role', start: 1, end: 12} + ] + ], + ['Time control', [{value: 'role', start: 0, end: 12}]], + ['Date control', [{value: 'role', start: 0, end: 12}]], + [ + 'No file chosen, Choose File|Button', + [ + {value: 'name', start: 0, end: 27}, + {value: new OutputEarconAction('BUTTON'), start: 0, end: 27}, + {value: 'role', start: 28, end: 34} + ] + ], + '||Search', '||Edit text' + ]; + // TODO(plundblad): Some of these are wrong, there should be an initial + // space for the cursor in edit fields. + const expectedBrailleValues = [ + ' ed', ' @ed 8dot', ' pwded', ' #ed', {string_: 'spnbtn', spans_: []}, + {string_: 'time'}, {string_: 'date'}, + {string_: 'No file chosen, Choose File btn'}, ' search', ' ed' + ]; + assertEquals(expectedSpeechValues.length, expectedBrailleValues.length); + + let el = root.firstChild.firstChild; + expectedSpeechValues.forEach(function(expectedValue) { + const range = cursors.Range.fromNode(el); + const o = new Output().withoutHints().withSpeechAndBraille( + range, null, 'navigate'); + let expectedSpansForValue = null; + if (typeof expectedValue === 'object') { + checkSpeechOutput(expectedValue[0], expectedValue[1], o); + } else { + expectedSpansForValue = expectedValue === '||Search' ? + expectedSpansForSearchBox : + expectedSpansNonSearchBox; + expectedSpansForValue[4].end = expectedValue.length; + checkSpeechOutput(expectedValue, expectedSpansForValue, o); + } + el = el.nextSibling; + }); + + el = root.firstChild.firstChild; + expectedBrailleValues.forEach(function(expectedValue) { + const range = cursors.Range.fromNode(el); + const o = new Output().withoutHints().withBraille(range, null, 'navigate'); + if (typeof expectedValue === 'string') { + checkBrailleOutput( + expectedValue, [ - '|Spin button', - [ - {value: 'name', start: 0, end: 0}, - {value: new OutputEarconAction('LISTBOX'), start: 0, end: 0}, - {value: 'role', start: 1, end: 12} - ] + {value: {startIndex: 0, endIndex: 0}, start: 0, end: 0}, + {value: new OutputNodeSpan(el), start: 0, end: expectedValue.length} ], - ['Time control', [{value: 'role', start: 0, end: 12}]], - ['Date control', [{value: 'role', start: 0, end: 12}]], - [ - 'No file chosen, Choose File|Button', - [ - {value: 'name', start: 0, end: 27}, - {value: new OutputEarconAction('BUTTON'), start: 0, end: 27}, - {value: 'role', start: 28, end: 34} - ] - ], - '||Search', '||Edit text' - ]; - // TODO(plundblad): Some of these are wrong, there should be an initial - // space for the cursor in edit fields. - const expectedBrailleValues = [ - ' ed', ' @ed 8dot', ' pwded', ' #ed', {string_: 'spnbtn', spans_: []}, - {string_: 'time'}, {string_: 'date'}, - {string_: 'No file chosen, Choose File btn'}, ' search', ' ed' - ]; - assertEquals(expectedSpeechValues.length, expectedBrailleValues.length); + o); + } else { + let spans = [{ + value: new OutputNodeSpan(el), + start: 0, + end: expectedValue.string_.length + }]; + if (expectedValue.spans_) { + spans = spans.concat(expectedValue.spans_); + } - let el = root.firstChild.firstChild; - expectedSpeechValues.forEach(function(expectedValue) { - const range = cursors.Range.fromNode(el); - const o = new Output().withoutHints().withSpeechAndBraille( - range, null, 'navigate'); - let expectedSpansForValue = null; - if (typeof expectedValue === 'object') { - checkSpeechOutput(expectedValue[0], expectedValue[1], o); - } else { - expectedSpansForValue = expectedValue === '||Search' ? - expectedSpansForSearchBox : - expectedSpansNonSearchBox; - expectedSpansForValue[4].end = expectedValue.length; - checkSpeechOutput(expectedValue, expectedSpansForValue, o); - } - el = el.nextSibling; - }); - - el = root.firstChild.firstChild; - expectedBrailleValues.forEach(function(expectedValue) { - const range = cursors.Range.fromNode(el); - const o = - new Output().withoutHints().withBraille(range, null, 'navigate'); - if (typeof expectedValue === 'string') { - checkBrailleOutput( - expectedValue, - [ - {value: {startIndex: 0, endIndex: 0}, start: 0, end: 0}, { - value: new OutputNodeSpan(el), - start: 0, - end: expectedValue.length - } - ], - o); - } else { - let spans = [{ - value: new OutputNodeSpan(el), - start: 0, - end: expectedValue.string_.length - }]; - if (expectedValue.spans_) { - spans = spans.concat(expectedValue.spans_); - } - - checkBrailleOutput(expectedValue.string_, spans, o); - } - el = el.nextSibling; - }); - }); + checkBrailleOutput(expectedValue.string_, spans, o); + } + el = el.nextSibling; + }); }); -TEST_F('ChromeVoxOutputE2ETest', 'List', function() { - this.runWithLoadedTree( - '<ul aria-label="first"><li aria-label="a">a<li>b<li>c</ul>', - function(root) { - const el = root.firstChild.firstChild; - const range = cursors.Range.fromNode(el); - const o = new Output().withSpeechAndBraille(range, null, 'navigate'); - checkSpeechOutput( - 'a|List item|first|List|with 3 items', - [ - {value: {earconId: 'LIST_ITEM'}, start: 0, end: 1}, - {value: 'name', start: 12, end: 17}, - {value: 'role', start: 18, end: 22} - ], - o); - // TODO(plundblad): This output is wrong. Add special handling for - // braille here. - checkBrailleOutput( - 'a lstitm first lst +3', - [ - {value: new OutputNodeSpan(el), start: 0, end: 8}, - {value: new OutputNodeSpan(el.parent), start: 9, end: 21} - ], - o); - }); +TEST_F('ChromeVoxOutputE2ETest', 'List', async function() { + const root = await this.runWithLoadedTree( + '<ul aria-label="first"><li aria-label="a">a<li>b<li>c</ul>'); + const el = root.firstChild.firstChild; + const range = cursors.Range.fromNode(el); + const o = new Output().withSpeechAndBraille(range, null, 'navigate'); + checkSpeechOutput( + 'a|List item|first|List|with 3 items', + [ + {value: {earconId: 'LIST_ITEM'}, start: 0, end: 1}, + {value: 'name', start: 12, end: 17}, {value: 'role', start: 18, end: 22} + ], + o); + // TODO(plundblad): This output is wrong. Add special handling for + // braille here. + checkBrailleOutput( + 'a lstitm first lst +3', + [ + {value: new OutputNodeSpan(el), start: 0, end: 8}, + {value: new OutputNodeSpan(el.parent), start: 9, end: 21} + ], + o); }); -TEST_F('ChromeVoxOutputE2ETest', 'Tree', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxOutputE2ETest', 'Tree', async function() { + const root = await this.runWithLoadedTree(` <ul role="tree" style="list-style-type:none"> <li aria-expanded="true" role="treeitem">a <li role="treeitem">b <li aria-expanded="false" role="treeitem">c </ul> - `, - function(root) { - let el = root.firstChild.children[0].firstChild; - let range = cursors.Range.fromNode(el); - let o = new Output().withSpeechAndBraille(range, null, 'navigate'); - checkSpeechOutput( - 'a|Tree item|Expanded| 1 of 3 | level 1 |Tree|with 3 items', - [ - {value: 'name', 'start': 0, end: 1}, - {value: 'state', start: 12, end: 20}, - {value: 'role', 'start': 40, end: 44}, - ], - o); - checkBrailleOutput( - 'a tritm - 1/3 level 1 tree +3', - [ - {value: new OutputNodeSpan(el), start: 0, end: 1}, - {value: new OutputNodeSpan(el.parent), start: 2, end: 22}, - {value: new OutputNodeSpan(el.parent.parent), start: 22, end: 29} - ], - o); + `); + let el = root.firstChild.children[0].firstChild; + let range = cursors.Range.fromNode(el); + let o = new Output().withSpeechAndBraille(range, null, 'navigate'); + checkSpeechOutput( + 'a|Tree item|Expanded| 1 of 3 | level 1 |Tree|with 3 items', + [ + {value: 'name', 'start': 0, end: 1}, + {value: 'state', start: 12, end: 20}, + {value: 'role', 'start': 40, end: 44}, + ], + o); + checkBrailleOutput( + 'a tritm - 1/3 level 1 tree +3', + [ + {value: new OutputNodeSpan(el), start: 0, end: 1}, + {value: new OutputNodeSpan(el.parent), start: 2, end: 22}, + {value: new OutputNodeSpan(el.parent.parent), start: 22, end: 29} + ], + o); - el = root.firstChild.children[1].firstChild; - range = cursors.Range.fromNode(el); - o = new Output().withSpeechAndBraille(range, null, 'navigate'); - checkSpeechOutput( - 'b|Tree item| 2 of 3 | level 1 |Tree|with 3 items', - [ - {value: 'name', start: 0, end: 1}, - {value: 'role', 'start': 31, end: 35} - ], - o); - checkBrailleOutput( - 'b tritm 2/3 level 1 tree +3', - [ - {value: new OutputNodeSpan(el), start: 0, end: 1}, - {value: new OutputNodeSpan(el.parent), start: 2, end: 20}, - {value: new OutputNodeSpan(el.parent.parent), start: 20, end: 27} - ], - o); + el = root.firstChild.children[1].firstChild; + range = cursors.Range.fromNode(el); + o = new Output().withSpeechAndBraille(range, null, 'navigate'); + checkSpeechOutput( + 'b|Tree item| 2 of 3 | level 1 |Tree|with 3 items', + [ + {value: 'name', start: 0, end: 1}, {value: 'role', 'start': 31, end: 35} + ], + o); + checkBrailleOutput( + 'b tritm 2/3 level 1 tree +3', + [ + {value: new OutputNodeSpan(el), start: 0, end: 1}, + {value: new OutputNodeSpan(el.parent), start: 2, end: 20}, + {value: new OutputNodeSpan(el.parent.parent), start: 20, end: 27} + ], + o); - el = root.firstChild.children[2].firstChild; - range = cursors.Range.fromNode(el); - o = new Output().withSpeechAndBraille(range, null, 'navigate'); - checkSpeechOutput( - 'c|Tree item|Collapsed| 3 of 3 | level 1 |Tree|with 3 items', - [ - {value: 'name', 'start': 0, end: 1}, - {value: 'state', start: 12, end: 21}, - {value: 'role', 'start': 41, end: 45}, - ], - o); - checkBrailleOutput( - 'c tritm + 3/3 level 1 tree +3', - [ - {value: new OutputNodeSpan(el), start: 0, end: 1}, - {value: new OutputNodeSpan(el.parent), start: 2, end: 22}, - {value: new OutputNodeSpan(el.parent.parent), start: 22, end: 29} - ], - o); - }); + el = root.firstChild.children[2].firstChild; + range = cursors.Range.fromNode(el); + o = new Output().withSpeechAndBraille(range, null, 'navigate'); + checkSpeechOutput( + 'c|Tree item|Collapsed| 3 of 3 | level 1 |Tree|with 3 items', + [ + {value: 'name', 'start': 0, end: 1}, + {value: 'state', start: 12, end: 21}, + {value: 'role', 'start': 41, end: 45}, + ], + o); + checkBrailleOutput( + 'c tritm + 3/3 level 1 tree +3', + [ + {value: new OutputNodeSpan(el), start: 0, end: 1}, + {value: new OutputNodeSpan(el.parent), start: 2, end: 22}, + {value: new OutputNodeSpan(el.parent.parent), start: 22, end: 29} + ], + o); }); -TEST_F('ChromeVoxOutputE2ETest', 'Menu', function() { +TEST_F('ChromeVoxOutputE2ETest', 'Menu', async function() { const site = ` <div role="menu"> <div role="menuitem">a</div> @@ -502,78 +479,72 @@ </div> <div role="menubar" aria-orientation="horizontal"></div> `; - this.runWithLoadedTree(site, function(root) { - let el = root.firstChild.firstChild; - let range = cursors.Range.fromNode(el); - let o = new Output().withSpeechAndBraille(range, null, 'navigate'); - checkSpeechOutput( - 'a|Menu item| 1 of 3 |Menu', - [ - {value: 'name', start: 0, end: 1}, {value: 'role', start: 21, end: 25} - ], - o); - checkBrailleOutput( - 'a mnuitm 1/3 mnu', - [ - {value: new OutputNodeSpan(el), start: 0, end: 12}, - {value: new OutputNodeSpan(el.parent), start: 13, end: 16} - ], - o); + const root = await this.runWithLoadedTree(site); + let el = root.firstChild.firstChild; + let range = cursors.Range.fromNode(el); + let o = new Output().withSpeechAndBraille(range, null, 'navigate'); + checkSpeechOutput( + 'a|Menu item| 1 of 3 |Menu', + [{value: 'name', start: 0, end: 1}, {value: 'role', start: 21, end: 25}], + o); + checkBrailleOutput( + 'a mnuitm 1/3 mnu', + [ + {value: new OutputNodeSpan(el), start: 0, end: 12}, + {value: new OutputNodeSpan(el.parent), start: 13, end: 16} + ], + o); - // Ancestry. - el = root.firstChild; - range = cursors.Range.fromNode(el); - o = new Output().withSpeechAndBraille(range, null, 'navigate'); - checkSpeechOutput( - 'Menu|with 3 items|' + - 'Press up or down arrow to navigate; enter to activate', - [ - {value: 'role', start: 0, end: 4}, - {value: {delay: true}, start: 18, end: 71} - ], - o); + // Ancestry. + el = root.firstChild; + range = cursors.Range.fromNode(el); + o = new Output().withSpeechAndBraille(range, null, 'navigate'); + checkSpeechOutput( + 'Menu|with 3 items|' + + 'Press up or down arrow to navigate; enter to activate', + [ + {value: 'role', start: 0, end: 4}, + {value: {delay: true}, start: 18, end: 71} + ], + o); - el = root.lastChild; - range = cursors.Range.fromNode(el); - o = new Output().withSpeechAndBraille(range, null, 'navigate'); - checkSpeechOutput( - 'Menu bar|Press left or right arrow to navigate; enter to activate', - [ - {value: 'role', start: 0, end: 8}, - {value: {delay: true}, start: 9, end: 65} - ], - o); - }); + el = root.lastChild; + range = cursors.Range.fromNode(el); + o = new Output().withSpeechAndBraille(range, null, 'navigate'); + checkSpeechOutput( + 'Menu bar|Press left or right arrow to navigate; enter to activate', + [ + {value: 'role', start: 0, end: 8}, + {value: {delay: true}, start: 9, end: 65} + ], + o); }); -TEST_F('ChromeVoxOutputE2ETest', 'ListBox', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxOutputE2ETest', 'ListBox', async function() { + const root = await this.runWithLoadedTree(` <select multiple> <option>1</option> <option>2</option> </select> - `, - function(root) { - const el = root.firstChild.firstChild.firstChild; - const range = cursors.Range.fromNode(el); - const o = new Output().withSpeechAndBraille(range, null, 'navigate'); - checkSpeechOutput( - '1|List item| 1 of 2 |Not selected|List box|with 2 items', - [ - {value: 'name', start: 0, end: 1}, - {value: new OutputEarconAction('LIST_ITEM'), start: 0, end: 1}, - {value: 'role', start: 34, end: 42} - ], - o); - checkBrailleOutput( - '1 lstitm 1/2 ( ) lstbx +2', - [ - {value: new OutputNodeSpan(el), start: 0, end: 16}, - {value: new OutputNodeSpan(el.parent), start: 17, end: 25} - ], - o); - }); + `); + const el = root.firstChild.firstChild.firstChild; + const range = cursors.Range.fromNode(el); + const o = new Output().withSpeechAndBraille(range, null, 'navigate'); + checkSpeechOutput( + '1|List item| 1 of 2 |Not selected|List box|with 2 items', + [ + {value: 'name', start: 0, end: 1}, + {value: new OutputEarconAction('LIST_ITEM'), start: 0, end: 1}, + {value: 'role', start: 34, end: 42} + ], + o); + checkBrailleOutput( + '1 lstitm 1/2 ( ) lstbx +2', + [ + {value: new OutputNodeSpan(el), start: 0, end: 16}, + {value: new OutputNodeSpan(el.parent), start: 17, end: 25} + ], + o); }); SYNC_TEST_F('ChromeVoxOutputE2ETest', 'MessageIdAndEarconValidity', function() { @@ -664,249 +635,208 @@ } }); -TEST_F('ChromeVoxOutputE2ETest', 'DivOmitsRole', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxOutputE2ETest', 'DivOmitsRole', async function() { + const root = await this.runWithLoadedTree(` <div>that has content</div> <div></div> <div role='group'><div>nested content</div></div> - `, - function(root) { - const el = root.firstChild.firstChild; - const range = cursors.Range.fromNode(el); - const o = new Output().withSpeechAndBraille(range, null, 'navigate'); - checkSpeechOutput( - 'that has content', [{value: 'name', start: 0, end: 16}], o); - checkBrailleOutput( - 'that has content', - [{value: new OutputNodeSpan(el), start: 0, end: 16}], o); - }); + `); + const el = root.firstChild.firstChild; + const range = cursors.Range.fromNode(el); + const o = new Output().withSpeechAndBraille(range, null, 'navigate'); + checkSpeechOutput( + 'that has content', [{value: 'name', start: 0, end: 16}], o); + checkBrailleOutput( + 'that has content', [{value: new OutputNodeSpan(el), start: 0, end: 16}], + o); }); -TEST_F('ChromeVoxOutputE2ETest', 'LessVerboseAncestry', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxOutputE2ETest', 'LessVerboseAncestry', async function() { + const root = await this.runWithLoadedTree(` <div role="banner"><p>inside</p></div> <div role="banner"><p>inside</p></div> <div role="navigation"><p>inside</p></div> - `, - function(root) { - const first = root.children[0].firstChild; - const second = root.children[1].firstChild; - const third = root.children[2].firstChild; - const firstRange = cursors.Range.fromNode(first); - const secondRange = cursors.Range.fromNode(second); - const thirdRange = cursors.Range.fromNode(third); + `); + const first = root.children[0].firstChild; + const second = root.children[1].firstChild; + const third = root.children[2].firstChild; + const firstRange = cursors.Range.fromNode(first); + const secondRange = cursors.Range.fromNode(second); + const thirdRange = cursors.Range.fromNode(third); - const oWithoutPrev = - new Output().withSpeech(firstRange, null, 'navigate'); - const oWithPrev = - new Output().withSpeech(secondRange, firstRange, 'navigate'); - const oWithPrevExit = - new Output().withSpeech(thirdRange, secondRange, 'navigate'); - assertEquals('inside|Banner', oWithoutPrev.speechOutputForTest.string_); + const oWithoutPrev = new Output().withSpeech(firstRange, null, 'navigate'); + const oWithPrev = + new Output().withSpeech(secondRange, firstRange, 'navigate'); + const oWithPrevExit = + new Output().withSpeech(thirdRange, secondRange, 'navigate'); + assertEquals('inside|Banner', oWithoutPrev.speechOutputForTest.string_); - // Make sure we don't read the exited ancestry change. - assertEquals('inside|Banner', oWithPrev.speechOutputForTest.string_); + // Make sure we don't read the exited ancestry change. + assertEquals('inside|Banner', oWithPrev.speechOutputForTest.string_); - // Different role; do read the exited ancestry here. - assertEquals( - 'inside|Navigation', oWithPrevExit.speechOutputForTest.string_); - }); + // Different role; do read the exited ancestry here. + assertEquals('inside|Navigation', oWithPrevExit.speechOutputForTest.string_); }); -TEST_F('ChromeVoxOutputE2ETest', 'Brief', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxOutputE2ETest', 'Brief', async function() { + const root = await this.runWithLoadedTree(` <div role="article"><p>inside</p></div> - `, - function(root) { - const node = root.children[0].firstChild; - const range = cursors.Range.fromNode(node); + `); + const node = root.children[0].firstChild; + const range = cursors.Range.fromNode(node); - localStorage['useVerboseMode'] = 'false'; - const oWithoutPrev = new Output().withSpeech(range, null, 'navigate'); - assertEquals('inside', oWithoutPrev.speechOutputForTest.string_); - }); + localStorage['useVerboseMode'] = 'false'; + const oWithoutPrev = new Output().withSpeech(range, null, 'navigate'); + assertEquals('inside', oWithoutPrev.speechOutputForTest.string_); }); -TEST_F('ChromeVoxOutputE2ETest', 'AuralStyledHeadings', function() { +TEST_F('ChromeVoxOutputE2ETest', 'AuralStyledHeadings', async function() { function toFixed(num) { return parseFloat(Number(num).toFixed(1)); } - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <h1>a</h1><h2>b</h2><h3>c</h3><h4>d</h4><h5>e</h5><h6>f</h6> - <h1><a href="a.com">b</a></h1> `, - function(root) { - let el = root.firstChild; - for (let i = 1; i <= 6; ++i) { - const range = cursors.Range.fromNode(el); - const o = new Output().withRichSpeech(range, null, 'navigate'); - const letter = String.fromCharCode('a'.charCodeAt(0) + i - 1); - assertEqualsJSON( - { - string_: letter + '|Heading ' + i, - 'spans_': [ - // Aural styles. - { - value: {'relativePitch': toFixed(-0.1 * i)}, - start: 0, - end: 0 - }, + <h1><a href="a.com">b</a></h1> `); + let el = root.firstChild; + for (let i = 1; i <= 6; ++i) { + const range = cursors.Range.fromNode(el); + const o = new Output().withRichSpeech(range, null, 'navigate'); + const letter = String.fromCharCode('a'.charCodeAt(0) + i - 1); + assertEqualsJSON( + { + string_: letter + '|Heading ' + i, + 'spans_': [ + // Aural styles. + {value: {'relativePitch': toFixed(-0.1 * i)}, start: 0, end: 0}, - // Attributes. - {value: 'nameOrDescendants', start: 0, end: 1}, + // Attributes. + {value: 'nameOrDescendants', start: 0, end: 1}, - {value: {'relativePitch': -0.2}, start: 2, end: 2} - ] - }, - o.speechOutputForTest); - el = el.nextSibling; - } - }); + {value: {'relativePitch': -0.2}, start: 2, end: 2} + ] + }, + o.speechOutputForTest); + el = el.nextSibling; + } }); -TEST_F('ChromeVoxOutputE2ETest', 'ToggleButton', function() { - this.runWithLoadedTree( - ` - <div role="button" aria-pressed="true">Subscribe</div>`, - function(root) { - const el = root.firstChild; - const o = new Output().withSpeechAndBraille(cursors.Range.fromNode(el)); - assertEqualsJSON( - { - string_: - '|Subscribe|Toggle Button|Pressed|Press Search+Space to toggle', - spans_: [ - {value: {earconId: 'CHECK_ON'}, start: 0, end: 0}, - {value: 'name', start: 1, end: 10}, - {value: 'role', start: 11, end: 24}, - {value: {'delay': true}, start: 33, end: 61} - ] - }, - o.speechOutputForTest); - assertEquals('Subscribe tgl btn =', o.brailleOutputForTest.string_); - }); +TEST_F('ChromeVoxOutputE2ETest', 'ToggleButton', async function() { + const root = await this.runWithLoadedTree(` + <div role="button" aria-pressed="true">Subscribe</div>`); + const el = root.firstChild; + const o = new Output().withSpeechAndBraille(cursors.Range.fromNode(el)); + assertEqualsJSON( + { + string_: + '|Subscribe|Toggle Button|Pressed|Press Search+Space to toggle', + spans_: [ + {value: {earconId: 'CHECK_ON'}, start: 0, end: 0}, + {value: 'name', start: 1, end: 10}, + {value: 'role', start: 11, end: 24}, + {value: {'delay': true}, start: 33, end: 61} + ] + }, + o.speechOutputForTest); + assertEquals('Subscribe tgl btn =', o.brailleOutputForTest.string_); }); -TEST_F('ChromeVoxOutputE2ETest', 'JoinDescendants', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxOutputE2ETest', 'JoinDescendants', async function() { + const root = await this.runWithLoadedTree(` <p>This</p> <p>fragment</p> <p>Should be separated</p> <p>with spaces</p> - `, - function(root) { - const unjoined = new Output().format('$descendants', root); - assertEquals( - 'This|fragment|Should be separated|with spaces', - unjoined.speechOutputForTest.string_); + `); + const unjoined = new Output().format('$descendants', root); + assertEquals( + 'This|fragment|Should be separated|with spaces', + unjoined.speechOutputForTest.string_); - const joined = new Output().format('$joinedDescendants', root); - assertEquals( - 'This fragment Should be separated with spaces', - joined.speechOutputForTest.string_); - }); + const joined = new Output().format('$joinedDescendants', root); + assertEquals( + 'This fragment Should be separated with spaces', + joined.speechOutputForTest.string_); }); -TEST_F('ChromeVoxOutputE2ETest', 'ComplexDiv', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxOutputE2ETest', 'ComplexDiv', async function() { + const root = await this.runWithLoadedTree(` <div><button>ok</button></div> - `, - function(root) { - const div = root.find({role: RoleType.GENERIC_CONTAINER}); - const o = new Output().withSpeech(cursors.Range.fromNode(div)); - assertEquals('ok', o.speechOutputForTest.string_); - }); + `); + const div = root.find({role: RoleType.GENERIC_CONTAINER}); + const o = new Output().withSpeech(cursors.Range.fromNode(div)); + assertEquals('ok', o.speechOutputForTest.string_); }); -TEST_F('ChromeVoxOutputE2ETest', 'ContainerFocus', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxOutputE2ETest', 'ContainerFocus', async function() { + const root = await this.runWithLoadedTree(` <div role="grid"> <div role="row" tabindex=0 aria-label="start"></div> <div role="row" tabindex=0 aria-label="end"></div> </div> - `, - function(root) { - const r1 = cursors.Range.fromNode(root.firstChild.firstChild); - const r2 = cursors.Range.fromNode(root.firstChild.lastChild); - assertEquals( - 'start|Row', - new Output().withSpeech(r1, r2).speechOutputForTest.string_); - }); + `); + const r1 = cursors.Range.fromNode(root.firstChild.firstChild); + const r2 = cursors.Range.fromNode(root.firstChild.lastChild); + assertEquals( + 'start|Row', new Output().withSpeech(r1, r2).speechOutputForTest.string_); }); -TEST_F('ChromeVoxOutputE2ETest', 'BraileWhitespace', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxOutputE2ETest', 'BraileWhitespace', async function() { + const root = await this.runWithLoadedTree(` <p>this is a <em>test</em>of emphasized text</p> - `, - function(root) { - const start = root.firstChild.firstChild; - const end = root.firstChild.lastChild; - const range = new cursors.Range( - cursors.Cursor.fromNode(start), cursors.Cursor.fromNode(end)); - const o = new Output().withBraille(range, null, 'navigate'); - checkBrailleOutput( - 'this is a test of emphasized text', - [ - {value: new OutputNodeSpan(start), start: 0, end: 10}, { - value: new OutputNodeSpan(start.nextSibling), - start: 10, - end: 14 - }, - {value: new OutputNodeSpan(end), start: 15, end: 33} - ], - o); - }); + `); + const start = root.firstChild.firstChild; + const end = root.firstChild.lastChild; + const range = new cursors.Range( + cursors.Cursor.fromNode(start), cursors.Cursor.fromNode(end)); + const o = new Output().withBraille(range, null, 'navigate'); + checkBrailleOutput( + 'this is a test of emphasized text', + [ + {value: new OutputNodeSpan(start), start: 0, end: 10}, + {value: new OutputNodeSpan(start.nextSibling), start: 10, end: 14}, + {value: new OutputNodeSpan(end), start: 15, end: 33} + ], + o); }); -TEST_F('ChromeVoxOutputE2ETest', 'BrailleAncestry', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxOutputE2ETest', 'BrailleAncestry', async function() { + const root = await this.runWithLoadedTree(` <ul><li><a href="#">test</a></li></ul> - `, - function(root) { - const link = root.find({role: RoleType.LINK}); - // The 'inlineTextBox' found from root would return the inlineTextBox of - // the list marker. Here we want the link's inlineTextBox. - const text = link.find({role: RoleType.INLINE_TEXT_BOX}); - const listItem = root.find({role: RoleType.LIST_ITEM}); - const list = root.find({role: RoleType.LIST}); - let range = cursors.Range.fromNode(text); - let o = new Output().withBraille(range, null, 'navigate'); - checkBrailleOutput( - 'test lnk lstitm lst end', - [ - {value: new OutputNodeSpan(text), start: 0, end: 4}, - {value: new OutputNodeSpan(link), start: 5, end: 8}, - {value: new OutputNodeSpan(listItem), start: 9, end: 15}, - {value: new OutputNodeSpan(list), start: 16, end: 23} - ], - o); + `); + const link = root.find({role: RoleType.LINK}); + // The 'inlineTextBox' found from root would return the inlineTextBox of + // the list marker. Here we want the link's inlineTextBox. + const text = link.find({role: RoleType.INLINE_TEXT_BOX}); + const listItem = root.find({role: RoleType.LIST_ITEM}); + const list = root.find({role: RoleType.LIST}); + let range = cursors.Range.fromNode(text); + let o = new Output().withBraille(range, null, 'navigate'); + checkBrailleOutput( + 'test lnk lstitm lst end', + [ + {value: new OutputNodeSpan(text), start: 0, end: 4}, + {value: new OutputNodeSpan(link), start: 5, end: 8}, + {value: new OutputNodeSpan(listItem), start: 9, end: 15}, + {value: new OutputNodeSpan(list), start: 16, end: 23} + ], + o); - // Now, test the "bullet" which comes before the above. - const bullet = root.find({role: RoleType.LIST_MARKER}); - range = cursors.Range.fromNode(bullet); - o = new Output().withBraille(range, null, 'navigate'); - checkBrailleOutput( - '\u2022 lstitm lst +1', - [ - {value: new OutputNodeSpan(bullet), start: 0, end: 2}, - {value: new OutputNodeSpan(listItem), start: 2, end: 8}, - {value: new OutputNodeSpan(list), start: 9, end: 15} - ], - o); - }); + // Now, test the "bullet" which comes before the above. + const bullet = root.find({role: RoleType.LIST_MARKER}); + range = cursors.Range.fromNode(bullet); + o = new Output().withBraille(range, null, 'navigate'); + checkBrailleOutput( + '\u2022 lstitm lst +1', + [ + {value: new OutputNodeSpan(bullet), start: 0, end: 2}, + {value: new OutputNodeSpan(listItem), start: 2, end: 8}, + {value: new OutputNodeSpan(list), start: 9, end: 15} + ], + o); }); -TEST_F('ChromeVoxOutputE2ETest', 'RangeOutput', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxOutputE2ETest', 'RangeOutput', async function() { + const root = await this.runWithLoadedTree(` <div role="slider" aria-valuemin="1" aria-valuemax="10" aria-valuenow="2" aria-label="volume"></div> <progress aria-valuemin="1" aria-valuemax="10" @@ -915,75 +845,64 @@ aria-label="volume"></meter> <div role="spinbutton" aria-valuemin="1" aria-valuemax="10" aria-valuenow="2" aria-label="volume"></div> - `, - function(root) { - let obj = root.find({role: RoleType.SLIDER}); - let o = - new Output().withoutHints().withSpeech(cursors.Range.fromNode(obj)); - checkSpeechOutput( - 'volume|Slider|2|Min 1|Max 10', - [ - {value: 'name', start: 0, end: 6}, - {value: new OutputEarconAction('SLIDER'), start: 0, end: 6}, - {value: 'role', start: 7, end: 13}, - {value: 'value', start: 14, end: 15} - ], - o); + `); + let obj = root.find({role: RoleType.SLIDER}); + let o = new Output().withoutHints().withSpeech(cursors.Range.fromNode(obj)); + checkSpeechOutput( + 'volume|Slider|2|Min 1|Max 10', + [ + {value: 'name', start: 0, end: 6}, + {value: new OutputEarconAction('SLIDER'), start: 0, end: 6}, + {value: 'role', start: 7, end: 13}, {value: 'value', start: 14, end: 15} + ], + o); - obj = root.find({role: RoleType.PROGRESS_INDICATOR}); - o = new Output().withoutHints().withSpeech(cursors.Range.fromNode(obj)); - checkSpeechOutput( - 'volume|Progress indicator|2|Min 1|Max 10', - [ - {value: 'name', start: 0, end: 6}, - {value: 'role', start: 7, end: 25}, - {value: 'value', start: 26, end: 27} - ], - o); + obj = root.find({role: RoleType.PROGRESS_INDICATOR}); + o = new Output().withoutHints().withSpeech(cursors.Range.fromNode(obj)); + checkSpeechOutput( + 'volume|Progress indicator|2|Min 1|Max 10', + [ + {value: 'name', start: 0, end: 6}, {value: 'role', start: 7, end: 25}, + {value: 'value', start: 26, end: 27} + ], + o); - obj = root.find({role: RoleType.METER}); - o = new Output().withoutHints().withSpeech(cursors.Range.fromNode(obj)); - checkSpeechOutput( - 'volume|Meter|2|Min 1|Max 10', - [ - {value: 'name', start: 0, end: 6}, - {value: 'role', start: 7, end: 12}, - {value: 'value', start: 13, end: 14} - ], - o); + obj = root.find({role: RoleType.METER}); + o = new Output().withoutHints().withSpeech(cursors.Range.fromNode(obj)); + checkSpeechOutput( + 'volume|Meter|2|Min 1|Max 10', + [ + {value: 'name', start: 0, end: 6}, {value: 'role', start: 7, end: 12}, + {value: 'value', start: 13, end: 14} + ], + o); - obj = root.find({role: RoleType.SPIN_BUTTON}); - o = new Output().withoutHints().withSpeech(cursors.Range.fromNode(obj)); - checkSpeechOutput( - 'volume|Spin button|2|Min 1|Max 10', - [ - {value: 'name', start: 0, end: 6}, - {value: new OutputEarconAction('LISTBOX'), start: 0, end: 6}, - {value: 'role', start: 7, end: 18}, - {value: 'value', start: 19, end: 20} - ], - o); - }); + obj = root.find({role: RoleType.SPIN_BUTTON}); + o = new Output().withoutHints().withSpeech(cursors.Range.fromNode(obj)); + checkSpeechOutput( + 'volume|Spin button|2|Min 1|Max 10', + [ + {value: 'name', start: 0, end: 6}, + {value: new OutputEarconAction('LISTBOX'), start: 0, end: 6}, + {value: 'role', start: 7, end: 18}, {value: 'value', start: 19, end: 20} + ], + o); }); -TEST_F('ChromeVoxOutputE2ETest', 'RoleDescription', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxOutputE2ETest', 'RoleDescription', async function() { + const root = await this.runWithLoadedTree(` <div aria-label="hi" role="button" aria-roledescription="foo"></div> - `, - function(root) { - const obj = root.find({role: RoleType.BUTTON}); - const o = - new Output().withoutHints().withSpeech(cursors.Range.fromNode(obj)); - checkSpeechOutput( - 'hi|foo', - [ - {value: 'name', start: 0, end: 2}, - {value: new OutputEarconAction('BUTTON'), start: 0, end: 2}, - {value: 'role', start: 3, end: 6} - ], - o); - }); + `); + const obj = root.find({role: RoleType.BUTTON}); + const o = new Output().withoutHints().withSpeech(cursors.Range.fromNode(obj)); + checkSpeechOutput( + 'hi|foo', + [ + {value: 'name', start: 0, end: 2}, + {value: new OutputEarconAction('BUTTON'), start: 0, end: 2}, + {value: 'role', start: 3, end: 6} + ], + o); }); SYNC_TEST_F('ChromeVoxOutputE2ETest', 'ValidateCommonProperties', function() { @@ -1128,53 +1047,45 @@ missingRole.join(' ')); }); -TEST_F('ChromeVoxOutputE2ETest', 'InlineBraille', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxOutputE2ETest', 'InlineBraille', async function() { + const root = await this.runWithLoadedTree(` <table border=1> <tr><td>Name</td><td id="active">Age</td><td>Address</td></tr> </table> - `, - function(root) { - const obj = root.find({role: RoleType.CELL}); - const o = - new Output().withRichSpeechAndBraille(cursors.Range.fromNode(obj)); - assertEquals( - 'Name|row 1 column 1|Table , 1 by 3', - o.speechOutputForTest.string_); - assertEquals( - 'Name r1c1 Age r1c2 Address r1c3', o.brailleOutputForTest.string_); - }); + `); + const obj = root.find({role: RoleType.CELL}); + const o = new Output().withRichSpeechAndBraille(cursors.Range.fromNode(obj)); + assertEquals( + 'Name|row 1 column 1|Table , 1 by 3', o.speechOutputForTest.string_); + assertEquals( + 'Name r1c1 Age r1c2 Address r1c3', o.brailleOutputForTest.string_); }); -TEST_F('ChromeVoxOutputE2ETest', 'TextFieldObeysRoleDescription', function() { - this.runWithLoadedTree( - ` +TEST_F( + 'ChromeVoxOutputE2ETest', 'TextFieldObeysRoleDescription', + async function() { + const root = await this.runWithLoadedTree(` <div role="textbox" aria-roledescription="square"></div> <div role="region" aria-roledescription="circle"></div> - `, - function(root) { - const text = root.find({role: RoleType.TEXT_FIELD}); + `); + const text = root.find({role: RoleType.TEXT_FIELD}); - // True even though |text| does not have editable state. - assertTrue(AutomationPredicate.editText(text)); + // True even though |text| does not have editable state. + assertTrue(AutomationPredicate.editText(text)); - let o = - new Output().withRichSpeechAndBraille(cursors.Range.fromNode(text)); - assertEquals('|square', o.speechOutputForTest.string_); - assertEquals('square', o.brailleOutputForTest.string_); + let o = + new Output().withRichSpeechAndBraille(cursors.Range.fromNode(text)); + assertEquals('|square', o.speechOutputForTest.string_); + assertEquals('square', o.brailleOutputForTest.string_); - const region = root.find({role: RoleType.REGION}); - o = new Output().withRichSpeechAndBraille( - cursors.Range.fromNode(region)); - assertEquals('circle', o.speechOutputForTest.string_); - assertEquals('circle', o.brailleOutputForTest.string_); - }); -}); + const region = root.find({role: RoleType.REGION}); + o = new Output().withRichSpeechAndBraille(cursors.Range.fromNode(region)); + assertEquals('circle', o.speechOutputForTest.string_); + assertEquals('circle', o.brailleOutputForTest.string_); + }); -TEST_F('ChromeVoxOutputE2ETest', 'NestedList', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxOutputE2ETest', 'NestedList', async function() { + const root = await this.runWithLoadedTree(` <ul role="tree">schedule <li role="treeitem">wake up <li role="treeitem">drink coffee @@ -1184,335 +1095,306 @@ </ul> <li role="treeitem">cook dinner </ul> - `, + `); + const lists = root.findAll({role: RoleType.TREE}); + const outerList = lists[0]; + const innerList = lists[1]; - function(root) { - const lists = root.findAll({role: RoleType.TREE}); - const outerList = lists[0]; - const innerList = lists[1]; + let el = outerList.children[0]; + let startRange = cursors.Range.fromNode(el); + let o = new Output().withSpeech(startRange, null, 'navigate'); + assertEquals('schedule|Tree|with 3 items', o.speechOutputForTest.string_); - let el = outerList.children[0]; - let startRange = cursors.Range.fromNode(el); - let o = new Output().withSpeech(startRange, null, 'navigate'); - assertEquals( - 'schedule|Tree|with 3 items', o.speechOutputForTest.string_); + el = outerList.children[1]; + startRange = cursors.Range.fromNode(el); + o = new Output().withSpeech( + startRange, cursors.Range.fromNode(outerList.children[0]), 'navigate'); + assertEquals( + 'wake up|Tree item|Not selected| 1 of 3 | level 1 ', + o.speechOutputForTest.string_); - el = outerList.children[1]; - startRange = cursors.Range.fromNode(el); - o = new Output().withSpeech( - startRange, cursors.Range.fromNode(outerList.children[0]), - 'navigate'); - assertEquals( - 'wake up|Tree item|Not selected| 1 of 3 | level 1 ', - o.speechOutputForTest.string_); + el = outerList.children[2]; + startRange = cursors.Range.fromNode(el); + o = new Output().withSpeech( + startRange, cursors.Range.fromNode(outerList.children[0]), 'navigate'); + assertEquals( + 'drink coffee|Tree item|Not selected| 2 of 3 | level 1 ', + o.speechOutputForTest.string_); - el = outerList.children[2]; - startRange = cursors.Range.fromNode(el); - o = new Output().withSpeech( - startRange, cursors.Range.fromNode(outerList.children[0]), - 'navigate'); - assertEquals( - 'drink coffee|Tree item|Not selected| 2 of 3 | level 1 ', - o.speechOutputForTest.string_); + el = outerList.children[3]; + startRange = cursors.Range.fromNode(el); + o = new Output().withSpeech( + startRange, cursors.Range.fromNode(outerList.children[0]), 'navigate'); + assertEquals( + 'cook dinner|Tree item|Not selected| 3 of 3 | level 1 ', + o.speechOutputForTest.string_); - el = outerList.children[3]; - startRange = cursors.Range.fromNode(el); - o = new Output().withSpeech( - startRange, cursors.Range.fromNode(outerList.children[0]), - 'navigate'); - assertEquals( - 'cook dinner|Tree item|Not selected| 3 of 3 | level 1 ', - o.speechOutputForTest.string_); + el = innerList.children[0]; + startRange = cursors.Range.fromNode(el); + o = new Output().withSpeech( + startRange, cursors.Range.fromNode(outerList.children[2]), 'navigate'); + assertEquals('tasks|Tree|with 2 items', o.speechOutputForTest.string_); - el = innerList.children[0]; - startRange = cursors.Range.fromNode(el); - o = new Output().withSpeech( - startRange, cursors.Range.fromNode(outerList.children[2]), - 'navigate'); - assertEquals('tasks|Tree|with 2 items', o.speechOutputForTest.string_); + el = innerList.children[1]; + startRange = cursors.Range.fromNode(el); + o = new Output().withSpeech( + startRange, cursors.Range.fromNode(innerList.children[0]), 'navigate'); + assertEquals( + 'meeting|Tree item|Not selected| 1 of 2 | level 2 ', + o.speechOutputForTest.string_); - el = innerList.children[1]; - startRange = cursors.Range.fromNode(el); - o = new Output().withSpeech( - startRange, cursors.Range.fromNode(innerList.children[0]), - 'navigate'); - assertEquals( - 'meeting|Tree item|Not selected| 1 of 2 | level 2 ', - o.speechOutputForTest.string_); - - el = innerList.children[2]; - startRange = cursors.Range.fromNode(el); - o = new Output().withSpeech( - startRange, cursors.Range.fromNode(innerList.children[0]), - 'navigate'); - assertEquals( - 'lunch|Tree item|Not selected| 2 of 2 | level 2 ', - o.speechOutputForTest.string_); - }); + el = innerList.children[2]; + startRange = cursors.Range.fromNode(el); + o = new Output().withSpeech( + startRange, cursors.Range.fromNode(innerList.children[0]), 'navigate'); + assertEquals( + 'lunch|Tree item|Not selected| 2 of 2 | level 2 ', + o.speechOutputForTest.string_); }); -TEST_F('ChromeVoxOutputE2ETest', 'NoTooltipWithNameTitle', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxOutputE2ETest', 'NoTooltipWithNameTitle', async function() { + const root = await this.runWithLoadedTree(` <div title="title"></div> <div aria-label="label" title="title"></div> <div aria-describedby="desc" title="title"></div> <div aria-label="label" aria-describedby="desc" title="title"></div> <div aria-label=""></div> <p id="desc">describedby</p> - `, - function(root) { - const title = root.children[0]; - let o = new Output().withSpeech( - cursors.Range.fromNode(title), null, 'navigate'); - assertEqualsJSON( - {string_: 'title', spans_: [{value: 'name', start: 0, end: 5}]}, - o.speechOutputForTest); + `); + const title = root.children[0]; + let o = + new Output().withSpeech(cursors.Range.fromNode(title), null, 'navigate'); + assertEqualsJSON( + {string_: 'title', spans_: [{value: 'name', start: 0, end: 5}]}, + o.speechOutputForTest); - const labelTitle = root.children[1]; - o = new Output().withSpeech( - cursors.Range.fromNode(labelTitle), null, 'navigate'); - assertEqualsJSON( - { - string_: 'label|title', - spans_: [ - {value: 'name', start: 0, end: 5}, - {value: 'description', start: 6, end: 11} - ] - }, - o.speechOutputForTest); + const labelTitle = root.children[1]; + o = new Output().withSpeech( + cursors.Range.fromNode(labelTitle), null, 'navigate'); + assertEqualsJSON( + { + string_: 'label|title', + spans_: [ + {value: 'name', start: 0, end: 5}, + {value: 'description', start: 6, end: 11} + ] + }, + o.speechOutputForTest); - const describedByTitle = root.children[2]; - o = new Output().withSpeech( - cursors.Range.fromNode(describedByTitle), null, 'navigate'); - assertEqualsJSON( - { - string_: 'title|describedby', - spans_: [ - {value: 'name', start: 0, end: 5}, - {value: 'description', start: 6, end: 17} - ] - }, - o.speechOutputForTest); + const describedByTitle = root.children[2]; + o = new Output().withSpeech( + cursors.Range.fromNode(describedByTitle), null, 'navigate'); + assertEqualsJSON( + { + string_: 'title|describedby', + spans_: [ + {value: 'name', start: 0, end: 5}, + {value: 'description', start: 6, end: 17} + ] + }, + o.speechOutputForTest); - const labelDescribedByTitle = root.children[3]; - o = new Output().withSpeech( - cursors.Range.fromNode(labelDescribedByTitle), null, 'navigate'); - assertEqualsJSON( - { - string_: 'label|describedby', - spans_: [ - {value: 'name', start: 0, end: 5}, - {value: 'description', start: 6, end: 17} - ] - }, - o.speechOutputForTest); + const labelDescribedByTitle = root.children[3]; + o = new Output().withSpeech( + cursors.Range.fromNode(labelDescribedByTitle), null, 'navigate'); + assertEqualsJSON( + { + string_: 'label|describedby', + spans_: [ + {value: 'name', start: 0, end: 5}, + {value: 'description', start: 6, end: 17} + ] + }, + o.speechOutputForTest); - // Hijack the 4th node to force tooltip to return a value. This can only - // occur on ARC++ where tooltip gets set even if name and description - // are both empty. - const tooltip = root.children[4]; - Object.defineProperty( - root.children[4], 'tooltip', {get: () => 'tooltip'}); + // Hijack the 4th node to force tooltip to return a value. This can only + // occur on ARC++ where tooltip gets set even if name and description + // are both empty. + const tooltip = root.children[4]; + Object.defineProperty(root.children[4], 'tooltip', {get: () => 'tooltip'}); - o = new Output().withSpeech( - cursors.Range.fromNode(tooltip), null, 'navigate'); - assertEqualsJSON( - { - string_: 'tooltip', - spans_: [{value: {'delay': true}, start: 0, end: 7}] - }, - o.speechOutputForTest); - }); + o = new Output().withSpeech( + cursors.Range.fromNode(tooltip), null, 'navigate'); + assertEqualsJSON( + { + string_: 'tooltip', + spans_: [{value: {'delay': true}, start: 0, end: 7}] + }, + o.speechOutputForTest); }); -TEST_F('ChromeVoxOutputE2ETest', 'InitialSpeechProperties', function() { - this.runWithLoadedTree( - ` - <p>test</p> `, - function(root) { - // Capture speech properties sent to tts. - this.currentProperties = []; - ChromeVox.tts.speak = (textString, queueMode, properties) => { - this.currentProperties.push(properties); - }; +TEST_F('ChromeVoxOutputE2ETest', 'InitialSpeechProperties', async function() { + const root = await this.runWithLoadedTree(` + <p>test</p> `); + // Capture speech properties sent to tts. + this.currentProperties = []; + ChromeVox.tts.speak = (textString, queueMode, properties) => { + this.currentProperties.push(properties); + }; - const o = - new Output().withSpeech(cursors.Range.fromNode(root.firstChild)); - o.go(); - assertEqualsJSON([{category: TtsCategory.NAV}], this.currentProperties); - this.currentProperties = []; + const o = new Output().withSpeech(cursors.Range.fromNode(root.firstChild)); + o.go(); + assertEqualsJSON([{category: TtsCategory.NAV}], this.currentProperties); + this.currentProperties = []; - o.withInitialSpeechProperties({ - phoneticCharacters: true, - // This should not override existing value. - category: TtsCategory.LIVE - }); - o.go(); - assertEqualsJSON( - [{phoneticCharacters: true, category: TtsCategory.NAV}], - this.currentProperties); - }); + o.withInitialSpeechProperties({ + phoneticCharacters: true, + // This should not override existing value. + category: TtsCategory.LIVE + }); + o.go(); + assertEqualsJSON( + [{phoneticCharacters: true, category: TtsCategory.NAV}], + this.currentProperties); }); -TEST_F('ChromeVoxOutputE2ETest', 'NameOrTextContent', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxOutputE2ETest', 'NameOrTextContent', async function() { + const root = await this.runWithLoadedTree(` <div tabindex=-1> <div aria-label="hello there world"> <p>hello world</p> </div> </div> - `, - function(root) { - const focusableDiv = root.firstChild; - assertEquals(RoleType.GENERIC_CONTAINER, focusableDiv.role); - assertEquals( - chrome.automation.NameFromType.CONTENTS, focusableDiv.nameFrom); - const o = new Output().withSpeech(cursors.Range.fromNode(focusableDiv)); - assertEquals('hello there world', o.speechOutputForTest.string_); - }); + `); + const focusableDiv = root.firstChild; + assertEquals(RoleType.GENERIC_CONTAINER, focusableDiv.role); + assertEquals(chrome.automation.NameFromType.CONTENTS, focusableDiv.nameFrom); + const o = new Output().withSpeech(cursors.Range.fromNode(focusableDiv)); + assertEquals('hello there world', o.speechOutputForTest.string_); }); -TEST_F('ChromeVoxOutputE2ETest', 'AriaCurrentHint', function() { +TEST_F('ChromeVoxOutputE2ETest', 'AriaCurrentHint', async function() { const site = ` <div aria-current="page">Home</div> <div aria-current="false">About</div> `; - this.runWithLoadedTree(site, function(root) { - const currentDiv = root.firstChild; - assertEquals( - chrome.automation.AriaCurrentState.PAGE, currentDiv.ariaCurrentState); - const o = new Output().withSpeech( - cursors.Range.fromNode(currentDiv), null, 'navigate'); - assertEquals('Home|Current page', o.speechOutputForTest.string_); - }); + const root = await this.runWithLoadedTree(site); + const currentDiv = root.firstChild; + assertEquals( + chrome.automation.AriaCurrentState.PAGE, currentDiv.ariaCurrentState); + const o = new Output().withSpeech( + cursors.Range.fromNode(currentDiv), null, 'navigate'); + assertEquals('Home|Current page', o.speechOutputForTest.string_); }); -TEST_F('ChromeVoxOutputE2ETest', 'DelayHintVariants', function() { - this.runWithLoadedTree( - ` +TEST_F('ChromeVoxOutputE2ETest', 'DelayHintVariants', async function() { + const root = await this.runWithLoadedTree(` <div aria-errormessage="error" aria-invalid="true">OK</div> <div id="error" aria-label="error"></div> - `, - function(root) { - const div = root.children[0]; - const range = cursors.Range.fromNode(div); + `); + const div = root.children[0]; + const range = cursors.Range.fromNode(div); - let o = new Output().withSpeech(range, null, 'navigate'); - assertEqualsJSON( - {string_: 'OK|error', spans_: [{value: 'name', start: 3, end: 8}]}, - o.speechOutputForTest); + let o = new Output().withSpeech(range, null, 'navigate'); + assertEqualsJSON( + {string_: 'OK|error', spans_: [{value: 'name', start: 3, end: 8}]}, + o.speechOutputForTest); - // Force a few properties to be set so that hints are triggered. - Object.defineProperty(div, 'clickable', {get: () => true}); + // Force a few properties to be set so that hints are triggered. + Object.defineProperty(div, 'clickable', {get: () => true}); - o = new Output().withSpeech(range, null, 'navigate'); - assertEqualsJSON( - { - string_: 'OK|error|Press Search+Space to activate', - spans_: [ - {value: 'name', start: 3, end: 8}, - {value: {delay: true}, start: 9, end: 39} - ] - }, - o.speechOutputForTest); + o = new Output().withSpeech(range, null, 'navigate'); + assertEqualsJSON( + { + string_: 'OK|error|Press Search+Space to activate', + spans_: [ + {value: 'name', start: 3, end: 8}, + {value: {delay: true}, start: 9, end: 39} + ] + }, + o.speechOutputForTest); - Object.defineProperty(div, 'placeholder', {get: () => 'placeholder'}); - o = new Output().withSpeech(range, null, 'navigate'); - assertEqualsJSON( - { - string_: 'OK|error|placeholder|Press Search+Space to activate', - spans_: [ - {value: 'name', start: 3, end: 8}, - {value: {delay: true}, start: 9, end: 20}, {start: 21, end: 51} - ] - }, - o.speechOutputForTest); - }); + Object.defineProperty(div, 'placeholder', {get: () => 'placeholder'}); + o = new Output().withSpeech(range, null, 'navigate'); + assertEqualsJSON( + { + string_: 'OK|error|placeholder|Press Search+Space to activate', + spans_: [ + {value: 'name', start: 3, end: 8}, + {value: {delay: true}, start: 9, end: 20}, {start: 21, end: 51} + ] + }, + o.speechOutputForTest); }); -TEST_F('ChromeVoxOutputE2ETest', 'WithoutFocusRing', function() { +TEST_F('ChromeVoxOutputE2ETest', 'WithoutFocusRing', async function() { const site = `<button></button>`; - this.runWithLoadedTree(site, function(root) { - let called = false; - ChromeVoxState.instance.setFocusBounds = this.newCallback(() => { - called = true; - }); - - const button = root.find({role: RoleType.BUTTON}); - - // Triggers drawing of the focus ring. - new Output().withSpeech(cursors.Range.fromNode(button)).go(); - assertTrue(called); - called = false; - - // Does not trigger drawing of the focus ring. - new Output() - .withSpeech(cursors.Range.fromNode(button)) - .withoutFocusRing() - .go(); - assertFalse(called); + const root = await this.runWithLoadedTree(site); + let called = false; + ChromeVoxState.instance.setFocusBounds = this.newCallback(() => { + called = true; }); + + const button = root.find({role: RoleType.BUTTON}); + + // Triggers drawing of the focus ring. + new Output().withSpeech(cursors.Range.fromNode(button)).go(); + assertTrue(called); + called = false; + + // Does not trigger drawing of the focus ring. + new Output() + .withSpeech(cursors.Range.fromNode(button)) + .withoutFocusRing() + .go(); + assertFalse(called); }); -TEST_F('ChromeVoxOutputE2ETest', 'ARCCheckbox', function() { - this.runWithLoadedTree('<input type="checkbox">', function(root) { - const checkbox = root.firstChild.firstChild; +TEST_F('ChromeVoxOutputE2ETest', 'ARCCheckbox', async function() { + const root = await this.runWithLoadedTree('<input type="checkbox">'); + const checkbox = root.firstChild.firstChild; - Object.defineProperty( - checkbox, 'checkedStateDescription', - {get: () => 'checked state description'}); - const range = cursors.Range.fromNode(checkbox); - const o = new Output().withoutHints().withSpeechAndBraille( - range, null, 'navigate'); - checkSpeechOutput( - '|Check box|checked state description', - [ - {value: new OutputEarconAction('CHECK_OFF'), start: 0, end: 0}, - {value: 'role', start: 1, end: 10}, - {value: 'checkedStateDescription', start: 11, end: 36} - ], - o); - }); + Object.defineProperty( + checkbox, 'checkedStateDescription', + {get: () => 'checked state description'}); + const range = cursors.Range.fromNode(checkbox); + const o = + new Output().withoutHints().withSpeechAndBraille(range, null, 'navigate'); + checkSpeechOutput( + '|Check box|checked state description', + [ + {value: new OutputEarconAction('CHECK_OFF'), start: 0, end: 0}, + {value: 'role', start: 1, end: 10}, + {value: 'checkedStateDescription', start: 11, end: 36} + ], + o); }); -TEST_F('ChromeVoxOutputE2ETest', 'ARCCustomAction', function() { - this.runWithLoadedTree('<p>test</p>', function(root) { - const actionable = root.firstChild.firstChild; - Object.defineProperty(actionable, 'customActions', { - get: () => [{id: 0, description: 'custom action description'}], - }); - const range = cursors.Range.fromNode(actionable); - const o = new Output().withSpeechAndBraille(range, null, 'navigate'); - checkSpeechOutput( - 'test|Actions available. Press Search+Ctrl+A to view', - [ - {value: 'name', start: 0, end: 4}, - {value: {delay: true}, start: 5, end: 51} - ], - o); +TEST_F('ChromeVoxOutputE2ETest', 'ARCCustomAction', async function() { + const root = await this.runWithLoadedTree('<p>test</p>'); + const actionable = root.firstChild.firstChild; + Object.defineProperty(actionable, 'customActions', { + get: () => [{id: 0, description: 'custom action description'}], }); + const range = cursors.Range.fromNode(actionable); + const o = new Output().withSpeechAndBraille(range, null, 'navigate'); + checkSpeechOutput( + 'test|Actions available. Press Search+Ctrl+A to view', + [ + {value: 'name', start: 0, end: 4}, + {value: {delay: true}, start: 5, end: 51} + ], + o); }); -TEST_F('ChromeVoxOutputE2ETest', 'ContextOrder', function() { +TEST_F('ChromeVoxOutputE2ETest', 'ContextOrder', async function() { this.resetContextualOutput(); - this.runWithLoadedTree('<p>test</p><div role="menu">a</div>', function(root) { - let o = new Output().withSpeech(cursors.Range.fromNode(root)); - assertEquals('last', o.contextOrder_); + const root = + await this.runWithLoadedTree('<p>test</p><div role="menu">a</div>'); + let o = new Output().withSpeech(cursors.Range.fromNode(root)); + assertEquals('last', o.contextOrder_); - const p = root.find({role: RoleType.PARAGRAPH}); - const menu = root.find({role: RoleType.MENU}); - o = new Output().withSpeech( - cursors.Range.fromNode(p), cursors.Range.fromNode(menu)); - assertEquals('last', o.contextOrder_); + const p = root.find({role: RoleType.PARAGRAPH}); + const menu = root.find({role: RoleType.MENU}); + o = new Output().withSpeech( + cursors.Range.fromNode(p), cursors.Range.fromNode(menu)); + assertEquals('last', o.contextOrder_); - o = new Output().withSpeech( - cursors.Range.fromNode(menu), cursors.Range.fromNode(p)); - assertEquals('first', o.contextOrder_); + o = new Output().withSpeech( + cursors.Range.fromNode(menu), cursors.Range.fromNode(p)); + assertEquals('first', o.contextOrder_); - o = new Output().withSpeech( - cursors.Range.fromNode(menu.firstChild), cursors.Range.fromNode(p)); - assertEquals('first', o.contextOrder_); - }); + o = new Output().withSpeech( + cursors.Range.fromNode(menu.firstChild), cursors.Range.fromNode(p)); + assertEquals('first', o.contextOrder_); });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/portals_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/portals_test.js index ee8d617..64e3113 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/portals_test.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/portals_test.js
@@ -64,44 +64,39 @@ } }; -TEST_F('ChromeVoxPortalsTest', 'ShouldFocusPortal', function() { - this.runWithLoadedTree( - undefined, function(root) { - const portal = root.find({role: RoleType.PORTAL}); - const button = root.find({role: RoleType.BUTTON}); - assertEquals(RoleType.PORTAL, portal.role); - assertEquals(RoleType.BUTTON, button.role); +TEST_F('ChromeVoxPortalsTest', 'ShouldFocusPortal', async function() { + const root = await this.runWithLoadedTree(null, { + url: `${testRunnerParams.testServerBaseUrl}portal/portal-and-button.html` + }); + const portal = root.find({role: RoleType.PORTAL}); + const button = root.find({role: RoleType.BUTTON}); + assertEquals(RoleType.PORTAL, portal.role); + assertEquals(RoleType.BUTTON, button.role); - const afterPortalIsReady = this.newCallback(() => { - const chromeVoxState = ChromeVoxState.instance; - portal.addEventListener(EventType.FOCUS, this.newCallback(function() { - assertEquals(portal, chromeVoxState.currentRange.start.node); - // test is done. - })); - assertEquals(button, chromeVoxState.currentRange.start.node); - doCmd('nextObject')(); - }); + const afterPortalIsReady = this.newCallback(() => { + const chromeVoxState = ChromeVoxState.instance; + portal.addEventListener(EventType.FOCUS, this.newCallback(function() { + assertEquals(portal, chromeVoxState.currentRange.start.node); + // test is done. + })); + assertEquals(button, chromeVoxState.currentRange.start.node); + doCmd('nextObject')(); + }); - button.focus(); - button.addEventListener( - EventType.FOCUS, - () => this.waitForPortal(portal).then(afterPortalIsReady)); - }.bind(this), { - url: - `${testRunnerParams.testServerBaseUrl}portal/portal-and-button.html` - }); + button.focus(); + button.addEventListener( + EventType.FOCUS, + () => this.waitForPortal(portal).then(afterPortalIsReady)); }); -TEST_F('ChromeVoxPortalsTest', 'PortalName', function() { - this.runWithLoadedTree( - undefined, function(root) { - const portal = root.find({role: RoleType.PORTAL}); - assertEquals(RoleType.PORTAL, portal.role); - this.waitForPortal(portal).then(this.newCallback(() => { - assertTrue(portal.firstChild.docLoaded); - assertEquals(portal.name, 'some text'); - })); - }.bind(this), { - url: `${testRunnerParams.testServerBaseUrl}portal/portal-with-text.html` - }); +TEST_F('ChromeVoxPortalsTest', 'PortalName', async function() { + const root = await this.runWithLoadedTree(null, { + url: `${testRunnerParams.testServerBaseUrl}portal/portal-with-text.html` + }); + const portal = root.find({role: RoleType.PORTAL}); + assertEquals(RoleType.PORTAL, portal.role); + this.waitForPortal(portal).then(this.newCallback(() => { + assertTrue(portal.firstChild.docLoaded); + assertEquals(portal.name, 'some text'); + })); });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/settings_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/settings_test.js index 55883fee..956a2ae 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/settings_test.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/settings_test.js
@@ -29,59 +29,65 @@ `); super.testGenPreamble(); } + + /** @override */ + async setUpDeferred() { + await super.setUpDeferred(); + await importModule('AbstractTts', '/chromevox/common/abstract_tts.js'); + } }; TEST_F( - 'ChromeVoxSettingsPagesTest', 'TtsRateCommandOnSettingsPage', function() { + 'ChromeVoxSettingsPagesTest', 'TtsRateCommandOnSettingsPage', + async function() { const realTts = ChromeVox.tts; const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(`unused`, function() { - const increaseRate = realTts.increaseOrDecreaseProperty.bind( - realTts, AbstractTts.RATE, true); - const decreaseRate = realTts.increaseOrDecreaseProperty.bind( - realTts, AbstractTts.RATE, false); + await this.runWithLoadedTree(`unused`); + const increaseRate = realTts.increaseOrDecreaseProperty.bind( + realTts, AbstractTts.RATE, true); + const decreaseRate = realTts.increaseOrDecreaseProperty.bind( + realTts, AbstractTts.RATE, false); - mockFeedback.call(doCmd('showTtsSettings')) - .expectSpeech( - /(Settings)|(Text-to-Speech voice settings subpage back button)/) + mockFeedback.call(doCmd('showTtsSettings')) + .expectSpeech( + /(Settings)|(Text-to-Speech voice settings subpage back button)/) - // ChromeVox presents a 0% to 100% scale. - // Ensure we have the default rate. - .call( - () => chrome.settingsPrivate.setPref( - 'settings.tts.speech_rate', 1.0)) + // ChromeVox presents a 0% to 100% scale. + // Ensure we have the default rate. + .call( + () => chrome.settingsPrivate.setPref( + 'settings.tts.speech_rate', 1.0)) - .call(increaseRate) - .expectSpeech('Rate 19 percent') - .call(increaseRate) - .expectSpeech('Rate 21 percent') + .call(increaseRate) + .expectSpeech('Rate 19 percent') + .call(increaseRate) + .expectSpeech('Rate 21 percent') - // Speed things up... - .call( - () => chrome.settingsPrivate.setPref( - 'settings.tts.speech_rate', 4.9)) - .expectSpeech('Rate 98 percent') - .call(increaseRate) - .expectSpeech('Rate 100 percent') + // Speed things up... + .call( + () => chrome.settingsPrivate.setPref( + 'settings.tts.speech_rate', 4.9)) + .expectSpeech('Rate 98 percent') + .call(increaseRate) + .expectSpeech('Rate 100 percent') - .call(decreaseRate) - .expectSpeech('Rate 98 percent') - .call(decreaseRate) - .expectSpeech('Rate 96 percent') + .call(decreaseRate) + .expectSpeech('Rate 98 percent') + .call(decreaseRate) + .expectSpeech('Rate 96 percent') - // Slow things down... - .call( - () => chrome.settingsPrivate.setPref( - 'settings.tts.speech_rate', 0.3)) - .expectSpeech('Rate 2 percent') - .call(decreaseRate) - .expectSpeech('Rate 0 percent') + // Slow things down... + .call( + () => chrome.settingsPrivate.setPref( + 'settings.tts.speech_rate', 0.3)) + .expectSpeech('Rate 2 percent') + .call(decreaseRate) + .expectSpeech('Rate 0 percent') - .call(increaseRate) - .expectSpeech('Rate 2 percent') - .call(increaseRate) - .expectSpeech('Rate 4 percent') + .call(increaseRate) + .expectSpeech('Rate 2 percent') + .call(increaseRate) + .expectSpeech('Rate 4 percent') - .replay(); - }); + .replay(); });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode_test.js index 05824b3e..83f737c 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode_test.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode_test.js
@@ -44,161 +44,154 @@ } }; -TEST_F('ChromeVoxSmartStickyModeTest', 'PossibleRangeTypes', function() { - this.runWithLoadedTree(this.relationsDoc, function(root) { - const [p, input, textarea, contenteditable, ul1, ul2] = root.children; +TEST_F('ChromeVoxSmartStickyModeTest', 'PossibleRangeTypes', async function() { + const root = await this.runWithLoadedTree(this.relationsDoc); + const [p, input, textarea, contenteditable, ul1, ul2] = root.children; - // First, turn on sticky mode and try changing range to various parts of - // the document. - ChromeVoxBackground.setPref( - 'sticky', true /* value */, true /* announce */); - this.assertDidTurnOffForNode(input); - this.assertDidTurnOffForNode(textarea); - this.assertDidNotTurnOffForNode(p); - this.assertDidTurnOffForNode(contenteditable); - this.assertDidTurnOffForNode(ul1); - this.assertDidNotTurnOffForNode(p); - this.assertDidTurnOffForNode(ul2); - this.assertDidTurnOffForNode(ul1.firstChild); - this.assertDidNotTurnOffForNode(ul1.parent); - this.assertDidNotTurnOffForNode(ul2.parent); - this.assertDidNotTurnOffForNode(p); - this.assertDidTurnOffForNode(ul2.firstChild); - this.assertDidNotTurnOffForNode(p); - this.assertDidNotTurnOffForNode(contenteditable.parent); - this.assertDidTurnOffForNode(contenteditable.find({role: 'heading'})); - this.assertDidTurnOffForNode(contenteditable.find({role: 'inlineTextBox'})); - }); + // First, turn on sticky mode and try changing range to various parts of + // the document. + ChromeVoxBackground.setPref('sticky', true /* value */, true /* announce */); + this.assertDidTurnOffForNode(input); + this.assertDidTurnOffForNode(textarea); + this.assertDidNotTurnOffForNode(p); + this.assertDidTurnOffForNode(contenteditable); + this.assertDidTurnOffForNode(ul1); + this.assertDidNotTurnOffForNode(p); + this.assertDidTurnOffForNode(ul2); + this.assertDidTurnOffForNode(ul1.firstChild); + this.assertDidNotTurnOffForNode(ul1.parent); + this.assertDidNotTurnOffForNode(ul2.parent); + this.assertDidNotTurnOffForNode(p); + this.assertDidTurnOffForNode(ul2.firstChild); + this.assertDidNotTurnOffForNode(p); + this.assertDidNotTurnOffForNode(contenteditable.parent); + this.assertDidTurnOffForNode(contenteditable.find({role: 'heading'})); + this.assertDidTurnOffForNode(contenteditable.find({role: 'inlineTextBox'})); }); TEST_F( - 'ChromeVoxSmartStickyModeTest', 'UserPressesStickyModeCommand', function() { - this.runWithLoadedTree(this.relationsDoc, function(root) { - const [p, input, textarea, contenteditable, ul1, ul2] = root.children; - ChromeVoxBackground.setPref( - 'sticky', true /* value */, true /* announce */); + 'ChromeVoxSmartStickyModeTest', 'UserPressesStickyModeCommand', + async function() { + const root = await this.runWithLoadedTree(this.relationsDoc); + const [p, input, textarea, contenteditable, ul1, ul2] = root.children; + ChromeVoxBackground.setPref( + 'sticky', true /* value */, true /* announce */); - // Mix in calls to turn on / off sticky mode while moving the range - // around. - this.assertDidTurnOffForNode(input); - this.ssm_.onStickyModeCommand(cursors.Range.fromNode(input)); - this.assertDidNotTurnOffForNode(input); - this.ssm_.onStickyModeCommand(cursors.Range.fromNode(input)); - this.assertDidNotTurnOffForNode(input); - this.assertDidNotTurnOffForNode(input.firstChild); - this.assertDidNotTurnOffForNode(p); + // Mix in calls to turn on / off sticky mode while moving the range + // around. + this.assertDidTurnOffForNode(input); + this.ssm_.onStickyModeCommand(cursors.Range.fromNode(input)); + this.assertDidNotTurnOffForNode(input); + this.ssm_.onStickyModeCommand(cursors.Range.fromNode(input)); + this.assertDidNotTurnOffForNode(input); + this.assertDidNotTurnOffForNode(input.firstChild); + this.assertDidNotTurnOffForNode(p); - // Make sure sticky mode is on again. This call doesn't impact our - // instance of SmartStickyMode. - ChromeVoxBackground.setPref( - 'sticky', true /* value */, true /* announce */); + // Make sure sticky mode is on again. This call doesn't impact our + // instance of SmartStickyMode. + ChromeVoxBackground.setPref( + 'sticky', true /* value */, true /* announce */); - // Mix in more sticky mode user commands and move to related nodes. - this.assertDidTurnOffForNode(contenteditable); - this.assertDidTurnOffForNode(ul2); - this.ssm_.onStickyModeCommand(cursors.Range.fromNode(ul2)); - this.assertDidNotTurnOffForNode(ul2); - this.assertDidNotTurnOffForNode(ul2.firstChild); - this.assertDidNotTurnOffForNode(contenteditable); - this.ssm_.onStickyModeCommand(cursors.Range.fromNode(input)); - this.assertDidNotTurnOffForNode(ul2); - this.assertDidNotTurnOffForNode(ul2.firstChild); - this.assertDidNotTurnOffForNode(contenteditable); + // Mix in more sticky mode user commands and move to related nodes. + this.assertDidTurnOffForNode(contenteditable); + this.assertDidTurnOffForNode(ul2); + this.ssm_.onStickyModeCommand(cursors.Range.fromNode(ul2)); + this.assertDidNotTurnOffForNode(ul2); + this.assertDidNotTurnOffForNode(ul2.firstChild); + this.assertDidNotTurnOffForNode(contenteditable); + this.ssm_.onStickyModeCommand(cursors.Range.fromNode(input)); + this.assertDidNotTurnOffForNode(ul2); + this.assertDidNotTurnOffForNode(ul2.firstChild); + this.assertDidNotTurnOffForNode(contenteditable); - // Finally, verify sticky mode isn't impacted on non-editables. - this.assertDidNotTurnOffForNode(p); - this.ssm_.onStickyModeCommand(cursors.Range.fromNode(p)); - this.assertDidNotTurnOffForNode(p); - this.ssm_.onStickyModeCommand(cursors.Range.fromNode(p)); - this.assertDidNotTurnOffForNode(p); - }); + // Finally, verify sticky mode isn't impacted on non-editables. + this.assertDidNotTurnOffForNode(p); + this.ssm_.onStickyModeCommand(cursors.Range.fromNode(p)); + this.assertDidNotTurnOffForNode(p); + this.ssm_.onStickyModeCommand(cursors.Range.fromNode(p)); + this.assertDidNotTurnOffForNode(p); }); TEST_F( - 'ChromeVoxSmartStickyModeTest', 'SmartStickyModeJumpCommands', function() { + 'ChromeVoxSmartStickyModeTest', 'SmartStickyModeJumpCommands', + async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` + const root = await this.runWithLoadedTree(` <p>start</p> <input type="text"></input> <button>end</button> - `, - function(root) { - mockFeedback.call(doCmd('toggleStickyMode')) - .expectSpeech('Sticky mode enabled') - .call(doCmd('nextFormField')) - .expectSpeech('Edit text') - .call(() => assertTrue(ChromeVox.isStickyModeOn())) - .call(doCmd('nextFormField')) - .expectSpeech('Button') - .call(doCmd('previousFormField')) - .expectSpeech('Edit text') - .call(() => assertTrue(ChromeVox.isStickyModeOn())) - .call(doCmd('previousObject')) - .expectSpeech('start') - .call(doCmd('nextEditText')) - .expectSpeech('Edit text') - .call(() => assertTrue(ChromeVox.isStickyModeOn())) - .call(doCmd('nextObject')) - .expectSpeech('Button') - .call(doCmd('previousEditText')) - .expectSpeech('Edit text') - .call(() => assertTrue(ChromeVox.isStickyModeOn())) - .call(doCmd('nextObject')) - .expectSpeech('Button') - .call(doCmd('previousObject')) - .expectSpeech('Sticky mode disabled') - .expectSpeech('Edit text') - .call(() => assertFalse(ChromeVox.isStickyModeOn())) - .replay(); - }); + `); + mockFeedback.call(doCmd('toggleStickyMode')) + .expectSpeech('Sticky mode enabled') + .call(doCmd('nextFormField')) + .expectSpeech('Edit text') + .call(() => assertTrue(ChromeVox.isStickyModeOn())) + .call(doCmd('nextFormField')) + .expectSpeech('Button') + .call(doCmd('previousFormField')) + .expectSpeech('Edit text') + .call(() => assertTrue(ChromeVox.isStickyModeOn())) + .call(doCmd('previousObject')) + .expectSpeech('start') + .call(doCmd('nextEditText')) + .expectSpeech('Edit text') + .call(() => assertTrue(ChromeVox.isStickyModeOn())) + .call(doCmd('nextObject')) + .expectSpeech('Button') + .call(doCmd('previousEditText')) + .expectSpeech('Edit text') + .call(() => assertTrue(ChromeVox.isStickyModeOn())) + .call(doCmd('nextObject')) + .expectSpeech('Button') + .call(doCmd('previousObject')) + .expectSpeech('Sticky mode disabled') + .expectSpeech('Edit text') + .call(() => assertFalse(ChromeVox.isStickyModeOn())) + .replay(); }); -TEST_F('ChromeVoxSmartStickyModeTest', 'SmartStickyModeEarcons', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree( - ` +TEST_F( + 'ChromeVoxSmartStickyModeTest', 'SmartStickyModeEarcons', async function() { + const mockFeedback = this.createMockFeedback(); + const root = await this.runWithLoadedTree(` <p>start</p> <input type="text"></input> <button>end</button> - `, - function(root) { - mockFeedback.call(doCmd('toggleStickyMode')) - .expectSpeech('Sticky mode enabled') - .call(doCmd('nextObject')) - .expectEarcon(Earcon.SMART_STICKY_MODE_OFF) - .expectSpeech('Sticky mode disabled') - .expectSpeech('Edit text') - .call(() => assertFalse(ChromeVox.isStickyModeOn())) - .call(doCmd('nextObject')) - .expectEarcon(Earcon.SMART_STICKY_MODE_ON) - .expectSpeech('Sticky mode enabled') - .expectSpeech('Button') - .call(() => assertTrue(ChromeVox.isStickyModeOn())) - .replay(); - }); -}); + `); + mockFeedback.call(doCmd('toggleStickyMode')) + .expectSpeech('Sticky mode enabled') + .call(doCmd('nextObject')) + .expectEarcon(Earcon.SMART_STICKY_MODE_OFF) + .expectSpeech('Sticky mode disabled') + .expectSpeech('Edit text') + .call(() => assertFalse(ChromeVox.isStickyModeOn())) + .call(doCmd('nextObject')) + .expectEarcon(Earcon.SMART_STICKY_MODE_ON) + .expectSpeech('Sticky mode enabled') + .expectSpeech('Button') + .call(() => assertTrue(ChromeVox.isStickyModeOn())) + .replay(); + }); -TEST_F('ChromeVoxSmartStickyModeTest', 'ContinuousRead', function() { +TEST_F('ChromeVoxSmartStickyModeTest', 'ContinuousRead', async function() { const mockFeedback = this.createMockFeedback(); const site = ` <p>start</p> <input type="text"></input> <button>end</button> `; - this.runWithLoadedTree(site, function(root) { - // Fake the read from here/continuous read state. - ChromeVoxState.isReadingContinuously = true; - mockFeedback.call(doCmd('toggleStickyMode')) - .expectSpeech('Sticky mode enabled') - .call(doCmd('nextObject')) - .expectNextSpeechUtteranceIsNot('Sticky mode disabled') - .expectSpeech('Edit text') - .call(() => assertTrue(ChromeVox.isStickyModeOn())) - .call(doCmd('nextObject')) - .expectNextSpeechUtteranceIsNot('Sticky mode enabled') - .expectSpeech('Button') - .call(() => assertTrue(ChromeVox.isStickyModeOn())) - .replay(); - }); + const root = await this.runWithLoadedTree(site); + // Fake the read from here/continuous read state. + ChromeVoxState.isReadingContinuously = true; + mockFeedback.call(doCmd('toggleStickyMode')) + .expectSpeech('Sticky mode enabled') + .call(doCmd('nextObject')) + .expectNextSpeechUtteranceIsNot('Sticky mode disabled') + .expectSpeech('Edit text') + .call(() => assertTrue(ChromeVox.isStickyModeOn())) + .call(doCmd('nextObject')) + .expectNextSpeechUtteranceIsNot('Sticky mode enabled') + .expectSpeech('Button') + .call(() => assertTrue(ChromeVox.isStickyModeOn())) + .replay(); });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/user_action_monitor_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/user_action_monitor_test.js index 9e2c262..2c10835 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/user_action_monitor_test.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/user_action_monitor_test.js
@@ -41,457 +41,447 @@ } }; -TEST_F('ChromeVoxUserActionMonitorTest', 'UnitTest', function() { - this.runWithLoadedTree(this.simpleDoc, function() { - let finished = false; - const actions = [ - { - type: 'key_sequence', - value: {'keys': {'keyCode': [KeyCode.SPACE]}}, - }, - {type: 'braille', value: 'jumpToTop'}, - {type: 'gesture', value: Gesture.SWIPE_UP1} - ]; - const onFinished = () => finished = true; - - const monitor = new UserActionMonitor(actions, onFinished); - assertEquals(3, monitor.actions_.length); - assertEquals(0, monitor.actionIndex_); - assertEquals('key_sequence', monitor.getExpectedAction_().type); - assertFalse(finished); - monitor.expectedActionMatched_(); - assertEquals(1, monitor.actionIndex_); - assertEquals('braille', monitor.getExpectedAction_().type); - assertFalse(finished); - monitor.expectedActionMatched_(); - assertEquals(2, monitor.actionIndex_); - assertEquals('gesture', monitor.getExpectedAction_().type); - assertFalse(finished); - monitor.expectedActionMatched_(); - assertTrue(finished); - assertEquals(3, monitor.actions_.length); - assertEquals(3, monitor.actionIndex_); - }); -}); - -TEST_F('ChromeVoxUserActionMonitorTest', 'ActionUnitTest', function() { - this.runWithLoadedTree(this.simpleDoc, function() { - const keySequenceActionOne = UserActionMonitor.Action.fromActionInfo( - {type: 'key_sequence', value: {keys: {keyCode: [KeyCode.SPACE]}}}); - const keySequenceActionTwo = new UserActionMonitor.Action({ +TEST_F('ChromeVoxUserActionMonitorTest', 'UnitTest', async function() { + await this.runWithLoadedTree(this.simpleDoc); + let finished = false; + const actions = [ + { type: 'key_sequence', - value: new KeySequence(TestUtils.createMockKeyEvent(KeyCode.A)) - }); - const gestureActionOne = UserActionMonitor.Action.fromActionInfo( - {type: 'gesture', value: Gesture.SWIPE_UP1}); - const gestureActionTwo = new UserActionMonitor.Action( - {type: 'gesture', value: Gesture.SWIPE_UP2}); + value: {'keys': {'keyCode': [KeyCode.SPACE]}}, + }, + {type: 'braille', value: 'jumpToTop'}, + {type: 'gesture', value: Gesture.SWIPE_UP1} + ]; + const onFinished = () => finished = true; - assertFalse(keySequenceActionOne.equals(keySequenceActionTwo)); - assertFalse(keySequenceActionOne.equals(gestureActionOne)); - assertFalse(keySequenceActionOne.equals(gestureActionTwo)); - assertFalse(keySequenceActionTwo.equals(gestureActionOne)); - assertFalse(keySequenceActionTwo.equals(gestureActionTwo)); - assertFalse(gestureActionOne.equals(gestureActionTwo)); - - const cloneKeySequenceActionOne = UserActionMonitor.Action.fromActionInfo( - {type: 'key_sequence', value: {keys: {keyCode: [KeyCode.SPACE]}}}); - const cloneGestureActionOne = new UserActionMonitor.Action( - {type: 'gesture', value: Gesture.SWIPE_UP1}); - assertTrue(keySequenceActionOne.equals(cloneKeySequenceActionOne)); - assertTrue(gestureActionOne.equals(cloneGestureActionOne)); - }); + const monitor = new UserActionMonitor(actions, onFinished); + assertEquals(3, monitor.actions_.length); + assertEquals(0, monitor.actionIndex_); + assertEquals('key_sequence', monitor.getExpectedAction_().type); + assertFalse(finished); + monitor.expectedActionMatched_(); + assertEquals(1, monitor.actionIndex_); + assertEquals('braille', monitor.getExpectedAction_().type); + assertFalse(finished); + monitor.expectedActionMatched_(); + assertEquals(2, monitor.actionIndex_); + assertEquals('gesture', monitor.getExpectedAction_().type); + assertFalse(finished); + monitor.expectedActionMatched_(); + assertTrue(finished); + assertEquals(3, monitor.actions_.length); + assertEquals(3, monitor.actionIndex_); }); -TEST_F('ChromeVoxUserActionMonitorTest', 'Errors', function() { - this.runWithLoadedTree(this.simpleDoc, function() { - let monitor; - let caught = false; - let finished = false; - const actions = [ - { - type: 'key_sequence', - value: {'keys': {'keyCode': [KeyCode.SPACE]}}, - }, - ]; - const onFinished = () => finished = true; - const assertCaughtAndReset = () => { - assertTrue(caught); - caught = false; - }; +TEST_F('ChromeVoxUserActionMonitorTest', 'ActionUnitTest', async function() { + await this.runWithLoadedTree(this.simpleDoc); + const keySequenceActionOne = UserActionMonitor.Action.fromActionInfo( + {type: 'key_sequence', value: {keys: {keyCode: [KeyCode.SPACE]}}}); + const keySequenceActionTwo = new UserActionMonitor.Action({ + type: 'key_sequence', + value: new KeySequence(TestUtils.createMockKeyEvent(KeyCode.A)) + }); + const gestureActionOne = UserActionMonitor.Action.fromActionInfo( + {type: 'gesture', value: Gesture.SWIPE_UP1}); + const gestureActionTwo = + new UserActionMonitor.Action({type: 'gesture', value: Gesture.SWIPE_UP2}); - try { - monitor = new UserActionMonitor([], onFinished); - assertTrue(false); // Shouldn't execute. - } catch (error) { - assertEquals( - `UserActionMonitor: actionInfos can't be empty`, error.message); - caught = true; - } - assertCaughtAndReset(); - try { - new UserActionMonitor.Action({type: 'key_sequence', value: 'invalid'}); - assertTrue(false); // Shouldn't execute - } catch (error) { - assertEquals( - 'UserActionMonitor: Must provide a KeySequence value for Actions ' + - 'of type ActionType.KEY_SEQUENCE', - error.message); - caught = true; - } - assertCaughtAndReset(); - try { - UserActionMonitor.Action.fromActionInfo({type: 'gesture', value: false}); - assertTrue(false); // Shouldn't execute. - } catch (error) { - assertEquals( - 'UserActionMonitor: Must provide a string value for Actions if ' + - 'type is other than ActionType.KEY_SEQUENCE', - error.message); - caught = true; - } - assertCaughtAndReset(); + assertFalse(keySequenceActionOne.equals(keySequenceActionTwo)); + assertFalse(keySequenceActionOne.equals(gestureActionOne)); + assertFalse(keySequenceActionOne.equals(gestureActionTwo)); + assertFalse(keySequenceActionTwo.equals(gestureActionOne)); + assertFalse(keySequenceActionTwo.equals(gestureActionTwo)); + assertFalse(gestureActionOne.equals(gestureActionTwo)); - monitor = new UserActionMonitor(actions, onFinished); - monitor.expectedActionMatched_(); - assertTrue(finished); + const cloneKeySequenceActionOne = UserActionMonitor.Action.fromActionInfo( + {type: 'key_sequence', value: {keys: {keyCode: [KeyCode.SPACE]}}}); + const cloneGestureActionOne = + new UserActionMonitor.Action({type: 'gesture', value: Gesture.SWIPE_UP1}); + assertTrue(keySequenceActionOne.equals(cloneKeySequenceActionOne)); + assertTrue(gestureActionOne.equals(cloneGestureActionOne)); +}); - try { - monitor.onKeySequence( - new KeySequence(TestUtils.createMockKeyEvent(KeyCode.SPACE))); - assertTrue(false); // Shouldn't execute. - } catch (error) { - assertEquals( - 'UserActionMonitor: actionIndex_ is invalid.', error.message); - caught = true; - } - assertCaughtAndReset(); - try { - monitor.expectedActionMatched_(); - assertTrue(false); // Shouldn't execute. - } catch (error) { - assertEquals( - 'UserActionMonitor: actionIndex_ is invalid.', error.message); - caught = true; - } - assertCaughtAndReset(); - try { - monitor.nextAction_(); - assertTrue(false); // Shouldn't execute. - } catch (error) { - assertEquals( - `UserActionMonitor: can't call nextAction_(), invalid index`, - error.message); - caught = true; - } +TEST_F('ChromeVoxUserActionMonitorTest', 'Errors', async function() { + await this.runWithLoadedTree(this.simpleDoc); + let monitor; + let caught = false; + let finished = false; + const actions = [ + { + type: 'key_sequence', + value: {'keys': {'keyCode': [KeyCode.SPACE]}}, + }, + ]; + const onFinished = () => finished = true; + const assertCaughtAndReset = () => { assertTrue(caught); - }); + caught = false; + }; + + try { + monitor = new UserActionMonitor([], onFinished); + assertTrue(false); // Shouldn't execute. + } catch (error) { + assertEquals( + `UserActionMonitor: actionInfos can't be empty`, error.message); + caught = true; + } + assertCaughtAndReset(); + try { + new UserActionMonitor.Action({type: 'key_sequence', value: 'invalid'}); + assertTrue(false); // Shouldn't execute + } catch (error) { + assertEquals( + 'UserActionMonitor: Must provide a KeySequence value for Actions ' + + 'of type ActionType.KEY_SEQUENCE', + error.message); + caught = true; + } + assertCaughtAndReset(); + try { + UserActionMonitor.Action.fromActionInfo({type: 'gesture', value: false}); + assertTrue(false); // Shouldn't execute. + } catch (error) { + assertEquals( + 'UserActionMonitor: Must provide a string value for Actions if ' + + 'type is other than ActionType.KEY_SEQUENCE', + error.message); + caught = true; + } + assertCaughtAndReset(); + + monitor = new UserActionMonitor(actions, onFinished); + monitor.expectedActionMatched_(); + assertTrue(finished); + + try { + monitor.onKeySequence( + new KeySequence(TestUtils.createMockKeyEvent(KeyCode.SPACE))); + assertTrue(false); // Shouldn't execute. + } catch (error) { + assertEquals('UserActionMonitor: actionIndex_ is invalid.', error.message); + caught = true; + } + assertCaughtAndReset(); + try { + monitor.expectedActionMatched_(); + assertTrue(false); // Shouldn't execute. + } catch (error) { + assertEquals('UserActionMonitor: actionIndex_ is invalid.', error.message); + caught = true; + } + assertCaughtAndReset(); + try { + monitor.nextAction_(); + assertTrue(false); // Shouldn't execute. + } catch (error) { + assertEquals( + `UserActionMonitor: can't call nextAction_(), invalid index`, + error.message); + caught = true; + } + assertTrue(caught); }); -TEST_F('ChromeVoxUserActionMonitorTest', 'Output', function() { +TEST_F('ChromeVoxUserActionMonitorTest', 'Output', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, function(rootNode) { - let monitor; - let finished = false; - const actions = [ - { - type: 'gesture', - value: Gesture.SWIPE_UP1, - beforeActionMsg: 'First instruction', - afterActionMsg: 'Congratulations!' - }, - { - type: 'gesture', - value: Gesture.SWIPE_UP1, - beforeActionMsg: 'Second instruction', - afterActionMsg: 'You did it!' - } - ]; - const onFinished = () => finished = true; + const rootNode = await this.runWithLoadedTree(this.simpleDoc); + let monitor; + let finished = false; + const actions = [ + { + type: 'gesture', + value: Gesture.SWIPE_UP1, + beforeActionMsg: 'First instruction', + afterActionMsg: 'Congratulations!' + }, + { + type: 'gesture', + value: Gesture.SWIPE_UP1, + beforeActionMsg: 'Second instruction', + afterActionMsg: 'You did it!' + } + ]; + const onFinished = () => finished = true; - mockFeedback - .call(() => { - monitor = new UserActionMonitor(actions, onFinished); - }) - .expectSpeech('First instruction') - .call(() => { - monitor.expectedActionMatched_(); - assertFalse(finished); - }) - .expectSpeech('Congratulations!', 'Second instruction') - .call(() => { - monitor.expectedActionMatched_(); - assertTrue(finished); - }) - .expectSpeech('You did it!'); - mockFeedback.replay(); - }); + mockFeedback + .call(() => { + monitor = new UserActionMonitor(actions, onFinished); + }) + .expectSpeech('First instruction') + .call(() => { + monitor.expectedActionMatched_(); + assertFalse(finished); + }) + .expectSpeech('Congratulations!', 'Second instruction') + .call(() => { + monitor.expectedActionMatched_(); + assertTrue(finished); + }) + .expectSpeech('You did it!'); + mockFeedback.replay(); }); // Tests that we can match a single key. Serves as an integration test // since we don't directly call a UserActionMonitor function. -TEST_F('ChromeVoxUserActionMonitorTest', 'SingleKey', function() { - this.runWithLoadedTree(this.simpleDoc, function() { - const keyboardHandler = new BackgroundKeyboardHandler(); - let finished = false; - const actions = - [{type: 'key_sequence', value: {'keys': {'keyCode': [KeyCode.SPACE]}}}]; - const onFinished = () => finished = true; +TEST_F('ChromeVoxUserActionMonitorTest', 'SingleKey', async function() { + await this.runWithLoadedTree(this.simpleDoc); + const keyboardHandler = new BackgroundKeyboardHandler(); + let finished = false; + const actions = + [{type: 'key_sequence', value: {'keys': {'keyCode': [KeyCode.SPACE]}}}]; + const onFinished = () => finished = true; - ChromeVoxState.instance.createUserActionMonitor(actions, onFinished); - keyboardHandler.onKeyDown(TestUtils.createMockKeyEvent(KeyCode.LEFT)); - keyboardHandler.onKeyUp(TestUtils.createMockKeyEvent(KeyCode.LEFT)); - assertFalse(finished); - keyboardHandler.onKeyDown(TestUtils.createMockKeyEvent(KeyCode.RIGHT)); - keyboardHandler.onKeyUp(TestUtils.createMockKeyEvent(KeyCode.RIGHT)); - assertFalse(finished); - keyboardHandler.onKeyDown(TestUtils.createMockKeyEvent(KeyCode.SPACE)); - keyboardHandler.onKeyUp(TestUtils.createMockKeyEvent(KeyCode.SPACE)); - assertTrue(finished); - }); + ChromeVoxState.instance.createUserActionMonitor(actions, onFinished); + keyboardHandler.onKeyDown(TestUtils.createMockKeyEvent(KeyCode.LEFT)); + keyboardHandler.onKeyUp(TestUtils.createMockKeyEvent(KeyCode.LEFT)); + assertFalse(finished); + keyboardHandler.onKeyDown(TestUtils.createMockKeyEvent(KeyCode.RIGHT)); + keyboardHandler.onKeyUp(TestUtils.createMockKeyEvent(KeyCode.RIGHT)); + assertFalse(finished); + keyboardHandler.onKeyDown(TestUtils.createMockKeyEvent(KeyCode.SPACE)); + keyboardHandler.onKeyUp(TestUtils.createMockKeyEvent(KeyCode.SPACE)); + assertTrue(finished); }); // Tests that we can match a key sequence. Serves as an integration test // since we don't directly call a UserActionMonitor function. -TEST_F('ChromeVoxUserActionMonitorTest', 'MultipleKeys', function() { - this.runWithLoadedTree(this.simpleDoc, function() { - const keyboardHandler = new BackgroundKeyboardHandler(); - let finished = false; - const actions = [{ - type: 'key_sequence', - value: {'cvoxModifier': true, 'keys': {'keyCode': [KeyCode.O, KeyCode.B]}} - }]; - const onFinished = () => finished = true; +TEST_F('ChromeVoxUserActionMonitorTest', 'MultipleKeys', async function() { + await this.runWithLoadedTree(this.simpleDoc); + const keyboardHandler = new BackgroundKeyboardHandler(); + let finished = false; + const actions = [{ + type: 'key_sequence', + value: {'cvoxModifier': true, 'keys': {'keyCode': [KeyCode.O, KeyCode.B]}} + }]; + const onFinished = () => finished = true; - ChromeVoxState.instance.createUserActionMonitor(actions, onFinished); - keyboardHandler.onKeyDown(TestUtils.createMockKeyEvent(KeyCode.O)); - keyboardHandler.onKeyUp(TestUtils.createMockKeyEvent(KeyCode.O)); - assertFalse(finished); - keyboardHandler.onKeyDown(TestUtils.createMockKeyEvent(KeyCode.B)); - keyboardHandler.onKeyUp(TestUtils.createMockKeyEvent(KeyCode.B)); - assertFalse(finished); - keyboardHandler.onKeyDown(TestUtils.createMockKeyEvent(KeyCode.SEARCH)); - keyboardHandler.onKeyUp(TestUtils.createMockKeyEvent(KeyCode.SEARCH)); - assertFalse(finished); - keyboardHandler.onKeyDown( - TestUtils.createMockKeyEvent(KeyCode.O, {searchKeyHeld: true})); - assertFalse(finished); - keyboardHandler.onKeyUp( - TestUtils.createMockKeyEvent(KeyCode.O, {searchKeyHeld: true})); - keyboardHandler.onKeyDown(TestUtils.createMockKeyEvent(KeyCode.B)); - assertTrue(finished); - }); + ChromeVoxState.instance.createUserActionMonitor(actions, onFinished); + keyboardHandler.onKeyDown(TestUtils.createMockKeyEvent(KeyCode.O)); + keyboardHandler.onKeyUp(TestUtils.createMockKeyEvent(KeyCode.O)); + assertFalse(finished); + keyboardHandler.onKeyDown(TestUtils.createMockKeyEvent(KeyCode.B)); + keyboardHandler.onKeyUp(TestUtils.createMockKeyEvent(KeyCode.B)); + assertFalse(finished); + keyboardHandler.onKeyDown(TestUtils.createMockKeyEvent(KeyCode.SEARCH)); + keyboardHandler.onKeyUp(TestUtils.createMockKeyEvent(KeyCode.SEARCH)); + assertFalse(finished); + keyboardHandler.onKeyDown( + TestUtils.createMockKeyEvent(KeyCode.O, {searchKeyHeld: true})); + assertFalse(finished); + keyboardHandler.onKeyUp( + TestUtils.createMockKeyEvent(KeyCode.O, {searchKeyHeld: true})); + keyboardHandler.onKeyDown(TestUtils.createMockKeyEvent(KeyCode.B)); + assertTrue(finished); }); // Tests that we can match multiple key sequences. -TEST_F('ChromeVoxUserActionMonitorTest', 'MultipleKeySequences', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, function() { - let finished = false; - const actions = [ - { - type: 'key_sequence', - value: { - 'keys': {'altKey': [true], 'shiftKey': [true], 'keyCode': [KeyCode.L]} +TEST_F( + 'ChromeVoxUserActionMonitorTest', 'MultipleKeySequences', async function() { + const mockFeedback = this.createMockFeedback(); + await this.runWithLoadedTree(this.simpleDoc); + let finished = false; + const actions = [ + { + type: 'key_sequence', + value: { + 'keys': + {'altKey': [true], 'shiftKey': [true], 'keyCode': [KeyCode.L]} + }, + afterActionMsg: 'You pressed the first sequence!' }, - afterActionMsg: 'You pressed the first sequence!' - }, - { - type: 'key_sequence', - value: { - 'keys': {'altKey': [true], 'shiftKey': [true], 'keyCode': [KeyCode.S]} - }, - afterActionMsg: 'You pressed the second sequence!' - } - ]; - const onFinished = () => finished = true; + { + type: 'key_sequence', + value: { + 'keys': + {'altKey': [true], 'shiftKey': [true], 'keyCode': [KeyCode.S]} + }, + afterActionMsg: 'You pressed the second sequence!' + } + ]; + const onFinished = () => finished = true; - const altShiftLSequence = new KeySequence(TestUtils.createMockKeyEvent( - KeyCode.L, {altKey: true, shiftKey: true})); - const altShiftSSequence = new KeySequence(TestUtils.createMockKeyEvent( - KeyCode.S, {altKey: true, shiftKey: true})); - let monitor; - mockFeedback - .call(() => { - monitor = new UserActionMonitor(actions, onFinished); - assertFalse(monitor.onKeySequence(altShiftSSequence)); - assertFalse(finished); - assertTrue(monitor.onKeySequence(altShiftLSequence)); - assertFalse(finished); - }) - .expectSpeech('You pressed the first sequence!') - .call(() => { - assertFalse(monitor.onKeySequence(altShiftLSequence)); - assertFalse(finished); - assertTrue(monitor.onKeySequence(altShiftSSequence)); - assertTrue(finished); - }) - .expectSpeech('You pressed the second sequence!'); - mockFeedback.replay(); - }); -}); + const altShiftLSequence = new KeySequence(TestUtils.createMockKeyEvent( + KeyCode.L, {altKey: true, shiftKey: true})); + const altShiftSSequence = new KeySequence(TestUtils.createMockKeyEvent( + KeyCode.S, {altKey: true, shiftKey: true})); + let monitor; + mockFeedback + .call(() => { + monitor = new UserActionMonitor(actions, onFinished); + assertFalse(monitor.onKeySequence(altShiftSSequence)); + assertFalse(finished); + assertTrue(monitor.onKeySequence(altShiftLSequence)); + assertFalse(finished); + }) + .expectSpeech('You pressed the first sequence!') + .call(() => { + assertFalse(monitor.onKeySequence(altShiftLSequence)); + assertFalse(finished); + assertTrue(monitor.onKeySequence(altShiftSSequence)); + assertTrue(finished); + }) + .expectSpeech('You pressed the second sequence!'); + mockFeedback.replay(); + }); // Tests that we can provide expectations for ChromeVox commands and block // command execution until the desired command is performed. Serves as an // integration test since we don't directly call a UserActionMonitor function. -TEST_F('ChromeVoxUserActionMonitorTest', 'BlockCommands', function() { +TEST_F('ChromeVoxUserActionMonitorTest', 'BlockCommands', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.paragraphDoc, function() { - const keyboardHandler = new BackgroundKeyboardHandler(); - let finished = false; - const actions = [ - { - type: 'key_sequence', - value: {'cvoxModifier': true, 'keys': {'keyCode': [KeyCode.RIGHT]}} - }, - { - type: 'key_sequence', - value: {'cvoxModifier': true, 'keys': {'keyCode': [KeyCode.LEFT]}} - } - ]; - const onFinished = () => finished = true; + await this.runWithLoadedTree(this.paragraphDoc); + const keyboardHandler = new BackgroundKeyboardHandler(); + let finished = false; + const actions = [ + { + type: 'key_sequence', + value: {'cvoxModifier': true, 'keys': {'keyCode': [KeyCode.RIGHT]}} + }, + { + type: 'key_sequence', + value: {'cvoxModifier': true, 'keys': {'keyCode': [KeyCode.LEFT]}} + } + ]; + const onFinished = () => finished = true; - const nextObject = - TestUtils.createMockKeyEvent(KeyCode.RIGHT, {searchKeyHeld: true}); - const nextLine = - TestUtils.createMockKeyEvent(KeyCode.DOWN, {searchKeyHeld: true}); - const previousObject = - TestUtils.createMockKeyEvent(KeyCode.LEFT, {searchKeyHeld: true}); - const previousLine = - TestUtils.createMockKeyEvent(KeyCode.UP, {searchKeyHeld: true}); + const nextObject = + TestUtils.createMockKeyEvent(KeyCode.RIGHT, {searchKeyHeld: true}); + const nextLine = + TestUtils.createMockKeyEvent(KeyCode.DOWN, {searchKeyHeld: true}); + const previousObject = + TestUtils.createMockKeyEvent(KeyCode.LEFT, {searchKeyHeld: true}); + const previousLine = + TestUtils.createMockKeyEvent(KeyCode.UP, {searchKeyHeld: true}); - ChromeVoxState.instance.createUserActionMonitor(actions, onFinished); - mockFeedback.expectSpeech('Start') - .call(() => { - assertEquals('Start', this.getRangeStart().name); - }) - .call(() => { - // Calling nextLine doesn't move ChromeVox because UserActionMonitor - // expects the nextObject command. - keyboardHandler.onKeyDown(nextLine); - keyboardHandler.onKeyUp(nextLine); - assertEquals('Start', this.getRangeStart().name); - }) - .call(() => { - keyboardHandler.onKeyDown(nextObject); - keyboardHandler.onKeyUp(nextObject); - assertEquals('End', this.getRangeStart().name); - }) - .expectSpeech('End') - .call(() => { - // Calling previousLine doesn't move ChromeVox because - // UserActionMonitor expects the previousObject command. - keyboardHandler.onKeyDown(previousLine); - keyboardHandler.onKeyUp(previousLine); - assertEquals('End', this.getRangeStart().name); - }) - .call(() => { - keyboardHandler.onKeyDown(previousObject); - keyboardHandler.onKeyUp(previousObject); - assertEquals('Start', this.getRangeStart().name); - }) - .expectSpeech('Start') - .replay(); - }); + ChromeVoxState.instance.createUserActionMonitor(actions, onFinished); + mockFeedback.expectSpeech('Start') + .call(() => { + assertEquals('Start', this.getRangeStart().name); + }) + .call(() => { + // Calling nextLine doesn't move ChromeVox because UserActionMonitor + // expects the nextObject command. + keyboardHandler.onKeyDown(nextLine); + keyboardHandler.onKeyUp(nextLine); + assertEquals('Start', this.getRangeStart().name); + }) + .call(() => { + keyboardHandler.onKeyDown(nextObject); + keyboardHandler.onKeyUp(nextObject); + assertEquals('End', this.getRangeStart().name); + }) + .expectSpeech('End') + .call(() => { + // Calling previousLine doesn't move ChromeVox because + // UserActionMonitor expects the previousObject command. + keyboardHandler.onKeyDown(previousLine); + keyboardHandler.onKeyUp(previousLine); + assertEquals('End', this.getRangeStart().name); + }) + .call(() => { + keyboardHandler.onKeyDown(previousObject); + keyboardHandler.onKeyUp(previousObject); + assertEquals('Start', this.getRangeStart().name); + }) + .expectSpeech('Start') + .replay(); }); // Tests that a user can close ChromeVox (Ctrl + Alt + Z) when UserActionMonitor // is active. -TEST_F('ChromeVoxUserActionMonitorTest', 'CloseChromeVox', function() { - this.runWithLoadedTree(this.simpleDoc, function() { - const keyboardHandler = new BackgroundKeyboardHandler(); - let finished = false; - let closed = false; - const actions = - [{type: 'key_sequence', value: {'keys': {'keyCode': [KeyCode.A]}}}]; - const onFinished = () => finished = true; - ChromeVoxState.instance.createUserActionMonitor(actions, onFinished); - // Swap in the below function so we don't actually close ChromeVox. - UserActionMonitor.closeChromeVox_ = () => { - closed = true; - }; +TEST_F('ChromeVoxUserActionMonitorTest', 'CloseChromeVox', async function() { + await this.runWithLoadedTree(this.simpleDoc); + const keyboardHandler = new BackgroundKeyboardHandler(); + let finished = false; + let closed = false; + const actions = + [{type: 'key_sequence', value: {'keys': {'keyCode': [KeyCode.A]}}}]; + const onFinished = () => finished = true; + ChromeVoxState.instance.createUserActionMonitor(actions, onFinished); + // Swap in the below function so we don't actually close ChromeVox. + UserActionMonitor.closeChromeVox_ = () => { + closed = true; + }; - assertFalse(closed); - assertFalse(finished); - keyboardHandler.onKeyDown( - TestUtils.createMockKeyEvent(KeyCode.CONTROL, {ctrlKey: true})); - assertFalse(closed); - assertFalse(finished); - keyboardHandler.onKeyDown(TestUtils.createMockKeyEvent( - KeyCode.ALT, {ctrlKey: true, altKey: true})); - assertFalse(closed); - assertFalse(finished); - keyboardHandler.onKeyDown( - TestUtils.createMockKeyEvent(KeyCode.Z, {ctrlKey: true, altKey: true})); - assertTrue(closed); - // |finished| remains false since we didn't press the expected key sequence. - assertFalse(finished); - }); + assertFalse(closed); + assertFalse(finished); + keyboardHandler.onKeyDown( + TestUtils.createMockKeyEvent(KeyCode.CONTROL, {ctrlKey: true})); + assertFalse(closed); + assertFalse(finished); + keyboardHandler.onKeyDown( + TestUtils.createMockKeyEvent(KeyCode.ALT, {ctrlKey: true, altKey: true})); + assertFalse(closed); + assertFalse(finished); + keyboardHandler.onKeyDown( + TestUtils.createMockKeyEvent(KeyCode.Z, {ctrlKey: true, altKey: true})); + assertTrue(closed); + // |finished| remains false since we didn't press the expected key sequence. + assertFalse(finished); }); // Tests that we can stop propagation of an action, even if it is matched. // In this test, we stop propagation of the Control key to avoid executing the // stopSpeech command. -TEST_F('ChromeVoxUserActionMonitorTest', 'StopPropagation', function() { - this.runWithLoadedTree(this.simpleDoc, function() { - const keyboardHandler = ChromeVoxState.instance.keyboardHandler_; - let finished = false; - let executedCommand = false; - const actions = [{ - type: 'key_sequence', - value: {keys: {keyCode: [KeyCode.CONTROL]}}, - shouldPropagate: false - }]; - const onFinished = () => finished = true; - ChromeVoxState.instance.createUserActionMonitor(actions, onFinished); - ChromeVoxKbHandler.commandHandler = function(command) { - executedCommand = true; - }; - assertFalse(finished); - assertFalse(executedCommand); - keyboardHandler.onKeyDown(TestUtils.createMockKeyEvent(KeyCode.CONTROL)); - keyboardHandler.onKeyUp(TestUtils.createMockKeyEvent(KeyCode.CONTROL)); - assertFalse(executedCommand); - assertTrue(finished); - }); +TEST_F('ChromeVoxUserActionMonitorTest', 'StopPropagation', async function() { + await this.runWithLoadedTree(this.simpleDoc); + const keyboardHandler = ChromeVoxState.instance.keyboardHandler_; + let finished = false; + let executedCommand = false; + const actions = [{ + type: 'key_sequence', + value: {keys: {keyCode: [KeyCode.CONTROL]}}, + shouldPropagate: false + }]; + const onFinished = () => finished = true; + ChromeVoxState.instance.createUserActionMonitor(actions, onFinished); + ChromeVoxKbHandler.commandHandler = function(command) { + executedCommand = true; + }; + assertFalse(finished); + assertFalse(executedCommand); + keyboardHandler.onKeyDown(TestUtils.createMockKeyEvent(KeyCode.CONTROL)); + keyboardHandler.onKeyUp(TestUtils.createMockKeyEvent(KeyCode.CONTROL)); + assertFalse(executedCommand); + assertTrue(finished); }); // Tests that we can match a gesture when it's performed. -TEST_F('ChromeVoxUserActionMonitorTest', 'Gestures', function() { - this.runWithLoadedTree(this.simpleDoc, function() { - let finished = false; - const actions = [{type: 'gesture', value: Gesture.SWIPE_RIGHT1}]; - const onFinished = () => finished = true; +TEST_F('ChromeVoxUserActionMonitorTest', 'Gestures', async function() { + await this.runWithLoadedTree(this.simpleDoc); + let finished = false; + const actions = [{type: 'gesture', value: Gesture.SWIPE_RIGHT1}]; + const onFinished = () => finished = true; - ChromeVoxState.instance.createUserActionMonitor(actions, onFinished); - doGesture(Gesture.SWIPE_LEFT1)(); - assertFalse(finished); - doGesture(Gesture.SWIPE_LEFT2)(); - assertFalse(finished); - doGesture(Gesture.SWIPE_RIGHT1)(); - assertTrue(finished); - }); + ChromeVoxState.instance.createUserActionMonitor(actions, onFinished); + doGesture(Gesture.SWIPE_LEFT1)(); + assertFalse(finished); + doGesture(Gesture.SWIPE_LEFT2)(); + assertFalse(finished); + doGesture(Gesture.SWIPE_RIGHT1)(); + assertTrue(finished); }); // Tests that we can perform a command when an action has been matched. -TEST_F('ChromeVoxUserActionMonitorTest', 'AfterActionCommand', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, function() { - let finished = false; - const actions = [{ - type: 'gesture', - value: Gesture.SWIPE_RIGHT1, - afterActionCmd: 'announceBatteryDescription' - }]; - const onFinished = () => finished = true; +TEST_F( + 'ChromeVoxUserActionMonitorTest', 'AfterActionCommand', async function() { + const mockFeedback = this.createMockFeedback(); + await this.runWithLoadedTree(this.simpleDoc); + let finished = false; + const actions = [{ + type: 'gesture', + value: Gesture.SWIPE_RIGHT1, + afterActionCmd: 'announceBatteryDescription' + }]; + const onFinished = () => finished = true; - ChromeVoxState.instance.createUserActionMonitor(actions, onFinished); - mockFeedback - .call(() => { - doGesture(Gesture.SWIPE_RIGHT1)(); - assertTrue(finished); - }) - .expectSpeech(/Battery at [0-9]+ percent/) - .replay(); - }); -}); + ChromeVoxState.instance.createUserActionMonitor(actions, onFinished); + mockFeedback + .call(() => { + doGesture(Gesture.SWIPE_RIGHT1)(); + assertTrue(finished); + }) + .expectSpeech(/Battery at [0-9]+ percent/) + .replay(); + });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/abstract_tts.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/abstract_tts.js index fed89f4..1e19dd5 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/abstract_tts.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/abstract_tts.js
@@ -8,17 +8,11 @@ * */ -goog.provide('AbstractTts'); - -goog.require('Msgs'); -goog.require('TtsInterface'); -goog.require('goog.i18n.MessageFormat'); - /** * Creates a new instance. * @implements {TtsInterface} */ -AbstractTts = class { +export class AbstractTts { constructor() { this.ttsProperties = new Object(); @@ -72,7 +66,12 @@ } } - /** @override */ + /** + * @param {string} textString + * @param {QueueMode} queueMode + * @param {Object=} properties + * @override + */ speak(textString, queueMode, properties) { return this; } @@ -274,7 +273,7 @@ this.ttsProperties[key] = value; } } -}; +} /**
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/command_store.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/command_store.js index 130ccf1..29d96864 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/command_store.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/command_store.js
@@ -30,8 +30,7 @@ * categories. */ - -goog.provide('CommandStore'); +export const CommandStore = {}; /** * Returns all of the categories in the store as an array.
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/console_tts.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/console_tts.js index ffca66f..90621fe 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/console_tts.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/console_tts.js
@@ -10,7 +10,6 @@ goog.require('LogStore'); goog.require('SpeechLog'); -goog.require('AbstractTts'); goog.require('TtsInterface'); /**
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/editable_text_base.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/editable_text_base.js index cbd4b34..940acfd 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/editable_text_base.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/editable_text_base.js
@@ -15,6 +15,8 @@ */ import {ChromeVoxEvent} from '../background/custom_automation_event.js'; +import {AbstractTts} from './abstract_tts.js'; + /** * A class containing the information needed to speak * a text change event to the user.
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/editable_text_base_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/editable_text_base_test.js index 5e69248d..7a43e934 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/editable_text_base_test.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/editable_text_base_test.js
@@ -96,7 +96,14 @@ })(); } + /** @override */ async setUpDeferred() { + await super.setUpDeferred(); + await importModules('AbstractTts', '/chromevox/common/abstract_tts.js'); + await importModules( + ['ChromeVoxEditableTextBase', 'TextChangedEvent', 'TypingEcho'], + '/chromevox/common/editable_text_base.js'); + // TODO: These tests are all assuming we used the IBeam cursor. // We need to add coverage for block cursor. ChromeVoxEditableTextBase.useIBeamCursor = true; @@ -122,7 +129,6 @@ ChromeVoxEditableTextUnitTest.prototype.extraLibraries = [ '../../common/testing/assert_additions.js', '../../common/closure_shim.js', - 'abstract_tts.js', 'chromevox.js', 'msgs.js', 'tts_interface.js',
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/keyboard_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/keyboard_handler.js index 431bc4b8..6f5ae18 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/keyboard_handler.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/keyboard_handler.js
@@ -5,6 +5,8 @@ /** * @fileoverview Handles user keyboard input events. */ +import {KeyMap} from '../background/keymaps/key_map.js'; + export const ChromeVoxKbHandler = {}; /**
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/tts_background.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/tts_background.js index b4ef6887..359e07a 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/tts_background.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/tts_background.js
@@ -7,6 +7,7 @@ * extension API. */ +import {AbstractTts} from './abstract_tts.js'; import {ChromeTtsBase} from './tts_base.js'; const Utterance = class {
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/tts_base.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/tts_base.js index 1104df3c..31b5f04 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/tts_base.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/tts_base.js
@@ -6,6 +6,7 @@ * @fileoverview A base class for Tts living on Chrome platforms. * */ +import {AbstractTts} from './abstract_tts.js'; export class ChromeTtsBase extends AbstractTts { constructor() {
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/learn_mode/kbexplorer_loader.js b/chrome/browser/resources/chromeos/accessibility/chromevox/learn_mode/kbexplorer_loader.js index 4089af5..db7ddb8b 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/learn_mode/kbexplorer_loader.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/learn_mode/kbexplorer_loader.js
@@ -7,16 +7,13 @@ */ goog.require('BrailleCommandData'); -goog.require('BrailleKeyEvent'); -goog.require('Spannable'); -goog.require('AbstractTts'); goog.require('BrailleKeyCommand'); +goog.require('BrailleKeyEvent'); goog.require('ChromeVox'); goog.require('ChromeVoxState'); -goog.require('CommandStore'); -goog.require('KeyMap'); goog.require('KeySequence'); goog.require('KeyUtil'); goog.require('LibLouis'); goog.require('Msgs'); goog.require('NavBraille'); +goog.require('Spannable');
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/learn_mode/learn_mode.js b/chrome/browser/resources/chromeos/accessibility/chromevox/learn_mode/learn_mode.js index 8cd5e9f..673b3360 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/learn_mode/learn_mode.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/learn_mode/learn_mode.js
@@ -7,14 +7,14 @@ * */ import {GestureCommandData} from '../background/gesture_command_data.js'; +import {KeyMap} from '../background/keymaps/key_map.js'; +import {CommandStore} from '../common/command_store.js'; import {ChromeVoxKbHandler} from '../common/keyboard_handler.js'; /** * Class to manage the keyboard explorer. */ export class LearnMode { - constructor() {} - /** * Initialize keyboard explorer. */
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/options/options.js b/chrome/browser/resources/chromeos/accessibility/chromevox/options/options.js index 6a79707..a1b4a018 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/options/options.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/options/options.js
@@ -8,6 +8,7 @@ */ import {ChromeVoxPrefs} from '../background/prefs.js'; +import {AbstractTts} from '../common/abstract_tts.js'; import {TtsBackground} from '../common/tts_background.js'; /** @const {string} */
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_loader.js b/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_loader.js index 3bc8cf77..6d265b2 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_loader.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_loader.js
@@ -6,7 +6,6 @@ * @fileoverview Loads the options script. */ -goog.require('AbstractTts'); goog.require('BluetoothBrailleDisplayUI'); goog.require('BrailleTable'); goog.require('BrailleTranslatorManager'); @@ -16,3 +15,8 @@ goog.require('ExtensionBridge'); goog.require('Msgs'); goog.require('PanelCommand'); +goog.require('PhoneticData'); +goog.require('TtsInterface'); + +goog.require('constants'); +goog.require('goog.i18n.MessageFormat');
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_test.js index ff97fe4..a6b6ec2 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_test.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_test.js
@@ -16,6 +16,12 @@ window.press = this.press; } + /** @override */ + async setUpDeferred() { + await super.setUpDeferred(); + await importModule('AbstractTts', '/chromevox/common/abstract_tts.js'); + } + runOnOptionsPage(callback) { const mockFeedback = this.createMockFeedback(); chrome.automation.getDesktop((desktop) => {
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/i_search_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/i_search_test.js index 4943206..eaea1b8 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/i_search_test.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/i_search_test.js
@@ -75,44 +75,43 @@ } -TEST_F('ChromeVoxISearchTest', 'Simple', function() { - this.runWithLoadedTree(this.linksAndHeadingsDoc, function(rootNode) { - const handler = new FakeISearchHandler(this); - const search = new ISearch(new cursors.Cursor(rootNode, 0)); - search.handler = handler; +TEST_F('ChromeVoxISearchTest', 'Simple', async function() { + const rootNode = await this.runWithLoadedTree(this.linksAndHeadingsDoc); + const handler = new FakeISearchHandler(this); + const search = new ISearch(new cursors.Cursor(rootNode, 0)); + search.handler = handler; - // Simple forward search. - search.search('US', 'forward'); - handler.expect( - 'start=6 end=8 text=About US', - search.search.bind(search, 'start', 'backward')); + // Simple forward search. + search.search('US', 'forward'); + handler.expect( + 'start=6 end=8 text=About US', + search.search.bind(search, 'start', 'backward')); - handler.expect( - 'start', - // Boundary (beginning). - search.search.bind(search, 'foo', 'backward')); + handler.expect( + 'start', + // Boundary (beginning). + search.search.bind(search, 'foo', 'backward')); - handler.expect( - 'boundary=start', - // Boundary (end). - search.search.bind(search, 'foo', 'forward')); + handler.expect( + 'boundary=start', + // Boundary (end). + search.search.bind(search, 'foo', 'forward')); - // Search "focus" doesn't move. - handler.expect( - 'boundary=start', - // Mixed case substring. - search.search.bind(search, 'bReak', 'forward')); + // Search "focus" doesn't move. + handler.expect( + 'boundary=start', + // Mixed case substring. + search.search.bind(search, 'bReak', 'forward')); - handler.expect( - 'start=7 end=12 text=Latest Breaking News', - search.search.bind(search, 'bReaki', 'forward')); + handler.expect( + 'start=7 end=12 text=Latest Breaking News', + search.search.bind(search, 'bReaki', 'forward')); - // Incremental search stays on the current node. - handler.expect( - 'start=7 end=13 text=Latest Breaking News', - search.search.bind(search, 'bReakio', 'forward')); + // Incremental search stays on the current node. + handler.expect( + 'start=7 end=13 text=Latest Breaking News', + search.search.bind(search, 'bReakio', 'forward')); - // No results for the search. - handler.expect('boundary=Latest Breaking News'); - }); + // No results for the search. + handler.expect('boundary=Latest Breaking News'); });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.js b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.js index ad8a0d72..4d7fc36 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.js
@@ -6,6 +6,8 @@ * @fileoverview The ChromeVox panel and menus. */ import {GestureCommandData} from '../background/gesture_command_data.js'; +import {KeyMap} from '../background/keymaps/key_map.js'; +import {CommandStore} from '../common/command_store.js'; import {ISearchUI} from './i_search.js'; import {PanelInterface} from './panel_interface.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_loader.js b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_loader.js index d31eb853..441da62 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_loader.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_loader.js
@@ -11,11 +11,9 @@ goog.require('AutomationUtil'); goog.require('BrailleCommandData'); goog.require('ChromeVoxState'); -goog.require('CommandStore'); goog.require('EventGenerator'); goog.require('EventSourceType'); goog.require('KeyCode'); -goog.require('KeyMap'); goog.require('KeyUtil'); goog.require('LocaleOutputHelper'); goog.require('Msgs');
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_test.js index 528fd10..f06a41c0 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_test.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_test.js
@@ -88,180 +88,167 @@ } }; -TEST_F('ChromeVoxPanelTest', 'ActivateMenu', function() { - this.runWithLoadedTree(this.linksDoc, async function(root) { - new PanelCommand(PanelCommandType.OPEN_MENUS).send(); - await this.waitForMenu('panel_search_menu'); - this.fireMockEvent('ArrowRight')(); - this.assertActiveMenuItem('panel_menu_jump', 'Go To Beginning Of Table'); - this.fireMockEvent('ArrowRight')(); - this.assertActiveMenuItem( - 'panel_menu_speech', 'Announce Current Battery Status'); - }); +TEST_F('ChromeVoxPanelTest', 'ActivateMenu', async function() { + await this.runWithLoadedTree(this.linksDoc); + new PanelCommand(PanelCommandType.OPEN_MENUS).send(); + await this.waitForMenu('panel_search_menu'); + this.fireMockEvent('ArrowRight')(); + this.assertActiveMenuItem('panel_menu_jump', 'Go To Beginning Of Table'); + this.fireMockEvent('ArrowRight')(); + this.assertActiveMenuItem( + 'panel_menu_speech', 'Announce Current Battery Status'); }); // TODO(https://crbug.com/1299765): Re-enable once flaky timeouts are fixed. -TEST_F('ChromeVoxPanelTest', 'DISABLED_LinkMenu', function() { - this.runWithLoadedTree(this.linksDoc, async function(root) { - CommandHandlerInterface.instance.onCommand('showLinksList'); - await this.waitForMenu('role_link'); - this.fireMockEvent('ArrowLeft')(); - this.assertActiveMenuItem('role_landmark', 'No items'); - this.fireMockEvent('ArrowRight')(); - this.assertActiveMenuItem('role_link', 'apple Internal link'); - this.fireMockEvent('ArrowUp')(); - this.assertActiveMenuItem('role_link', 'banana Internal link'); - }); +TEST_F('ChromeVoxPanelTest', 'DISABLED_LinkMenu', async function() { + await this.runWithLoadedTree(this.linksDoc); + CommandHandlerInterface.instance.onCommand('showLinksList'); + await this.waitForMenu('role_link'); + this.fireMockEvent('ArrowLeft')(); + this.assertActiveMenuItem('role_landmark', 'No items'); + this.fireMockEvent('ArrowRight')(); + this.assertActiveMenuItem('role_link', 'apple Internal link'); + this.fireMockEvent('ArrowUp')(); + this.assertActiveMenuItem('role_link', 'banana Internal link'); }); -TEST_F('ChromeVoxPanelTest', 'FormControlsMenu', function() { - this.runWithLoadedTree( - `<button>Cancel</button><button>OK</button>`, async function(root) { - CommandHandlerInterface.instance.onCommand('showFormsList'); - await this.waitForMenu('panel_menu_form_controls'); - this.fireMockEvent('ArrowDown')(); - this.assertActiveMenuItem('panel_menu_form_controls', 'OK Button'); - this.fireMockEvent('ArrowUp')(); - this.assertActiveMenuItem('panel_menu_form_controls', 'Cancel Button'); - }); +TEST_F('ChromeVoxPanelTest', 'FormControlsMenu', async function() { + await this.runWithLoadedTree(`<button>Cancel</button><button>OK</button>`); + CommandHandlerInterface.instance.onCommand('showFormsList'); + await this.waitForMenu('panel_menu_form_controls'); + this.fireMockEvent('ArrowDown')(); + this.assertActiveMenuItem('panel_menu_form_controls', 'OK Button'); + this.fireMockEvent('ArrowUp')(); + this.assertActiveMenuItem('panel_menu_form_controls', 'Cancel Button'); }); -TEST_F('ChromeVoxPanelTest', 'SearchMenu', function() { +TEST_F('ChromeVoxPanelTest', 'SearchMenu', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.linksDoc, async function(root) { - new PanelCommand(PanelCommandType.OPEN_MENUS).send(); - await this.waitForMenu('panel_search_menu'); - await mockFeedback - .expectSpeech('Search the menus', /Type to search the menus/) - .call(() => { - this.fireMockQuery('jump')(); - this.assertActiveSearchMenuItem('Jump To Details'); - }) - .expectSpeech(/Jump/, 'Menu item', /[0-9]+ of [0-9]+/) - .call(() => { - this.fireMockEvent('ArrowDown')(); - this.assertActiveSearchMenuItem('Jump To The Bottom Of The Page'); - }) - .expectSpeech(/Jump/, 'Menu item', /[0-9]+ of [0-9]+/) - .call(() => { - this.fireMockEvent('ArrowDown')(); - this.assertActiveSearchMenuItem('Jump To The Top Of The Page'); - }) - .expectSpeech(/Jump/, 'Menu item', /[0-9]+ of [0-9]+/) - .call(() => { - this.fireMockEvent('ArrowDown')(); - this.assertActiveSearchMenuItem('Jump To Details'); - }) - .expectSpeech(/Jump/, 'Menu item', /[0-9]+ of [0-9]+/) - .replay(); - }); + await this.runWithLoadedTree(this.linksDoc); + new PanelCommand(PanelCommandType.OPEN_MENUS).send(); + await this.waitForMenu('panel_search_menu'); + await mockFeedback + .expectSpeech('Search the menus', /Type to search the menus/) + .call(() => { + this.fireMockQuery('jump')(); + this.assertActiveSearchMenuItem('Jump To Details'); + }) + .expectSpeech(/Jump/, 'Menu item', /[0-9]+ of [0-9]+/) + .call(() => { + this.fireMockEvent('ArrowDown')(); + this.assertActiveSearchMenuItem('Jump To The Bottom Of The Page'); + }) + .expectSpeech(/Jump/, 'Menu item', /[0-9]+ of [0-9]+/) + .call(() => { + this.fireMockEvent('ArrowDown')(); + this.assertActiveSearchMenuItem('Jump To The Top Of The Page'); + }) + .expectSpeech(/Jump/, 'Menu item', /[0-9]+ of [0-9]+/) + .call(() => { + this.fireMockEvent('ArrowDown')(); + this.assertActiveSearchMenuItem('Jump To Details'); + }) + .expectSpeech(/Jump/, 'Menu item', /[0-9]+ of [0-9]+/) + .replay(); }); // TODO(crbug.com/1088438): flaky crashes. -TEST_F('ChromeVoxPanelTest', 'DISABLED_Gestures', function() { +TEST_F('ChromeVoxPanelTest', 'DISABLED_Gestures', async function() { const doGestureAsync = async (gesture) => { doGesture(gesture)(); }; - this.runWithLoadedTree( - `<button>Cancel</button><button>OK</button>`, async function(root) { - doGestureAsync(Gesture.TAP4); - await this.waitForMenu('panel_search_menu'); - // GestureCommandHandler behaves in special ways only with range over - // the panel. Fake this out by setting range there. - const desktop = root.parent.root; - const panelNode = desktop.find( - {role: 'rootWebArea', attributes: {name: 'ChromeVox Panel'}}); - ChromeVoxState.instance.setCurrentRange( - cursors.Range.fromNode(panelNode)); + await this.runWithLoadedTree(`<button>Cancel</button><button>OK</button>`); + doGestureAsync(Gesture.TAP4); + await this.waitForMenu('panel_search_menu'); + // GestureCommandHandler behaves in special ways only with range over + // the panel. Fake this out by setting range there. + const desktop = root.parent.root; + const panelNode = desktop.find( + {role: 'rootWebArea', attributes: {name: 'ChromeVox Panel'}}); + ChromeVoxState.instance.setCurrentRange(cursors.Range.fromNode(panelNode)); - doGestureAsync(Gesture.SWIPE_RIGHT1); - await this.waitForMenu('panel_menu_jump'); + doGestureAsync(Gesture.SWIPE_RIGHT1); + await this.waitForMenu('panel_menu_jump'); - doGestureAsync(Gesture.SWIPE_RIGHT1); - await this.waitForMenu('panel_menu_speech'); + doGestureAsync(Gesture.SWIPE_RIGHT1); + await this.waitForMenu('panel_menu_speech'); - doGestureAsync(Gesture.SWIPE_LEFT1); - await this.waitForMenu('panel_menu_jump'); - }); + doGestureAsync(Gesture.SWIPE_LEFT1); + await this.waitForMenu('panel_menu_jump'); }); -TEST_F('ChromeVoxPanelTest', 'InternationalFormControlsMenu', function() { - this.runWithLoadedTree(this.internationalButtonDoc, async function(root) { - // Turn on language switching and set available voice list. - localStorage['languageSwitching'] = 'true'; - this.getPanelWindow().LocaleOutputHelper.instance.availableVoices_ = - [{'lang': 'en-US'}, {'lang': 'es-ES'}]; - CommandHandlerInterface.instance.onCommand('showFormsList'); - await this.waitForMenu('panel_menu_form_controls'); - this.fireMockEvent('ArrowDown')(); - this.assertActiveMenuItem( - 'panel_menu_form_controls', 'español: Prueba Button'); - this.fireMockEvent('ArrowUp')(); - this.assertActiveMenuItem('panel_menu_form_controls', 'Test Button'); - }); +TEST_F('ChromeVoxPanelTest', 'InternationalFormControlsMenu', async function() { + await this.runWithLoadedTree(this.internationalButtonDoc); + // Turn on language switching and set available voice list. + localStorage['languageSwitching'] = 'true'; + this.getPanelWindow().LocaleOutputHelper.instance.availableVoices_ = + [{'lang': 'en-US'}, {'lang': 'es-ES'}]; + CommandHandlerInterface.instance.onCommand('showFormsList'); + await this.waitForMenu('panel_menu_form_controls'); + this.fireMockEvent('ArrowDown')(); + this.assertActiveMenuItem( + 'panel_menu_form_controls', 'español: Prueba Button'); + this.fireMockEvent('ArrowUp')(); + this.assertActiveMenuItem('panel_menu_form_controls', 'Test Button'); }); -TEST_F('ChromeVoxPanelTest', 'ActionsMenu', function() { - this.runWithLoadedTree(this.linksDoc, async function(root) { - CommandHandlerInterface.instance.onCommand('showActionsMenu'); - await this.waitForMenu('panel_menu_actions'); - this.fireMockEvent('ArrowDown')(); - this.assertActiveMenuItem('panel_menu_actions', 'Start Or End Selection'); - this.fireMockEvent('ArrowUp')(); - this.assertActiveMenuItem('panel_menu_actions', 'Click On Current Item'); - }); +TEST_F('ChromeVoxPanelTest', 'ActionsMenu', async function() { + await this.runWithLoadedTree(this.linksDoc); + CommandHandlerInterface.instance.onCommand('showActionsMenu'); + await this.waitForMenu('panel_menu_actions'); + this.fireMockEvent('ArrowDown')(); + this.assertActiveMenuItem('panel_menu_actions', 'Start Or End Selection'); + this.fireMockEvent('ArrowUp')(); + this.assertActiveMenuItem('panel_menu_actions', 'Click On Current Item'); }); -TEST_F('ChromeVoxPanelTest', 'ShortcutsAreInternationalized', function() { - this.runWithLoadedTree(this.linksDoc, async function(root) { - new PanelCommand(PanelCommandType.OPEN_MENUS).send(); - await this.waitForMenu('panel_search_menu'); - this.fireMockEvent('ArrowRight')(); - this.assertActiveMenuItem( - 'panel_menu_jump', 'Go To Beginning Of Table', - 'Search+Alt+Shift+ArrowLeft'); - this.fireMockEvent('ArrowRight')(); - this.assertActiveMenuItem( - 'panel_menu_speech', 'Announce Current Battery Status', - 'Search+O, then B'); - // Skip the tabs menu. - this.fireMockEvent('ArrowRight')(); - this.fireMockEvent('ArrowRight')(); - this.assertActiveMenuItem( - 'panel_menu_chromevox', 'Open keyboard shortcuts menu', 'Ctrl+Alt+/'); - }); +TEST_F('ChromeVoxPanelTest', 'ShortcutsAreInternationalized', async function() { + await this.runWithLoadedTree(this.linksDoc); + new PanelCommand(PanelCommandType.OPEN_MENUS).send(); + await this.waitForMenu('panel_search_menu'); + this.fireMockEvent('ArrowRight')(); + this.assertActiveMenuItem( + 'panel_menu_jump', 'Go To Beginning Of Table', + 'Search+Alt+Shift+ArrowLeft'); + this.fireMockEvent('ArrowRight')(); + this.assertActiveMenuItem( + 'panel_menu_speech', 'Announce Current Battery Status', + 'Search+O, then B'); + // Skip the tabs menu. + this.fireMockEvent('ArrowRight')(); + this.fireMockEvent('ArrowRight')(); + this.assertActiveMenuItem( + 'panel_menu_chromevox', 'Open keyboard shortcuts menu', 'Ctrl+Alt+/'); }); // Ensure 'Touch Gestures' is not in the panel menus by default. TEST_F( 'ChromeVoxPanelTest', 'TouchGesturesMenuNotAvailableWhenNotInTouchMode', - function() { - this.runWithLoadedTree(this.linksDoc, async function(root) { - new PanelCommand(PanelCommandType.OPEN_MENUS).send(); - await this.waitForMenu('panel_search_menu'); - do { - this.fireMockEvent('ArrowRight')(); - assertFalse(this.isMenuTitleMessage('panel_menu_touchgestures')); - } while (!this.isMenuTitleMessage('panel_search_menu')); - }); + async function() { + await this.runWithLoadedTree(this.linksDoc); + new PanelCommand(PanelCommandType.OPEN_MENUS).send(); + await this.waitForMenu('panel_search_menu'); + do { + this.fireMockEvent('ArrowRight')(); + assertFalse(this.isMenuTitleMessage('panel_menu_touchgestures')); + } while (!this.isMenuTitleMessage('panel_search_menu')); }); // Ensure 'Touch Gesture' is in the panel menus when touch mode is enabled. TEST_F( 'ChromeVoxPanelTest', 'TouchGesturesMenuAvailableWhenInTouchMode', - function() { - this.runWithLoadedTree(this.linksDoc, async function(root) { - this.getPanel().setTouchGestureSourceForTesting(); - new PanelCommand(PanelCommandType.OPEN_MENUS).send(); - await this.waitForMenu('panel_search_menu'); + async function() { + await this.runWithLoadedTree(this.linksDoc); + this.getPanel().setTouchGestureSourceForTesting(); + new PanelCommand(PanelCommandType.OPEN_MENUS).send(); + await this.waitForMenu('panel_search_menu'); - // Look for Touch Gestures menu, fail if getting back to start. - do { - this.fireMockEvent('ArrowRight')(); - assertFalse(this.isMenuTitleMessage('panel_search_menu')); - } while (!this.isMenuTitleMessage('panel_menu_touchgestures')); + // Look for Touch Gestures menu, fail if getting back to start. + do { + this.fireMockEvent('ArrowRight')(); + assertFalse(this.isMenuTitleMessage('panel_search_menu')); + } while (!this.isMenuTitleMessage('panel_menu_touchgestures')); - this.assertActiveMenuItem( - 'panel_menu_touchgestures', 'Click on current item'); - }); + this.assertActiveMenuItem( + 'panel_menu_touchgestures', 'Click on current item'); });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/tutorial_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/tutorial_test.js index c84dcd0..04399a3 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/tutorial_test.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/tutorial_test.js
@@ -77,398 +77,387 @@ } }; -TEST_F('ChromeVoxTutorialTest', 'BasicTest', function() { +TEST_F('ChromeVoxTutorialTest', 'BasicTest', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, async function(root) { - await this.launchAndWaitForTutorial(); - mockFeedback - .expectSpeech( - 'ChromeVox tutorial', 'Heading 1', - 'Press Search + Right Arrow, or Search + Left Arrow to browse' + - ' topics') - .call(doCmd('nextObject')) - .expectSpeech('Quick orientation', 'Link') - .call(doCmd('nextObject')) - .expectSpeech('Essential keys', 'Link') - .call(doCmd('nextObject')) - .expectSpeech('Navigation', 'Link') - .call(doCmd('nextObject')) - .expectSpeech('Command references', 'Link') - .call(doCmd('nextObject')) - .expectSpeech('Sounds and settings', 'Link') - .call(doCmd('nextObject')) - .expectSpeech('Resources', 'Link') - .call(doCmd('nextObject')) - .expectSpeech('Exit tutorial', 'Button') - .replay(); - }); + const root = await this.runWithLoadedTree(this.simpleDoc); + await this.launchAndWaitForTutorial(); + mockFeedback + .expectSpeech( + 'ChromeVox tutorial', 'Heading 1', + 'Press Search + Right Arrow, or Search + Left Arrow to browse' + + ' topics') + .call(doCmd('nextObject')) + .expectSpeech('Quick orientation', 'Link') + .call(doCmd('nextObject')) + .expectSpeech('Essential keys', 'Link') + .call(doCmd('nextObject')) + .expectSpeech('Navigation', 'Link') + .call(doCmd('nextObject')) + .expectSpeech('Command references', 'Link') + .call(doCmd('nextObject')) + .expectSpeech('Sounds and settings', 'Link') + .call(doCmd('nextObject')) + .expectSpeech('Resources', 'Link') + .call(doCmd('nextObject')) + .expectSpeech('Exit tutorial', 'Button') + .replay(); }); // Tests that different lessons are shown when choosing an experience from the // main menu. // TODO(crbug.com/1193799): fix ax node errors causing console spew and // breaking tests -TEST_F('ChromeVoxTutorialTest', 'DISABLED_LessonSetTest', function() { +TEST_F('ChromeVoxTutorialTest', 'DISABLED_LessonSetTest', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, async function(root) { - await this.launchAndWaitForTutorial(); - const tutorial = this.getTutorial(); - mockFeedback.expectSpeech('ChromeVox tutorial') - .call(doCmd('nextObject')) - .expectSpeech('Quick orientation') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech(/Quick Orientation Tutorial, [0-9]+ Lessons/) - .expectSpeech( - 'Press Search + Right Arrow, or Search + Left Arrow to browse ' + - 'lessons for this topic') - .call(doCmd('nextObject')) - .expectSpeech('Welcome to ChromeVox!') - .call(() => { - // Call from the tutorial directly, instead of navigating to and - // clicking on the main menu button. - tutorial.showMainMenu_(); - }) - .expectSpeech('ChromeVox tutorial') - .call(doCmd('nextObject')) - .expectSpeech('Quick orientation') - .call(doCmd('nextObject')) - .expectSpeech('Essential keys', 'Link') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech(/Essential Keys Tutorial, [0-9]+ Lessons/) - .call(doCmd('nextObject')) - .expectSpeech('On, Off, and Stop') - .replay(); - }); + const root = await this.runWithLoadedTree(this.simpleDoc); + await this.launchAndWaitForTutorial(); + const tutorial = this.getTutorial(); + mockFeedback.expectSpeech('ChromeVox tutorial') + .call(doCmd('nextObject')) + .expectSpeech('Quick orientation') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech(/Quick Orientation Tutorial, [0-9]+ Lessons/) + .expectSpeech( + 'Press Search + Right Arrow, or Search + Left Arrow to browse ' + + 'lessons for this topic') + .call(doCmd('nextObject')) + .expectSpeech('Welcome to ChromeVox!') + .call(() => { + // Call from the tutorial directly, instead of navigating to and + // clicking on the main menu button. + tutorial.showMainMenu_(); + }) + .expectSpeech('ChromeVox tutorial') + .call(doCmd('nextObject')) + .expectSpeech('Quick orientation') + .call(doCmd('nextObject')) + .expectSpeech('Essential keys', 'Link') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech(/Essential Keys Tutorial, [0-9]+ Lessons/) + .call(doCmd('nextObject')) + .expectSpeech('On, Off, and Stop') + .replay(); }); // Tests that a static lesson does not show the 'Practice area' button. // TODO(crbug.com/1193799): fix ax node errors causing console spew and // breaking tests -TEST_F('ChromeVoxTutorialTest', 'DISABLED_NoPracticeAreaTest', function() { +TEST_F('ChromeVoxTutorialTest', 'DISABLED_NoPracticeAreaTest', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, async function(root) { - await this.launchAndWaitForTutorial(); - const tutorial = this.getTutorial(); - mockFeedback.expectSpeech('ChromeVox tutorial') - .call(doCmd('nextObject')) - .expectSpeech('Quick orientation') - .call(doCmd('nextObject')) - .expectSpeech('Essential keys') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech(/Essential Keys Tutorial, [0-9]+ Lessons/) - .call(() => { - tutorial.showLesson_(0); - }) - .expectSpeech( - 'On, Off, and Stop', 'Heading 1', - ' Press Search + Right Arrow, or Search + Left Arrow to navigate ' + - 'this lesson ') - .call(doCmd('nextButton')) - .expectSpeech('Next lesson') - .replay(); - }); + const root = await this.runWithLoadedTree(this.simpleDoc); + await this.launchAndWaitForTutorial(); + const tutorial = this.getTutorial(); + mockFeedback.expectSpeech('ChromeVox tutorial') + .call(doCmd('nextObject')) + .expectSpeech('Quick orientation') + .call(doCmd('nextObject')) + .expectSpeech('Essential keys') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech(/Essential Keys Tutorial, [0-9]+ Lessons/) + .call(() => { + tutorial.showLesson_(0); + }) + .expectSpeech( + 'On, Off, and Stop', 'Heading 1', + ' Press Search + Right Arrow, or Search + Left Arrow to navigate ' + + 'this lesson ') + .call(doCmd('nextButton')) + .expectSpeech('Next lesson') + .replay(); }); // Tests that an interactive lesson shows the 'Practice area' button. // TODO(crbug.com/1193799): fix ax node errors causing console spew and // breaking tests -TEST_F('ChromeVoxTutorialTest', 'DISABLED_HasPracticeAreaTest', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, async function(root) { - await this.launchAndWaitForTutorial(); - const tutorial = this.getTutorial(); - mockFeedback.expectSpeech('ChromeVox tutorial') - .call(doCmd('nextObject')) - .expectSpeech('Quick orientation') - .call(doCmd('nextObject')) - .expectSpeech('Essential keys') - .call(doCmd('nextObject')) - .expectSpeech('Navigation') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech(/Navigation Tutorial, [0-9]+ Lessons/) - .call(() => { - tutorial.showLesson_(1); - }) - .expectSpeech('Jump Commands', 'Heading 1') - .call(doCmd('nextButton')) - .expectSpeech('Practice area') - .replay(); - }); -}); +TEST_F( + 'ChromeVoxTutorialTest', 'DISABLED_HasPracticeAreaTest', async function() { + const mockFeedback = this.createMockFeedback(); + const root = await this.runWithLoadedTree(this.simpleDoc); + await this.launchAndWaitForTutorial(); + const tutorial = this.getTutorial(); + mockFeedback.expectSpeech('ChromeVox tutorial') + .call(doCmd('nextObject')) + .expectSpeech('Quick orientation') + .call(doCmd('nextObject')) + .expectSpeech('Essential keys') + .call(doCmd('nextObject')) + .expectSpeech('Navigation') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech(/Navigation Tutorial, [0-9]+ Lessons/) + .call(() => { + tutorial.showLesson_(1); + }) + .expectSpeech('Jump Commands', 'Heading 1') + .call(doCmd('nextButton')) + .expectSpeech('Practice area') + .replay(); + }); // Tests nudges given in the general tutorial context. // The first three nudges should read the current item with full context. // Afterward, general hints will be given about using ChromeVox. Lastly, // we will give a hint for exiting the tutorial. -TEST_F('ChromeVoxTutorialTest', 'GeneralNudgesTest', function() { +TEST_F('ChromeVoxTutorialTest', 'GeneralNudgesTest', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, async function(root) { - await this.launchAndWaitForTutorial(); - const tutorial = this.getTutorial(); - const giveNudge = () => { - tutorial.giveNudge(); - }; - mockFeedback.expectSpeech('ChromeVox tutorial'); - for (let i = 0; i < 3; ++i) { - mockFeedback.call(giveNudge).expectSpeech( - 'ChromeVox tutorial', 'Heading 1'); - } - mockFeedback.call(giveNudge) - .expectSpeech('Hint: Hold Search and press the arrow keys to navigate.') - .call(giveNudge) - .expectSpeech( - 'Hint: Press Search + Space to activate the current item.') - .call(giveNudge) - .expectSpeech( - 'Hint: Press Escape if you would like to exit this tutorial.') - .replay(); - }); + const root = await this.runWithLoadedTree(this.simpleDoc); + await this.launchAndWaitForTutorial(); + const tutorial = this.getTutorial(); + const giveNudge = () => { + tutorial.giveNudge(); + }; + mockFeedback.expectSpeech('ChromeVox tutorial'); + for (let i = 0; i < 3; ++i) { + mockFeedback.call(giveNudge).expectSpeech( + 'ChromeVox tutorial', 'Heading 1'); + } + mockFeedback.call(giveNudge) + .expectSpeech('Hint: Hold Search and press the arrow keys to navigate.') + .call(giveNudge) + .expectSpeech('Hint: Press Search + Space to activate the current item.') + .call(giveNudge) + .expectSpeech( + 'Hint: Press Escape if you would like to exit this tutorial.') + .replay(); }); // Tests nudges given in the practice area context. Note, each practice area // can have different nudge messages; this test confirms that nudges given in // the practice area differ from those given in the general tutorial context. -TEST_F('ChromeVoxTutorialTest', 'DISABLED_PracticeAreaNudgesTest', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, async function(root) { - await this.launchAndWaitForTutorial(); - const tutorial = this.getTutorial(); - const giveNudge = () => { - tutorial.giveNudge(); - }; - mockFeedback.expectSpeech('ChromeVox tutorial') - .call(doCmd('nextObject')) - .expectSpeech('Quick orientation') - .call(doCmd('nextObject')) - .expectSpeech('Essential keys') - .call(doCmd('nextObject')) - .expectSpeech('Navigation') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech(/Navigation Tutorial, [0-9]+ Lessons/) - .call(() => { - tutorial.showLesson_(0); - }) - .expectSpeech('Basic Navigation', 'Heading 1') - .call(doCmd('nextButton')) - .expectSpeech('Practice area') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech(/Try using basic navigation to navigate/) - .call(giveNudge) - .expectSpeech( - 'Try pressing Search + left/right arrow. The search key is ' + - 'directly above the shift key') - .call(giveNudge) - .expectSpeech('Press Search + Space to activate the current item.') - .replay(); - }); -}); +TEST_F( + 'ChromeVoxTutorialTest', 'DISABLED_PracticeAreaNudgesTest', + async function() { + const mockFeedback = this.createMockFeedback(); + const root = await this.runWithLoadedTree(this.simpleDoc); + await this.launchAndWaitForTutorial(); + const tutorial = this.getTutorial(); + const giveNudge = () => { + tutorial.giveNudge(); + }; + mockFeedback.expectSpeech('ChromeVox tutorial') + .call(doCmd('nextObject')) + .expectSpeech('Quick orientation') + .call(doCmd('nextObject')) + .expectSpeech('Essential keys') + .call(doCmd('nextObject')) + .expectSpeech('Navigation') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech(/Navigation Tutorial, [0-9]+ Lessons/) + .call(() => { + tutorial.showLesson_(0); + }) + .expectSpeech('Basic Navigation', 'Heading 1') + .call(doCmd('nextButton')) + .expectSpeech('Practice area') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech(/Try using basic navigation to navigate/) + .call(giveNudge) + .expectSpeech( + 'Try pressing Search + left/right arrow. The search key is ' + + 'directly above the shift key') + .call(giveNudge) + .expectSpeech('Press Search + Space to activate the current item.') + .replay(); + }); // Tests that the tutorial closes when the 'Exit tutorial' button is clicked. -TEST_F('ChromeVoxTutorialTest', 'ExitButtonTest', function() { +TEST_F('ChromeVoxTutorialTest', 'ExitButtonTest', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, async function(root) { - await this.launchAndWaitForTutorial(); - const tutorial = this.getTutorial(); - mockFeedback.expectSpeech('ChromeVox tutorial') - .call(doCmd('previousButton')) - .expectSpeech('Exit tutorial') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech('Some web content') - .replay(); - }); + const root = await this.runWithLoadedTree(this.simpleDoc); + await this.launchAndWaitForTutorial(); + const tutorial = this.getTutorial(); + mockFeedback.expectSpeech('ChromeVox tutorial') + .call(doCmd('previousButton')) + .expectSpeech('Exit tutorial') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech('Some web content') + .replay(); }); // Tests that the tutorial closes when Escape is pressed. -TEST_F('ChromeVoxTutorialTest', 'EscapeTest', function() { +TEST_F('ChromeVoxTutorialTest', 'EscapeTest', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, async function(root) { - await this.launchAndWaitForTutorial(); - const tutorial = this.getTutorial(); - mockFeedback.expectSpeech('ChromeVox tutorial') - .call(() => { - // Press Escape. - tutorial.onKeyDown({ - key: 'Escape', - preventDefault: () => {}, - stopPropagation: () => {} - }); - }) - .expectSpeech('Some web content') - .replay(); - }); + const root = await this.runWithLoadedTree(this.simpleDoc); + await this.launchAndWaitForTutorial(); + const tutorial = this.getTutorial(); + mockFeedback.expectSpeech('ChromeVox tutorial') + .call(() => { + // Press Escape. + tutorial.onKeyDown({ + key: 'Escape', + preventDefault: () => {}, + stopPropagation: () => {} + }); + }) + .expectSpeech('Some web content') + .replay(); }); // Tests that the main menu button navigates the user to the main menu screen. // TODO(crbug.com/1193799): fix ax node errors causing console spew and // breaking tests -TEST_F('ChromeVoxTutorialTest', 'DISABLED_MainMenuButton', function() { +TEST_F('ChromeVoxTutorialTest', 'DISABLED_MainMenuButton', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, async function(root) { - await this.launchAndWaitForTutorial(); - const tutorial = this.getTutorial(); - mockFeedback.expectSpeech('ChromeVox tutorial') - .call(this.assertActiveScreen.bind(this, 'main_menu')) - .call(doCmd('nextObject')) - .expectSpeech('Quick orientation') - .call(doCmd('nextObject')) - .expectSpeech('Essential keys') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech(/Essential Keys Tutorial, [0-9]+ Lessons/) - .call(this.assertActiveScreen.bind(this, 'lesson_menu')) - .call(doCmd('previousButton')) - .expectSpeech('Exit tutorial') - .call(doCmd('previousButton')) - .expectSpeech('Main menu') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech('ChromeVox tutorial') - .call(this.assertActiveScreen.bind(this, 'main_menu')) - .replay(); - }); + const root = await this.runWithLoadedTree(this.simpleDoc); + await this.launchAndWaitForTutorial(); + const tutorial = this.getTutorial(); + mockFeedback.expectSpeech('ChromeVox tutorial') + .call(this.assertActiveScreen.bind(this, 'main_menu')) + .call(doCmd('nextObject')) + .expectSpeech('Quick orientation') + .call(doCmd('nextObject')) + .expectSpeech('Essential keys') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech(/Essential Keys Tutorial, [0-9]+ Lessons/) + .call(this.assertActiveScreen.bind(this, 'lesson_menu')) + .call(doCmd('previousButton')) + .expectSpeech('Exit tutorial') + .call(doCmd('previousButton')) + .expectSpeech('Main menu') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech('ChromeVox tutorial') + .call(this.assertActiveScreen.bind(this, 'main_menu')) + .replay(); }); // Tests that the all lessons button navigates the user to the lesson menu // screen. // TODO(crbug.com/1193799): fix ax node errors causing console spew and // breaking tests -TEST_F('ChromeVoxTutorialTest', 'DISABLED_AllLessonsButton', function() { +TEST_F('ChromeVoxTutorialTest', 'DISABLED_AllLessonsButton', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, async function(root) { - await this.launchAndWaitForTutorial(); - const tutorial = this.getTutorial(); - mockFeedback.expectSpeech('ChromeVox tutorial') - .call(this.assertActiveScreen.bind(this, 'main_menu')) - .call(doCmd('nextObject')) - .expectSpeech('Quick orientation') - .call(doCmd('nextObject')) - .expectSpeech('Essential keys') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech(/Essential Keys Tutorial, [0-9]+ Lessons/) - .call(this.assertActiveScreen.bind(this, 'lesson_menu')) - .call(doCmd('nextObject')) - .expectSpeech('On, Off, and Stop') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech('On, Off, and Stop', 'Heading 1') - .call(this.assertActiveScreen.bind(this, 'lesson')) - .call(doCmd('nextButton')) - .expectSpeech('Next lesson') - .call(doCmd('nextButton')) - .expectSpeech('All lessons') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech(/Essential Keys Tutorial, [0-9]+ Lessons/) - .call(this.assertActiveScreen.bind(this, 'lesson_menu')) - .replay(); - }); + const root = await this.runWithLoadedTree(this.simpleDoc); + await this.launchAndWaitForTutorial(); + const tutorial = this.getTutorial(); + mockFeedback.expectSpeech('ChromeVox tutorial') + .call(this.assertActiveScreen.bind(this, 'main_menu')) + .call(doCmd('nextObject')) + .expectSpeech('Quick orientation') + .call(doCmd('nextObject')) + .expectSpeech('Essential keys') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech(/Essential Keys Tutorial, [0-9]+ Lessons/) + .call(this.assertActiveScreen.bind(this, 'lesson_menu')) + .call(doCmd('nextObject')) + .expectSpeech('On, Off, and Stop') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech('On, Off, and Stop', 'Heading 1') + .call(this.assertActiveScreen.bind(this, 'lesson')) + .call(doCmd('nextButton')) + .expectSpeech('Next lesson') + .call(doCmd('nextButton')) + .expectSpeech('All lessons') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech(/Essential Keys Tutorial, [0-9]+ Lessons/) + .call(this.assertActiveScreen.bind(this, 'lesson_menu')) + .replay(); }); // Tests that the next and previous lesson buttons navigate properly. // TODO(crbug.com/1193799): fix ax node errors causing console spew and // breaking tests -TEST_F('ChromeVoxTutorialTest', 'DISABLED_NextPreviousButtons', function() { - const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, async function(root) { - await this.launchAndWaitForTutorial(); - const tutorial = this.getTutorial(); - mockFeedback.expectSpeech('ChromeVox tutorial') - .call(() => { - tutorial.curriculum = 'essential_keys'; - tutorial.showLesson_(0); - this.assertActiveLessonIndex(0); - this.assertActiveScreen('lesson'); - }) - .expectSpeech('On, Off, and Stop', 'Heading 1') - .call(doCmd('nextButton')) - .expectSpeech('Next lesson') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech('The ChromeVox modifier key', 'Heading 1') - .call(this.assertActiveLessonIndex.bind(this, 1)) - .call(doCmd('nextButton')) - .expectSpeech('Previous lesson') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech('On, Off, and Stop', 'Heading 1') - .call(this.assertActiveLessonIndex.bind(this, 0)) - .replay(); - }); -}); +TEST_F( + 'ChromeVoxTutorialTest', 'DISABLED_NextPreviousButtons', async function() { + const mockFeedback = this.createMockFeedback(); + const root = await this.runWithLoadedTree(this.simpleDoc); + await this.launchAndWaitForTutorial(); + const tutorial = this.getTutorial(); + mockFeedback.expectSpeech('ChromeVox tutorial') + .call(() => { + tutorial.curriculum = 'essential_keys'; + tutorial.showLesson_(0); + this.assertActiveLessonIndex(0); + this.assertActiveScreen('lesson'); + }) + .expectSpeech('On, Off, and Stop', 'Heading 1') + .call(doCmd('nextButton')) + .expectSpeech('Next lesson') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech('The ChromeVox modifier key', 'Heading 1') + .call(this.assertActiveLessonIndex.bind(this, 1)) + .call(doCmd('nextButton')) + .expectSpeech('Previous lesson') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech('On, Off, and Stop', 'Heading 1') + .call(this.assertActiveLessonIndex.bind(this, 0)) + .replay(); + }); // Tests that the title of an interactive lesson is read when shown. -TEST_F('ChromeVoxTutorialTest', 'AutoReadTitle', function() { +TEST_F('ChromeVoxTutorialTest', 'AutoReadTitle', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, async function(root) { - await this.launchAndWaitForTutorial(); - const tutorial = this.getTutorial(); - mockFeedback.expectSpeech('ChromeVox tutorial') - .call(doCmd('nextObject')) - .expectSpeech('Quick orientation') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech(/Quick Orientation Tutorial, [0-9]+ Lessons/) - .call(doCmd('nextObject')) - .expectSpeech('Welcome to ChromeVox!') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech('Welcome to ChromeVox!') - .expectSpeech( - 'Welcome to the ChromeVox tutorial. To exit this tutorial at any ' + - 'time, press the Escape key on the top left corner of the ' + - 'keyboard. To turn off ChromeVox, hold Control and Alt, and ' + - `press Z. When you're ready, use the spacebar to move to the ` + - 'next lesson.') - .replay(); - }); + const root = await this.runWithLoadedTree(this.simpleDoc); + await this.launchAndWaitForTutorial(); + const tutorial = this.getTutorial(); + mockFeedback.expectSpeech('ChromeVox tutorial') + .call(doCmd('nextObject')) + .expectSpeech('Quick orientation') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech(/Quick Orientation Tutorial, [0-9]+ Lessons/) + .call(doCmd('nextObject')) + .expectSpeech('Welcome to ChromeVox!') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech('Welcome to ChromeVox!') + .expectSpeech( + 'Welcome to the ChromeVox tutorial. To exit this tutorial at any ' + + 'time, press the Escape key on the top left corner of the ' + + 'keyboard. To turn off ChromeVox, hold Control and Alt, and ' + + `press Z. When you're ready, use the spacebar to move to the ` + + 'next lesson.') + .replay(); }); // Tests that we read a hint for navigating a lesson when it is shown. // TODO(crbug.com/1193799): fix ax node errors causing console spew and // breaking tests -TEST_F('ChromeVoxTutorialTest', 'DISABLED_LessonHint', function() { +TEST_F('ChromeVoxTutorialTest', 'DISABLED_LessonHint', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, async function(root) { - await this.launchAndWaitForTutorial(); - const tutorial = this.getTutorial(); - mockFeedback.expectSpeech('ChromeVox tutorial') - .call(doCmd('nextObject')) - .expectSpeech('Quick orientation') - .call(doCmd('nextObject')) - .expectSpeech('Essential keys') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech(/Essential Keys Tutorial, [0-9]+ Lessons/) - .call(() => { - tutorial.showLesson_(0); - }) - .expectSpeech('On, Off, and Stop', 'Heading 1') - .expectSpeech( - ' Press Search + Right Arrow, or Search + Left Arrow to navigate' + - ' this lesson ') - .replay(); - }); + const root = await this.runWithLoadedTree(this.simpleDoc); + await this.launchAndWaitForTutorial(); + const tutorial = this.getTutorial(); + mockFeedback.expectSpeech('ChromeVox tutorial') + .call(doCmd('nextObject')) + .expectSpeech('Quick orientation') + .call(doCmd('nextObject')) + .expectSpeech('Essential keys') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech(/Essential Keys Tutorial, [0-9]+ Lessons/) + .call(() => { + tutorial.showLesson_(0); + }) + .expectSpeech('On, Off, and Stop', 'Heading 1') + .expectSpeech( + ' Press Search + Right Arrow, or Search + Left Arrow to navigate' + + ' this lesson ') + .replay(); }); // Tests for correct speech and earcons on the earcons lesson. -TEST_F('ChromeVoxTutorialTest', 'EarconLesson', function() { +TEST_F('ChromeVoxTutorialTest', 'EarconLesson', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, async function(root) { - await this.launchAndWaitForTutorial(); - const tutorial = this.getTutorial(); - const nextObjectAndExpectSpeechAndEarcon = (speech, earcon) => { - mockFeedback.call(doCmd('nextObject')) - .expectSpeech(speech) - .expectEarcon(earcon); - }; - mockFeedback.expectSpeech('ChromeVox tutorial') - .call(() => { - // Show the lesson. - tutorial.curriculum = 'sounds_and_settings'; - tutorial.showLesson_(0); - }) - .expectSpeech('Sounds') - .call(doCmd('nextObject')) - .expectSpeech(new RegExp( - 'ChromeVox uses sounds to give you essential and additional ' + - 'information.')); - nextObjectAndExpectSpeechAndEarcon('A modal alert', Earcon.ALERT_MODAL); - nextObjectAndExpectSpeechAndEarcon( - 'A non modal alert', Earcon.ALERT_NONMODAL); - nextObjectAndExpectSpeechAndEarcon('A button', Earcon.BUTTON); - mockFeedback.replay(); - }); + const root = await this.runWithLoadedTree(this.simpleDoc); + await this.launchAndWaitForTutorial(); + const tutorial = this.getTutorial(); + const nextObjectAndExpectSpeechAndEarcon = (speech, earcon) => { + mockFeedback.call(doCmd('nextObject')) + .expectSpeech(speech) + .expectEarcon(earcon); + }; + mockFeedback.expectSpeech('ChromeVox tutorial') + .call(() => { + // Show the lesson. + tutorial.curriculum = 'sounds_and_settings'; + tutorial.showLesson_(0); + }) + .expectSpeech('Sounds') + .call(doCmd('nextObject')) + .expectSpeech(new RegExp( + 'ChromeVox uses sounds to give you essential and additional ' + + 'information.')); + nextObjectAndExpectSpeechAndEarcon('A modal alert', Earcon.ALERT_MODAL); + nextObjectAndExpectSpeechAndEarcon( + 'A non modal alert', Earcon.ALERT_NONMODAL); + nextObjectAndExpectSpeechAndEarcon('A button', Earcon.BUTTON); + mockFeedback.replay(); }); // Tests that a lesson from the quick orientation blocks ChromeVox execution @@ -476,308 +465,298 @@ // TODO(crbug.com/1193799): fix ax node errors causing console spew and // breaking tests TEST_F( - 'ChromeVoxTutorialTest', 'DISABLED_QuickOrientationLessonTest', function() { + 'ChromeVoxTutorialTest', 'DISABLED_QuickOrientationLessonTest', + async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, async function(root) { - await this.launchAndWaitForTutorial(); - const tutorial = this.getTutorial(); - const keyboardHandler = ChromeVoxState.instance.keyboardHandler_; + const root = await this.runWithLoadedTree(this.simpleDoc); + await this.launchAndWaitForTutorial(); + const tutorial = this.getTutorial(); + const keyboardHandler = ChromeVoxState.instance.keyboardHandler_; - // Helper functions. For this test, activate commands by hooking into - // the BackgroundKeyboardHandler. This is necessary because - // UserActionMonitor intercepts key sequences before they are routed to - // CommandHandler. - const getRangeStartNode = () => { - return ChromeVoxState.instance.getCurrentRange().start.node; - }; + // Helper functions. For this test, activate commands by hooking into + // the BackgroundKeyboardHandler. This is necessary because + // UserActionMonitor intercepts key sequences before they are routed to + // CommandHandler. + const getRangeStartNode = () => { + return ChromeVoxState.instance.getCurrentRange().start.node; + }; - const simulateKeyPress = (keyCode, opt_modifiers) => { - const keyEvent = TestUtils.createMockKeyEvent(keyCode, opt_modifiers); - keyboardHandler.onKeyDown(keyEvent); - keyboardHandler.onKeyUp(keyEvent); - }; + const simulateKeyPress = (keyCode, opt_modifiers) => { + const keyEvent = TestUtils.createMockKeyEvent(keyCode, opt_modifiers); + keyboardHandler.onKeyDown(keyEvent); + keyboardHandler.onKeyUp(keyEvent); + }; - let firstLessonNode; - await mockFeedback.expectSpeech('ChromeVox tutorial') - .call(doCmd('nextObject')) - .expectSpeech('Quick orientation') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech(/Quick Orientation Tutorial, [0-9]+ Lessons/) - .call(doCmd('nextObject')) - .expectSpeech('Welcome to ChromeVox!') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech(/Welcome to the ChromeVox tutorial./) - .call(() => { - assertEquals(0, tutorial.activeLessonId); - firstLessonNode = getRangeStartNode(); - }) - .call(simulateKeyPress.bind( - this, KeyCode.RIGHT, {searchKeyHeld: true})) - .call(() => { - assertEquals(firstLessonNode, getRangeStartNode()); - assertEquals(0, tutorial.activeLessonId); - }) - .call(simulateKeyPress.bind( - this, KeyCode.LEFT, {searchKeyHeld: true})) - .call(() => { - assertEquals(firstLessonNode, getRangeStartNode()); - assertEquals(0, tutorial.activeLessonId); - }) - // Pressing space, which is the desired key sequence, should move us - // to the next lesson. - .call(simulateKeyPress.bind(this, KeyCode.SPACE, {})) - .expectSpeech('Essential Keys: Control') - .expectSpeech(/Let's start with a few keys you'll use regularly./) - .call(() => { - assertEquals(1, tutorial.activeLessonId); - assertNotEquals(firstLessonNode, getRangeStartNode()); - }) - // Pressing control, which is the desired key sequence, should move - // us to the next lesson. - .call(simulateKeyPress.bind(this, KeyCode.CONTROL, {})) - .expectSpeech('Essential Keys: Shift') - .call(() => { - assertEquals(2, tutorial.activeLessonId); - }) - .replay(); - }); + let firstLessonNode; + await mockFeedback.expectSpeech('ChromeVox tutorial') + .call(doCmd('nextObject')) + .expectSpeech('Quick orientation') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech(/Quick Orientation Tutorial, [0-9]+ Lessons/) + .call(doCmd('nextObject')) + .expectSpeech('Welcome to ChromeVox!') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech(/Welcome to the ChromeVox tutorial./) + .call(() => { + assertEquals(0, tutorial.activeLessonId); + firstLessonNode = getRangeStartNode(); + }) + .call( + simulateKeyPress.bind(this, KeyCode.RIGHT, {searchKeyHeld: true})) + .call(() => { + assertEquals(firstLessonNode, getRangeStartNode()); + assertEquals(0, tutorial.activeLessonId); + }) + .call( + simulateKeyPress.bind(this, KeyCode.LEFT, {searchKeyHeld: true})) + .call(() => { + assertEquals(firstLessonNode, getRangeStartNode()); + assertEquals(0, tutorial.activeLessonId); + }) + // Pressing space, which is the desired key sequence, should move us + // to the next lesson. + .call(simulateKeyPress.bind(this, KeyCode.SPACE, {})) + .expectSpeech('Essential Keys: Control') + .expectSpeech(/Let's start with a few keys you'll use regularly./) + .call(() => { + assertEquals(1, tutorial.activeLessonId); + assertNotEquals(firstLessonNode, getRangeStartNode()); + }) + // Pressing control, which is the desired key sequence, should move + // us to the next lesson. + .call(simulateKeyPress.bind(this, KeyCode.CONTROL, {})) + .expectSpeech('Essential Keys: Shift') + .call(() => { + assertEquals(2, tutorial.activeLessonId); + }) + .replay(); }); // Tests that tutorial nudges are restarted whenever the current range changes. -TEST_F('ChromeVoxTutorialTest', 'RestartNudges', function() { - this.runWithLoadedTree(this.simpleDoc, async function(root) { - await this.launchAndWaitForTutorial(); - const tutorial = this.getTutorial(); - let restart = false; - // Swap in below function to track when nudges get restarted. - tutorial.restartNudges = () => { - restart = true; - }; - const waitForRestartNudges = async () => { - return new Promise(resolve => { - let intervalId; - const nudgesRestarted = () => { - return restart; - }; - if (nudgesRestarted()) { - resolve(); - } else { - intervalId = setInterval(() => { - if (nudgesRestarted()) { - clearInterval(intervalId); - resolve(); - } - }, 500); - } - }); - }; - restart = false; - CommandHandlerInterface.instance.onCommand('nextObject'); - await waitForRestartNudges(); - // Show a lesson. - tutorial.curriculum = 'essential_keys'; - tutorial.showLesson_(0); - restart = false; - CommandHandlerInterface.instance.onCommand('nextObject'); - await waitForRestartNudges(); - restart = false; - CommandHandlerInterface.instance.onCommand('nextObject'); - await waitForRestartNudges(); - }); +TEST_F('ChromeVoxTutorialTest', 'RestartNudges', async function() { + const root = await this.runWithLoadedTree(this.simpleDoc); + await this.launchAndWaitForTutorial(); + const tutorial = this.getTutorial(); + let restart = false; + // Swap in below function to track when nudges get restarted. + tutorial.restartNudges = () => { + restart = true; + }; + const waitForRestartNudges = async () => { + return new Promise(resolve => { + let intervalId; + const nudgesRestarted = () => { + return restart; + }; + if (nudgesRestarted()) { + resolve(); + } else { + intervalId = setInterval(() => { + if (nudgesRestarted()) { + clearInterval(intervalId); + resolve(); + } + }, 500); + } + }); + }; + restart = false; + CommandHandlerInterface.instance.onCommand('nextObject'); + await waitForRestartNudges(); + // Show a lesson. + tutorial.curriculum = 'essential_keys'; + tutorial.showLesson_(0); + restart = false; + CommandHandlerInterface.instance.onCommand('nextObject'); + await waitForRestartNudges(); + restart = false; + CommandHandlerInterface.instance.onCommand('nextObject'); + await waitForRestartNudges(); }); // Tests that the tutorial closes and ChromeVox navigates to a resource link. -TEST_F('ChromeVoxTutorialTest', 'ResourcesTest', function() { +TEST_F('ChromeVoxTutorialTest', 'ResourcesTest', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, async function(root) { - await this.launchAndWaitForTutorial(); - const tutorial = this.getTutorial(); - mockFeedback.expectSpeech('ChromeVox tutorial') - .call(() => { - tutorial.curriculum = 'resources'; - tutorial.showLesson_(0); - }) - .expectSpeech('Learn More') - .call(doCmd('nextObject')) - .expectSpeech(/Congratulations/) - .call(doCmd('nextObject')) - .expectSpeech('ChromeVox Command Reference', 'Link') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech('support.google.com') - .replay(); - }); + const root = await this.runWithLoadedTree(this.simpleDoc); + await this.launchAndWaitForTutorial(); + const tutorial = this.getTutorial(); + mockFeedback.expectSpeech('ChromeVox tutorial') + .call(() => { + tutorial.curriculum = 'resources'; + tutorial.showLesson_(0); + }) + .expectSpeech('Learn More') + .call(doCmd('nextObject')) + .expectSpeech(/Congratulations/) + .call(doCmd('nextObject')) + .expectSpeech('ChromeVox Command Reference', 'Link') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech('support.google.com') + .replay(); }); // Tests that choosing a curriculum with only 1 lesson automatically opens the // lesson. -TEST_F('ChromeVoxTutorialTest', 'OnlyLessonTest', function() { +TEST_F('ChromeVoxTutorialTest', 'OnlyLessonTest', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, async function(root) { - await this.launchAndWaitForTutorial(); - const tutorial = this.getTutorial(); - mockFeedback.expectSpeech('ChromeVox tutorial') - .call(doCmd('nextObject')) - .expectSpeech('Quick orientation') - .call(doCmd('nextObject')) - .expectSpeech('Essential keys') - .call(doCmd('nextObject')) - .expectSpeech('Navigation') - .call(doCmd('nextObject')) - .expectSpeech('Command references') - .call(doCmd('nextObject')) - .expectSpeech('Sounds and settings') - .call(doCmd('nextObject')) - .expectSpeech('Resources') - .call(doCmd('forceClickOnCurrentItem')) - .expectSpeech('Learn More', 'Heading 1') - .expectSpeech( - ' Press Search + Right Arrow, or Search + Left Arrow to' + - ' navigate this lesson ') - // The 'All lessons' button should be hidden since this is the only - // lesson for the curriculum. - .call(doCmd('nextButton')) - .expectSpeech('Main menu') - .call(doCmd('nextButton')) - .expectSpeech('Exit tutorial') - .replay(); - }); + const root = this.runWithLoadedTree(this.simpleDoc); + await this.launchAndWaitForTutorial(); + const tutorial = this.getTutorial(); + mockFeedback.expectSpeech('ChromeVox tutorial') + .call(doCmd('nextObject')) + .expectSpeech('Quick orientation') + .call(doCmd('nextObject')) + .expectSpeech('Essential keys') + .call(doCmd('nextObject')) + .expectSpeech('Navigation') + .call(doCmd('nextObject')) + .expectSpeech('Command references') + .call(doCmd('nextObject')) + .expectSpeech('Sounds and settings') + .call(doCmd('nextObject')) + .expectSpeech('Resources') + .call(doCmd('forceClickOnCurrentItem')) + .expectSpeech('Learn More', 'Heading 1') + .expectSpeech( + ' Press Search + Right Arrow, or Search + Left Arrow to' + + ' navigate this lesson ') + // The 'All lessons' button should be hidden since this is the only + // lesson for the curriculum. + .call(doCmd('nextButton')) + .expectSpeech('Main menu') + .call(doCmd('nextButton')) + .expectSpeech('Exit tutorial') + .replay(); }); // Tests that interactive mode and UserActionMonitor are properly set when // showing different screens in the tutorial. -TEST_F('ChromeVoxTutorialTest', 'StartStopInteractiveMode', function() { - this.runWithLoadedTree(this.simpleDoc, async function(root) { - await this.launchAndWaitForTutorial(); - const tutorial = this.getTutorial(); - let userActionMonitorCreatedCount = 0; - let userActionMonitorDestroyedCount = 0; - let isUserActionMonitorActive = false; +TEST_F('ChromeVoxTutorialTest', 'StartStopInteractiveMode', async function() { + const root = await this.runWithLoadedTree(this.simpleDoc); + await this.launchAndWaitForTutorial(); + const tutorial = this.getTutorial(); + let userActionMonitorCreatedCount = 0; + let userActionMonitorDestroyedCount = 0; + let isUserActionMonitorActive = false; - // Swap in functions below so we can track the number of times - // UserActionMonitor is created and destroyed. - ChromeVoxState.instance.createUserActionMonitor = (actions, callback) => { - userActionMonitorCreatedCount += 1; - isUserActionMonitorActive = true; - }; - ChromeVoxState.instance.destroyUserActionMonitor = () => { - userActionMonitorDestroyedCount += 1; - isUserActionMonitorActive = false; - }; + // Swap in functions below so we can track the number of times + // UserActionMonitor is created and destroyed. + ChromeVoxState.instance.createUserActionMonitor = (actions, callback) => { + userActionMonitorCreatedCount += 1; + isUserActionMonitorActive = true; + }; + ChromeVoxState.instance.destroyUserActionMonitor = () => { + userActionMonitorDestroyedCount += 1; + isUserActionMonitorActive = false; + }; - // A helper to make assertions on four variables of interest. - const makeAssertions = (expectedVars) => { - assertEquals(expectedVars.createdCount, userActionMonitorCreatedCount); - assertEquals( - expectedVars.destroyedCount, userActionMonitorDestroyedCount); - assertEquals(expectedVars.interactiveMode, tutorial.interactiveMode_); - // Note: Interactive mode and UserActionMonitor should always be in - // sync in the context of the tutorial. - assertEquals(expectedVars.interactiveMode, isUserActionMonitorActive); - }; + // A helper to make assertions on four variables of interest. + const makeAssertions = (expectedVars) => { + assertEquals(expectedVars.createdCount, userActionMonitorCreatedCount); + assertEquals(expectedVars.destroyedCount, userActionMonitorDestroyedCount); + assertEquals(expectedVars.interactiveMode, tutorial.interactiveMode_); + // Note: Interactive mode and UserActionMonitor should always be in + // sync in the context of the tutorial. + assertEquals(expectedVars.interactiveMode, isUserActionMonitorActive); + }; - makeAssertions( - {createdCount: 0, destroyedCount: 0, interactiveMode: false}); - // Show the first lesson of the quick orientation, which is interactive. - tutorial.curriculum = 'quick_orientation'; - tutorial.showLesson_(0); - makeAssertions({createdCount: 1, destroyedCount: 0, interactiveMode: true}); + makeAssertions({createdCount: 0, destroyedCount: 0, interactiveMode: false}); + // Show the first lesson of the quick orientation, which is interactive. + tutorial.curriculum = 'quick_orientation'; + tutorial.showLesson_(0); + makeAssertions({createdCount: 1, destroyedCount: 0, interactiveMode: true}); - // Move to the next lesson in the quick orientation. This lesson is also - // interactive, so UserActionMonitor should be destroyed and re-created. - tutorial.showNextLesson(); - makeAssertions({createdCount: 2, destroyedCount: 1, interactiveMode: true}); + // Move to the next lesson in the quick orientation. This lesson is also + // interactive, so UserActionMonitor should be destroyed and re-created. + tutorial.showNextLesson(); + makeAssertions({createdCount: 2, destroyedCount: 1, interactiveMode: true}); - // Leave the quick orientation by navigating to the lesson menu. This should - // stop interactive mode and destroy UserActionMonitor. - tutorial.showLessonMenu_(); - makeAssertions( - {createdCount: 2, destroyedCount: 2, interactiveMode: false}); - }); + // Leave the quick orientation by navigating to the lesson menu. This should + // stop interactive mode and destroy UserActionMonitor. + tutorial.showLessonMenu_(); + makeAssertions({createdCount: 2, destroyedCount: 2, interactiveMode: false}); }); // Tests that gestures can be used in the tutorial to navigate. -TEST_F('ChromeVoxTutorialTest', 'Gestures', function() { +TEST_F('ChromeVoxTutorialTest', 'Gestures', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, async function(root) { - await this.launchAndWaitForTutorial(); - const tutorial = this.getTutorial(); - mockFeedback.expectSpeech('ChromeVox tutorial') - .call(doGesture(Gesture.SWIPE_RIGHT1)) - .expectSpeech('Quick orientation', 'Link') - .call(doGesture(Gesture.SWIPE_RIGHT1)) - .expectSpeech('Essential keys', 'Link') - .call(doGesture(Gesture.SWIPE_LEFT1)) - .expectSpeech('Quick orientation', 'Link') - .call(doGesture(Gesture.SWIPE_LEFT2)) - .expectSpeech('Some web content') - .replay(); - }); + const root = await this.runWithLoadedTree(this.simpleDoc); + await this.launchAndWaitForTutorial(); + const tutorial = this.getTutorial(); + mockFeedback.expectSpeech('ChromeVox tutorial') + .call(doGesture(Gesture.SWIPE_RIGHT1)) + .expectSpeech('Quick orientation', 'Link') + .call(doGesture(Gesture.SWIPE_RIGHT1)) + .expectSpeech('Essential keys', 'Link') + .call(doGesture(Gesture.SWIPE_LEFT1)) + .expectSpeech('Quick orientation', 'Link') + .call(doGesture(Gesture.SWIPE_LEFT2)) + .expectSpeech('Some web content') + .replay(); }); // Tests that touch orientation loads properly. Tests string content, but does // not test interactivity of lessons. // TODO(crbug.com/1193799): fix ax node errors causing console spew and // breaking tests -TEST_F('ChromeVoxTutorialTest', 'DISABLED_TouchOrientation', function() { +TEST_F('ChromeVoxTutorialTest', 'DISABLED_TouchOrientation', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, async function(root) { - await this.launchAndWaitForTutorial(); - const tutorial = this.getTutorial(); - mockFeedback.expectSpeech('ChromeVox tutorial') - .call(() => { - tutorial.curriculum = 'touch_orientation'; - tutorial.medium = 'touch'; - tutorial.showLesson_(0); - this.assertActiveLessonIndex(0); - this.assertActiveScreen('lesson'); - }) - .expectSpeech('ChromeVox touch tutorial') - .expectSpeech(/Welcome to the ChromeVox tutorial/) - .call(doGesture(Gesture.CLICK)) - .expectSpeech('Activate an item') - .expectSpeech(/To continue, double-tap now/) - .call(doGesture(Gesture.CLICK)) - .expectSpeech('Move to the next or previous item') - .call(() => { - // Jump to the penultimate lesson. - tutorial.showLesson_(6); - }) - .expectSpeech('Move to the next or previous section') - .expectSpeech(/swipe from left to right with four fingers/) - .call(doGesture(Gesture.SWIPE_RIGHT4)) - .expectSpeech(/swiping with four fingers from right to left/) - .call(doGesture(Gesture.SWIPE_LEFT4)) - .expectSpeech('Touch tutorial complete') - .replay(); - }); + const root = await this.runWithLoadedTree(this.simpleDoc); + await this.launchAndWaitForTutorial(); + const tutorial = this.getTutorial(); + mockFeedback.expectSpeech('ChromeVox tutorial') + .call(() => { + tutorial.curriculum = 'touch_orientation'; + tutorial.medium = 'touch'; + tutorial.showLesson_(0); + this.assertActiveLessonIndex(0); + this.assertActiveScreen('lesson'); + }) + .expectSpeech('ChromeVox touch tutorial') + .expectSpeech(/Welcome to the ChromeVox tutorial/) + .call(doGesture(Gesture.CLICK)) + .expectSpeech('Activate an item') + .expectSpeech(/To continue, double-tap now/) + .call(doGesture(Gesture.CLICK)) + .expectSpeech('Move to the next or previous item') + .call(() => { + // Jump to the penultimate lesson. + tutorial.showLesson_(6); + }) + .expectSpeech('Move to the next or previous section') + .expectSpeech(/swipe from left to right with four fingers/) + .call(doGesture(Gesture.SWIPE_RIGHT4)) + .expectSpeech(/swiping with four fingers from right to left/) + .call(doGesture(Gesture.SWIPE_LEFT4)) + .expectSpeech('Touch tutorial complete') + .replay(); }); -TEST_F('ChromeVoxTutorialTest', 'GeneralTouchNudges', function() { +TEST_F('ChromeVoxTutorialTest', 'GeneralTouchNudges', async function() { const mockFeedback = this.createMockFeedback(); - this.runWithLoadedTree(this.simpleDoc, async function(root) { - await this.launchAndWaitForTutorial(); - const tutorial = this.getTutorial(); - const giveNudge = () => { - tutorial.giveNudge(); - }; - mockFeedback.expectSpeech('ChromeVox tutorial'); - mockFeedback.call(() => { - tutorial.medium = 'touch'; - tutorial.initializeNudges('general'); - }); - for (let i = 0; i < 3; ++i) { - mockFeedback.call(giveNudge).expectSpeech( - 'ChromeVox tutorial', 'Heading 1'); - } - mockFeedback.call(giveNudge) - .expectSpeech('Hint: Swipe left or right with one finger to navigate.') - .call(giveNudge) - .expectSpeech( - 'Hint: Double-tap with one finger to activate the current item.') - .call(giveNudge) - .expectSpeech( - 'Hint: Swipe from right to left with two fingers if you would ' + - 'like to exit this tutorial.') - .replay(); + const root = await this.runWithLoadedTree(this.simpleDoc); + await this.launchAndWaitForTutorial(); + const tutorial = this.getTutorial(); + const giveNudge = () => { + tutorial.giveNudge(); + }; + mockFeedback.expectSpeech('ChromeVox tutorial'); + mockFeedback.call(() => { + tutorial.medium = 'touch'; + tutorial.initializeNudges('general'); }); + for (let i = 0; i < 3; ++i) { + mockFeedback.call(giveNudge).expectSpeech( + 'ChromeVox tutorial', 'Heading 1'); + } + mockFeedback.call(giveNudge) + .expectSpeech('Hint: Swipe left or right with one finger to navigate.') + .call(giveNudge) + .expectSpeech( + 'Hint: Double-tap with one finger to activate the current item.') + .call(giveNudge) + .expectSpeech( + 'Hint: Swipe from right to left with two fingers if you would ' + + 'like to exit this tutorial.') + .replay(); });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/testing/chromevox_next_e2e_test_base.js b/chrome/browser/resources/chromeos/accessibility/chromevox/testing/chromevox_next_e2e_test_base.js index 9c133e1..a7bb66574 100644 --- a/chrome/browser/resources/chromeos/accessibility/chromevox/testing/chromevox_next_e2e_test_base.js +++ b/chrome/browser/resources/chromeos/accessibility/chromevox/testing/chromevox_next_e2e_test_base.js
@@ -46,8 +46,7 @@ /** @return {!MockFeedback} */ createMockFeedback() { - const mockFeedback = - new MockFeedback(this.newCallback(), this.newCallback.bind(this)); + const mockFeedback = new MockFeedback(this.newCallback()); mockFeedback.install(); return mockFeedback; } @@ -126,14 +125,10 @@ } /** @override */ - runWithLoadedTree(doc, callback, opt_params = {}) { - callback = this.newCallback(callback); - const wrappedCallback = (node) => { - CommandHandlerInterface.instance.onCommand('nextObject'); - callback(node); - }; - - super.runWithLoadedTree(doc, wrappedCallback, opt_params); + async runWithLoadedTree(doc, opt_params = {}) { + const rootWebArea = await super.runWithLoadedTree(doc, opt_params); + CommandHandlerInterface.instance.onCommand('nextObject'); + return rootWebArea; } /**
diff --git a/chrome/browser/resources/chromeos/accessibility/common/array_util_test.js b/chrome/browser/resources/chromeos/accessibility/common/array_util_test.js index f642c707..5d93f255 100644 --- a/chrome/browser/resources/chromeos/accessibility/common/array_util_test.js +++ b/chrome/browser/resources/chromeos/accessibility/common/array_util_test.js
@@ -9,42 +9,40 @@ /** Test fixture for array_util.js. */ ArrayUtilTest = class extends ChromeVoxNextE2ETest {}; -TEST_F('ArrayUtilTest', 'ContentsAreEqual', function() { - this.runWithLoadedTree('', (root) => { - const even1 = [2, 4, 6, 8]; - const even2 = [2, 4, 6, 8]; - const odd = [1, 3, 5, 7, 9]; - const powersOf2 = [2, 4, 8, 16]; +SYNC_TEST_F('ArrayUtilTest', 'ContentsAreEqual', function() { + const even1 = [2, 4, 6, 8]; + const even2 = [2, 4, 6, 8]; + const odd = [1, 3, 5, 7, 9]; + const powersOf2 = [2, 4, 8, 16]; - assertFalse( - ArrayUtil.contentsAreEqual(even1, odd), - 'Arrays with different lengths should not be equal.'); - assertFalse( - ArrayUtil.contentsAreEqual(even1, powersOf2), - 'Arrays with some common elements should not be equal.'); - assertTrue( - ArrayUtil.contentsAreEqual(even1, even1), - 'Arrays should equal themselves.'); - assertTrue( - ArrayUtil.contentsAreEqual(even1, even2), - 'Two different array objects with the same elements should be equal.'); + assertFalse( + ArrayUtil.contentsAreEqual(even1, odd), + 'Arrays with different lengths should not be equal.'); + assertFalse( + ArrayUtil.contentsAreEqual(even1, powersOf2), + 'Arrays with some common elements should not be equal.'); + assertTrue( + ArrayUtil.contentsAreEqual(even1, even1), + 'Arrays should equal themselves.'); + assertTrue( + ArrayUtil.contentsAreEqual(even1, even2), + 'Two different array objects with the same elements should be equal.'); - const obj = {}; - const arrayWithObj = [obj]; - const secondArrayWithObj = [obj]; - const arrayWithDifferentObj = [{}]; + const obj = {}; + const arrayWithObj = [obj]; + const secondArrayWithObj = [obj]; + const arrayWithDifferentObj = [{}]; - assertNotEquals( - arrayWithObj, secondArrayWithObj, - 'Different array instances with the same contents should not be ' + - 'equal with ===.'); - assertTrue( - ArrayUtil.contentsAreEqual(arrayWithObj, secondArrayWithObj), - 'Different array instances with references to the same object ' + - 'instance should be equal with contentsAreEqual.'); - assertFalse( - ArrayUtil.contentsAreEqual(arrayWithObj, arrayWithDifferentObj), - 'Arrays with different objects should not be equal (ArrayUtil.' + - 'contentsAreEqual uses shallow equality for the elements).'); - }); + assertNotEquals( + arrayWithObj, secondArrayWithObj, + 'Different array instances with the same contents should not be ' + + 'equal with ===.'); + assertTrue( + ArrayUtil.contentsAreEqual(arrayWithObj, secondArrayWithObj), + 'Different array instances with references to the same object ' + + 'instance should be equal with contentsAreEqual.'); + assertFalse( + ArrayUtil.contentsAreEqual(arrayWithObj, arrayWithDifferentObj), + 'Arrays with different objects should not be equal (ArrayUtil.' + + 'contentsAreEqual uses shallow equality for the elements).'); });
diff --git a/chrome/browser/resources/chromeos/accessibility/common/automation_predicate_test.js b/chrome/browser/resources/chromeos/accessibility/common/automation_predicate_test.js index 7cc5dc27..e3d0449 100644 --- a/chrome/browser/resources/chromeos/accessibility/common/automation_predicate_test.js +++ b/chrome/browser/resources/chromeos/accessibility/common/automation_predicate_test.js
@@ -8,34 +8,33 @@ /** Test fixture for automation_predicate.js. */ AutomationPredicateTest = class extends ChromeVoxNextE2ETest {}; -TEST_F('AutomationPredicateTest', 'EquivalentRoles', function() { +TEST_F('AutomationPredicateTest', 'EquivalentRoles', async function() { const site = ` <input type="text"></input> <input role="combobox"></input> `; - this.runWithLoadedTree(site, (root) => { - // Text field is equivalent to text field with combo box. - const textField = root.find({role: RoleType.TEXT_FIELD}); - assertTrue(!!textField, 'No text field found.'); - const textFieldWithComboBox = - root.find({role: RoleType.TEXT_FIELD_WITH_COMBO_BOX}); - assertTrue(!!textFieldWithComboBox, 'No text field with combo box found.'); + const root = await this.runWithLoadedTree(site); + // Text field is equivalent to text field with combo box. + const textField = root.find({role: RoleType.TEXT_FIELD}); + assertTrue(!!textField, 'No text field found.'); + const textFieldWithComboBox = + root.find({role: RoleType.TEXT_FIELD_WITH_COMBO_BOX}); + assertTrue(!!textFieldWithComboBox, 'No text field with combo box found.'); - // Gather all potential predicate names. - const keys = Object.getOwnPropertyNames(AutomationPredicate); - for (const key of keys) { - // Not all keys are functions or predicates e.g. makeTableCellPredicate. - if (typeof (AutomationPredicate[key]) !== 'function' || - key.indexOf('make') === 0) { - continue; - } - - const predicate = AutomationPredicate[key]; - if (predicate(textField)) { - assertTrue( - !!predicate(textFieldWithComboBox), - `Textfield with combo box should match predicate ${key}`); - } + // Gather all potential predicate names. + const keys = Object.getOwnPropertyNames(AutomationPredicate); + for (const key of keys) { + // Not all keys are functions or predicates e.g. makeTableCellPredicate. + if (typeof (AutomationPredicate[key]) !== 'function' || + key.indexOf('make') === 0) { + continue; } - }); + + const predicate = AutomationPredicate[key]; + if (predicate(textField)) { + assertTrue( + !!predicate(textFieldWithComboBox), + `Textfield with combo box should match predicate ${key}`); + } + } });
diff --git a/chrome/browser/resources/chromeos/accessibility/common/automation_util_test.js b/chrome/browser/resources/chromeos/accessibility/common/automation_util_test.js index 8d56b867..c20602f 100644 --- a/chrome/browser/resources/chromeos/accessibility/common/automation_util_test.js +++ b/chrome/browser/resources/chromeos/accessibility/common/automation_util_test.js
@@ -64,206 +64,180 @@ TEST_F( - 'AccessibilityExtensionAutomationUtilE2ETest', 'GetAncestors', function() { - this.runWithLoadedTree(this.basicDoc(), function(root) { - let expectedLength = 1; - while (root) { - const ancestors = getNonDesktopAncestors(root); - assertEquals(expectedLength++, ancestors.length); - root = root.firstChild; - } - }); + 'AccessibilityExtensionAutomationUtilE2ETest', 'GetAncestors', + async function() { + const root = await this.runWithLoadedTree(this.basicDoc()); + let expectedLength = 1; + while (root) { + const ancestors = getNonDesktopAncestors(root); + assertEquals(expectedLength++, ancestors.length); + root = root.firstChild; + } }); TEST_F( 'AccessibilityExtensionAutomationUtilE2ETest', 'GetFirstAncestorWithRole', - function() { - this.runWithLoadedTree( - ` + async function() { + const root = await this.runWithLoadedTree(` <div tabindex="0" aria-label="x"> <div tabindex="0" aria-label="y"> <p> <button>Hello world</div> </p> </div> - </div>`, - function(root) { - const buttonNode = root.firstChild.firstChild.firstChild; - const containerNode = AutomationUtil.getFirstAncestorWithRole( - buttonNode, RoleType.GENERIC_CONTAINER); - assertEquals(containerNode.name, 'y'); + </div>`); + const buttonNode = root.firstChild.firstChild.firstChild; + const containerNode = AutomationUtil.getFirstAncestorWithRole( + buttonNode, RoleType.GENERIC_CONTAINER); + assertEquals(containerNode.name, 'y'); - const parentContainerNode = AutomationUtil.getFirstAncestorWithRole( - containerNode, RoleType.GENERIC_CONTAINER); - assertEquals(parentContainerNode.name, 'x'); - }); + const parentContainerNode = AutomationUtil.getFirstAncestorWithRole( + containerNode, RoleType.GENERIC_CONTAINER); + assertEquals(parentContainerNode.name, 'x'); }); TEST_F( 'AccessibilityExtensionAutomationUtilE2ETest', 'GetUniqueAncestors', - function() { - this.runWithLoadedTree(this.basicDoc(), function(root) { - let leftmost = root, rightmost = root; - while (leftmost.firstChild) { - leftmost = leftmost.firstChild; - } - while (rightmost.lastChild) { - rightmost = rightmost.lastChild; - } + async function() { + const root = await this.runWithLoadedTree(this.basicDoc()); + let leftmost = root, rightmost = root; + while (leftmost.firstChild) { + leftmost = leftmost.firstChild; + } + while (rightmost.lastChild) { + rightmost = rightmost.lastChild; + } - const leftAncestors = getNonDesktopAncestors(leftmost); - const rightAncestors = getNonDesktopAncestors(rightmost); - assertEquals(RoleType.LINK, leftmost.role); - assertEquals(RoleType.BUTTON, rightmost.role); - assertEquals( - 1, AutomationUtil.getDivergence(leftAncestors, rightAncestors)); + const leftAncestors = getNonDesktopAncestors(leftmost); + const rightAncestors = getNonDesktopAncestors(rightmost); + assertEquals(RoleType.LINK, leftmost.role); + assertEquals(RoleType.BUTTON, rightmost.role); + assertEquals( + 1, AutomationUtil.getDivergence(leftAncestors, rightAncestors)); - assertEquals( - -1, AutomationUtil.getDivergence(leftAncestors, leftAncestors)); + assertEquals( + -1, AutomationUtil.getDivergence(leftAncestors, leftAncestors)); - const uniqueAncestorsLeft = - getNonDesktopUniqueAncestors(rightmost, leftmost); - const uniqueAncestorsRight = - getNonDesktopUniqueAncestors(leftmost, rightmost); + const uniqueAncestorsLeft = + getNonDesktopUniqueAncestors(rightmost, leftmost); + const uniqueAncestorsRight = + getNonDesktopUniqueAncestors(leftmost, rightmost); - assertEquals(2, uniqueAncestorsLeft.length); - assertEquals(RoleType.PARAGRAPH, uniqueAncestorsLeft[0].role); - assertEquals(RoleType.LINK, uniqueAncestorsLeft[1].role); + assertEquals(2, uniqueAncestorsLeft.length); + assertEquals(RoleType.PARAGRAPH, uniqueAncestorsLeft[0].role); + assertEquals(RoleType.LINK, uniqueAncestorsLeft[1].role); - assertEquals(3, uniqueAncestorsRight.length); - assertEquals(RoleType.HEADING, uniqueAncestorsRight[0].role); - assertEquals(RoleType.GROUP, uniqueAncestorsRight[1].role); - assertEquals(RoleType.BUTTON, uniqueAncestorsRight[2].role); + assertEquals(3, uniqueAncestorsRight.length); + assertEquals(RoleType.HEADING, uniqueAncestorsRight[0].role); + assertEquals(RoleType.GROUP, uniqueAncestorsRight[1].role); + assertEquals(RoleType.BUTTON, uniqueAncestorsRight[2].role); - assertEquals( - 1, getNonDesktopUniqueAncestors(leftmost, leftmost).length); - }.bind(this)); + assertEquals(1, getNonDesktopUniqueAncestors(leftmost, leftmost).length); }); TEST_F( - 'AccessibilityExtensionAutomationUtilE2ETest', 'GetDirection', function() { - this.runWithLoadedTree(this.basicDoc(), function(root) { - let left = root, right = root; + 'AccessibilityExtensionAutomationUtilE2ETest', 'GetDirection', + async function() { + const root = await this.runWithLoadedTree(this.basicDoc()); + let left = root, right = root; - // Same node. - assertEquals(Dir.FORWARD, AutomationUtil.getDirection(left, right)); + // Same node. + assertEquals(Dir.FORWARD, AutomationUtil.getDirection(left, right)); - // Ancestry. - left = left.firstChild; - // Upward movement is backward (in dfs). - assertEquals(Dir.BACKWARD, AutomationUtil.getDirection(left, right)); - // Downward movement is forward. - assertEquals(Dir.FORWARD, AutomationUtil.getDirection(right, left)); + // Ancestry. + left = left.firstChild; + // Upward movement is backward (in dfs). + assertEquals(Dir.BACKWARD, AutomationUtil.getDirection(left, right)); + // Downward movement is forward. + assertEquals(Dir.FORWARD, AutomationUtil.getDirection(right, left)); - // Ordered. - right = right.lastChild; - assertEquals(Dir.BACKWARD, AutomationUtil.getDirection(right, left)); - assertEquals(Dir.FORWARD, AutomationUtil.getDirection(left, right)); - }); + // Ordered. + right = right.lastChild; + assertEquals(Dir.BACKWARD, AutomationUtil.getDirection(right, left)); + assertEquals(Dir.FORWARD, AutomationUtil.getDirection(left, right)); }); TEST_F( 'AccessibilityExtensionAutomationUtilE2ETest', 'VisitContainer', - function() { - this.runWithLoadedTree(toolbarDoc(), function(r) { - const pred = function(n) { - return n.role !== 'rootWebArea'; - }; + async function() { + const r = await this.runWithLoadedTree(toolbarDoc()); + const pred = function(n) { + return n.role !== 'rootWebArea'; + }; - const toolbar = AutomationUtil.findNextNode(r, 'forward', pred); - assertEquals('toolbar', toolbar.role); + const toolbar = AutomationUtil.findNextNode(r, 'forward', pred); + assertEquals('toolbar', toolbar.role); - const back = AutomationUtil.findNextNode(toolbar, 'forward', pred); - assertEquals('Back', back.name); - assertEquals( - toolbar, AutomationUtil.findNextNode(back, 'backward', pred)); + const back = AutomationUtil.findNextNode(toolbar, 'forward', pred); + assertEquals('Back', back.name); + assertEquals( + toolbar, AutomationUtil.findNextNode(back, 'backward', pred)); - const forward = AutomationUtil.findNextNode(back, 'forward', pred); - assertEquals('Forward', forward.name); - assertEquals( - back, AutomationUtil.findNextNode(forward, 'backward', pred)); - }); + const forward = AutomationUtil.findNextNode(back, 'forward', pred); + assertEquals('Forward', forward.name); + assertEquals( + back, AutomationUtil.findNextNode(forward, 'backward', pred)); }); -TEST_F('AccessibilityExtensionAutomationUtilE2ETest', 'HitTest', function() { - this.runWithLoadedTree(headingDoc, function(r) { - const [h1, h2, a] = r.findAll({role: 'inlineTextBox'}); +TEST_F( + 'AccessibilityExtensionAutomationUtilE2ETest', 'HitTest', async function() { + const r = await this.runWithLoadedTree(headingDoc); + const [h1, h2, a] = r.findAll({role: 'inlineTextBox'}); - assertEquals(h1, AutomationUtil.hitTest(r, RectUtil.center(h1.location))); - assertEquals( - h1, AutomationUtil.hitTest(r, RectUtil.center(h1.parent.location))); - assertEquals( - h1.parent.parent, - AutomationUtil.hitTest(r, RectUtil.center(h1.parent.parent.location))); + assertEquals(h1, AutomationUtil.hitTest(r, RectUtil.center(h1.location))); + assertEquals( + h1, AutomationUtil.hitTest(r, RectUtil.center(h1.parent.location))); + assertEquals( + h1.parent.parent, + AutomationUtil.hitTest( + r, RectUtil.center(h1.parent.parent.location))); - assertEquals(a, AutomationUtil.hitTest(r, RectUtil.center(a.location))); - assertEquals( - a, AutomationUtil.hitTest(r, RectUtil.center(a.parent.location))); - assertEquals( - a.parent.parent, - AutomationUtil.hitTest(r, RectUtil.center(a.parent.parent.location))); - }); -}); + assertEquals(a, AutomationUtil.hitTest(r, RectUtil.center(a.location))); + assertEquals( + a, AutomationUtil.hitTest(r, RectUtil.center(a.parent.location))); + assertEquals( + a.parent.parent, + AutomationUtil.hitTest(r, RectUtil.center(a.parent.parent.location))); + }); TEST_F( 'AccessibilityExtensionAutomationUtilE2ETest', 'FindLastNodeSimple', - function() { - this.runWithLoadedTree( - `<p aria-label=" "><div tabindex="0" aria-label="x"></div></p>`, - function(r) { - assertEquals( - 'x', - AutomationUtil - .findLastNode( - r, - function(n) { - return n.role === RoleType.GENERIC_CONTAINER; - }) - .name); - }); + async function() { + const r = await this.runWithLoadedTree( + `<p aria-label=" "><div tabindex="0" aria-label="x"></div></p>`); + assertEquals( + 'x', + AutomationUtil + .findLastNode(r, (n) => n.role === RoleType.GENERIC_CONTAINER) + .name); }); TEST_F( 'AccessibilityExtensionAutomationUtilE2ETest', 'FindLastNodeNonLeaf', - function() { - this.runWithLoadedTree( - ` + async function() { + const r = await this.runWithLoadedTree(` <div role="button" aria-label="outer"> <div role="heading" aria-label="inner"> </div> </div> - `, - function(r) { - assertEquals( - 'outer', - AutomationUtil - .findLastNode( - r, - function(n) { - return n.role === RoleType.BUTTON; - }) - .name); - }); + `); + assertEquals( + 'outer', + AutomationUtil.findLastNode(r, (n) => n.role === RoleType.BUTTON) + .name); }); TEST_F( 'AccessibilityExtensionAutomationUtilE2ETest', 'FindLastNodeLeaf', - function() { - this.runWithLoadedTree( - ` + async function() { + const r = await this.runWithLoadedTree(` <p>start</p> <div aria-label="outer"><div tabindex="0" aria-label="inner"></div></div> <p>end</p> - `, - function(r) { - assertEquals( - 'inner', - AutomationUtil - .findLastNode( - r, - function(n) { - return n.role === RoleType.GENERIC_CONTAINER; - }) - .name); - }); + `); + assertEquals( + 'inner', + AutomationUtil + .findLastNode(r, (n) => n.role === RoleType.GENERIC_CONTAINER) + .name); });
diff --git a/chrome/browser/resources/chromeos/accessibility/common/cursors/cursors_test.js b/chrome/browser/resources/chromeos/accessibility/common/cursors/cursors_test.js index a7e983f6..ffbf3af 100644 --- a/chrome/browser/resources/chromeos/accessibility/common/cursors/cursors_test.js +++ b/chrome/browser/resources/chromeos/accessibility/common/cursors/cursors_test.js
@@ -105,23 +105,22 @@ * |rangeMoveAndAssert|. * @param {string=} opt_testType Either CURSOR or RANGE. */ - runCursorMovesOnDocument(doc, moves, opt_testType) { - this.runWithLoadedTree(doc, function(root) { - let start = null; + async runCursorMovesOnDocument(doc, moves, opt_testType) { + const root = await this.runWithLoadedTree(doc); + let start = null; - // This occurs as a result of a load complete. - start = - AutomationUtil.findNodePost(root, FORWARD, AutomationPredicate.leaf); + // This occurs as a result of a load complete. + start = + AutomationUtil.findNodePost(root, FORWARD, AutomationPredicate.leaf); + const cursor = new cursors.Cursor(start, 0); + if (!opt_testType || opt_testType === this.CURSOR) { const cursor = new cursors.Cursor(start, 0); - if (!opt_testType || opt_testType === this.CURSOR) { - const cursor = new cursors.Cursor(start, 0); - this.cursorMoveAndAssert(cursor, moves); - } else if (opt_testType === this.RANGE) { - const range = new cursors.Range(cursor, cursor); - this.rangeMoveAndAssert(range, moves); - } - }); + this.cursorMoveAndAssert(cursor, moves); + } else if (opt_testType === this.RANGE) { + const range = new cursors.Range(cursor, cursor); + this.rangeMoveAndAssert(range, moves); + } } get simpleDoc() { @@ -147,29 +146,30 @@ }; -TEST_F('AccessibilityExtensionCursorsTest', 'CharacterCursor', function() { - this.runCursorMovesOnDocument(this.simpleDoc, [ - [CHARACTER, DIRECTIONAL, FORWARD, {index: 1, value: 'start '}], - [CHARACTER, DIRECTIONAL, BACKWARD, {index: 0, value: 'start '}], - // Bumps up against edge. - [CHARACTER, DIRECTIONAL, BACKWARD, {index: 0, value: 'start '}], +TEST_F( + 'AccessibilityExtensionCursorsTest', 'CharacterCursor', async function() { + await this.runCursorMovesOnDocument(this.simpleDoc, [ + [CHARACTER, DIRECTIONAL, FORWARD, {index: 1, value: 'start '}], + [CHARACTER, DIRECTIONAL, BACKWARD, {index: 0, value: 'start '}], + // Bumps up against edge. + [CHARACTER, DIRECTIONAL, BACKWARD, {index: 0, value: 'start '}], - [CHARACTER, DIRECTIONAL, FORWARD, {index: 1, value: 'start '}], - [CHARACTER, DIRECTIONAL, FORWARD, {index: 2, value: 'start '}], - [CHARACTER, DIRECTIONAL, FORWARD, {index: 3, value: 'start '}], - [CHARACTER, DIRECTIONAL, FORWARD, {index: 4, value: 'start '}], - [CHARACTER, DIRECTIONAL, FORWARD, {index: 5, value: 'start '}], + [CHARACTER, DIRECTIONAL, FORWARD, {index: 1, value: 'start '}], + [CHARACTER, DIRECTIONAL, FORWARD, {index: 2, value: 'start '}], + [CHARACTER, DIRECTIONAL, FORWARD, {index: 3, value: 'start '}], + [CHARACTER, DIRECTIONAL, FORWARD, {index: 4, value: 'start '}], + [CHARACTER, DIRECTIONAL, FORWARD, {index: 5, value: 'start '}], - [CHARACTER, DIRECTIONAL, FORWARD, {index: 0, value: 'same line'}], - [CHARACTER, DIRECTIONAL, FORWARD, {index: 1, value: 'same line'}], - [CHARACTER, DIRECTIONAL, BACKWARD, {index: 0, value: 'same line'}], + [CHARACTER, DIRECTIONAL, FORWARD, {index: 0, value: 'same line'}], + [CHARACTER, DIRECTIONAL, FORWARD, {index: 1, value: 'same line'}], + [CHARACTER, DIRECTIONAL, BACKWARD, {index: 0, value: 'same line'}], - [CHARACTER, DIRECTIONAL, BACKWARD, {index: 5, value: 'start '}], - ]); -}); + [CHARACTER, DIRECTIONAL, BACKWARD, {index: 5, value: 'start '}], + ]); + }); -TEST_F('AccessibilityExtensionCursorsTest', 'WordCursor', function() { - this.runCursorMovesOnDocument(this.simpleDoc, [ +TEST_F('AccessibilityExtensionCursorsTest', 'WordCursor', async function() { + await this.runCursorMovesOnDocument(this.simpleDoc, [ // Word (BOUND). [WORD, BOUND, BACKWARD, {index: 0, value: 'start '}], [WORD, BOUND, BACKWARD, {index: 0, value: 'start '}], @@ -191,25 +191,27 @@ ]); }); -TEST_F('AccessibilityExtensionCursorsTest', 'CharacterWordCursor', function() { - this.runCursorMovesOnDocument(this.simpleDoc, [ - [CHARACTER, DIRECTIONAL, FORWARD, {index: 1, value: 'start '}], +TEST_F( + 'AccessibilityExtensionCursorsTest', 'CharacterWordCursor', + async function() { + await this.runCursorMovesOnDocument(this.simpleDoc, [ + [CHARACTER, DIRECTIONAL, FORWARD, {index: 1, value: 'start '}], - [WORD, DIRECTIONAL, FORWARD, {index: 0, value: 'same line'}], - [CHARACTER, DIRECTIONAL, FORWARD, {index: 1, value: 'same line'}], - [WORD, DIRECTIONAL, FORWARD, {index: 5, value: 'same line'}], - [CHARACTER, DIRECTIONAL, BACKWARD, {index: 4, value: 'same line'}], - [WORD, DIRECTIONAL, FORWARD, {index: 5, value: 'same line'}], - [CHARACTER, DIRECTIONAL, FORWARD, {index: 6, value: 'same line'}], - [WORD, DIRECTIONAL, BACKWARD, {index: 0, value: 'same line'}], - [CHARACTER, DIRECTIONAL, BACKWARD, {index: 5, value: 'start '}], - [CHARACTER, DIRECTIONAL, BACKWARD, {index: 4, value: 'start '}], - [WORD, DIRECTIONAL, BACKWARD, {index: 0, value: 'start '}] - ]); -}); + [WORD, DIRECTIONAL, FORWARD, {index: 0, value: 'same line'}], + [CHARACTER, DIRECTIONAL, FORWARD, {index: 1, value: 'same line'}], + [WORD, DIRECTIONAL, FORWARD, {index: 5, value: 'same line'}], + [CHARACTER, DIRECTIONAL, BACKWARD, {index: 4, value: 'same line'}], + [WORD, DIRECTIONAL, FORWARD, {index: 5, value: 'same line'}], + [CHARACTER, DIRECTIONAL, FORWARD, {index: 6, value: 'same line'}], + [WORD, DIRECTIONAL, BACKWARD, {index: 0, value: 'same line'}], + [CHARACTER, DIRECTIONAL, BACKWARD, {index: 5, value: 'start '}], + [CHARACTER, DIRECTIONAL, BACKWARD, {index: 4, value: 'start '}], + [WORD, DIRECTIONAL, BACKWARD, {index: 0, value: 'start '}] + ]); + }); -TEST_F('AccessibilityExtensionCursorsTest', 'LineCursor', function() { - this.runCursorMovesOnDocument(this.simpleDoc, [ +TEST_F('AccessibilityExtensionCursorsTest', 'LineCursor', async function() { + await this.runCursorMovesOnDocument(this.simpleDoc, [ // Line (BOUND). [LINE, BOUND, FORWARD, {value: 'same line'}], [LINE, BOUND, FORWARD, {value: 'same line'}], @@ -226,8 +228,8 @@ ]); }); -TEST_F('AccessibilityExtensionCursorsTest', 'SyncCursor', function() { - this.runCursorMovesOnDocument(this.simpleDoc, [ +TEST_F('AccessibilityExtensionCursorsTest', 'SyncCursor', async function() { + await this.runCursorMovesOnDocument(this.simpleDoc, [ [WORD, SYNC, FORWARD, {index: 0, value: 'start '}], [NODE, DIRECTIONAL, FORWARD, {value: 'same line'}], @@ -241,8 +243,8 @@ ]); }); -TEST_F('AccessibilityExtensionCursorsTest', 'CharacterRange', function() { - this.runCursorMovesOnDocument( +TEST_F('AccessibilityExtensionCursorsTest', 'CharacterRange', async function() { + await this.runCursorMovesOnDocument( this.simpleDoc, [ [ @@ -303,8 +305,8 @@ this.RANGE); }); -TEST_F('AccessibilityExtensionCursorsTest', 'WordRange', function() { - this.runCursorMovesOnDocument( +TEST_F('AccessibilityExtensionCursorsTest', 'WordRange', async function() { + await this.runCursorMovesOnDocument( this.simpleDoc, [ [ @@ -341,8 +343,8 @@ }); -TEST_F('AccessibilityExtensionCursorsTest', 'LineRange', function() { - this.runCursorMovesOnDocument( +TEST_F('AccessibilityExtensionCursorsTest', 'LineRange', async function() { + await this.runCursorMovesOnDocument( this.simpleDoc, [ [LINE, FORWARD, {value: 'end', index: 0}, {value: 'end', index: 3}], @@ -365,251 +367,238 @@ TEST_F( 'AccessibilityExtensionCursorsTest', 'DontSplitOnNodeNavigation', - function() { - this.runWithLoadedTree(this.multiInlineDoc, function(root) { - const para = root.firstChild; - assertEquals('paragraph', para.role); - let cursor = new cursors.Cursor(para.firstChild, 0); - cursor = cursor.move(NODE, DIRECTIONAL, FORWARD); - assertEquals('staticText', cursor.node.role); - assertEquals('end', cursor.node.name); + async function() { + const root = await this.runWithLoadedTree(this.multiInlineDoc); + const para = root.firstChild; + assertEquals('paragraph', para.role); + let cursor = new cursors.Cursor(para.firstChild, 0); + cursor = cursor.move(NODE, DIRECTIONAL, FORWARD); + assertEquals('staticText', cursor.node.role); + assertEquals('end', cursor.node.name); - cursor = cursor.move(NODE, DIRECTIONAL, BACKWARD); - assertEquals('staticText', cursor.node.role); - assertEquals('start diff line', cursor.node.name); + cursor = cursor.move(NODE, DIRECTIONAL, BACKWARD); + assertEquals('staticText', cursor.node.role); + assertEquals('start diff line', cursor.node.name); - assertEquals('inlineTextBox', cursor.node.firstChild.role); - assertEquals('start ', cursor.node.firstChild.name); - assertEquals('diff ', cursor.node.firstChild.nextSibling.name); - assertEquals('line', cursor.node.lastChild.name); - }); + assertEquals('inlineTextBox', cursor.node.firstChild.role); + assertEquals('start ', cursor.node.firstChild.name); + assertEquals('diff ', cursor.node.firstChild.nextSibling.name); + assertEquals('line', cursor.node.lastChild.name); }); -TEST_F('AccessibilityExtensionCursorsTest', 'WrappingCursors', function() { - this.runWithLoadedTree(this.multiInlineDoc, function(root) { - const first = root; - const last = root.lastChild.firstChild; - let cursor = new cursors.WrappingCursor(first, -1); +TEST_F( + 'AccessibilityExtensionCursorsTest', 'WrappingCursors', async function() { + const root = await this.runWithLoadedTree(this.multiInlineDoc); + const first = root; + const last = root.lastChild.firstChild; + let cursor = new cursors.WrappingCursor(first, -1); - // Wrap from first node to last node. - cursor = cursor.move(NODE, DIRECTIONAL, BACKWARD); - assertEquals(last, cursor.node); + // Wrap from first node to last node. + cursor = cursor.move(NODE, DIRECTIONAL, BACKWARD); + assertEquals(last, cursor.node); - // Wrap from last node to first node. - cursor = cursor.move(NODE, DIRECTIONAL, FORWARD); - assertEquals(first, cursor.node); - }); -}); + // Wrap from last node to first node. + cursor = cursor.move(NODE, DIRECTIONAL, FORWARD); + assertEquals(first, cursor.node); + }); -TEST_F('AccessibilityExtensionCursorsTest', 'IsInWebRange', function() { - this.runWithLoadedTree(this.simpleDoc, function(root) { - const para = root.firstChild; - const webRange = cursors.Range.fromNode(para); - const auraRange = cursors.Range.fromNode(root.parent); - assertFalse(auraRange.isWebRange()); - assertTrue(webRange.isWebRange()); - }); +TEST_F('AccessibilityExtensionCursorsTest', 'IsInWebRange', async function() { + const root = await this.runWithLoadedTree(this.simpleDoc); + const para = root.firstChild; + const webRange = cursors.Range.fromNode(para); + const auraRange = cursors.Range.fromNode(root.parent); + assertFalse(auraRange.isWebRange()); + assertTrue(webRange.isWebRange()); }); // Disabled due to being flaky on ChromeOS. See https://crbug.com/1227435. TEST_F( 'AccessibilityExtensionCursorsTest', 'DISABLED_SingleDocSelection', - function() { - this.runWithLoadedTree( - ` + async function() { + const root = await this.runWithLoadedTree(` <span>start</span> <p><a href="google.com">google home page</a></p> <p>some more text</p> <p>end of text</p> - `, - function(root) { - // For some reason, Blink fails if we don't first select something - // on the page. - ChromeVoxState.instance.currentRange.select(); - const link = root.find({role: RoleType.LINK}); - const p1 = root.find({role: RoleType.PARAGRAPH}); - const p2 = p1.nextSibling; + `); + // For some reason, Blink fails if we don't first select something + // on the page. + ChromeVoxState.instance.currentRange.select(); + const link = root.find({role: RoleType.LINK}); + const p1 = root.find({role: RoleType.PARAGRAPH}); + const p2 = p1.nextSibling; - const singleSel = new cursors.Range( - new cursors.Cursor(link, 0), new cursors.Cursor(link, 1)); + const singleSel = new cursors.Range( + new cursors.Cursor(link, 0), new cursors.Cursor(link, 1)); - const multiSel = new cursors.Range( - new cursors.Cursor(p1.firstChild, 2), - new cursors.Cursor(p2.firstChild, 4)); + const multiSel = new cursors.Range( + new cursors.Cursor(p1.firstChild, 2), + new cursors.Cursor(p2.firstChild, 4)); - function verifySel() { - if (root.selectionStartObject === link.firstChild) { - assertEquals(link.firstChild, root.selectionStartObject); - assertEquals(0, root.selectionStartOffset); - assertEquals(link.firstChild, root.selectionEndObject); - assertEquals(1, root.selectionEndOffset); - this.listenOnce(root, 'textSelectionChanged', verifySel); - multiSel.select(); - } else if (root.selectionStartObject === p1.firstChild) { - assertEquals(p1.firstChild, root.selectionStartObject); - assertEquals(2, root.selectionStartOffset); - assertEquals(p2.firstChild, root.selectionEndObject); - assertEquals(4, root.selectionEndOffset); - } - } + function verifySel() { + if (root.selectionStartObject === link.firstChild) { + assertEquals(link.firstChild, root.selectionStartObject); + assertEquals(0, root.selectionStartOffset); + assertEquals(link.firstChild, root.selectionEndObject); + assertEquals(1, root.selectionEndOffset); + this.listenOnce(root, 'textSelectionChanged', verifySel); + multiSel.select(); + } else if (root.selectionStartObject === p1.firstChild) { + assertEquals(p1.firstChild, root.selectionStartObject); + assertEquals(2, root.selectionStartOffset); + assertEquals(p2.firstChild, root.selectionEndObject); + assertEquals(4, root.selectionEndOffset); + } + } - this.listenOnce(root, 'textSelectionChanged', verifySel, true); - singleSel.select(); - }); + this.listenOnce(root, 'textSelectionChanged', verifySel, true); + singleSel.select(); }); -TEST_F('AccessibilityExtensionCursorsTest', 'InlineElementOffset', function() { - this.runWithLoadedTree( - ` +TEST_F( + 'AccessibilityExtensionCursorsTest', 'InlineElementOffset', + async function() { + const root = await this.runWithLoadedTree(` <span>start</span> <p>This<br> is a<a href="#g">test</a>of selection</p> - `, - function(root) { - root.addEventListener( - 'textSelectionChanged', this.newCallback(function(evt) { - // Test setup moves initial focus; ensure we don't test that here. - if (testNode !== root.selectionStartObject) { - return; - } + `); + root.addEventListener( + 'textSelectionChanged', this.newCallback(function(evt) { + // Test setup moves initial focus; ensure we don't test that here. + if (testNode !== root.selectionStartObject) { + return; + } - // This is a little unexpected though not really incorrect; Ctrl+C - // works. - assertEquals(testNode, root.selectionStartObject); - assertEquals(ofSelectionNode, root.selectionEndObject); - assertEquals(4, root.selectionStartOffset); - assertEquals(1, root.selectionEndOffset); - })); + // This is a little unexpected though not really incorrect; Ctrl+C + // works. + assertEquals(testNode, root.selectionStartObject); + assertEquals(ofSelectionNode, root.selectionEndObject); + assertEquals(4, root.selectionStartOffset); + assertEquals(1, root.selectionEndOffset); + })); - // This is the link's static text. - const testNode = root.lastChild.lastChild.previousSibling.firstChild; - assertEquals(RoleType.STATIC_TEXT, testNode.role); - assertEquals('test', testNode.name); + // This is the link's static text. + const testNode = root.lastChild.lastChild.previousSibling.firstChild; + assertEquals(RoleType.STATIC_TEXT, testNode.role); + assertEquals('test', testNode.name); - const ofSelectionNode = root.lastChild.lastChild; - const cur = new cursors.Cursor(ofSelectionNode, 0); - assertEquals('of selection', cur.selectionNode.name); - assertEquals(RoleType.STATIC_TEXT, cur.selectionNode.role); - assertEquals(0, cur.selectionIndex); + const ofSelectionNode = root.lastChild.lastChild; + const cur = new cursors.Cursor(ofSelectionNode, 0); + assertEquals('of selection', cur.selectionNode.name); + assertEquals(RoleType.STATIC_TEXT, cur.selectionNode.role); + assertEquals(0, cur.selectionIndex); - const curIntoO = new cursors.Cursor(ofSelectionNode, 1); - assertEquals('of selection', curIntoO.selectionNode.name); - assertEquals(RoleType.STATIC_TEXT, curIntoO.selectionNode.role); - assertEquals(1, curIntoO.selectionIndex); + const curIntoO = new cursors.Cursor(ofSelectionNode, 1); + assertEquals('of selection', curIntoO.selectionNode.name); + assertEquals(RoleType.STATIC_TEXT, curIntoO.selectionNode.role); + assertEquals(1, curIntoO.selectionIndex); - const oRange = new cursors.Range(cur, curIntoO); - oRange.select(); - }); -}); + const oRange = new cursors.Range(cur, curIntoO); + oRange.select(); + }); -TEST_F('AccessibilityExtensionCursorsTest', 'ContentEquality', function() { - this.runWithLoadedTree( - ` +TEST_F( + 'AccessibilityExtensionCursorsTest', 'ContentEquality', async function() { + const root = await this.runWithLoadedTree(` <div role="region" aria-label="test region">this is a test</button> - `, - function(root) { - const region = root.firstChild; - assertEquals(RoleType.REGION, region.role); - const staticText = region.firstChild; - assertEquals(RoleType.STATIC_TEXT, staticText.role); - const inlineTextBox = staticText.firstChild; - assertEquals(RoleType.INLINE_TEXT_BOX, inlineTextBox.role); + `); + const region = root.firstChild; + assertEquals(RoleType.REGION, region.role); + const staticText = region.firstChild; + assertEquals(RoleType.STATIC_TEXT, staticText.role); + const inlineTextBox = staticText.firstChild; + assertEquals(RoleType.INLINE_TEXT_BOX, inlineTextBox.role); - const rootRange = cursors.Range.fromNode(root); - const regionRange = cursors.Range.fromNode(region); - const staticTextRange = cursors.Range.fromNode(staticText); - const inlineTextBoxRange = cursors.Range.fromNode(inlineTextBox); + const rootRange = cursors.Range.fromNode(root); + const regionRange = cursors.Range.fromNode(region); + const staticTextRange = cursors.Range.fromNode(staticText); + const inlineTextBoxRange = cursors.Range.fromNode(inlineTextBox); - // Positive cases. - assertTrue(regionRange.contentEquals(staticTextRange)); - assertTrue(staticTextRange.contentEquals(regionRange)); - assertTrue(inlineTextBoxRange.contentEquals(staticTextRange)); - assertTrue(staticTextRange.contentEquals(inlineTextBoxRange)); + // Positive cases. + assertTrue(regionRange.contentEquals(staticTextRange)); + assertTrue(staticTextRange.contentEquals(regionRange)); + assertTrue(inlineTextBoxRange.contentEquals(staticTextRange)); + assertTrue(staticTextRange.contentEquals(inlineTextBoxRange)); - // Negative cases. - assertFalse(rootRange.contentEquals(regionRange)); - assertFalse(rootRange.contentEquals(staticTextRange)); - assertFalse(rootRange.contentEquals(inlineTextBoxRange)); - assertFalse(regionRange.contentEquals(rootRange)); - assertFalse(staticTextRange.contentEquals(rootRange)); - assertFalse(inlineTextBoxRange.contentEquals(rootRange)); - }); -}); + // Negative cases. + assertFalse(rootRange.contentEquals(regionRange)); + assertFalse(rootRange.contentEquals(staticTextRange)); + assertFalse(rootRange.contentEquals(inlineTextBoxRange)); + assertFalse(regionRange.contentEquals(rootRange)); + assertFalse(staticTextRange.contentEquals(rootRange)); + assertFalse(inlineTextBoxRange.contentEquals(rootRange)); + }); -TEST_F('AccessibilityExtensionCursorsTest', 'DeepEquivalency', function() { - this.runWithLoadedTree( - ` +TEST_F( + 'AccessibilityExtensionCursorsTest', 'DeepEquivalency', async function() { + const root = await this.runWithLoadedTree(` <p style="word-spacing:100000px">this is a test</p> - `, - function(root) { - const textNode = root.find({role: RoleType.STATIC_TEXT}); + `); + const textNode = root.find({role: RoleType.STATIC_TEXT}); - let text = new cursors.Cursor(textNode, 2); - deep = text.deepEquivalent; - assertEquals('this ', deep.node.name); - assertEquals(RoleType.INLINE_TEXT_BOX, deep.node.role); - assertEquals(2, deep.index); + let text = new cursors.Cursor(textNode, 2); + deep = text.deepEquivalent; + assertEquals('this ', deep.node.name); + assertEquals(RoleType.INLINE_TEXT_BOX, deep.node.role); + assertEquals(2, deep.index); - text = new cursors.Cursor(textNode, 5); - deep = text.deepEquivalent; - assertEquals('is ', deep.node.name); - assertEquals(RoleType.INLINE_TEXT_BOX, deep.node.role); - assertEquals(0, deep.index); + text = new cursors.Cursor(textNode, 5); + deep = text.deepEquivalent; + assertEquals('is ', deep.node.name); + assertEquals(RoleType.INLINE_TEXT_BOX, deep.node.role); + assertEquals(0, deep.index); - text = new cursors.Cursor(textNode, 7); - deep = text.deepEquivalent; - assertEquals('is ', deep.node.name); - assertEquals(RoleType.INLINE_TEXT_BOX, deep.node.role); - assertEquals(2, deep.index); + text = new cursors.Cursor(textNode, 7); + deep = text.deepEquivalent; + assertEquals('is ', deep.node.name); + assertEquals(RoleType.INLINE_TEXT_BOX, deep.node.role); + assertEquals(2, deep.index); - text = new cursors.Cursor(textNode, 8); - deep = text.deepEquivalent; - assertEquals('a ', deep.node.name); - assertEquals(RoleType.INLINE_TEXT_BOX, deep.node.role); - assertEquals(0, deep.index); + text = new cursors.Cursor(textNode, 8); + deep = text.deepEquivalent; + assertEquals('a ', deep.node.name); + assertEquals(RoleType.INLINE_TEXT_BOX, deep.node.role); + assertEquals(0, deep.index); - text = new cursors.Cursor(textNode, 11); - deep = text.deepEquivalent; - assertEquals('test', deep.node.name); - assertEquals(RoleType.INLINE_TEXT_BOX, deep.node.role); - assertEquals(1, deep.index); + text = new cursors.Cursor(textNode, 11); + deep = text.deepEquivalent; + assertEquals('test', deep.node.name); + assertEquals(RoleType.INLINE_TEXT_BOX, deep.node.role); + assertEquals(1, deep.index); - // This is the only selection that can be placed at the length of the - // node's text. This only happens at the end of a line. - text = new cursors.Cursor(textNode, 14); - deep = text.deepEquivalent; - assertEquals('test', deep.node.name); - assertEquals(RoleType.INLINE_TEXT_BOX, deep.node.role); - assertEquals(4, deep.index); + // This is the only selection that can be placed at the length of the + // node's text. This only happens at the end of a line. + text = new cursors.Cursor(textNode, 14); + deep = text.deepEquivalent; + assertEquals('test', deep.node.name); + assertEquals(RoleType.INLINE_TEXT_BOX, deep.node.role); + assertEquals(4, deep.index); - // However, any offset larger is invalid. - text = new cursors.Cursor(textNode, 15); - deep = text.deepEquivalent; - assertTrue(text.equals(deep)); - }); -}); + // However, any offset larger is invalid. + text = new cursors.Cursor(textNode, 15); + deep = text.deepEquivalent; + assertTrue(text.equals(deep)); + }); TEST_F( 'AccessibilityExtensionCursorsTest', 'DeepEquivalencyBeyondLastChild', - function() { - this.runWithLoadedTree( - ` + async function() { + const root = await this.runWithLoadedTree(` <p>test</p> - `, - function(root) { - const paragraph = root.find({role: RoleType.PARAGRAPH}); - assertEquals(1, paragraph.children.length); - const cursor = new cursors.Cursor(paragraph, 1); + `); + const paragraph = root.find({role: RoleType.PARAGRAPH}); + assertEquals(1, paragraph.children.length); + const cursor = new cursors.Cursor(paragraph, 1); - const deep = cursor.deepEquivalent; - assertEquals(RoleType.STATIC_TEXT, deep.node.role); - assertEquals(4, deep.index); - }); + const deep = cursor.deepEquivalent; + assertEquals(RoleType.STATIC_TEXT, deep.node.role); + assertEquals(4, deep.index); }); TEST_F( 'AccessibilityExtensionCursorsTest', 'MovementByWordThroughNonInlineText', - function() { - this.runCursorMovesOnDocument(this.buttonAndInlineTextDoc, [ + async function() { + await this.runCursorMovesOnDocument(this.buttonAndInlineTextDoc, [ // Move forward by word. // 'text' start and end indices. [WORD, DIRECTIONAL, FORWARD, {index: 7, value: 'Inline text content'}],
diff --git a/chrome/browser/resources/chromeos/accessibility/common/cursors/recovery_strategy_test.js b/chrome/browser/resources/chromeos/accessibility/common/cursors/recovery_strategy_test.js index bf648a7..0c4f8ee 100644 --- a/chrome/browser/resources/chromeos/accessibility/common/cursors/recovery_strategy_test.js +++ b/chrome/browser/resources/chromeos/accessibility/common/cursors/recovery_strategy_test.js
@@ -20,9 +20,8 @@ TEST_F( 'AccessibilityExtensionRecoveryStrategyTest', 'ReparentedRecovery', - function() { - this.runWithLoadedTree( - ` + async function() { + const root = await this.runWithLoadedTree(` <input type="text"></input> <p id="p">hi</p> <button id="go"</button> @@ -33,65 +32,63 @@ document.body.appendChild(p); }); </script> - `, - function(root) { - const p = root.find({role: RoleType.PARAGRAPH}); - const s = root.find({role: RoleType.STATIC_TEXT}); - const b = root.find({role: RoleType.BUTTON}); - const bAncestryRecovery = new AncestryRecoveryStrategy(b); - const pAncestryRecovery = new AncestryRecoveryStrategy(p); - const sAncestryRecovery = new AncestryRecoveryStrategy(s); - const bTreePathRecovery = new TreePathRecoveryStrategy(b); - const pTreePathRecovery = new TreePathRecoveryStrategy(p); - const sTreePathRecovery = new TreePathRecoveryStrategy(s); - this.listenOnce(b, 'clicked', function() { - assertFalse( - bAncestryRecovery.requiresRecovery(), - 'bAncestryRecovery.requiresRecovery'); - assertTrue( - pAncestryRecovery.requiresRecovery(), - 'pAncestryRecovery.requiresRecovery()'); - assertTrue( - sAncestryRecovery.requiresRecovery(), - 'sAncestryRecovery.requiresRecovery()'); - assertFalse( - bTreePathRecovery.requiresRecovery(), - 'bTreePathRecovery.requiresRecovery()'); - assertTrue( - pTreePathRecovery.requiresRecovery(), - 'pTreePathRecovery.requiresRecovery()'); - assertTrue( - sTreePathRecovery.requiresRecovery(), - 'sTreePathRecovery.requiresRecovery()'); + `); + const p = root.find({role: RoleType.PARAGRAPH}); + const s = root.find({role: RoleType.STATIC_TEXT}); + const b = root.find({role: RoleType.BUTTON}); + const bAncestryRecovery = new AncestryRecoveryStrategy(b); + const pAncestryRecovery = new AncestryRecoveryStrategy(p); + const sAncestryRecovery = new AncestryRecoveryStrategy(s); + const bTreePathRecovery = new TreePathRecoveryStrategy(b); + const pTreePathRecovery = new TreePathRecoveryStrategy(p); + const sTreePathRecovery = new TreePathRecoveryStrategy(s); + this.listenOnce(b, 'clicked', function() { + assertFalse( + bAncestryRecovery.requiresRecovery(), + 'bAncestryRecovery.requiresRecovery'); + assertTrue( + pAncestryRecovery.requiresRecovery(), + 'pAncestryRecovery.requiresRecovery()'); + assertTrue( + sAncestryRecovery.requiresRecovery(), + 'sAncestryRecovery.requiresRecovery()'); + assertFalse( + bTreePathRecovery.requiresRecovery(), + 'bTreePathRecovery.requiresRecovery()'); + assertTrue( + pTreePathRecovery.requiresRecovery(), + 'pTreePathRecovery.requiresRecovery()'); + assertTrue( + sTreePathRecovery.requiresRecovery(), + 'sTreePathRecovery.requiresRecovery()'); - assertEquals(RoleType.BUTTON, bAncestryRecovery.node.role); - assertEquals(root, pAncestryRecovery.node); - assertEquals(root, sAncestryRecovery.node); + assertEquals(RoleType.BUTTON, bAncestryRecovery.node.role); + assertEquals(root, pAncestryRecovery.node); + assertEquals(root, sAncestryRecovery.node); - assertEquals(b, bTreePathRecovery.node); - assertEquals(b, pTreePathRecovery.node); - assertEquals(b, sTreePathRecovery.node); + assertEquals(b, bTreePathRecovery.node); + assertEquals(b, pTreePathRecovery.node); + assertEquals(b, sTreePathRecovery.node); - assertFalse( - bAncestryRecovery.requiresRecovery(), - 'bAncestryRecovery.requiresRecovery()'); - assertFalse( - pAncestryRecovery.requiresRecovery(), - 'pAncestryRecovery.requiresRecovery()'); - assertFalse( - sAncestryRecovery.requiresRecovery(), - 'sAncestryRecovery.requiresRecovery()'); - assertFalse( - bTreePathRecovery.requiresRecovery(), - 'bTreePathRecovery.requiresRecovery()'); - assertFalse( - pTreePathRecovery.requiresRecovery(), - 'pTreePathRecovery.requiresRecovery()'); - assertFalse( - sTreePathRecovery.requiresRecovery(), - 'sTreePathRecovery.requiresRecovery()'); - }); - // Trigger the change. - b.doDefault(); - }); + assertFalse( + bAncestryRecovery.requiresRecovery(), + 'bAncestryRecovery.requiresRecovery()'); + assertFalse( + pAncestryRecovery.requiresRecovery(), + 'pAncestryRecovery.requiresRecovery()'); + assertFalse( + sAncestryRecovery.requiresRecovery(), + 'sAncestryRecovery.requiresRecovery()'); + assertFalse( + bTreePathRecovery.requiresRecovery(), + 'bTreePathRecovery.requiresRecovery()'); + assertFalse( + pTreePathRecovery.requiresRecovery(), + 'pTreePathRecovery.requiresRecovery()'); + assertFalse( + sTreePathRecovery.requiresRecovery(), + 'sTreePathRecovery.requiresRecovery()'); + }); + // Trigger the change. + b.doDefault(); });
diff --git a/chrome/browser/resources/chromeos/accessibility/common/repeated_event_handler_test.js b/chrome/browser/resources/chromeos/accessibility/common/repeated_event_handler_test.js index 83aa598..afa7e56 100644 --- a/chrome/browser/resources/chromeos/accessibility/common/repeated_event_handler_test.js +++ b/chrome/browser/resources/chromeos/accessibility/common/repeated_event_handler_test.js
@@ -11,45 +11,43 @@ /** Test fixture for array_util.js. */ RepeatedEventHandlerTest = class extends ChromeVoxNextE2ETest {}; -TEST_F('RepeatedEventHandlerTest', 'RepeatedEventHandledOnce', function() { - this.runWithLoadedTree('', (root) => { - this.handlerCallCount = 0; - const handler = () => this.handlerCallCount++; +TEST_F( + 'RepeatedEventHandlerTest', 'RepeatedEventHandledOnce', async function() { + const root = await this.runWithLoadedTree(''); + this.handlerCallCount = 0; + const handler = () => this.handlerCallCount++; - const repeatedHandler = new RepeatedEventHandler(root, 'focus', handler); + const repeatedHandler = new RepeatedEventHandler(root, 'focus', handler); - // Simulate events being fired. - repeatedHandler.onEvent_(); - repeatedHandler.onEvent_(); - repeatedHandler.onEvent_(); - repeatedHandler.onEvent_(); - repeatedHandler.onEvent_(); + // Simulate events being fired. + repeatedHandler.onEvent_(); + repeatedHandler.onEvent_(); + repeatedHandler.onEvent_(); + repeatedHandler.onEvent_(); + repeatedHandler.onEvent_(); - // Yield before verify how many times the handler was called. - setTimeout( - this.newCallback(() => assertEquals(this.handlerCallCount, 1)), 0); - }); -}); + // Yield before verify how many times the handler was called. + setTimeout( + this.newCallback(() => assertEquals(this.handlerCallCount, 1)), 0); + }); TEST_F( 'RepeatedEventHandlerTest', 'NoEventsHandledAfterStopListening', - function() { - this.runWithLoadedTree('', (root) => { - this.handlerCallCount = 0; - const handler = () => this.handlerCallCount++; + async function() { + const root = await this.runWithLoadedTree(''); + this.handlerCallCount = 0; + const handler = () => this.handlerCallCount++; - const repeatedHandler = - new RepeatedEventHandler(root, 'focus', handler); + const repeatedHandler = new RepeatedEventHandler(root, 'focus', handler); - // Simulate events being fired. - repeatedHandler.onEvent_(); - repeatedHandler.onEvent_(); - repeatedHandler.onEvent_(); + // Simulate events being fired. + repeatedHandler.onEvent_(); + repeatedHandler.onEvent_(); + repeatedHandler.onEvent_(); - repeatedHandler.stop(); + repeatedHandler.stop(); - // Yield before verifying how many times the handler was called. - setTimeout( - this.newCallback(() => assertEquals(this.handlerCallCount, 0)), 0); - }); + // Yield before verifying how many times the handler was called. + setTimeout( + this.newCallback(() => assertEquals(this.handlerCallCount, 0)), 0); });
diff --git a/chrome/browser/resources/chromeos/accessibility/common/testing/e2e_test_base.js b/chrome/browser/resources/chromeos/accessibility/common/testing/e2e_test_base.js index 831af49..90a52be2 100644 --- a/chrome/browser/resources/chromeos/accessibility/common/testing/e2e_test_base.js +++ b/chrome/browser/resources/chromeos/accessibility/common/testing/e2e_test_base.js
@@ -135,6 +135,61 @@ } /** + * Waits for the given |eventType| to be fired on |node|. + * @param {!chrome.automation.AutomationNode} node + * @param {!chrome.automation.EventType} eventType + */ + async waitForEvent(node, eventType) { + return new Promise(resolve => { + const callback = () => { + node.removeEventListener(eventType, callback); + resolve(); + }; + node.addEventListener(eventType, callback); + }); + } + + /** + * @param {!chrome.automation.AutomationNode} app + * @return {boolean} + */ + isInLacrosWindow(app) { + // We validate we're actually within a Lacros window by scanning upward + // until we see the presence of an app id, which indicates an app subtree. + // See go/lacros-accessibility for details. + while (app && !app.appId) { + app = app.parent; + } + return Boolean(app); + } + + /** + * @param {string} url + * @param {!chrome.automation.AutomationNode} addressBar + */ + async navigateToUrlForLacros(url, addressBar) { + // This populates the address bar as if we typed the url. + addressBar.setValue(url); + + // We have two choices to confirm navigation. + if (!this.navigateLacrosWithAutoComplete) { + // 1. (default), hit enter. + await this.waitForEvent(addressBar, 'valueChanged'); + EventGenerator.sendKeyPress(KeyCode.RETURN); + } else { + // 2. use the auto completion. + await this.waitForEvent(addressBar, 'controlsChanged'); + // The text field relates to the auto complete list box via controlledBy. + // The |controls| node structure here nests several levels until the + // listBoxOption we want. + const autoCompleteListBoxOption = + addressBar.controls[0].firstChild.firstChild; + assertEquals('listBoxOption', autoCompleteListBoxOption.role); + autoCompleteListBoxOption.doDefault(); + } + } + + /** * Creates a callback that optionally calls {@code opt_callback} when * called. If this method is called one or more times, then * {@code testDone()} will be called when all callbacks have been called. @@ -150,7 +205,7 @@ /** * Gets the desktop from the automation API and runs |callback|. * Arranges to call |testDone()| after |callback| returns. - * NOTE: Callbacks created inside |opt_callback| must be wrapped with + * NOTE: Callbacks created inside |callback| must be wrapped with * |this.newCallback| if passed to asynchronous calls. Otherwise, the test * will be finished prematurely. * @param {function(chrome.automation.AutomationNode)} callback @@ -162,128 +217,85 @@ /** * Gets the desktop from the automation API and Launches a new tab with - * the given document, and runs |callback| when a load complete fires. - * Arranges to call |testDone()| after |callback| returns. - * NOTE: Callbacks created inside |callback| must be wrapped with - * |this.newCallback| if passed to asynchronous calls. Otherwise, the test - * will be finished prematurely. + * the given document, and returns the root web area when a load complete + * fires. * @param {string|function(): string} doc An HTML snippet, optionally wrapped * inside of a function. - * @param {function(chrome.automation.AutomationNode)} callback - * Called with the root web area node once the document is ready. - * @param {{url: (string=), returnDesktop: (boolean=)}} + * @param {{url: (string=)}} * opt_params * url Optional url to wait for. Defaults to undefined. + * @return {chrome.automation.AutomationNode} the root web area node, only + * returned once the document is ready. */ - runWithLoadedTree(doc, callback, opt_params = {}) { - callback = this.newCallback(callback); - chrome.automation.getDesktop((desktop) => { + async runWithLoadedTree(doc, opt_params = {}) { + return new Promise(async resolve => { + // Make sure the test doesn't finish until this function has resolved. + let callback = this.newCallback(resolve); + this.desktop_ = await new Promise(r => chrome.automation.getDesktop(r)); const url = opt_params.url || DocUtils.createUrlForDoc(doc); - chrome.commandLinePrivate.hasSwitch( - 'lacros-chrome-path', hasLacrosChromePath => { - // The below block handles opening a url either in a Lacros tab or - // Ash tab. For Lacros, we re-use an already open Lacros tab. For - // Ash, we use the chrome.tabs api. + const hasLacrosChromePath = await new Promise( + r => chrome.commandLinePrivate.hasSwitch('lacros-chrome-path', r)); + // The below block handles opening a url either in a Lacros tab or Ash + // tab. For Lacros, we re-use an already open Lacros tab. For Ash, we use + // the chrome.tabs api. - // This flag controls whether we've requested navigation to |url| - // within the open Lacros tab. - let didNavigateForLacros = false; + // This flag controls whether we've requested navigation to |url| within + // the open Lacros tab. + let didNavigateForLacros = false; - // Listens to both load completes and focus events to eventually - // trigger the test callback. - const listener = (event) => { - if (hasLacrosChromePath && !didNavigateForLacros) { - // We have yet to request navigation in the Lacros tab. Do so - // now by getting the default focus (the address bar), setting - // the value to the url and then performing do default on the - // auto completion node. This is somewhat involved, so each step - // is commented. - chrome.automation.getFocus(focus => { - // It's possible focus is elsewhere; wait until it lands on - // the address bar text field. - if (focus.role !== chrome.automation.RoleType.TEXT_FIELD) { - return; - } + // Listener for both load complete and focus events that eventually + // triggers the test. + const listener = async (event) => { + if (hasLacrosChromePath && !didNavigateForLacros) { + // We have yet to request navigation in the Lacros tab. Do so now by + // getting the default focus (the address bar), setting the value to + // the url and then performing do default on the auto completion node. + const focus = await new Promise(r => chrome.automation.getFocus(r)); + // It's possible focus is elsewhere; wait until it lands on the + // address bar text field. + if (focus.role !== chrome.automation.RoleType.TEXT_FIELD) { + return; + } - // Next, we want to validate we're actually within a Lacros - // window. Do so by scanning upward until we see the presence - // of an app id which indicates an app subtree. See - // go/lacros-accessibility for details. - let app = focus; - while (app && !app.appId) { - app = app.parent; - } + if (this.isInLacrosWindow(focus)) { + didNavigateForLacros = true; + await this.navigateToUrlForLacros(url, focus); + } + return; // exit listener. + } - if (app) { - didNavigateForLacros = true; + // Navigation has occurred, but we need to ensure the url we want has + // loaded. + if (event.target.root.url !== url || !event.target.root.docLoaded) { + return; // exit listener. + } - // This populates the address bar as if we typed the url. - focus.setValue(url); + // Finally, when we get here, we've successfully navigated to + // the |url| in either Lacros or Ash. + this.desktop_.removeEventListener('focus', listener, true); + this.desktop_.removeEventListener('loadComplete', listener, true); - // We have two choices to confirm navigation. - if (!this.navigateLacrosWithAutoComplete) { - // 1. (default), hit enter. - const onValueChanged = e => { - focus.removeEventListener( - 'valueChanged', onValueChanged); - EventGenerator.sendKeyPress(KeyCode.RETURN); - }; - focus.addEventListener('valueChanged', onValueChanged); - } else { - // 2. use the auto completion. - // Wait until the auto completion shows up. - const clickAutocomplete = () => { - focus.removeEventListener( - 'controlsChanged', clickAutocomplete); + if (callback) { + callback(event.target.root); + } + // Avoid calling |callback| twice (which would cause the test to fail). + callback = null; + }; // end listener. - // The text field relates to the auto complete list box - // via controlledBy. The |controls| node structure here - // nests several levels until the listBoxOption we want. - const autoCompleteListBoxOption = - focus.controls[0].firstChild.firstChild; - assertEquals( - 'listBoxOption', autoCompleteListBoxOption.role); - autoCompleteListBoxOption.doDefault(); - }; - focus.addEventListener( - 'controlsChanged', clickAutocomplete); - } - } - }); - return; - } + // Setup the listener above for focus and load complete listening. + this.desktop_.addEventListener('focus', listener, true); + this.desktop_.addEventListener('loadComplete', listener, true); - // Navigation has occurred, but we need to ensure the url we want - // has loaded. - if (event.target.root.url !== url || - !event.target.root.docLoaded) { - return; - } - - // Finally, when we get here, we've successfully navigated to the - // |url| in either Lacros or Ash. - desktop.removeEventListener('focus', listener, true); - desktop.removeEventListener('loadComplete', listener, true); - callback && callback(event.target.root); - callback = null; - }; - - // Setup the listener above for focus and load complete listening. - this.desktop_ = desktop; - desktop.addEventListener('focus', listener, true); - desktop.addEventListener('loadComplete', listener, true); - - // The easy case -- just open the Ash tab. - if (!hasLacrosChromePath) { - const createParams = {active: true, url}; - chrome.tabs.create(createParams); - } else { - chrome.automation.getFocus(f => { - listener({target: f}); - }); - } - }); + // The easy case -- just open the Ash tab. + if (!hasLacrosChromePath) { + const createParams = {active: true, url}; + chrome.tabs.create(createParams); + } else { + chrome.automation.getFocus(f => { + listener({target: f}); + }); + } }); }
diff --git a/chrome/browser/resources/chromeos/accessibility/common/tree_walker_test.js b/chrome/browser/resources/chromeos/accessibility/common/tree_walker_test.js index f6c890a8..992a476a 100644 --- a/chrome/browser/resources/chromeos/accessibility/common/tree_walker_test.js +++ b/chrome/browser/resources/chromeos/accessibility/common/tree_walker_test.js
@@ -121,9 +121,8 @@ TEST_F( 'AccessibilityExtensionAutomationTreeWalkerTest', 'RootLeafRestriction', - function() { - this.runWithLoadedTree( - ` + async function() { + const r = await this.runWithLoadedTree(` <div role="group" aria-label="1"> <div role="group" aria-label="2"> <div role="group" aria-label="3"> @@ -133,121 +132,116 @@ </div> <div role="group" aria-label="6"></div> </div> - `, - function(r) { - const node2 = r.firstChild.firstChild; - assertEquals('2', node2.name); + `); + const node2 = r.firstChild.firstChild; + assertEquals('2', node2.name); - // Restrict to 2's subtree and consider 3 and 5 leaves. - const leafP = function(n) { - return n.name === '3' || n.name === '5'; - }; - const rootP = function(n) { - return n.name === '2'; - }; + // Restrict to 2's subtree and consider 3 and 5 leaves. + const leafP = function(n) { + return n.name === '3' || n.name === '5'; + }; + const rootP = function(n) { + return n.name === '2'; + }; - // Track the nodes we've visited. - let visited = ''; - const visit = function(n) { - visited += n.name; - }; - const restrictions = {leaf: leafP, root: rootP, visit}; - let walker = - new AutomationTreeWalker(node2, 'forward', restrictions); - while (walker.next().node) { - } - assertEquals('35', visited); - assertEquals(AutomationTreeWalkerPhase.OTHER, walker.phase); + // Track the nodes we've visited. + let visited = ''; + const visit = function(n) { + visited += n.name; + }; + const restrictions = {leaf: leafP, root: rootP, visit}; + let walker = new AutomationTreeWalker(node2, 'forward', restrictions); + while (walker.next().node) { + } + assertEquals('35', visited); + assertEquals(AutomationTreeWalkerPhase.OTHER, walker.phase); - // And the reverse. - // Note that walking into a root is allowed. - visited = ''; - const node6 = r.lastChild.lastChild; - assertEquals('6', node6.name); - walker = new AutomationTreeWalker(node6, 'backward', restrictions); - while (walker.next().node) { - } - assertEquals('532', visited); + // And the reverse. + // Note that walking into a root is allowed. + visited = ''; + const node6 = r.lastChild.lastChild; + assertEquals('6', node6.name); + walker = new AutomationTreeWalker(node6, 'backward', restrictions); + while (walker.next().node) { + } + assertEquals('532', visited); - // Test not visiting ancestors of initial node. - const node5 = r.firstChild.firstChild.lastChild; - assertEquals('5', node5.name); - restrictions.root = function(n) { - return n.name === '1'; - }; - restrictions.leaf = function(n) { - return !n.firstChild; - }; + // Test not visiting ancestors of initial node. + const node5 = r.firstChild.firstChild.lastChild; + assertEquals('5', node5.name); + restrictions.root = function(n) { + return n.name === '1'; + }; + restrictions.leaf = function(n) { + return !n.firstChild; + }; - visited = ''; - restrictions.skipInitialAncestry = false; - walker = new AutomationTreeWalker(node5, 'backward', restrictions); - while (walker.next().node) { - } - assertEquals('4321', visited); + visited = ''; + restrictions.skipInitialAncestry = false; + walker = new AutomationTreeWalker(node5, 'backward', restrictions); + while (walker.next().node) { + } + assertEquals('4321', visited); - // 2 and 1 are ancestors; check they get skipped. - visited = ''; - restrictions.skipInitialAncestry = true; - walker = new AutomationTreeWalker(node5, 'backward', restrictions); - while (walker.next().node) { - } - assertEquals('43', visited); + // 2 and 1 are ancestors; check they get skipped. + visited = ''; + restrictions.skipInitialAncestry = true; + walker = new AutomationTreeWalker(node5, 'backward', restrictions); + while (walker.next().node) { + } + assertEquals('43', visited); - // We should skip node 2's subtree. - walker = new AutomationTreeWalker( - node2, 'forward', {skipInitialSubtree: true}); - assertEquals(node6, walker.next().node); - }); + // We should skip node 2's subtree. + walker = new AutomationTreeWalker( + node2, 'forward', {skipInitialSubtree: true}); + assertEquals(node6, walker.next().node); }); TEST_F( 'AccessibilityExtensionAutomationTreeWalkerTest', 'LeafPredicateSymmetry', - function() { - this.runWithLoadedTree(toolbarDoc, function(r) { - const d = r.root.parent.root; - const forwardWalker = new AutomationTreeWalker(d, 'forward'); - const forwardNodes = []; + async function() { + const r = await this.runWithLoadedTree(toolbarDoc); + const d = r.root.parent.root; + const forwardWalker = new AutomationTreeWalker(d, 'forward'); + const forwardNodes = []; - // Get all nodes according to the walker in the forward direction. - do { - forwardNodes.push(forwardWalker.node); - } while (forwardWalker.next().node); + // Get all nodes according to the walker in the forward direction. + do { + forwardNodes.push(forwardWalker.node); + } while (forwardWalker.next().node); - // Now, verify the walker moving backwards matches the forwards list. - const backwardWalker = new AutomationTreeWalker( - forwardNodes[forwardNodes.length - 1], 'backward'); + // Now, verify the walker moving backwards matches the forwards list. + const backwardWalker = new AutomationTreeWalker( + forwardNodes[forwardNodes.length - 1], 'backward'); - do { - const next = forwardNodes.pop(); - assertEquals(next, backwardWalker.node); - } while (backwardWalker.next().node); - }); + do { + const next = forwardNodes.pop(); + assertEquals(next, backwardWalker.node); + } while (backwardWalker.next().node); }); TEST_F( 'AccessibilityExtensionAutomationTreeWalkerTest', 'RootPredicateEnding', - function() { - this.runWithLoadedTree(toolbarDoc(), function(r) { - const backwardWalker = - new AutomationTreeWalker(r.firstChild, 'backward', { - root(node) { - return node === r; - } - }); - assertEquals(r, backwardWalker.next().node); - assertEquals(null, backwardWalker.next().node); + async function() { + const r = await this.runWithLoadedTree(toolbarDoc()); + const backwardWalker = + new AutomationTreeWalker(r.firstChild, 'backward', { + root(node) { + return node === r; + } + }); + assertEquals(r, backwardWalker.next().node); + assertEquals(null, backwardWalker.next().node); - const forwardWalker = - new AutomationTreeWalker(r.firstChild.lastChild, 'forward', { - root(node) { - return node === r; - } - }); - // Advance to the static text box of button contains text "Forward". - assertEquals('Forward', forwardWalker.next().node.name); - // Advance to the inline text box of button contains text "Forward". - assertEquals('Forward', forwardWalker.next().node.name); - assertEquals(null, forwardWalker.next().node); - }); + const forwardWalker = + new AutomationTreeWalker(r.firstChild.lastChild, 'forward', { + root(node) { + return node === r; + } + }); + // Advance to the static text box of button contains text "Forward". + assertEquals('Forward', forwardWalker.next().node.name); + // Advance to the inline text box of button contains text "Forward". + assertEquals('Forward', forwardWalker.next().node.name); + assertEquals(null, forwardWalker.next().node); });
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/paragraph_utils_overflow_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/paragraph_utils_overflow_test.js index 5ba8abb..4c09c27de 100644 --- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/paragraph_utils_overflow_test.js +++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/paragraph_utils_overflow_test.js
@@ -43,126 +43,123 @@ TEST_F( 'SelectToSpeakParagraphOverflowTest', - 'ReplaceseHorizentalOverflowTextWithSpace', function() { + 'ReplaceseHorizentalOverflowTextWithSpace', async function() { const inputText = 'This text overflows partially'; - this.runWithLoadedTree( - this.generateHorizentalOverflowText(inputText), function(root) { - const overflowText = root.find({ - role: chrome.automation.RoleType.INLINE_TEXT_BOX, - attributes: {name: inputText}, - }); - var nodeGroup = ParagraphUtils.buildNodeGroup( - [overflowText], 0 /* index */, {clipOverflowWords: true}); + const root = await this.runWithLoadedTree( + this.generateHorizentalOverflowText(inputText)); + const overflowText = root.find({ + role: chrome.automation.RoleType.INLINE_TEXT_BOX, + attributes: {name: inputText}, + }); + var nodeGroup = ParagraphUtils.buildNodeGroup( + [overflowText], 0 /* index */, {clipOverflowWords: true}); - // The output text should have the same length of the input text - // plus a space character at the end. - assertEquals(nodeGroup.text.length, inputText.length + 1); - // The output text should have less non-empty characters compared - // to the input text, as any overflow word will be replaced as - // space characters. - assertTrue( - nodeGroup.text.replace(/ /g, '').length < - inputText.replace(/ /g, '').length); - }); + // The output text should have the same length of the input text + // plus a space character at the end. + assertEquals(nodeGroup.text.length, inputText.length + 1); + // The output text should have less non-empty characters compared + // to the input text, as any overflow word will be replaced as + // space characters. + assertTrue( + nodeGroup.text.replace(/ /g, '').length < + inputText.replace(/ /g, '').length); }); TEST_F( 'SelectToSpeakParagraphOverflowTest', - 'ReplaceseVerticalOverflowTextWithSpace', function() { + 'ReplaceseVerticalOverflowTextWithSpace', async function() { const visibleText = 'This text is visible'; const overflowText = 'This text overflows'; - this.runWithLoadedTree( - this.generateVerticalOverflowText(visibleText, overflowText), - function(root) { - // Find the visible text. - const visibleTextNode = root.find({ - role: chrome.automation.RoleType.INLINE_TEXT_BOX, - attributes: {name: visibleText}, - }); - var nodeGroup = ParagraphUtils.buildNodeGroup( - [visibleTextNode], 0 /* index */, {clipOverflowWords: true}); - // The output text should have the same length of the visible text - // plus a space character at the end. - assertEquals(nodeGroup.text.length, visibleText.length + 1); - // The output text should be the same of the input text. - assertEquals( - nodeGroup.text.replace(/ /g, ''), - visibleText.replace(/ /g, '')); + const root = await this.runWithLoadedTree( + this.generateVerticalOverflowText(visibleText, overflowText)); + // Find the visible text. + const visibleTextNode = root.find({ + role: chrome.automation.RoleType.INLINE_TEXT_BOX, + attributes: {name: visibleText}, + }); + var nodeGroup = ParagraphUtils.buildNodeGroup( + [visibleTextNode], 0 /* index */, {clipOverflowWords: true}); + // The output text should have the same length of the visible text + // plus a space character at the end. + assertEquals(nodeGroup.text.length, visibleText.length + 1); + // The output text should be the same of the input text. + assertEquals( + nodeGroup.text.replace(/ /g, ''), visibleText.replace(/ /g, '')); - // Find the overflow text. - const overflowTextNode = root.find({ - role: chrome.automation.RoleType.INLINE_TEXT_BOX, - attributes: {name: overflowText}, - }); - var nodeGroup = ParagraphUtils.buildNodeGroup( - [overflowTextNode], 0 /* index */, {clipOverflowWords: true}); + // Find the overflow text. + const overflowTextNode = root.find({ + role: chrome.automation.RoleType.INLINE_TEXT_BOX, + attributes: {name: overflowText}, + }); + var nodeGroup = ParagraphUtils.buildNodeGroup( + [overflowTextNode], 0 /* index */, {clipOverflowWords: true}); - // The output text should have the same length of the overflow text - // plus a space character at the end. - assertEquals(nodeGroup.text.length, overflowText.length + 1); - // The output text should only have space characters. - assertEquals(nodeGroup.text.replace(/ /g, '').length, 0); - }); + // The output text should have the same length of the overflow text + // plus a space character at the end. + assertEquals(nodeGroup.text.length, overflowText.length + 1); + // The output text should only have space characters. + assertEquals(nodeGroup.text.replace(/ /g, '').length, 0); }); TEST_F( 'SelectToSpeakParagraphOverflowTest', - 'ReplacesEntirelyOverflowTextWithSpace', function() { + 'ReplacesEntirelyOverflowTextWithSpace', async function() { const inputText = 'This text overflows entirely'; - this.runWithLoadedTree( - this.generateEntirelyOverflowText(inputText), function(root) { - const overflowText = root.find({ - role: chrome.automation.RoleType.INLINE_TEXT_BOX, - attributes: {name: inputText}, - }); - var nodeGroup = ParagraphUtils.buildNodeGroup( - [overflowText], 0 /* index */, {clipOverflowWords: true}); + const root = await this.runWithLoadedTree( + this.generateEntirelyOverflowText(inputText)); + const overflowText = root.find({ + role: chrome.automation.RoleType.INLINE_TEXT_BOX, + attributes: {name: inputText}, + }); + var nodeGroup = ParagraphUtils.buildNodeGroup( + [overflowText], 0 /* index */, {clipOverflowWords: true}); - // The output text should have the same length of the input text - // plus a space character at the end. - assertEquals(nodeGroup.text.length, inputText.length + 1); - // The output text should have zero non-empty character. - assertEquals(nodeGroup.text.replace(/ /g, '').length, 0); - }); + // The output text should have the same length of the input text + // plus a space character at the end. + assertEquals(nodeGroup.text.length, inputText.length + 1); + // The output text should have zero non-empty character. + assertEquals(nodeGroup.text.replace(/ /g, '').length, 0); }); -TEST_F('SelectToSpeakParagraphOverflowTest', 'OutputsVisibleText', function() { - const inputText = 'This text is visible'; - this.runWithLoadedTree(this.generateVisibleText(inputText), function(root) { - const visibleText = root.find({ - role: chrome.automation.RoleType.INLINE_TEXT_BOX, - attributes: {name: inputText}, - }); - var nodeGroup = ParagraphUtils.buildNodeGroup( - [visibleText], 0 /* index */, {clipOverflowWords: true}); +TEST_F( + 'SelectToSpeakParagraphOverflowTest', 'OutputsVisibleText', + async function() { + const inputText = 'This text is visible'; + const root = + await this.runWithLoadedTree(this.generateVisibleText(inputText)); + const visibleText = root.find({ + role: chrome.automation.RoleType.INLINE_TEXT_BOX, + attributes: {name: inputText}, + }); + var nodeGroup = ParagraphUtils.buildNodeGroup( + [visibleText], 0 /* index */, {clipOverflowWords: true}); - // The output text should have the same length of the input text plus a - // space character at the end. - assertEquals(nodeGroup.text.length, inputText.length + 1); - // The output text should have same non-empty words as the input text. - assertEquals(nodeGroup.text.replace(/ /g, ''), inputText.replace(/ /g, '')); - }); -}); + // The output text should have the same length of the input text plus a + // space character at the end. + assertEquals(nodeGroup.text.length, inputText.length + 1); + // The output text should have same non-empty words as the input text. + assertEquals( + nodeGroup.text.replace(/ /g, ''), inputText.replace(/ /g, '')); + }); TEST_F( 'SelectToSpeakParagraphOverflowTest', - 'DoesNotClipOverflowWordsWhenDisabled', function() { + 'DoesNotClipOverflowWordsWhenDisabled', async function() { const inputText = 'This text overflows entirely'; - this.runWithLoadedTree( - this.generateEntirelyOverflowText(inputText), function(root) { - const overflowText = root.find({ - role: chrome.automation.RoleType.INLINE_TEXT_BOX, - attributes: {name: inputText}, - }); - var nodeGroup = ParagraphUtils.buildNodeGroup( - [overflowText], 0 /* index */, {clipOverflowWords: false}); + const root = await this.runWithLoadedTree( + this.generateEntirelyOverflowText(inputText)); + const overflowText = root.find({ + role: chrome.automation.RoleType.INLINE_TEXT_BOX, + attributes: {name: inputText}, + }); + var nodeGroup = ParagraphUtils.buildNodeGroup( + [overflowText], 0 /* index */, {clipOverflowWords: false}); - // The output text should have the same length of the input text - // plus a space character at the end. - assertEquals(nodeGroup.text.length, inputText.length + 1); - // The output text should have same non-empty words as the input - // text. - assertEquals( - nodeGroup.text.replace(/ /g, ''), inputText.replace(/ /g, '')); - }); + // The output text should have the same length of the input text + // plus a space character at the end. + assertEquals(nodeGroup.text.length, inputText.length + 1); + // The output text should have same non-empty words as the input + // text. + assertEquals( + nodeGroup.text.replace(/ /g, ''), inputText.replace(/ /g, '')); });
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_enhanced_voices_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_enhanced_voices_test.js index ae41a7e5..3ac8409 100644 --- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_enhanced_voices_test.js +++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_enhanced_voices_test.js
@@ -66,133 +66,112 @@ TEST_F( 'SelectToSpeakEnhancedNetworkTtsVoicesTest', - 'EnablesVoicesIfConfirmedInDialog', function() { + 'EnablesVoicesIfConfirmedInDialog', async function() { this.confirmationDialogResponse_ = true; - this.runWithLoadedTree( + const root = await this.runWithLoadedTree( 'data:text/html;charset=utf-8,' + - '<p>This is some text</p>', - function(root) { - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - this.mockTts.setOnSpeechCallbacks([this.newCallback(function( - utterance) { - // Speech starts asynchronously. - assertEquals(this.confirmationDialogShowCount_, 1); - assertTrue( - selectToSpeak.prefsManager_.enhancedVoicesDialogShown()); - assertTrue( - selectToSpeak.prefsManager_.enhancedNetworkVoicesEnabled()); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'This is some text'); - })]); - const textNode = this.findTextNode(root, 'This is some text'); - const event = { - screenX: textNode.location.left + 1, - screenY: textNode.location.top + 1 - }; - this.triggerReadMouseSelectedText(event, event); - }); + '<p>This is some text</p>'); + assertFalse(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 0); + this.mockTts.setOnSpeechCallbacks([this.newCallback(function(utterance) { + // Speech starts asynchronously. + assertEquals(this.confirmationDialogShowCount_, 1); + assertTrue(selectToSpeak.prefsManager_.enhancedVoicesDialogShown()); + assertTrue(selectToSpeak.prefsManager_.enhancedNetworkVoicesEnabled()); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'This is some text'); + })]); + const textNode = this.findTextNode(root, 'This is some text'); + const event = { + screenX: textNode.location.left + 1, + screenY: textNode.location.top + 1 + }; + this.triggerReadMouseSelectedText(event, event); }); TEST_F( 'SelectToSpeakEnhancedNetworkTtsVoicesTest', - 'DisablesVoicesIfCanceledInDialog', function() { + 'DisablesVoicesIfCanceledInDialog', async function() { this.confirmationDialogResponse_ = false; - this.runWithLoadedTree( + const root = await this.runWithLoadedTree( 'data:text/html;charset=utf-8,' + - '<p>This is some text</p>', - function(root) { - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - this.mockTts.setOnSpeechCallbacks([this.newCallback(function( - utterance) { - // Speech starts asynchronously. - assertEquals(this.confirmationDialogShowCount_, 1); - assertTrue( - selectToSpeak.prefsManager_.enhancedVoicesDialogShown()); - assertFalse( - selectToSpeak.prefsManager_.enhancedNetworkVoicesEnabled()); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'This is some text'); - })]); - const textNode = this.findTextNode(root, 'This is some text'); - const event = { - screenX: textNode.location.left + 1, - screenY: textNode.location.top + 1 - }; - this.triggerReadMouseSelectedText(event, event); - }); + '<p>This is some text</p>'); + assertFalse(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 0); + this.mockTts.setOnSpeechCallbacks([this.newCallback(function(utterance) { + // Speech starts asynchronously. + assertEquals(this.confirmationDialogShowCount_, 1); + assertTrue(selectToSpeak.prefsManager_.enhancedVoicesDialogShown()); + assertFalse(selectToSpeak.prefsManager_.enhancedNetworkVoicesEnabled()); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'This is some text'); + })]); + const textNode = this.findTextNode(root, 'This is some text'); + const event = { + screenX: textNode.location.left + 1, + screenY: textNode.location.top + 1 + }; + this.triggerReadMouseSelectedText(event, event); }); TEST_F( 'SelectToSpeakEnhancedNetworkTtsVoicesTest', - 'DisablesVoicesIfDisallowedByPolicy', function() { + 'DisablesVoicesIfDisallowedByPolicy', async function() { this.confirmationDialogResponse_ = true; - this.runWithLoadedTree( + const root = await this.runWithLoadedTree( 'data:text/html;charset=utf-8,' + - '<p>This is some text</p>', - function(root) { - this.mockTts.setOnSpeechCallbacks([this.newCallback(function( - utterance) { - // Network voices are enabled initially because of the - // confirmation. - assertEquals(this.confirmationDialogShowCount_, 1); - assertTrue( - selectToSpeak.prefsManager_.enhancedVoicesDialogShown()); - assertTrue( - selectToSpeak.prefsManager_.enhancedNetworkVoicesEnabled()); + '<p>This is some text</p>'); + this.mockTts.setOnSpeechCallbacks([this.newCallback(function(utterance) { + // Network voices are enabled initially because of the + // confirmation. + assertEquals(this.confirmationDialogShowCount_, 1); + assertTrue(selectToSpeak.prefsManager_.enhancedVoicesDialogShown()); + assertTrue(selectToSpeak.prefsManager_.enhancedNetworkVoicesEnabled()); - // Sets the policy to disallow network voices. - this.setEnhancedNetworkVoicesPolicy(/* allowed= */ false); - assertFalse( - selectToSpeak.prefsManager_.enhancedNetworkVoicesEnabled()); - })]); - const textNode = this.findTextNode(root, 'This is some text'); - const event = { - screenX: textNode.location.left + 1, - screenY: textNode.location.top + 1 - }; - this.triggerReadMouseSelectedText(event, event); - }); + // Sets the policy to disallow network voices. + this.setEnhancedNetworkVoicesPolicy(/* allowed= */ false); + assertFalse(selectToSpeak.prefsManager_.enhancedNetworkVoicesEnabled()); + })]); + const textNode = this.findTextNode(root, 'This is some text'); + const event = { + screenX: textNode.location.left + 1, + screenY: textNode.location.top + 1 + }; + this.triggerReadMouseSelectedText(event, event); }); TEST_F( 'SelectToSpeakEnhancedNetworkTtsVoicesTest', - 'DisablesDialogIfDisallowedByPolicy', function() { + 'DisablesDialogIfDisallowedByPolicy', async function() { this.setEnhancedNetworkVoicesPolicy(/* allowed= */ false); - this.runWithLoadedTree( + const root = await this.runWithLoadedTree( 'data:text/html;charset=utf-8,' + - '<p>This is some text</p>', - function(root) { - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - this.mockTts.setOnSpeechCallbacks([this.newCallback(function( - utterance) { - // Dialog was not shown. - assertEquals(this.confirmationDialogShowCount_, 0); - assertFalse( - selectToSpeak.prefsManager_.enhancedVoicesDialogShown()); + '<p>This is some text</p>'); + assertFalse(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 0); + this.mockTts.setOnSpeechCallbacks([this.newCallback(function(utterance) { + // Dialog was not shown. + assertEquals(this.confirmationDialogShowCount_, 0); + assertFalse(selectToSpeak.prefsManager_.enhancedVoicesDialogShown()); - // Speech proceeds without enhanced voices. - assertFalse( - selectToSpeak.prefsManager_.enhancedNetworkVoicesEnabled()); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'This is some text'); - })]); - const textNode = this.findTextNode(root, 'This is some text'); - const event = { - screenX: textNode.location.left + 1, - screenY: textNode.location.top + 1 - }; - this.triggerReadMouseSelectedText(event, event); - }); + // Speech proceeds without enhanced voices. + assertFalse(selectToSpeak.prefsManager_.enhancedNetworkVoicesEnabled()); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'This is some text'); + })]); + const textNode = this.findTextNode(root, 'This is some text'); + const event = { + screenX: textNode.location.left + 1, + screenY: textNode.location.top + 1 + }; + this.triggerReadMouseSelectedText(event, event); });
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_keystroke_selection_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_keystroke_selection_test.js index 900e40c..6655e2b 100644 --- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_keystroke_selection_test.js +++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_keystroke_selection_test.js
@@ -39,8 +39,8 @@ * @param {string} expected The expected string that will be read, ignoring * extra whitespace, after this selection is triggered. */ - testSimpleTextAtKeystroke(text, anchorOffset, focusOffset, expected) { - this.testReadTextAtKeystroke('<p>' + text + '</p>', function(root) { + async testSimpleTextAtKeystroke(text, anchorOffset, focusOffset, expected) { + await this.testReadTextAtKeystroke('<p>' + text + '</p>', function(root) { // Set the document selection. This will fire the changed event // above, allowing us to do the keystroke and test that speech // occurred properly. @@ -69,22 +69,21 @@ * @param {string} expected The expected string that will be read, ignoring * extra whitespace, after this selection is triggered. */ - testReadTextAtKeystroke(contents, setFocusCallback, expected) { + async testReadTextAtKeystroke(contents, setFocusCallback, expected) { setFocusCallback = this.newCallback(setFocusCallback); - this.runWithLoadedTree(contents, function(root) { - // Add an event listener that will start the user interaction - // of the test once the selection is completed. - root.addEventListener( - 'documentSelectionChanged', this.newCallback(function(event) { - this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], expected); - }), - false); - setFocusCallback(root); - }); + const root = await this.runWithLoadedTree(contents); + // Add an event listener that will start the user interaction + // of the test once the selection is completed. + root.addEventListener( + 'documentSelectionChanged', this.newCallback(function(event) { + this.triggerReadSelectedText(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], expected); + }), + false); + setFocusCallback(root); } generateHtmlWithSelection(selectionCode, bodyHtml) { @@ -101,34 +100,34 @@ TEST_F( 'SelectToSpeakKeystrokeSelectionTest', 'SpeaksTextAtKeystrokeFullText', - function() { - this.testSimpleTextAtKeystroke( + async function() { + await this.testSimpleTextAtKeystroke( 'This is some text', 0, 17, 'This is some text'); }); TEST_F( 'SelectToSpeakKeystrokeSelectionTest', 'SpeaksTextAtKeystrokePartialText', - function() { - this.testSimpleTextAtKeystroke( + async function() { + await this.testSimpleTextAtKeystroke( 'This is some text', 0, 12, 'This is some'); }); TEST_F( 'SelectToSpeakKeystrokeSelectionTest', 'SpeaksTextAtKeystrokeSingleWord', - function() { - this.testSimpleTextAtKeystroke('This is some text', 8, 12, 'some'); + async function() { + await this.testSimpleTextAtKeystroke('This is some text', 8, 12, 'some'); }); TEST_F( 'SelectToSpeakKeystrokeSelectionTest', 'SpeaksTextAtKeystrokePartialWord', - function() { - this.testSimpleTextAtKeystroke('This is some text', 8, 10, 'so'); + async function() { + await this.testSimpleTextAtKeystroke('This is some text', 8, 10, 'so'); }); TEST_F( 'SelectToSpeakKeystrokeSelectionTest', 'SpeaksAcrossNodesAtKeystroke', - function() { - this.testReadTextAtKeystroke( + async function() { + await this.testReadTextAtKeystroke( '<p>This is some <b>bold</b> text</p><p>Second paragraph</p>', function(root) { const firstNode = this.findTextNode(root, 'This is some '); @@ -145,8 +144,8 @@ TEST_F( 'SelectToSpeakKeystrokeSelectionTest', - 'SpeaksAcrossNodesSelectedBackwardsAtKeystroke', function() { - this.testReadTextAtKeystroke( + 'SpeaksAcrossNodesSelectedBackwardsAtKeystroke', async function() { + await this.testReadTextAtKeystroke( '<p>This is some <b>bold</b> text</p><p>Second paragraph</p>', function(root) { // Set the document selection backwards in page order. @@ -164,7 +163,7 @@ TEST_F( 'SelectToSpeakKeystrokeSelectionTest', 'SpeakTextSurroundedByBrs', - function() { + async function() { // If you load this html and double-click on "Selected text", this is the // document selection that occurs -- into the second <br/> element. @@ -179,32 +178,31 @@ }); }; setFocusCallback = this.newCallback(setFocusCallback); - this.runWithLoadedTree( - '<br/><p>Selected text</p><br/>', function(root) { - // Add an event listener that will start the user interaction - // of the test once the selection is completed. - root.addEventListener( - 'documentSelectionChanged', this.newCallback(function(event) { - this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Selected text'); + const root = + await this.runWithLoadedTree('<br/><p>Selected text</p><br/>'); + // Add an event listener that will start the user interaction + // of the test once the selection is completed. + root.addEventListener( + 'documentSelectionChanged', this.newCallback(function(event) { + this.triggerReadSelectedText(); + assertTrue(this.mockTts.currentlySpeaking()); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Selected text'); - this.mockTts.finishPendingUtterance(); - if (this.mockTts.pendingUtterances().length === 1) { - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], ''); - } - }), - false); - setFocusCallback(root); - }); + this.mockTts.finishPendingUtterance(); + if (this.mockTts.pendingUtterances().length === 1) { + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], ''); + } + }), + false); + setFocusCallback(root); }); TEST_F( 'SelectToSpeakKeystrokeSelectionTest', 'StartsReadingAtFirstNodeWithText', - function() { - this.testReadTextAtKeystroke( + async function() { + await this.testReadTextAtKeystroke( '<div id="empty"></div><div><p>This is some <b>bold</b> text</p></div>', function(root) { const firstNode = @@ -222,8 +220,8 @@ TEST_F( 'SelectToSpeakKeystrokeSelectionTest', 'IgnoresTextMarkedNotUserSelectable', - function() { - this.testReadTextAtKeystroke( + async function() { + await this.testReadTextAtKeystroke( '<div><p>This is some <span style="user-select:none">unselectable</span> text</p></div>', function(root) { const firstNode = @@ -241,8 +239,8 @@ TEST_F( 'SelectToSpeakKeystrokeSelectionTest', - 'HandlesSingleImageCorrectlyWithAutomation', function() { - this.testReadTextAtKeystroke( + 'HandlesSingleImageCorrectlyWithAutomation', async function() { + await this.testReadTextAtKeystroke( '<img src="pipe.jpg" alt="one"/>', function(root) { const container = root.findAll({role: 'genericContainer'})[0]; chrome.automation.setDocumentSelection({ @@ -256,8 +254,8 @@ TEST_F( 'SelectToSpeakKeystrokeSelectionTest', - 'HandlesMultipleImagesCorrectlyWithAutomation', function() { - this.testReadTextAtKeystroke( + 'HandlesMultipleImagesCorrectlyWithAutomation', async function() { + await this.testReadTextAtKeystroke( '<img src="pipe.jpg" alt="one"/>' + '<img src="pipe.jpg" alt="two"/><img src="pipe.jpg" alt="three"/>', function(root) { @@ -274,104 +272,93 @@ TEST_F( 'SelectToSpeakKeystrokeSelectionTest', - 'HandlesMultipleImagesCorrectlyWithJS1', function() { + 'HandlesMultipleImagesCorrectlyWithJS1', async function() { // Using JS to do the selection instead of Automation, so that we can // ensure this is stable against changes in chrome.automation. const selectionCode = 'let body = document.getElementsByTagName("body")[0];' + 'range.setStart(body, 1);' + 'range.setEnd(body, 2);'; - this.runWithLoadedTree( - this.generateHtmlWithSelection( - selectionCode, - '<img id="one" src="pipe.jpg" alt="one"/>' + - '<img id="two" src="pipe.jpg" alt="two"/>' + - '<img id="three" src="pipe.jpg" alt="three"/>'), - function() { - this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'two'); - }); + await this.runWithLoadedTree(this.generateHtmlWithSelection( + selectionCode, + '<img id="one" src="pipe.jpg" alt="one"/>' + + '<img id="two" src="pipe.jpg" alt="two"/>' + + '<img id="three" src="pipe.jpg" alt="three"/>')); + this.triggerReadSelectedText(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'two'); }); TEST_F( 'SelectToSpeakKeystrokeSelectionTest', - 'HandlesMultipleImagesCorrectlyWithJS2', function() { + 'HandlesMultipleImagesCorrectlyWithJS2', async function() { const selectionCode = 'let body = document.getElementsByTagName("body")[0];' + 'range.setStart(body, 1);' + 'range.setEnd(body, 3);'; - this.runWithLoadedTree( - this.generateHtmlWithSelection( - selectionCode, - '<img id="one" src="pipe.jpg" alt="one"/>' + - '<img id="two" src="pipe.jpg" alt="two"/>' + - '<img id="three" src="pipe.jpg" alt="three"/>'), - function() { - this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'two three'); - }); + await this.runWithLoadedTree(this.generateHtmlWithSelection( + selectionCode, + '<img id="one" src="pipe.jpg" alt="one"/>' + + '<img id="two" src="pipe.jpg" alt="two"/>' + + '<img id="three" src="pipe.jpg" alt="three"/>')); + this.triggerReadSelectedText(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'two three'); }); TEST_F( 'SelectToSpeakKeystrokeSelectionTest', 'TextFieldFullySelected', - function() { + async function() { const selectionCode = 'let p = document.getElementsByTagName("p")[0];' + 'let body = document.getElementsByTagName("body")[0];' + 'range.setStart(p, 0);' + 'range.setEnd(body, 2);'; - this.runWithLoadedTree( - this.generateHtmlWithSelection( - selectionCode, - '<p>paragraph</p>' + - '<input type="text" value="text field">'), - function() { - this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'paragraph'); + await this.runWithLoadedTree(this.generateHtmlWithSelection( + selectionCode, + '<p>paragraph</p>' + + '<input type="text" value="text field">')); + this.triggerReadSelectedText(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'paragraph'); - this.mockTts.finishPendingUtterance(); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'text field'); - }); + this.mockTts.finishPendingUtterance(); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'text field'); }); TEST_F( 'SelectToSpeakKeystrokeSelectionTest', 'TwoTextFieldsFullySelected', - function() { + async function() { const selectionCode = 'let body = document.getElementsByTagName("body")[0];' + 'range.setStart(body, 0);' + 'range.setEnd(body, 2);'; - this.runWithLoadedTree( - this.generateHtmlWithSelection( - selectionCode, - '<input type="text" value="one"></input><textarea cols="5">two three</textarea>'), - function() { - this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'one'); + await this.runWithLoadedTree(this.generateHtmlWithSelection( + selectionCode, + '<input type="text" value="one"></input>' + + '<textarea cols="5">two three</textarea>')); + this.triggerReadSelectedText(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'one'); - this.mockTts.finishPendingUtterance(); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'two three'); - }); + this.mockTts.finishPendingUtterance(); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'two three'); }); TEST_F( 'SelectToSpeakKeystrokeSelectionTest', 'TextInputPartiallySelected', - function() { + async function() { const html = '<script type="text/javascript">' + 'function doSelection() {' + 'let input = document.getElementById("input");' + @@ -382,18 +369,17 @@ '<body onload="doSelection()">' + '<input id="input" type="text" value="text field"></input>' + '</body>'; - this.runWithLoadedTree(html, function() { - this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'field'); - }); + await this.runWithLoadedTree(html); + this.triggerReadSelectedText(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'field'); }); TEST_F( 'SelectToSpeakKeystrokeSelectionTest', 'TextAreaPartiallySelected', - function() { + async function() { const html = '<script type="text/javascript">' + 'function doSelection() {' + 'let input = document.getElementById("input");' + @@ -404,52 +390,49 @@ '<body onload="doSelection()">' + '<textarea id="input" type="text" cols="10">first line second line</textarea>' + '</body>'; - this.runWithLoadedTree(html, function() { - this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'line second'); - }); + await this.runWithLoadedTree(html); + this.triggerReadSelectedText(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'line second'); }); -TEST_F('SelectToSpeakKeystrokeSelectionTest', 'HandlesTextWithBr', function() { - const selectionCode = 'let body = document.getElementsByTagName("body")[0];' + - 'range.setStart(body, 0);' + - 'range.setEnd(body, 3);'; - this.runWithLoadedTree( - this.generateHtmlWithSelection(selectionCode, 'Test<br/><br/>Unread'), - function() { - this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Test'); - }); -}); +TEST_F( + 'SelectToSpeakKeystrokeSelectionTest', 'HandlesTextWithBr', + async function() { + const selectionCode = + 'let body = document.getElementsByTagName("body")[0];' + + 'range.setStart(body, 0);' + + 'range.setEnd(body, 3);'; + await this.runWithLoadedTree(this.generateHtmlWithSelection( + selectionCode, 'Test<br/><br/>Unread')); + this.triggerReadSelectedText(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Test'); + }); TEST_F( 'SelectToSpeakKeystrokeSelectionTest', 'HandlesTextWithBrComplex', - function() { + async function() { const selectionCode = 'let p = document.getElementsByTagName("p")[0];' + 'let body = document.getElementsByTagName("body")[0];' + 'range.setStart(p, 0);' + 'range.setEnd(body, 2);'; - this.runWithLoadedTree( - this.generateHtmlWithSelection( - selectionCode, '<p>Some text</p><br/><br/>Unread'), - function() { - this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Some text'); - }); + await this.runWithLoadedTree(this.generateHtmlWithSelection( + selectionCode, '<p>Some text</p><br/><br/>Unread')); + this.triggerReadSelectedText(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Some text'); }); TEST_F( 'SelectToSpeakKeystrokeSelectionTest', 'HandlesTextWithBrAfterText1', - function() { + async function() { // A bug was that if the selection was on the rootWebArea, paragraphs were // not counted correctly. The more divs and paragraphs before the // selection, the further off it got. @@ -457,21 +440,18 @@ 'let body = document.getElementsByTagName("body")[0];' + 'range.setStart(p, 1);' + 'range.setEnd(body, 2);'; - this.runWithLoadedTree( - this.generateHtmlWithSelection( - selectionCode, '<p>Unread</p><p>Some text</p><br/>Unread'), - function() { - this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Some text'); - }); + await this.runWithLoadedTree(this.generateHtmlWithSelection( + selectionCode, '<p>Unread</p><p>Some text</p><br/>Unread')); + this.triggerReadSelectedText(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Some text'); }); TEST_F( 'SelectToSpeakKeystrokeSelectionTest', 'HandlesTextWithBrAfterText2', - function() { + async function() { // A bug was that if the selection was on the rootWebArea, paragraphs were // not counted correctly. The more divs and paragraphs before the // selection, the further off it got. @@ -479,69 +459,61 @@ 'let body = document.getElementsByTagName("body")[0];' + 'range.setStart(p, 1);' + 'range.setEnd(body, 3);'; - this.runWithLoadedTree( - this.generateHtmlWithSelection( - selectionCode, '<p>Unread</p><p>Some text</p><br/>Unread'), - function() { - this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - assertTrue(this.mockTts.pendingUtterances().length > 0); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Some text'); + await this.runWithLoadedTree(this.generateHtmlWithSelection( + selectionCode, '<p>Unread</p><p>Some text</p><br/>Unread')); + this.triggerReadSelectedText(); + assertTrue(this.mockTts.currentlySpeaking()); + assertTrue(this.mockTts.pendingUtterances().length > 0); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Some text'); - this.mockTts.finishPendingUtterance(); - if (this.mockTts.pendingUtterances().length > 0) { - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], ''); - } - }); + this.mockTts.finishPendingUtterance(); + if (this.mockTts.pendingUtterances().length > 0) { + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], ''); + } }); TEST_F( - 'SelectToSpeakKeystrokeSelectionTest', 'HandlesTextAreaAndBrs', function() { + 'SelectToSpeakKeystrokeSelectionTest', 'HandlesTextAreaAndBrs', + async function() { const selectionCode = 'let body = document.getElementsByTagName("body")[0];' + 'range.setStart(body, 1);' + 'range.setEnd(body, 4);'; - this.runWithLoadedTree( - this.generateHtmlWithSelection( - selectionCode, - '<br/><br/><textarea>Some text</textarea><br/><br/>Unread'), - function() { - this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Some text'); - }); + await this.runWithLoadedTree(this.generateHtmlWithSelection( + selectionCode, + '<br/><br/><textarea>Some text</textarea><br/><br/>Unread')); + this.triggerReadSelectedText(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Some text'); }); TEST_F( 'SelectToSpeakKeystrokeSelectionTest', 'textFieldWithComboBoxSimple', - function() { + async function() { const selectionCode = 'let body = document.getElementsByTagName("body")[0];' + 'range.setStart(body, 0);' + 'range.setEnd(body, 1);'; - this.runWithLoadedTree( - this.generateHtmlWithSelection( - selectionCode, - '<input list="list" value="one"></label><datalist id="list">' + - '<option value="one"></datalist>'), - function() { - this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'one'); - }); + await this.runWithLoadedTree(this.generateHtmlWithSelection( + selectionCode, + '<input list="list" value="one"></label><datalist id="list">' + + '<option value="one"></datalist>')); + this.triggerReadSelectedText(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'one'); }); // TODO(katie): It doesn't seem possible to programatically specify a range that // selects only part of the text in a combo box. TEST_F( 'SelectToSpeakKeystrokeSelectionTest', 'contentEditableInternallySelected', - function() { + async function() { const html = '<script type="text/javascript">' + 'function doSelection() {' + 'let input = document.getElementById("input");' + @@ -559,145 +531,131 @@ '<body onload="doSelection()">' + '<div id="input" contenteditable><p>a b c</p><p>d e f</p></div>' + '</body>'; - this.runWithLoadedTree(html, function() { - this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'b c'); + await this.runWithLoadedTree(html); + this.triggerReadSelectedText(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'b c'); - this.mockTts.finishPendingUtterance(); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'd e'); - }); + this.mockTts.finishPendingUtterance(); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'd e'); }); TEST_F( 'SelectToSpeakKeystrokeSelectionTest', 'contentEditableExternallySelected', - function() { + async function() { const selectionCode = 'let body = document.getElementsByTagName("body")[0];' + 'range.setStart(body, 1);' + 'range.setEnd(body, 2);'; - this.runWithLoadedTree( - this.generateHtmlWithSelection( - selectionCode, - 'Unread <div id="input" contenteditable><p>a b c</p><p>d e f</p></div>' + - ' Unread'), - function() { - this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'a b c'); + await this.runWithLoadedTree(this.generateHtmlWithSelection( + selectionCode, + 'Unread <div id="input" contenteditable><p>a b c</p><p>d e f</p>' + + '</div> Unread')); + this.triggerReadSelectedText(); + assertTrue(this.mockTts.currentlySpeaking()); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'a b c'); - this.mockTts.finishPendingUtterance(); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'd e f'); - }); + this.mockTts.finishPendingUtterance(); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'd e f'); }); TEST_F( - 'SelectToSpeakKeystrokeSelectionTest', 'ReordersSvgSingleLine', function() { + 'SelectToSpeakKeystrokeSelectionTest', 'ReordersSvgSingleLine', + async function() { const selectionCode = 'let body = document.getElementsByTagName("body")[0];' + 'range.setStart(body, 0);' + 'range.setEnd(body, 1);'; - this.runWithLoadedTree( - this.generateHtmlWithSelection( - selectionCode, - '<svg viewBox="0 0 240 80" xmlns="http://www.w3.org/2000/svg">' + - ' <text x="65" y="55">Grumpy!</text>' + - ' <text x="20" y="35">My</text>' + - ' <text x="40" y="35">cat</text>' + - ' <text x="55" y="55">is</text>' + - '</svg>'), - function() { - this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'My cat is Grumpy!'); - }); + await this.runWithLoadedTree(this.generateHtmlWithSelection( + selectionCode, + '<svg viewBox="0 0 240 80" xmlns="http://www.w3.org/2000/svg">' + + ' <text x="65" y="55">Grumpy!</text>' + + ' <text x="20" y="35">My</text>' + + ' <text x="40" y="35">cat</text>' + + ' <text x="55" y="55">is</text>' + + '</svg>')); + this.triggerReadSelectedText(); + assertTrue(this.mockTts.currentlySpeaking()); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'My cat is Grumpy!'); }); TEST_F( - 'SelectToSpeakKeystrokeSelectionTest', 'ReordersSvgWithGroups', function() { + 'SelectToSpeakKeystrokeSelectionTest', 'ReordersSvgWithGroups', + async function() { const selectionCode = 'let body = document.getElementsByTagName("body")[0];' + 'range.setStart(body, 0);' + 'range.setEnd(body, 1);'; - this.runWithLoadedTree( - this.generateHtmlWithSelection( - selectionCode, - '<svg viewBox="0 0 240 80" xmlns="http://www.w3.org/2000/svg">' + - ' <g>' + - ' <text x="65" y="0">Column 2, Text 1</text>' + - ' <text x="65" y="50">Column 2, Text 2</text>' + - ' </g>' + - ' <g>' + - ' <text x="0" y="50">Column 1, Text 2</text>' + - ' <text x="0" y="0">Column 1, Text 1</text>' + - ' </g>' + - '</svg>'), - function() { - this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Column 1, Text 1'); + await this.runWithLoadedTree(this.generateHtmlWithSelection( + selectionCode, + '<svg viewBox="0 0 240 80" xmlns="http://www.w3.org/2000/svg">' + + ' <g>' + + ' <text x="65" y="0">Column 2, Text 1</text>' + + ' <text x="65" y="50">Column 2, Text 2</text>' + + ' </g>' + + ' <g>' + + ' <text x="0" y="50">Column 1, Text 2</text>' + + ' <text x="0" y="0">Column 1, Text 1</text>' + + ' </g>' + + '</svg>')); + this.triggerReadSelectedText(); + assertTrue(this.mockTts.currentlySpeaking()); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Column 1, Text 1'); - this.mockTts.finishPendingUtterance(); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Column 1, Text 2'); + this.mockTts.finishPendingUtterance(); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Column 1, Text 2'); - this.mockTts.finishPendingUtterance(); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Column 2, Text 1'); + this.mockTts.finishPendingUtterance(); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Column 2, Text 1'); - this.mockTts.finishPendingUtterance(); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Column 2, Text 2'); - }); + this.mockTts.finishPendingUtterance(); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Column 2, Text 2'); }); TEST_F( 'SelectToSpeakKeystrokeSelectionTest', - 'NonReorderedSvgPreservesSelectionStartEnd', function() { + 'NonReorderedSvgPreservesSelectionStartEnd', async function() { const selectionCode = 'const t1 = document.getElementById("t1");' + 'const t2 = document.getElementById("t2");' + 'range.setStart(t1.childNodes[0], 3);' + 'range.setEnd(t2.childNodes[0], 2);'; - this.runWithLoadedTree( - this.generateHtmlWithSelection( - selectionCode, - '<svg viewBox="0 0 240 80" xmlns="http://www.w3.org/2000/svg">' + - ' <text id="t1" x="0" y="55">My cat</text>' + - ' <text id="t2" x="100" y="55">is Grumpy!</text>' + - '</svg>'), - function() { - this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'cat is'); - }); + await this.runWithLoadedTree(this.generateHtmlWithSelection( + selectionCode, + '<svg viewBox="0 0 240 80" xmlns="http://www.w3.org/2000/svg">' + + ' <text id="t1" x="0" y="55">My cat</text>' + + ' <text id="t2" x="100" y="55">is Grumpy!</text>' + + '</svg>')); + this.triggerReadSelectedText(); + assertTrue(this.mockTts.currentlySpeaking()); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'cat is'); }); TEST_F( 'SelectToSpeakKeystrokeSelectionTest', - 'ReorderedSvgIgnoresSelectionStartEnd', function() { + 'ReorderedSvgIgnoresSelectionStartEnd', async function() { const selectionCode = 'const t1 = document.getElementById("t1");' + 'const t2 = document.getElementById("t2");' + 'range.setStart(t1.childNodes[0], 3);' + 'range.setEnd(t2.childNodes[0], 2);'; - this.runWithLoadedTree( - this.generateHtmlWithSelection( - selectionCode, - '<svg viewBox="0 0 240 80" xmlns="http://www.w3.org/2000/svg">' + - ' <text id="t1" x="100" y="55">is Grumpy!</text>' + - ' <text id="t2" x="0" y="55">My cat</text>' + - '</svg>'), - function() { - this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'My cat is Grumpy!'); - }); + await this.runWithLoadedTree(this.generateHtmlWithSelection( + selectionCode, + '<svg viewBox="0 0 240 80" xmlns="http://www.w3.org/2000/svg">' + + ' <text id="t1" x="100" y="55">is Grumpy!</text>' + + ' <text id="t2" x="0" y="55">My cat</text>' + + '</svg>')); + this.triggerReadSelectedText(); + assertTrue(this.mockTts.currentlySpeaking()); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'My cat is Grumpy!'); });
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_mouse_selection_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_mouse_selection_test.js index 8cbdf97..be8bc07 100644 --- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_mouse_selection_test.js +++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_mouse_selection_test.js
@@ -46,191 +46,176 @@ } }; -TEST_F('SelectToSpeakMouseSelectionTest', 'SpeaksNodeWhenClicked', function() { - this.runWithLoadedTree( - 'data:text/html;charset=utf-8,' + - '<p>This is some text</p>', - function(root) { - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - this.mockTts.setOnSpeechCallbacks( - [this.newCallback(function(utterance) { - // Speech starts asynchronously. - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'This is some text'); - })]); - const textNode = this.findTextNode(root, 'This is some text'); - const event = { - screenX: textNode.location.left + 1, - screenY: textNode.location.top + 1 - }; - this.triggerReadMouseSelectedText(event, event); - }); -}); +TEST_F( + 'SelectToSpeakMouseSelectionTest', 'SpeaksNodeWhenClicked', + async function() { + const root = await this.runWithLoadedTree( + 'data:text/html;charset=utf-8,' + + '<p>This is some text</p>'); + assertFalse(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 0); + this.mockTts.setOnSpeechCallbacks([this.newCallback(function(utterance) { + // Speech starts asynchronously. + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'This is some text'); + })]); + const textNode = this.findTextNode(root, 'This is some text'); + const event = { + screenX: textNode.location.left + 1, + screenY: textNode.location.top + 1 + }; + this.triggerReadMouseSelectedText(event, event); + }); TEST_F( 'SelectToSpeakMouseSelectionTest', 'SpeaksMultipleNodesWhenDragged', - function() { - this.runWithLoadedTree( + async function() { + const root = await this.runWithLoadedTree( 'data:text/html;charset=utf-8,' + - '<p>This is some text</p><p>This is some more text</p>', - function(root) { - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - this.mockTts.setOnSpeechCallbacks([ - this.newCallback(function(utterance) { - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - utterance, 'This is some text'); - this.mockTts.finishPendingUtterance(); - }), - this.newCallback(function(utterance) { - this.assertEqualsCollapseWhitespace( - utterance, 'This is some more text'); - }) - ]); - const firstNode = this.findTextNode(root, 'This is some text'); - const downEvent = { - screenX: firstNode.location.left + 1, - screenY: firstNode.location.top + 1 - }; - const lastNode = this.findTextNode(root, 'This is some more text'); - const upEvent = { - screenX: lastNode.location.left + lastNode.location.width, - screenY: lastNode.location.top + lastNode.location.height - }; - this.triggerReadMouseSelectedText(downEvent, upEvent); - }); + '<p>This is some text</p><p>This is some more text</p>'); + assertFalse(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 0); + this.mockTts.setOnSpeechCallbacks([ + this.newCallback(function(utterance) { + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace(utterance, 'This is some text'); + this.mockTts.finishPendingUtterance(); + }), + this.newCallback(function(utterance) { + this.assertEqualsCollapseWhitespace( + utterance, 'This is some more text'); + }) + ]); + const firstNode = this.findTextNode(root, 'This is some text'); + const downEvent = { + screenX: firstNode.location.left + 1, + screenY: firstNode.location.top + 1 + }; + const lastNode = this.findTextNode(root, 'This is some more text'); + const upEvent = { + screenX: lastNode.location.left + lastNode.location.width, + screenY: lastNode.location.top + lastNode.location.height + }; + this.triggerReadMouseSelectedText(downEvent, upEvent); }); TEST_F( 'SelectToSpeakMouseSelectionTest', 'SpeaksAcrossNodesInAParagraph', - function() { - this.runWithLoadedTree( + async function() { + const root = await this.runWithLoadedTree( 'data:text/html;charset=utf-8,' + - '<p style="width:200px">This is some text in a paragraph that wraps. ' + - '<i>Italic text</i></p>', - function(root) { - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - this.mockTts.setOnSpeechCallbacks( - [this.newCallback(function(utterance) { - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - utterance, - 'This is some text in a paragraph that wraps. ' + - 'Italic text'); - })]); - const firstNode = this.findTextNode( - root, 'This is some text in a paragraph that wraps. '); - const downEvent = { - screenX: firstNode.location.left + 1, - screenY: firstNode.location.top + 1 - }; - const lastNode = this.findTextNode(root, 'Italic text'); - const upEvent = { - screenX: lastNode.location.left + lastNode.location.width, - screenY: lastNode.location.top + lastNode.location.height - }; - this.triggerReadMouseSelectedText(downEvent, upEvent); - }); + '<p style="width:200px">This is some text in a paragraph that ' + + 'wraps. <i>Italic text</i></p>'); + assertFalse(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 0); + this.mockTts.setOnSpeechCallbacks([this.newCallback(function(utterance) { + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + utterance, + 'This is some text in a paragraph that wraps. ' + + 'Italic text'); + })]); + const firstNode = this.findTextNode( + root, 'This is some text in a paragraph that wraps. '); + const downEvent = { + screenX: firstNode.location.left + 1, + screenY: firstNode.location.top + 1 + }; + const lastNode = this.findTextNode(root, 'Italic text'); + const upEvent = { + screenX: lastNode.location.left + lastNode.location.width, + screenY: lastNode.location.top + lastNode.location.height + }; + this.triggerReadMouseSelectedText(downEvent, upEvent); }); TEST_F( 'SelectToSpeakMouseSelectionTest', 'SpeaksNodeAfterTrayTapAndMouseClick', - function() { - this.runWithLoadedTree( + async function() { + const root = await this.runWithLoadedTree( 'data:text/html;charset=utf-8,' + - '<p>This is some text</p>', - function(root) { - assertFalse(this.mockTts.currentlySpeaking()); - this.mockTts.setOnSpeechCallbacks( - [this.newCallback(function(utterance) { - // Speech starts asynchronously. - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'This is some text'); - })]); + '<p>This is some text</p>'); + assertFalse(this.mockTts.currentlySpeaking()); + this.mockTts.setOnSpeechCallbacks([this.newCallback(function(utterance) { + // Speech starts asynchronously. + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'This is some text'); + })]); - const textNode = this.findTextNode(root, 'This is some text'); - const event = { - screenX: textNode.location.left + 1, - screenY: textNode.location.top + 1 - }; - // A state change request should shift us into 'selecting' state - // from 'inactive'. - const desktop = root.parent.root; - this.tapTrayButton(desktop, () => { - selectToSpeak.fireMockMouseDownEvent(event); - selectToSpeak.fireMockMouseUpEvent(event); - }); - }); + const textNode = this.findTextNode(root, 'This is some text'); + const event = { + screenX: textNode.location.left + 1, + screenY: textNode.location.top + 1 + }; + // A state change request should shift us into 'selecting' state + // from 'inactive'. + const desktop = root.parent.root; + this.tapTrayButton(desktop, () => { + selectToSpeak.fireMockMouseDownEvent(event); + selectToSpeak.fireMockMouseUpEvent(event); + }); }); TEST_F( 'SelectToSpeakMouseSelectionTest', 'CancelsSelectionModeWithStateChange', - function() { - this.runWithLoadedTree( + async function() { + const root = await this.runWithLoadedTree( 'data:text/html;charset=utf-8,' + - '<p>This is some text</p>', - function(root) { - const textNode = this.findTextNode(root, 'This is some text'); - const event = { - screenX: textNode.location.left + 1, - screenY: textNode.location.top + 1 - }; - // A state change request should shift us into 'selecting' state - // from 'inactive'. - const desktop = root.parent.root; - this.tapTrayButton(desktop, () => { - selectToSpeak.fireMockMouseDownEvent(event); - assertEquals(SelectToSpeakState.SELECTING, selectToSpeak.state_); + '<p>This is some text</p>'); + const textNode = this.findTextNode(root, 'This is some text'); + const event = { + screenX: textNode.location.left + 1, + screenY: textNode.location.top + 1 + }; + // A state change request should shift us into 'selecting' state + // from 'inactive'. + const desktop = root.parent.root; + this.tapTrayButton(desktop, () => { + selectToSpeak.fireMockMouseDownEvent(event); + assertEquals(SelectToSpeakState.SELECTING, selectToSpeak.state_); - // Another state change puts us back in 'inactive'. - this.tapTrayButton(desktop, () => { - assertEquals(SelectToSpeakState.INACTIVE, selectToSpeak.state_); - }); - }); - }); + // Another state change puts us back in 'inactive'. + this.tapTrayButton(desktop, () => { + assertEquals(SelectToSpeakState.INACTIVE, selectToSpeak.state_); + }); + }); }); TEST_F( - 'SelectToSpeakMouseSelectionTest', 'CancelsSpeechWithTrayTap', function() { - this.runWithLoadedTree( + 'SelectToSpeakMouseSelectionTest', 'CancelsSpeechWithTrayTap', + async function() { + const root = await this.runWithLoadedTree( 'data:text/html;charset=utf-8,' + - '<p>This is some text</p>', - function(root) { - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - this.mockTts.setOnSpeechCallbacks( - [this.newCallback(function(utterance) { - // Speech starts asynchronously. - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'This is some text'); + '<p>This is some text</p>'); + assertFalse(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 0); + this.mockTts.setOnSpeechCallbacks([this.newCallback(function(utterance) { + // Speech starts asynchronously. + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'This is some text'); - // Cancel speech and make sure state resets to INACTIVE. - const desktop = root.parent.root; - this.tapTrayButton(desktop, () => { - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - assertEquals( - SelectToSpeakState.INACTIVE, selectToSpeak.state_); - }); - })]); - const textNode = this.findTextNode(root, 'This is some text'); - const event = { - screenX: textNode.location.left + 1, - screenY: textNode.location.top + 1 - }; - this.triggerReadMouseSelectedText(event, event); - }); + // Cancel speech and make sure state resets to INACTIVE. + const desktop = root.parent.root; + this.tapTrayButton(desktop, () => { + assertFalse(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 0); + assertEquals(SelectToSpeakState.INACTIVE, selectToSpeak.state_); + }); + })]); + const textNode = this.findTextNode(root, 'This is some text'); + const event = { + screenX: textNode.location.left + 1, + screenY: textNode.location.top + 1 + }; + this.triggerReadMouseSelectedText(event, event); }); // TODO(crbug.com/1177140) Re-enable test
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_navigation_control_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_navigation_control_test.js index 46b410a..2af2df5 100644 --- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_navigation_control_test.js +++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_navigation_control_test.js
@@ -76,76 +76,76 @@ TEST_F( 'SelectToSpeakNavigationControlTest', 'NavigatesToNextParagraph', - function() { + async function() { const bodyHtml = ` <p id="p1">Paragraph 1</p> <p id="p2">Paragraph 2</p>' `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml), () => { - this.triggerReadSelectedText(); + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('p1', bodyHtml)); + this.triggerReadSelectedText(); - // Speaks first paragraph - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph 1'); + // Speaks first paragraph + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Paragraph 1'); - // TODO(joelriley@google.com): Figure out a better way to trigger - // the actual floating panel button rather than calling private - // method directly. - selectToSpeak.onNextParagraphRequested(); + // TODO(joelriley@google.com): Figure out a better way to trigger + // the actual floating panel button rather than calling private + // method directly. + selectToSpeak.onNextParagraphRequested(); - // Speaks second paragraph - this.waitOneEventLoop(() => { - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph 2'); - }); - }); + // Speaks second paragraph + this.waitOneEventLoop(() => { + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Paragraph 2'); + }); }); TEST_F( 'SelectToSpeakNavigationControlTest', 'NavigatesToPreviousParagraph', - function() { + async function() { const bodyHtml = ` <p id="p1">Paragraph 1</p> <p id="p2">Paragraph 2</p>' `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p2', bodyHtml), () => { - this.triggerReadSelectedText(); + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('p2', bodyHtml)); + this.triggerReadSelectedText(); - // Speaks first paragraph - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph 2'); + // Speaks first paragraph + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Paragraph 2'); - // TODO(joelriley@google.com): Figure out a better way to trigger - // the actual floating panel button rather than calling private - // method directly. - selectToSpeak.onPreviousParagraphRequested(); + // TODO(joelriley@google.com): Figure out a better way to trigger + // the actual floating panel button rather than calling private + // method directly. + selectToSpeak.onPreviousParagraphRequested(); - // Speaks second paragraph - this.waitOneEventLoop(() => { - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph 1'); - }); - }); + // Speaks second paragraph + this.waitOneEventLoop(() => { + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Paragraph 1'); + }); }); TEST_F( - 'SelectToSpeakNavigationControlTest', 'ReadsParagraphOnClick', function() { + 'SelectToSpeakNavigationControlTest', 'ReadsParagraphOnClick', + async function() { const bodyHtml = ` <p id="p1">Sentence <span>one</span>. Sentence two.</p> <p id="p2">Paragraph <span>two</span></p>' `; - this.runWithLoadedTree(bodyHtml, (root) => { - this.mockTts.setOnSpeechCallbacks([this.newCallback((utterance) => { + const root = await this.runWithLoadedTree(bodyHtml); + this.mockTts.setOnSpeechCallbacks([ + this.newCallback((utterance) => { // Speech for first click. assertTrue(this.mockTts.currentlySpeaking()); assertEquals(this.mockTts.pendingUtterances().length, 1); @@ -168,120 +168,116 @@ screenY: textNode2.location.top + 1 }; this.triggerReadMouseSelectedText(mouseEvent2, mouseEvent2); - })]); + }) + ]); - // Click on node in first paragraph. - const textNode1 = this.findTextNode(root, 'one'); - const event1 = { - screenX: textNode1.location.left + 1, - screenY: textNode1.location.top + 1 - }; - this.triggerReadMouseSelectedText(event1, event1); - }); + // Click on node in first paragraph. + const textNode1 = this.findTextNode(root, 'one'); + const event1 = { + screenX: textNode1.location.left + 1, + screenY: textNode1.location.top + 1 + }; + this.triggerReadMouseSelectedText(event1, event1); }); TEST_F( 'SelectToSpeakNavigationControlTest', 'PauseResumeWithinTheSentence', - function() { + async function() { const bodyHtml = ` <p id="p1">First sentence. Second sentence. Third sentence.</p>' `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml), () => { - this.triggerReadSelectedText(); + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('p1', bodyHtml)); + this.triggerReadSelectedText(); - // Speaks until the second word of the second sentence. - this.mockTts.speakUntilCharIndex(23); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], - 'First sentence. Second sentence. Third sentence.'); + // Speaks until the second word of the second sentence. + this.mockTts.speakUntilCharIndex(23); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], + 'First sentence. Second sentence. Third sentence.'); - // Hitting pause will stop the current TTS. - selectToSpeak.onPauseRequested(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); + // Hitting pause will stop the current TTS. + selectToSpeak.onPauseRequested(); + assertFalse(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 0); - // Hitting resume will start from the remaining content of the - // second sentence. - selectToSpeak.onResumeRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], - 'sentence. Third sentence.'); - }); + // Hitting resume will start from the remaining content of the + // second sentence. + selectToSpeak.onResumeRequested(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'sentence. Third sentence.'); }); TEST_F( 'SelectToSpeakNavigationControlTest', 'PauseResumeAtTheBeginningOfSentence', - function() { + async function() { const bodyHtml = ` <p id="p1">First sentence. Second sentence. Third sentence.</p>' `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml), () => { - this.triggerReadSelectedText(); + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('p1', bodyHtml)); + this.triggerReadSelectedText(); - // Speaks until the third sentence. - this.mockTts.speakUntilCharIndex(33); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], - 'First sentence. Second sentence. Third sentence.'); + // Speaks until the third sentence. + this.mockTts.speakUntilCharIndex(33); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], + 'First sentence. Second sentence. Third sentence.'); - // Hitting pause will stop the current TTS. - selectToSpeak.onPauseRequested(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); + // Hitting pause will stop the current TTS. + selectToSpeak.onPauseRequested(); + assertFalse(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 0); - // Hitting resume will start from the beginning of the third - // sentence. - selectToSpeak.onResumeRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Third sentence.'); - }); + // Hitting resume will start from the beginning of the third + // sentence. + selectToSpeak.onResumeRequested(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Third sentence.'); }); TEST_F( 'SelectToSpeakNavigationControlTest', - 'PauseResumeAtTheBeginningOfParagraph', function() { + 'PauseResumeAtTheBeginningOfParagraph', async function() { const bodyHtml = ` <p id="p1">first sentence.</p>' `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml), () => { - this.triggerReadSelectedText(); + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('p1', bodyHtml)); + this.triggerReadSelectedText(); - // Speaks until the second word. - this.mockTts.speakUntilCharIndex(6); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'first sentence.'); + // Speaks until the second word. + this.mockTts.speakUntilCharIndex(6); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'first sentence.'); - // Hitting pause will stop the current TTS. - selectToSpeak.onPauseRequested(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); + // Hitting pause will stop the current TTS. + selectToSpeak.onPauseRequested(); + assertFalse(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 0); - // Hitting resume will start from the remaining content of the - // paragraph. - selectToSpeak.onResumeRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'sentence.'); - }); + // Hitting resume will start from the remaining content of the + // paragraph. + selectToSpeak.onResumeRequested(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'sentence.'); }); TEST_F( 'SelectToSpeakNavigationControlTest', - 'PauseResumeInTheMiddleOfMultiParagraphs', function() { + 'PauseResumeInTheMiddleOfMultiParagraphs', async function() { const bodyHtml = ` <span id='s1'> <p>Paragraph one.</p> @@ -289,47 +285,46 @@ <p>Paragraph three.</p> </span>' `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('s1', bodyHtml), () => { - this.triggerReadSelectedText(); + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('s1', bodyHtml)); + this.triggerReadSelectedText(); - // Speaks until the second word. - this.mockTts.speakUntilCharIndex(10); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph one.'); + // Speaks until the second word. + this.mockTts.speakUntilCharIndex(10); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Paragraph one.'); - // Hitting pause will stop the current TTS. - selectToSpeak.onPauseRequested(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); + // Hitting pause will stop the current TTS. + selectToSpeak.onPauseRequested(); + assertFalse(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 0); - // Hitting resume will start from the remaining content of the - // paragraph. - selectToSpeak.onResumeRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'one.'); + // Hitting resume will start from the remaining content of the + // paragraph. + selectToSpeak.onResumeRequested(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'one.'); - // Keep reading will finish all the content. - this.mockTts.finishPendingUtterance(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph two.'); - this.mockTts.finishPendingUtterance(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph three.'); - }); + // Keep reading will finish all the content. + this.mockTts.finishPendingUtterance(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Paragraph two.'); + this.mockTts.finishPendingUtterance(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Paragraph three.'); }); TEST_F( 'SelectToSpeakNavigationControlTest', 'PauseResumeAfterParagraphNavigation', - function() { + async function() { const bodyHtml = ` <span id='s1'> <p>Paragraph one.</p> @@ -337,113 +332,107 @@ <p>Paragraph three.</p> </span>' `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('s1', bodyHtml), - async function() { - this.triggerReadSelectedText(); + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('s1', bodyHtml)); + this.triggerReadSelectedText(); - // Navigates to the next paragraph and speaks until the second word. - await selectToSpeak.onNextParagraphRequested(); - this.mockTts.speakUntilCharIndex(10); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph two.'); + // Navigates to the next paragraph and speaks until the second word. + await selectToSpeak.onNextParagraphRequested(); + this.mockTts.speakUntilCharIndex(10); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Paragraph two.'); - // Hitting pause and resume will start reading the remaining content - // in the second paragraph. - selectToSpeak.onPauseRequested(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - selectToSpeak.onResumeRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'two.'); + // Hitting pause and resume will start reading the remaining content + // in the second paragraph. + selectToSpeak.onPauseRequested(); + assertFalse(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 0); + selectToSpeak.onResumeRequested(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'two.'); - // Should not keep reading beyond the second paragraph. - this.mockTts.finishPendingUtterance(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - }); + // Should not keep reading beyond the second paragraph. + this.mockTts.finishPendingUtterance(); + assertFalse(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 0); }); TEST_F( 'SelectToSpeakNavigationControlTest', 'PauseResumeAfterSentenceNavigation', - function() { + async function() { const bodyHtml = ` <span id='s1'> <p>Sentence one. Sentence two.</p> <p>Paragraph two.</p> </span>' `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('s1', bodyHtml), - async function() { - this.triggerReadSelectedText(); - // Navigates to the next sentence and speaks until the last word - // (i.e., "two") in the first pargraph. - await selectToSpeak.onNextSentenceRequested(); - this.mockTts.speakUntilCharIndex(23); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Sentence two.'); + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('s1', bodyHtml)); + this.triggerReadSelectedText(); + // Navigates to the next sentence and speaks until the last word + // (i.e., "two") in the first pargraph. + await selectToSpeak.onNextSentenceRequested(); + this.mockTts.speakUntilCharIndex(23); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Sentence two.'); - // Hitting pause and resume will start reading the remaining content - // in the first paragraph. - selectToSpeak.onPauseRequested(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - selectToSpeak.onResumeRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'two.'); + // Hitting pause and resume will start reading the remaining content + // in the first paragraph. + selectToSpeak.onPauseRequested(); + assertFalse(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 0); + selectToSpeak.onResumeRequested(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'two.'); - // Should not keep reading beyond the first paragraph. - this.mockTts.finishPendingUtterance(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - }); + // Should not keep reading beyond the first paragraph. + this.mockTts.finishPendingUtterance(); + assertFalse(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 0); }); TEST_F( 'SelectToSpeakNavigationControlTest', 'PauseResumeAtTheEndOfNodeGroupItem', - function() { + async function() { const bodyHtml = ` <p id="p1">Sentence <span>one</span>. Sentence two.</p> `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml), () => { - this.triggerReadSelectedText(); + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('p1', bodyHtml)); + this.triggerReadSelectedText(); - // Finishes the second word. - this.mockTts.speakUntilCharIndex(13); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], - 'Sentence one . Sentence two.'); + // Finishes the second word. + this.mockTts.speakUntilCharIndex(13); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Sentence one . Sentence two.'); - // Hitting pause will stop the current TTS. - selectToSpeak.onPauseRequested(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); + // Hitting pause will stop the current TTS. + selectToSpeak.onPauseRequested(); + assertFalse(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 0); - // Hitting resume will start from the remaining content of the - // paragraph. - selectToSpeak.onResumeRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], '. Sentence two.'); - }); + // Hitting resume will start from the remaining content of the + // paragraph. + selectToSpeak.onResumeRequested(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], '. Sentence two.'); }); TEST_F( 'SelectToSpeakNavigationControlTest', 'PauseResumeFromKeystrokeSelection', - function() { + async function() { const bodyHtml = '<p>This is some <b>bold</b> text</p><p>Second paragraph</p>'; const setFocusCallback = this.newCallback((root) => { @@ -457,387 +446,374 @@ focusOffset: 6 }); }); - this.runWithLoadedTree(bodyHtml, function(root) { - root.addEventListener( - 'documentSelectionChanged', this.newCallback(function(event) { - this.triggerReadSelectedText(); - - // Speaks the first word 'is', the char index will count from the - // beginning of the node (i.e., from "This"). - this.mockTts.speakUntilCharIndex(8); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'is some bold text'); - - // Hitting pause will stop the current TTS. - selectToSpeak.onPauseRequested(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - - // Hitting resume will start from the remaining content of the - // paragraph. - selectToSpeak.onResumeRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'some bold text'); - - // Keep reading will finish all the content. - this.mockTts.finishPendingUtterance(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Second'); - }), - false); - setFocusCallback(root); - }); - }); - -TEST_F('SelectToSpeakNavigationControlTest', 'NextSentence', function() { - const bodyHtml = ` - <p id="p1">This is the first. This is the second.</p>' - `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml), async function() { - this.triggerReadSelectedText(); - - // Speaks the first word. - this.mockTts.speakUntilCharIndex(5); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], - 'This is the first. This is the second.'); - - // Hitting next sentence will start another TTS. - await selectToSpeak.onNextSentenceRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'This is the second.'); - }); -}); - -TEST_F( - 'SelectToSpeakNavigationControlTest', 'NextSentenceWithinParagraph', - function() { - const bodyHtml = ` - <p id="p1">Sent 1. <span id="s1">Sent 2.</span> Sent 3. Sent 4.</p> - `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('s1', bodyHtml), () => { + const root = await this.runWithLoadedTree(bodyHtml); + root.addEventListener( + 'documentSelectionChanged', this.newCallback(function(event) { this.triggerReadSelectedText(); - // Speaks the first word. - this.mockTts.speakUntilCharIndex(5); + // Speaks the first word 'is', the char index will count from the + // beginning of the node (i.e., from "This"). + this.mockTts.speakUntilCharIndex(8); assertTrue(this.mockTts.currentlySpeaking()); assertEquals(this.mockTts.pendingUtterances().length, 1); this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Sent 2.'); + this.mockTts.pendingUtterances()[0], 'is some bold text'); - // Hitting next sentence will start from the next sentence. - selectToSpeak.onNextSentenceRequested(); - this.waitOneEventLoop(() => { - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Sent 3. Sent 4.'); - }); - }); - }); - -TEST_F( - 'SelectToSpeakNavigationControlTest', 'NextSentenceAcrossParagraph', - function() { - const bodyHtml = ` - <p id="p1">Sent 1.</p> - <p id="p2">Sent 2. Sent 3.</p>' - `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml), () => { - this.triggerReadSelectedText(); - - // Speaks the first word. - this.mockTts.speakUntilCharIndex(5); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Sent 1.'); - - // Hitting next sentence will star from the next paragraph as there - // is no more sentence in the current paragraph. - selectToSpeak.onNextSentenceRequested(); - this.waitOneEventLoop(() => { - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Sent 2. Sent 3.'); - }); - }); - }); - -TEST_F('SelectToSpeakNavigationControlTest', 'PrevSentence', function() { - const bodyHtml = ` - <p id="p1">First sentence. Second sentence. Third sentence.</p>' - `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml), async function() { - this.triggerReadSelectedText(); - - // Speaks util the start of the second sentence. - this.mockTts.speakUntilCharIndex(33); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], - 'First sentence. Second sentence. Third sentence.'); - - // Hitting prev sentence will start another TTS. - await selectToSpeak.onPreviousSentenceRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], - 'Second sentence. Third sentence.'); - }); -}); - -TEST_F( - 'SelectToSpeakNavigationControlTest', 'PrevSentenceFromMiddleOfSentence', - function() { - const bodyHtml = ` - <p id="p1">First sentence. Second sentence. Third sentence.</p>' - `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml), - async function() { - this.triggerReadSelectedText(); - - // Speaks util the start of "sentence" in "Second sentence". - this.mockTts.speakUntilCharIndex(23); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], - 'First sentence. Second sentence. Third sentence.'); - - // Hitting prev sentence will start another TTS. - await selectToSpeak.onPreviousSentenceRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], - 'First sentence. Second sentence. Third sentence.'); - }); - }); - -TEST_F( - 'SelectToSpeakNavigationControlTest', 'PrevSentenceWithinParagraph', - function() { - const bodyHtml = ` - <p id="p1">Sent 0. Sent 1. <span id="s1">Sent 2.</span> Sent 3.</p> - `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('s1', bodyHtml), () => { - this.triggerReadSelectedText(); - - // Supposing we are at the start of the sentence. - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Sent 2.'); - - // Hitting previous sentence will start from the previous sentence. - selectToSpeak.onPreviousSentenceRequested(); - this.waitOneEventLoop(() => { - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], - 'Sent 1. Sent 2. Sent 3.'); - }); - }); - }); - -TEST_F( - 'SelectToSpeakNavigationControlTest', 'PrevSentenceAcrossParagraph', - function() { - const bodyHtml = ` - <p id="p1">Sent 1. Sent 2.</p> - <p id="p2">Sent 3.</p>' - `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p2', bodyHtml), () => { - this.triggerReadSelectedText(); - - // We are at the start of the sentence. - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Sent 3.'); - - // Hitting previous sentence will start from the last sentence in - // the previous paragraph as there is no more sentence in the - // current paragraph. - selectToSpeak.onPreviousSentenceRequested(); - this.waitOneEventLoop(() => { - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Sent 2.'); - }); - }); - }); - -TEST_F( - 'SelectToSpeakNavigationControlTest', 'ChangeSpeedWhilePlaying', - function() { - chrome.settingsPrivate.setPref('settings.tts.speech_rate', 1.2); - const bodyHtml = ` - <p id="p1">Paragraph 1</p>' - `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml), () => { - this.triggerReadSelectedText(); - - // Speaks the first word. - this.mockTts.speakUntilCharIndex(10); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph 1'); - assertEquals(this.mockTts.getOptions().rate, 1.2); - - // Changing speed will resume with the remaining content of the - // current sentence. - selectToSpeak.onChangeSpeedRequested(1.5); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - - // Wait an event loop so all pending promises are resolved prior to - // asserting that TTS resumed with the proper rate. - setTimeout( - this.newCallback(() => { - // Should resume TTS with the remaining content with adjusted - // rate. - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.getOptions().rate, 1.8); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], '1'); - }), - 0); - }); - }); - -TEST_F('SelectToSpeakNavigationControlTest', 'RetainsSpeedChange', function() { - chrome.settingsPrivate.setPref('settings.tts.speech_rate', 1.0); - const bodyHtml = ` - <p id="p1">Paragraph 1</p>' - `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml), () => { - this.triggerReadSelectedText(); - - // Changing speed then exit. - selectToSpeak.onChangeSpeedRequested(1.5); - selectToSpeak.onExitRequested(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - - // Next TTS session should remember previous rate. - this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.getOptions().rate, 1.5); - }); -}); - -TEST_F( - 'SelectToSpeakNavigationControlTest', 'ChangeSpeedWhilePaused', function() { - chrome.settingsPrivate.setPref('settings.tts.speech_rate', 1.2); - const bodyHtml = ` - <p id="p1">Paragraph 1</p>' - `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml), () => { - this.triggerReadSelectedText(); - - // Speaks the first word. - this.mockTts.speakUntilCharIndex(10); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph 1'); - assertEquals(this.mockTts.getOptions().rate, 1.2); - - // User-intiated pause. + // Hitting pause will stop the current TTS. selectToSpeak.onPauseRequested(); assertFalse(this.mockTts.currentlySpeaking()); assertEquals(this.mockTts.pendingUtterances().length, 0); - // Changing speed will remain paused. - selectToSpeak.onChangeSpeedRequested(1.5); + // Hitting resume will start from the remaining content of the + // paragraph. + selectToSpeak.onResumeRequested(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'some bold text'); - // Wait an event loop so all pending promises are resolved prior to - // asserting that TTS remains paused. - setTimeout(this.newCallback(() => { - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - }, 0)); - }); + // Keep reading will finish all the content. + this.mockTts.finishPendingUtterance(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Second'); + }), + false); + setFocusCallback(root); + }); + +TEST_F('SelectToSpeakNavigationControlTest', 'NextSentence', async function() { + const bodyHtml = ` + <p id="p1">This is the first. This is the second.</p>' + `; + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('p1', bodyHtml)); + this.triggerReadSelectedText(); + + // Speaks the first word. + this.mockTts.speakUntilCharIndex(5); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], + 'This is the first. This is the second.'); + + // Hitting next sentence will start another TTS. + await selectToSpeak.onNextSentenceRequested(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'This is the second.'); +}); + +TEST_F( + 'SelectToSpeakNavigationControlTest', 'NextSentenceWithinParagraph', + async function() { + const bodyHtml = ` + <p id="p1">Sent 1. <span id="s1">Sent 2.</span> Sent 3. Sent 4.</p> + `; + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('s1', bodyHtml)); + this.triggerReadSelectedText(); + + // Speaks the first word. + this.mockTts.speakUntilCharIndex(5); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Sent 2.'); + + // Hitting next sentence will start from the next sentence. + selectToSpeak.onNextSentenceRequested(); + this.waitOneEventLoop(() => { + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Sent 3. Sent 4.'); + }); + }); + +TEST_F( + 'SelectToSpeakNavigationControlTest', 'NextSentenceAcrossParagraph', + async function() { + const bodyHtml = ` + <p id="p1">Sent 1.</p> + <p id="p2">Sent 2. Sent 3.</p>' + `; + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('p1', bodyHtml)); + this.triggerReadSelectedText(); + + // Speaks the first word. + this.mockTts.speakUntilCharIndex(5); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Sent 1.'); + + // Hitting next sentence will star from the next paragraph as there + // is no more sentence in the current paragraph. + selectToSpeak.onNextSentenceRequested(); + this.waitOneEventLoop(() => { + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Sent 2. Sent 3.'); + }); + }); + +TEST_F('SelectToSpeakNavigationControlTest', 'PrevSentence', async function() { + const bodyHtml = ` + <p id="p1">First sentence. Second sentence. Third sentence.</p>' + `; + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('p1', bodyHtml)); + this.triggerReadSelectedText(); + + // Speaks util the start of the second sentence. + this.mockTts.speakUntilCharIndex(33); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], + 'First sentence. Second sentence. Third sentence.'); + + // Hitting prev sentence will start another TTS. + await selectToSpeak.onPreviousSentenceRequested(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Second sentence. Third sentence.'); +}); + +TEST_F( + 'SelectToSpeakNavigationControlTest', 'PrevSentenceFromMiddleOfSentence', + async function() { + const bodyHtml = ` + <p id="p1">First sentence. Second sentence. Third sentence.</p>' + `; + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('p1', bodyHtml)); + this.triggerReadSelectedText(); + + // Speaks util the start of "sentence" in "Second sentence". + this.mockTts.speakUntilCharIndex(23); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], + 'First sentence. Second sentence. Third sentence.'); + + // Hitting prev sentence will start another TTS. + await selectToSpeak.onPreviousSentenceRequested(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], + 'First sentence. Second sentence. Third sentence.'); + }); + +TEST_F( + 'SelectToSpeakNavigationControlTest', 'PrevSentenceWithinParagraph', + async function() { + const bodyHtml = ` + <p id="p1">Sent 0. Sent 1. <span id="s1">Sent 2.</span> Sent 3.</p> + `; + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('s1', bodyHtml)); + this.triggerReadSelectedText(); + + // Supposing we are at the start of the sentence. + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Sent 2.'); + + // Hitting previous sentence will start from the previous sentence. + selectToSpeak.onPreviousSentenceRequested(); + this.waitOneEventLoop(() => { + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Sent 1. Sent 2. Sent 3.'); + }); + }); + +TEST_F( + 'SelectToSpeakNavigationControlTest', 'PrevSentenceAcrossParagraph', + async function() { + const bodyHtml = ` + <p id="p1">Sent 1. Sent 2.</p> + <p id="p2">Sent 3.</p>' + `; + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('p2', bodyHtml)); + this.triggerReadSelectedText(); + + // We are at the start of the sentence. + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Sent 3.'); + + // Hitting previous sentence will start from the last sentence in + // the previous paragraph as there is no more sentence in the + // current paragraph. + selectToSpeak.onPreviousSentenceRequested(); + this.waitOneEventLoop(() => { + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Sent 2.'); + }); + }); + +TEST_F( + 'SelectToSpeakNavigationControlTest', 'ChangeSpeedWhilePlaying', + async function() { + chrome.settingsPrivate.setPref('settings.tts.speech_rate', 1.2); + const bodyHtml = ` + <p id="p1">Paragraph 1</p>' + `; + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('p1', bodyHtml)); + this.triggerReadSelectedText(); + + // Speaks the first word. + this.mockTts.speakUntilCharIndex(10); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Paragraph 1'); + assertEquals(this.mockTts.getOptions().rate, 1.2); + + // Changing speed will resume with the remaining content of the + // current sentence. + selectToSpeak.onChangeSpeedRequested(1.5); + assertFalse(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 0); + + // Wait an event loop so all pending promises are resolved prior to + // asserting that TTS resumed with the proper rate. + setTimeout( + this.newCallback(() => { + // Should resume TTS with the remaining content with adjusted + // rate. + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.getOptions().rate, 1.8); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], '1'); + }), + 0); + }); + +TEST_F( + 'SelectToSpeakNavigationControlTest', 'RetainsSpeedChange', + async function() { + chrome.settingsPrivate.setPref('settings.tts.speech_rate', 1.0); + const bodyHtml = ` + <p id="p1">Paragraph 1</p>' + `; + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('p1', bodyHtml)); + this.triggerReadSelectedText(); + + // Changing speed then exit. + selectToSpeak.onChangeSpeedRequested(1.5); + selectToSpeak.onExitRequested(); + assertFalse(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 0); + + // Next TTS session should remember previous rate. + this.triggerReadSelectedText(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.getOptions().rate, 1.5); + }); + +TEST_F( + 'SelectToSpeakNavigationControlTest', 'ChangeSpeedWhilePaused', + async function() { + chrome.settingsPrivate.setPref('settings.tts.speech_rate', 1.2); + const bodyHtml = ` + <p id="p1">Paragraph 1</p>' + `; + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('p1', bodyHtml)); + this.triggerReadSelectedText(); + + // Speaks the first word. + this.mockTts.speakUntilCharIndex(10); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Paragraph 1'); + assertEquals(this.mockTts.getOptions().rate, 1.2); + + // User-intiated pause. + selectToSpeak.onPauseRequested(); + assertFalse(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 0); + + // Changing speed will remain paused. + selectToSpeak.onChangeSpeedRequested(1.5); + + // Wait an event loop so all pending promises are resolved prior to + // asserting that TTS remains paused. + setTimeout(this.newCallback(() => { + assertFalse(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 0); + }, 0)); }); TEST_F( 'SelectToSpeakNavigationControlTest', 'ResumeAtTheEndOfParagraph', - function() { + async function() { const bodyHtml = ` <p id="p1">Paragraph 1</p> <p id="p2">Paragraph 2</p> `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml), () => { - this.triggerReadSelectedText(); + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('p1', bodyHtml)); + this.triggerReadSelectedText(); - // Finishes the current utterance. - this.mockTts.finishPendingUtterance(); + // Finishes the current utterance. + this.mockTts.finishPendingUtterance(); - // Hitting resume will start the next paragraph. - selectToSpeak.onResumeRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph 2'); - }); + // Hitting resume will start the next paragraph. + selectToSpeak.onResumeRequested(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'Paragraph 2'); }); TEST_F( 'SelectToSpeakNavigationControlTest', 'ResumeAtTheEndOfUserSelection', - function() { + async function() { const bodyHtml = ` <p id="p1">Sentence <span id="s1">one</span>. Sentence two.</p> <p id="p2">Paragraph 2</p> `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('s1', bodyHtml), () => { - this.triggerReadSelectedText(); + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('s1', bodyHtml)); + this.triggerReadSelectedText(); - // Finishes the current utterance. - this.mockTts.finishPendingUtterance(); + // Finishes the current utterance. + this.mockTts.finishPendingUtterance(); - // Hitting resume will start the remaining content. - selectToSpeak.onResumeRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], '. Sentence two.'); - }); + // Hitting resume will start the remaining content. + selectToSpeak.onResumeRequested(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], '. Sentence two.'); }); TEST_F( 'SelectToSpeakNavigationControlTest', 'ResumeFromSelectionEndingInSpace', - function() { + async function() { const bodyHtml = '<p>This is some text with space.</p>'; const setFocusCallback = this.newCallback((root) => { const node = this.findTextNode(root, 'This is some text with space.'); @@ -849,37 +825,39 @@ focusOffset: 5 }); }); - this.runWithLoadedTree(bodyHtml, (root) => { - root.addEventListener( - 'documentSelectionChanged', this.newCallback((event) => { - this.triggerReadSelectedText(); + const root = await this.runWithLoadedTree(bodyHtml); + root.addEventListener( + 'documentSelectionChanged', this.newCallback((event) => { + this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'This'); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'This'); - // Finishes the current utterance. - this.mockTts.finishPendingUtterance(); + // Finishes the current utterance. + this.mockTts.finishPendingUtterance(); - // Hitting resume will start from the remaining content of the - // paragraph. - selectToSpeak.onResumeRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], - 'is some text with space.'); - }), - false); - setFocusCallback(root); - }); + // Hitting resume will start from the remaining content of the + // paragraph. + selectToSpeak.onResumeRequested(); + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], + 'is some text with space.'); + }), + false); + setFocusCallback(root); }); -TEST_F('SelectToSpeakNavigationControlTest', 'ResizeWhilePlaying', function() { - const longLine = - 'Second paragraph is longer than 300 pixels and will wrap when resized'; - const bodyHtml = ` +TEST_F( + 'SelectToSpeakNavigationControlTest', 'ResizeWhilePlaying', + async function() { + const longLine = + 'Second paragraph is longer than 300 pixels and will wrap when' + + 'resized'; + const bodyHtml = ` <script type="text/javascript"> function doResize() { document.getElementById('resize').style.width = '100px'; @@ -893,180 +871,172 @@ </div> <button onclick="doResize()">Resize</button> `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('content', bodyHtml), (root) => { - this.triggerReadSelectedText(); + const root = await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('content', bodyHtml)); + this.triggerReadSelectedText(); - // Speaks the first paragraph. + // Speaks the first paragraph. + assertTrue(this.mockTts.currentlySpeaking()); + assertEquals(this.mockTts.pendingUtterances().length, 1); + this.assertEqualsCollapseWhitespace( + this.mockTts.pendingUtterances()[0], 'First paragraph'); + + const resizeButton = + root.find({role: 'button', attributes: {name: 'Resize'}}); + + // Wait for click event, at which point the automation tree should + // be updated from the resize. + resizeButton.addEventListener(EventType.CLICKED, this.newCallback(() => { + // Trigger next node group by completing first TTS request. + this.mockTts.finishPendingUtterance(); + + // Should still read second paragraph, even though some nodes + // were invalided from the resize. assertTrue(this.mockTts.currentlySpeaking()); assertEquals(this.mockTts.pendingUtterances().length, 1); this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'First paragraph'); + this.mockTts.pendingUtterances()[0], longLine); + })); - const resizeButton = - root.find({role: 'button', attributes: {name: 'Resize'}}); - - // Wait for click event, at which point the automation tree should - // be updated from the resize. - resizeButton.addEventListener( - EventType.CLICKED, this.newCallback(() => { - // Trigger next node group by completing first TTS request. - this.mockTts.finishPendingUtterance(); - - // Should still read second paragraph, even though some nodes - // were invalided from the resize. - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], longLine); - })); - - // Perform resize. - resizeButton.doDefault(); - }); -}); - -TEST_F( - 'SelectToSpeakNavigationControlTest', - 'RemainsActiveAfterCompletingUtterance', function() { - const bodyHtml = '<p id="p1">Paragraph 1</p>'; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml), () => { - // Simulate starting and completing TTS. - this.triggerReadSelectedText(); - this.mockTts.finishPendingUtterance(); - - // Should remain in speaking state. - assertEquals(selectToSpeak.state_, SelectToSpeakState.SPEAKING); - }); + // Perform resize. + resizeButton.doDefault(); }); TEST_F( 'SelectToSpeakNavigationControlTest', - 'AutoDismissesIfNavigationControlsDisabled', function() { + 'RemainsActiveAfterCompletingUtterance', async function() { + const bodyHtml = '<p id="p1">Paragraph 1</p>'; + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('p1', bodyHtml)); + // Simulate starting and completing TTS. + this.triggerReadSelectedText(); + this.mockTts.finishPendingUtterance(); + + // Should remain in speaking state. + assertEquals(selectToSpeak.state_, SelectToSpeakState.SPEAKING); + }); + +TEST_F( + 'SelectToSpeakNavigationControlTest', + 'AutoDismissesIfNavigationControlsDisabled', async function() { // Disable navigation controls via settings. chrome.storage.sync.set({'navigationControls': false}); const bodyHtml = '<p id="p1">Paragraph 1</p>'; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml), () => { - // Simulate starting and completing TTS. - this.triggerReadSelectedText(); - this.mockTts.finishPendingUtterance(); + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('p1', bodyHtml)); + // Simulate starting and completing TTS. + this.triggerReadSelectedText(); + this.mockTts.finishPendingUtterance(); - // Should auto-dismiss. - assertEquals(selectToSpeak.state_, SelectToSpeakState.INACTIVE); - }); + // Should auto-dismiss. + assertEquals(selectToSpeak.state_, SelectToSpeakState.INACTIVE); }); TEST_F( 'SelectToSpeakNavigationControlTest', 'NavigatesToNextParagraphQuickly', - function() { + async function() { const bodyHtml = ` <p id="p1">Paragraph 1</p> <p id="p2">Paragraph 2</p>' `; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml), () => { - // Have mock TTS engine wait to send events so we can simulate a - // delayed 'start' event. - this.mockTts.setWaitToSendEvents(true); - this.triggerReadSelectedText(); - const speakOptions = this.mockTts.getOptions(); + await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('p1', bodyHtml)); + // Have mock TTS engine wait to send events so we can simulate a + // delayed 'start' event. + this.mockTts.setWaitToSendEvents(true); + this.triggerReadSelectedText(); + const speakOptions = this.mockTts.getOptions(); - // Navigate to next paragraph before speech begins. - selectToSpeak.onNextParagraphRequested(); + // Navigate to next paragraph before speech begins. + selectToSpeak.onNextParagraphRequested(); - this.waitOneEventLoop(() => { - // Manually triggered delayed events. - this.mockTts.sendPendingEvents(); + this.waitOneEventLoop(() => { + // Manually triggered delayed events. + this.mockTts.sendPendingEvents(); - // Should remain in speaking state. - assertEquals(selectToSpeak.state_, SelectToSpeakState.SPEAKING); - }); - }); + // Should remain in speaking state. + assertEquals(selectToSpeak.state_, SelectToSpeakState.SPEAKING); + }); }); TEST_F( 'SelectToSpeakNavigationControlTest', 'SetsInitialFocusToPanel', - function() { + async function() { const bodyHtml = '<p id="p1">Sample text</p>'; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml), (root) => { - const desktop = root.parent.root; + const root = await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('p1', bodyHtml)); + const desktop = root.parent.root; - // Wait for button in STS panel to be focused. - // Test will fail if panel is never focused. - this.waitForPanelFocus(desktop, () => {}); + // Wait for button in STS panel to be focused. + // Test will fail if panel is never focused. + this.waitForPanelFocus(desktop, () => {}); - // Trigger STS, which will initially set focus to the panel. - this.triggerReadSelectedText(); - }); + // Trigger STS, which will initially set focus to the panel. + this.triggerReadSelectedText(); }); TEST_F( 'SelectToSpeakNavigationControlTest', 'KeyboardShortcutKeepsFocusInPanel', - function() { + async function() { const bodyHtml = '<p id="p1">Sample text</p>'; - this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml), (root) => { - const desktop = root.parent.root; + const root = await this.runWithLoadedTree( + this.generateHtmlWithSelectedElement('p1', bodyHtml)); + const desktop = root.parent.root; - // Wait for button within STS panel is focused. - this.waitForPanelFocus(desktop, () => { - // Remove text selection. - const textNode = this.findTextNode(root, 'Sample text'); - chrome.automation.setDocumentSelection({ - anchorObject: textNode, - anchorOffset: 0, - focusObject: textNode, - focusOffset: 0 - }); + // Wait for button within STS panel is focused. + this.waitForPanelFocus(desktop, () => { + // Remove text selection. + const textNode = this.findTextNode(root, 'Sample text'); + chrome.automation.setDocumentSelection({ + anchorObject: textNode, + anchorOffset: 0, + focusObject: textNode, + focusOffset: 0 + }); - // Perform Search key + S, which should restore focus to - // panel. - selectToSpeak.fireMockKeyDownEvent( - {keyCode: SelectToSpeakConstants.SEARCH_KEY_CODE}); - selectToSpeak.fireMockKeyDownEvent( - {keyCode: SelectToSpeakConstants.READ_SELECTION_KEY_CODE}); - selectToSpeak.fireMockKeyUpEvent( - {keyCode: SelectToSpeakConstants.READ_SELECTION_KEY_CODE}); - selectToSpeak.fireMockKeyUpEvent( - {keyCode: SelectToSpeakConstants.SEARCH_KEY_CODE}); + // Perform Search key + S, which should restore focus to + // panel. + selectToSpeak.fireMockKeyDownEvent( + {keyCode: SelectToSpeakConstants.SEARCH_KEY_CODE}); + selectToSpeak.fireMockKeyDownEvent( + {keyCode: SelectToSpeakConstants.READ_SELECTION_KEY_CODE}); + selectToSpeak.fireMockKeyUpEvent( + {keyCode: SelectToSpeakConstants.READ_SELECTION_KEY_CODE}); + selectToSpeak.fireMockKeyUpEvent( + {keyCode: SelectToSpeakConstants.SEARCH_KEY_CODE}); - // Verify focus is still on button within panel. - chrome.automation.getFocus(this.newCallback((focusedNode) => { - assertEquals(focusedNode.role, RoleType.TOGGLE_BUTTON); - assertTrue(this.isNodeWithinPanel(focusedNode)); - })); - }); + // Verify focus is still on button within panel. + chrome.automation.getFocus(this.newCallback((focusedNode) => { + assertEquals(focusedNode.role, RoleType.TOGGLE_BUTTON); + assertTrue(this.isNodeWithinPanel(focusedNode)); + })); + }); - // Trigger STS, which will initially set focus to the panel. - this.triggerReadSelectedText(); - }); + // Trigger STS, which will initially set focus to the panel. + this.triggerReadSelectedText(); }); TEST_F( 'SelectToSpeakNavigationControlTest', 'SelectingWindowDoesNotShowPanel', - function() { + async function() { const bodyHtml = ` <title>Test</title> <div style="position: absolute; top: 300px;"> Hello </div> `; - this.runWithLoadedTree(bodyHtml, (root) => { - // Expect call to updateSelectToSpeakPanel to set panel to be hidden. - chrome.accessibilityPrivate.updateSelectToSpeakPanel = - this.newCallback((visible) => { - assertFalse(visible); - }); + const root = await this.runWithLoadedTree(bodyHtml); + // Expect call to updateSelectToSpeakPanel to set panel to be hidden. + chrome.accessibilityPrivate.updateSelectToSpeakPanel = + this.newCallback((visible) => { + assertFalse(visible); + }); - // Trigger mouse selection on a part of the page where no text nodes - // exist, should select entire page. - const mouseEvent = { - screenX: root.location.left + 1, - screenY: root.location.top + 1, - }; - this.triggerReadMouseSelectedText(mouseEvent, mouseEvent); - }); + // Trigger mouse selection on a part of the page where no text nodes + // exist, should select entire page. + const mouseEvent = { + screenX: root.location.left + 1, + screenY: root.location.top + 1, + }; + this.triggerReadMouseSelectedText(mouseEvent, mouseEvent); });
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/auto_scan_manager_test.js b/chrome/browser/resources/chromeos/accessibility/switch_access/auto_scan_manager_test.js index bf5b82531..1456f44 100644 --- a/chrome/browser/resources/chromeos/accessibility/switch_access/auto_scan_manager_test.js +++ b/chrome/browser/resources/chromeos/accessibility/switch_access/auto_scan_manager_test.js
@@ -54,7 +54,7 @@ }; TEST_F('SwitchAccessAutoScanManagerTest', 'SetEnabled', function() { - this.runWithLoadedTree('', () => { + this.runWithLoadedDesktop(() => { assertFalse( AutoScanManager.instance.isRunning_(), 'Auto scan manager is running prematurely');
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/focus_ring_manager_test.js b/chrome/browser/resources/chromeos/accessibility/switch_access/focus_ring_manager_test.js index d394a95..6e5eb18 100644 --- a/chrome/browser/resources/chromeos/accessibility/switch_access/focus_ring_manager_test.js +++ b/chrome/browser/resources/chromeos/accessibility/switch_access/focus_ring_manager_test.js
@@ -51,87 +51,82 @@ }); TEST_F( - 'SwitchAccessFocusRingManagerTest', 'BackButtonForMenuFocus', function() { + 'SwitchAccessFocusRingManagerTest', 'BackButtonForMenuFocus', + async function() { const site = '<input type="text">'; - this.runWithLoadedTree(site, async (rootWebArea) => { - // Open the menu and focus the back button. - TestUtility.startFocusInside(rootWebArea); - // Check the current node directly, as the TestUtility relies on the - // FocusManager to identify the current focus. - assertEquals( - chrome.automation.RoleType.TEXT_FIELD, Navigator.byItem.node_.role); - TestUtility.pressSelectSwitch(); + const rootWebArea = await this.runWithLoadedTree(site); + // Open the menu and focus the back button. + TestUtility.startFocusInside(rootWebArea); + // Check the current node directly, as the TestUtility relies on the + // FocusManager to identify the current focus. + assertEquals( + chrome.automation.RoleType.TEXT_FIELD, Navigator.byItem.node_.role); + TestUtility.pressSelectSwitch(); - let found = false; - while (!found) { - TestUtility.pressNextSwitch(); - if (Navigator.byItem.node_ instanceof BackButtonNode) { - found = true; - } + let found = false; + while (!found) { + TestUtility.pressNextSwitch(); + if (Navigator.byItem.node_ instanceof BackButtonNode) { + found = true; } + } - const rings = FocusRingManager.instance.rings_; - const primary = rings.get(SAConstants.Focus.ID.PRIMARY); - const preview = rings.get(SAConstants.Focus.ID.PREVIEW); - // Primary and preview focus should be empty. - assertEquals(0, primary.rects.length); - assertEquals(0, preview.rects.length); - }); + const rings = FocusRingManager.instance.rings_; + const primary = rings.get(SAConstants.Focus.ID.PRIMARY); + const preview = rings.get(SAConstants.Focus.ID.PREVIEW); + // Primary and preview focus should be empty. + assertEquals(0, primary.rects.length); + assertEquals(0, preview.rects.length); }); -TEST_F('SwitchAccessFocusRingManagerTest', 'ButtonFocus', function() { +TEST_F('SwitchAccessFocusRingManagerTest', 'ButtonFocus', async function() { const site = '<button>Test</button>'; - this.runWithLoadedTree(site, async (rootWebArea) => { - const button = rootWebArea.find({role: chrome.automation.RoleType.BUTTON}); - Navigator.byItem.moveTo_(button); + const rootWebArea = await this.runWithLoadedTree(site); + const button = rootWebArea.find({role: chrome.automation.RoleType.BUTTON}); + Navigator.byItem.moveTo_(button); - const rings = FocusRingManager.instance.rings_; - const primary = rings.get(SAConstants.Focus.ID.PRIMARY); - const preview = rings.get(SAConstants.Focus.ID.PREVIEW); - assertEquals(1, primary.rects.length); - assertEquals(0, preview.rects.length); - // Primary focus should be on the button. - const focusLocation = primary.rects[0]; - const buttonLocation = button.location; - assertTrue(RectUtil.equal(buttonLocation, focusLocation)); - }); + const rings = FocusRingManager.instance.rings_; + const primary = rings.get(SAConstants.Focus.ID.PRIMARY); + const preview = rings.get(SAConstants.Focus.ID.PREVIEW); + assertEquals(1, primary.rects.length); + assertEquals(0, preview.rects.length); + // Primary focus should be on the button. + const focusLocation = primary.rects[0]; + const buttonLocation = button.location; + assertTrue(RectUtil.equal(buttonLocation, focusLocation)); }); -TEST_F('SwitchAccessFocusRingManagerTest', 'GroupFocus', function() { +TEST_F('SwitchAccessFocusRingManagerTest', 'GroupFocus', async function() { const site = ` <div role="menu"> <div role="menuitem">Dog</div> <div role="menuitem">Cat</div> </div> `; - this.runWithLoadedTree(site, async (rootWebArea) => { - const menu = rootWebArea.find({role: chrome.automation.RoleType.MENU}); - const menuItem = rootWebArea.find({ - role: chrome.automation.RoleType.MENU_ITEM, - attributes: {name: 'Dog'} - }); - assertNotNullNorUndefined(menu); - assertNotNullNorUndefined(menuItem); - Navigator.byItem.moveTo_(menu); + const rootWebArea = await this.runWithLoadedTree(site); + const menu = rootWebArea.find({role: chrome.automation.RoleType.MENU}); + const menuItem = rootWebArea.find( + {role: chrome.automation.RoleType.MENU_ITEM, attributes: {name: 'Dog'}}); + assertNotNullNorUndefined(menu); + assertNotNullNorUndefined(menuItem); + Navigator.byItem.moveTo_(menu); - // Verify the number of rings. - const rings = FocusRingManager.instance.rings_; - const primary = rings.get(SAConstants.Focus.ID.PRIMARY); - const preview = rings.get(SAConstants.Focus.ID.PREVIEW); - assertEquals(1, primary.rects.length); - assertEquals(1, preview.rects.length); + // Verify the number of rings. + const rings = FocusRingManager.instance.rings_; + const primary = rings.get(SAConstants.Focus.ID.PRIMARY); + const preview = rings.get(SAConstants.Focus.ID.PREVIEW); + assertEquals(1, primary.rects.length); + assertEquals(1, preview.rects.length); - // Use ringNodesForTesting_ to verify the underlying nodes. - const ringNodes = FocusRingManager.instance.ringNodesForTesting_; - const primaryNode = - ringNodes.get(SAConstants.Focus.ID.PRIMARY).automationNode; - const previewNode = - ringNodes.get(SAConstants.Focus.ID.PREVIEW).automationNode; + // Use ringNodesForTesting_ to verify the underlying nodes. + const ringNodes = FocusRingManager.instance.ringNodesForTesting_; + const primaryNode = + ringNodes.get(SAConstants.Focus.ID.PRIMARY).automationNode; + const previewNode = + ringNodes.get(SAConstants.Focus.ID.PREVIEW).automationNode; - assertEquals( - menu, primaryNode, - 'primary focus should be around the group (the menu)'); - assertEquals( - menuItem, previewNode, 'preview focus should be around the menu item'); - }); + assertEquals( + menu, primaryNode, 'primary focus should be around the group (the menu)'); + assertEquals( + menuItem, previewNode, 'preview focus should be around the menu item'); });
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/item_scan_manager_test.js b/chrome/browser/resources/chromeos/accessibility/switch_access/item_scan_manager_test.js index 71c6c04..4d297237 100644 --- a/chrome/browser/resources/chromeos/accessibility/switch_access/item_scan_manager_test.js +++ b/chrome/browser/resources/chromeos/accessibility/switch_access/item_scan_manager_test.js
@@ -48,7 +48,7 @@ return Navigator.byItem.node_; } -TEST_F('SwitchAccessItemScanManagerTest', 'MoveTo', function() { +TEST_F('SwitchAccessItemScanManagerTest', 'MoveTo', async function() { const website = `<div id="outerGroup"> <div id="group"> <input type="text"> @@ -56,64 +56,63 @@ </div> <button></button> </div>`; - this.runWithLoadedTree(website, (rootWebArea) => { - const desktop = rootWebArea.parent.root; - const textFields = - desktop.findAll({role: chrome.automation.RoleType.TEXT_FIELD}); - assertTrue( - textFields.length === 2 || textFields.length === 3, - 'Should be exactly 2 or 3 text fields.'); - const omnibar = textFields[0]; - const textInput = textFields[textFields.length - 1]; - const sliders = desktop.findAll({role: chrome.automation.RoleType.SLIDER}); - assertEquals(1, sliders.length, 'Should be exactly 1 slider.'); - const slider = sliders[0]; - const group = this.findNodeById('group'); - const outerGroup = this.findNodeById('outerGroup'); + const rootWebArea = await this.runWithLoadedTree(website); + const desktop = rootWebArea.parent.root; + const textFields = + desktop.findAll({role: chrome.automation.RoleType.TEXT_FIELD}); + assertTrue( + textFields.length === 2 || textFields.length === 3, + 'Should be exactly 2 or 3 text fields.'); + const omnibar = textFields[0]; + const textInput = textFields[textFields.length - 1]; + const sliders = desktop.findAll({role: chrome.automation.RoleType.SLIDER}); + assertEquals(1, sliders.length, 'Should be exactly 1 slider.'); + const slider = sliders[0]; + const group = this.findNodeById('group'); + const outerGroup = this.findNodeById('outerGroup'); - Navigator.byItem.moveTo_(omnibar); - assertEquals( - chrome.automation.RoleType.TEXT_FIELD, Navigator.byItem.node_.role, - 'Did not successfully move to the omnibar'); - assertFalse( - Navigator.byItem.group_.isEquivalentTo(group), - 'Omnibar is in the group in page contents somehow'); - assertFalse( - Navigator.byItem.group_.isEquivalentTo(outerGroup), - 'Omnibar is in the outer group in page contents somehow'); - const grandGroup = Navigator.byItem.history_.peek().group; - assertFalse( - grandGroup.isEquivalentTo(group), - 'Group stack contains the group from page contents'); - assertFalse( - grandGroup.isEquivalentTo(outerGroup), - 'Group stack contains the outer group from page contents'); + Navigator.byItem.moveTo_(omnibar); + assertEquals( + chrome.automation.RoleType.TEXT_FIELD, Navigator.byItem.node_.role, + 'Did not successfully move to the omnibar'); + assertFalse( + Navigator.byItem.group_.isEquivalentTo(group), + 'Omnibar is in the group in page contents somehow'); + assertFalse( + Navigator.byItem.group_.isEquivalentTo(outerGroup), + 'Omnibar is in the outer group in page contents somehow'); + const grandGroup = Navigator.byItem.history_.peek().group; + assertFalse( + grandGroup.isEquivalentTo(group), + 'Group stack contains the group from page contents'); + assertFalse( + grandGroup.isEquivalentTo(outerGroup), + 'Group stack contains the outer group from page contents'); - Navigator.byItem.moveTo_(textInput); - assertEquals( - chrome.automation.RoleType.TEXT_FIELD, Navigator.byItem.node_.role, - 'Did not successfully move to the text input'); - assertTrue( - Navigator.byItem.group_.isEquivalentTo(group), - 'Group node was not successfully populated'); - assertTrue( - Navigator.byItem.history_.peek().group.isEquivalentTo(outerGroup), - 'History was not built properly'); + Navigator.byItem.moveTo_(textInput); + assertEquals( + chrome.automation.RoleType.TEXT_FIELD, Navigator.byItem.node_.role, + 'Did not successfully move to the text input'); + assertTrue( + Navigator.byItem.group_.isEquivalentTo(group), + 'Group node was not successfully populated'); + assertTrue( + Navigator.byItem.history_.peek().group.isEquivalentTo(outerGroup), + 'History was not built properly'); - Navigator.byItem.moveTo_(slider); - assertEquals( - chrome.automation.RoleType.SLIDER, Navigator.byItem.node_.role, - 'Did not successfully move to the slider'); + Navigator.byItem.moveTo_(slider); + assertEquals( + chrome.automation.RoleType.SLIDER, Navigator.byItem.node_.role, + 'Did not successfully move to the slider'); - Navigator.byItem.moveTo_(group); - assertTrue(Navigator.byItem.node_.isGroup(), 'Current node is not a group'); - assertTrue( - Navigator.byItem.node_.isEquivalentTo(group), - 'Did not find the right group'); - }); + Navigator.byItem.moveTo_(group); + assertTrue(Navigator.byItem.node_.isGroup(), 'Current node is not a group'); + assertTrue( + Navigator.byItem.node_.isEquivalentTo(group), + 'Did not find the right group'); }); -TEST_F('SwitchAccessItemScanManagerTest', 'JumpTo', function() { +TEST_F('SwitchAccessItemScanManagerTest', 'JumpTo', async function() { const website = `<div id="group1"> <input id="testinput" type="text"> <button></button> @@ -122,37 +121,36 @@ <button></button> <button></button> </div>`; - this.runWithLoadedTree(website, (rootWebArea) => { - const desktop = rootWebArea.parent.root; - const textInput = this.findNodeById('testinput'); - assertNotNullNorUndefined(textInput, 'Text field is undefined'); - const group1 = this.findNodeById('group1'); - const group2 = this.findNodeById('group2'); + const rootWebArea = await this.runWithLoadedTree(website); + const desktop = rootWebArea.parent.root; + const textInput = this.findNodeById('testinput'); + assertNotNullNorUndefined(textInput, 'Text field is undefined'); + const group1 = this.findNodeById('group1'); + const group2 = this.findNodeById('group2'); - Navigator.byItem.moveTo_(textInput); - const textInputNode = Navigator.byItem.node_; - assertEquals( - chrome.automation.RoleType.TEXT_FIELD, textInputNode.role, - 'Did not successfully move to the text input'); - assertTrue( - Navigator.byItem.group_.isEquivalentTo(group1), - 'Group is initialized in an unexpected manner'); + Navigator.byItem.moveTo_(textInput); + const textInputNode = Navigator.byItem.node_; + assertEquals( + chrome.automation.RoleType.TEXT_FIELD, textInputNode.role, + 'Did not successfully move to the text input'); + assertTrue( + Navigator.byItem.group_.isEquivalentTo(group1), + 'Group is initialized in an unexpected manner'); - Navigator.byItem.jumpTo_(BasicRootNode.buildTree(group2)); - assertFalse( - Navigator.byItem.group_.isEquivalentTo(group1), 'Jump did nothing'); - assertTrue( - Navigator.byItem.group_.isEquivalentTo(group2), - 'Jumped to the wrong group'); + Navigator.byItem.jumpTo_(BasicRootNode.buildTree(group2)); + assertFalse( + Navigator.byItem.group_.isEquivalentTo(group1), 'Jump did nothing'); + assertTrue( + Navigator.byItem.group_.isEquivalentTo(group2), + 'Jumped to the wrong group'); - Navigator.byItem.exitGroup_(); - assertTrue( - Navigator.byItem.group_.isEquivalentTo(group1), - 'Did not jump back to the right group.'); - }); + Navigator.byItem.exitGroup_(); + assertTrue( + Navigator.byItem.group_.isEquivalentTo(group1), + 'Did not jump back to the right group.'); }); -TEST_F('SwitchAccessItemScanManagerTest', 'SelectButton', function() { +TEST_F('SwitchAccessItemScanManagerTest', 'SelectButton', async function() { const website = `<button id="test" aria-pressed=false>First Button</button> <button>Second Button</button> <script> @@ -164,380 +162,375 @@ }; </script>`; - this.runWithLoadedTree(website, function(pageContents) { - this.moveToPageContents(pageContents); + const pageContents = await this.runWithLoadedTree(website); + this.moveToPageContents(pageContents); - const node = currentNode().automationNode; - assertNotNullNorUndefined(node, 'Node is invalid'); - assertEquals(node.name, 'First Button', 'Did not find the right node'); + const node = currentNode().automationNode; + assertNotNullNorUndefined(node, 'Node is invalid'); + assertEquals(node.name, 'First Button', 'Did not find the right node'); - node.addEventListener( - chrome.automation.EventType.CHECKED_STATE_CHANGED, - this.newCallback((event) => { - assertEquals( - node.name, event.target.name, - 'Checked state changed on unexpected node'); - })); + node.addEventListener( + chrome.automation.EventType.CHECKED_STATE_CHANGED, + this.newCallback((event) => { + assertEquals( + node.name, event.target.name, + 'Checked state changed on unexpected node'); + })); - Navigator.byItem.node_.performAction('select'); - }); + Navigator.byItem.node_.performAction('select'); }); -TEST_F('SwitchAccessItemScanManagerTest', 'EnterGroup', function() { +TEST_F('SwitchAccessItemScanManagerTest', 'EnterGroup', async function() { const website = `<div id="group"> <button></button> <button></button> </div> <input type="range">`; - this.runWithLoadedTree(website, (rootWebArea) => { - const targetGroup = this.findNodeById('group'); - Navigator.byItem.moveTo_(targetGroup); + const rootWebArea = await this.runWithLoadedTree(website); + const targetGroup = this.findNodeById('group'); + Navigator.byItem.moveTo_(targetGroup); - const originalGroup = Navigator.byItem.group_; - assertEquals( - Navigator.byItem.node_.automationNode.htmlAttributes.id, 'group', - 'Did not move to group properly'); + const originalGroup = Navigator.byItem.group_; + assertEquals( + Navigator.byItem.node_.automationNode.htmlAttributes.id, 'group', + 'Did not move to group properly'); - Navigator.byItem.enterGroup(); - assertEquals( - chrome.automation.RoleType.BUTTON, Navigator.byItem.node_.role, - 'Current node is not a button'); - assertTrue( - Navigator.byItem.group_.isEquivalentTo(targetGroup), - 'Target group was not entered'); + Navigator.byItem.enterGroup(); + assertEquals( + chrome.automation.RoleType.BUTTON, Navigator.byItem.node_.role, + 'Current node is not a button'); + assertTrue( + Navigator.byItem.group_.isEquivalentTo(targetGroup), + 'Target group was not entered'); - Navigator.byItem.exitGroup_(); - assertTrue( - originalGroup.equals(Navigator.byItem.group_), - 'Did not move back to the original group'); - }); + Navigator.byItem.exitGroup_(); + assertTrue( + originalGroup.equals(Navigator.byItem.group_), + 'Did not move back to the original group'); }); -TEST_F('SwitchAccessItemScanManagerTest', 'MoveForward', function() { +TEST_F('SwitchAccessItemScanManagerTest', 'MoveForward', async function() { const website = `<div> <button id="button1"></button> <button id="button2"></button> <button id="button3"></button> </div>`; - this.runWithLoadedTree(website, (rootWebArea) => { - Navigator.byItem.moveTo_(this.findNodeById('button1')); - const button1 = Navigator.byItem.node_; - assertFalse( - button1 instanceof BackButtonNode, - 'button1 should not be a BackButtonNode'); - assertEquals( - 'button1', button1.automationNode.htmlAttributes.id, - 'Current node is not button1'); + const rootWebArea = await this.runWithLoadedTree(website); + Navigator.byItem.moveTo_(this.findNodeById('button1')); + const button1 = Navigator.byItem.node_; + assertFalse( + button1 instanceof BackButtonNode, + 'button1 should not be a BackButtonNode'); + assertEquals( + 'button1', button1.automationNode.htmlAttributes.id, + 'Current node is not button1'); - Navigator.byItem.moveForward(); - assertFalse( - button1.equals(Navigator.byItem.node_), - 'Still on button1 after moveForward()'); - const button2 = Navigator.byItem.node_; - assertFalse( - button2 instanceof BackButtonNode, - 'button2 should not be a BackButtonNode'); - assertEquals( - 'button2', button2.automationNode.htmlAttributes.id, - 'Current node is not button2'); + Navigator.byItem.moveForward(); + assertFalse( + button1.equals(Navigator.byItem.node_), + 'Still on button1 after moveForward()'); + const button2 = Navigator.byItem.node_; + assertFalse( + button2 instanceof BackButtonNode, + 'button2 should not be a BackButtonNode'); + assertEquals( + 'button2', button2.automationNode.htmlAttributes.id, + 'Current node is not button2'); - Navigator.byItem.moveForward(); - assertFalse( - button1.equals(Navigator.byItem.node_), - 'Unexpected navigation to button1'); - assertFalse( - button2.equals(Navigator.byItem.node_), - 'Still on button2 after moveForward()'); - const button3 = Navigator.byItem.node_; - assertFalse( - button3 instanceof BackButtonNode, - 'button3 should not be a BackButtonNode'); - assertEquals( - 'button3', button3.automationNode.htmlAttributes.id, - 'Current node is not button3'); + Navigator.byItem.moveForward(); + assertFalse( + button1.equals(Navigator.byItem.node_), + 'Unexpected navigation to button1'); + assertFalse( + button2.equals(Navigator.byItem.node_), + 'Still on button2 after moveForward()'); + const button3 = Navigator.byItem.node_; + assertFalse( + button3 instanceof BackButtonNode, + 'button3 should not be a BackButtonNode'); + assertEquals( + 'button3', button3.automationNode.htmlAttributes.id, + 'Current node is not button3'); - Navigator.byItem.moveForward(); - assertTrue( - Navigator.byItem.node_ instanceof BackButtonNode, - 'BackButtonNode should come after button3'); + Navigator.byItem.moveForward(); + assertTrue( + Navigator.byItem.node_ instanceof BackButtonNode, + 'BackButtonNode should come after button3'); - Navigator.byItem.moveForward(); - assertTrue( - button1.equals(Navigator.byItem.node_), - 'button1 should come after the BackButtonNode'); - }); + Navigator.byItem.moveForward(); + assertTrue( + button1.equals(Navigator.byItem.node_), + 'button1 should come after the BackButtonNode'); }); -TEST_F('SwitchAccessItemScanManagerTest', 'MoveBackward', function() { +TEST_F('SwitchAccessItemScanManagerTest', 'MoveBackward', async function() { const website = `<div> <button id="button1"></button> <button id="button2"></button> <button id="button3"></button> </div>`; - this.runWithLoadedTree(website, (rootWebArea) => { - Navigator.byItem.moveTo_(this.findNodeById('button1')); - const button1 = Navigator.byItem.node_; - assertFalse( - button1 instanceof BackButtonNode, - 'button1 should not be a BackButtonNode'); - assertEquals( - 'button1', button1.automationNode.htmlAttributes.id, - 'Current node is not button1'); + const rootWebArea = await this.runWithLoadedTree(website); + Navigator.byItem.moveTo_(this.findNodeById('button1')); + const button1 = Navigator.byItem.node_; + assertFalse( + button1 instanceof BackButtonNode, + 'button1 should not be a BackButtonNode'); + assertEquals( + 'button1', button1.automationNode.htmlAttributes.id, + 'Current node is not button1'); - Navigator.byItem.moveBackward(); - assertTrue( - Navigator.byItem.node_ instanceof BackButtonNode, - 'BackButtonNode should come before button1'); + Navigator.byItem.moveBackward(); + assertTrue( + Navigator.byItem.node_ instanceof BackButtonNode, + 'BackButtonNode should come before button1'); - Navigator.byItem.moveBackward(); - assertFalse( - button1.equals(Navigator.byItem.node_), - 'Unexpected navigation to button1'); - const button3 = Navigator.byItem.node_; - assertFalse( - button3 instanceof BackButtonNode, - 'button3 should not be a BackButtonNode'); - assertEquals( - 'button3', button3.automationNode.htmlAttributes.id, - 'Current node is not button3'); + Navigator.byItem.moveBackward(); + assertFalse( + button1.equals(Navigator.byItem.node_), + 'Unexpected navigation to button1'); + const button3 = Navigator.byItem.node_; + assertFalse( + button3 instanceof BackButtonNode, + 'button3 should not be a BackButtonNode'); + assertEquals( + 'button3', button3.automationNode.htmlAttributes.id, + 'Current node is not button3'); - Navigator.byItem.moveBackward(); - assertFalse( - button3.equals(Navigator.byItem.node_), - 'Still on button3 after moveBackward()'); - assertFalse(button1.equals(Navigator.byItem.node_), 'Skipped button2'); - const button2 = Navigator.byItem.node_; - assertFalse( - button2 instanceof BackButtonNode, - 'button2 should not be a BackButtonNode'); - assertEquals( - 'button2', button2.automationNode.htmlAttributes.id, - 'Current node is not button2'); + Navigator.byItem.moveBackward(); + assertFalse( + button3.equals(Navigator.byItem.node_), + 'Still on button3 after moveBackward()'); + assertFalse(button1.equals(Navigator.byItem.node_), 'Skipped button2'); + const button2 = Navigator.byItem.node_; + assertFalse( + button2 instanceof BackButtonNode, + 'button2 should not be a BackButtonNode'); + assertEquals( + 'button2', button2.automationNode.htmlAttributes.id, + 'Current node is not button2'); - Navigator.byItem.moveBackward(); - assertTrue( - button1.equals(Navigator.byItem.node_), - 'button1 should come before button2'); - }); + Navigator.byItem.moveBackward(); + assertTrue( + button1.equals(Navigator.byItem.node_), + 'button1 should come before button2'); }); TEST_F( 'SwitchAccessItemScanManagerTest', 'NodeUndefinedBeforeTreeChangeRemoved', - function() { + async function() { const website = `<div> <button id="button1"></button> </div>`; - this.runWithLoadedTree(website, (rootWebArea) => { - Navigator.byItem.moveTo_(this.findNodeById('button1')); - const button1 = Navigator.byItem.node_; - assertFalse( - button1 instanceof BackButtonNode, - 'button1 should not be a BackButtonNode'); - assertEquals( - 'button1', button1.automationNode.htmlAttributes.id, - 'Current node is not button1'); + const rootWebArea = await this.runWithLoadedTree(website); + Navigator.byItem.moveTo_(this.findNodeById('button1')); + const button1 = Navigator.byItem.node_; + assertFalse( + button1 instanceof BackButtonNode, + 'button1 should not be a BackButtonNode'); + assertEquals( + 'button1', button1.automationNode.htmlAttributes.id, + 'Current node is not button1'); - // Simulate the underlying node's deletion. Note that this is different - // than an orphaned node (which can have a valid AutomationNode - // instance, but no backing C++ object, so attributes returned like role - // are undefined). - Navigator.byItem.node_.baseNode_ = undefined; + // Simulate the underlying node's deletion. Note that this is different + // than an orphaned node (which can have a valid AutomationNode + // instance, but no backing C++ object, so attributes returned like role + // are undefined). + Navigator.byItem.node_.baseNode_ = undefined; - // Tree change removed gets sent by C++ after the tree has already - // applied changes (so this comes after the above clearing). - Navigator.byItem.onTreeChange_( - {type: chrome.automation.TreeChangeType.NODE_REMOVED}); - }); + // Tree change removed gets sent by C++ after the tree has already + // applied changes (so this comes after the above clearing). + Navigator.byItem.onTreeChange_( + {type: chrome.automation.TreeChangeType.NODE_REMOVED}); }); TEST_F( 'SwitchAccessItemScanManagerTest', 'ScanAndTypeVirtualKeyboard', - function() { + async function() { const website = `<input type="text" id="testinput"></input>`; - this.runWithLoadedTree(website, async (rootWebArea) => { - // SA initially focuses this node; wait for it first. + const rootWebArea = await this.runWithLoadedTree(website); + // SA initially focuses this node; wait for it first. + await new Promise(resolve => { + chrome.commandLinePrivate.hasSwitch( + 'lacros-chrome-path', async hasLacrosChromePath => { + if (!hasLacrosChromePath) { + await this.untilFocusIs( + {className: 'BrowserNonClientFrameViewChromeOS'}); + } + resolve(); + }); + }); + + // Move to the text field. + Navigator.byItem.moveTo_(this.findNodeById('testinput')); + const input = Navigator.byItem.node_; + assertEquals( + 'testinput', input.automationNode.htmlAttributes.id, + 'Current node is not input'); + input.performAction(SwitchAccessMenuAction.KEYBOARD); + + const keyboard = + await this.untilFocusIs({role: chrome.automation.RoleType.KEYBOARD}); + keyboard.performAction('select'); + + const key = await this.untilFocusIs({instance: KeyboardNode}); + + key.performAction('select'); + + if (input.automationNode.value !== 'q') { + // Wait for the potential value change. await new Promise(resolve => { - chrome.commandLinePrivate.hasSwitch( - 'lacros-chrome-path', async hasLacrosChromePath => { - if (!hasLacrosChromePath) { - await this.untilFocusIs( - {className: 'BrowserNonClientFrameViewChromeOS'}); + input.automationNode.addEventListener( + chrome.automation.EventType.VALUE_CHANGED, (event) => { + if (event.target.value === 'q') { + resolve(); } - resolve(); }); }); - - // Move to the text field. - Navigator.byItem.moveTo_(this.findNodeById('testinput')); - const input = Navigator.byItem.node_; - assertEquals( - 'testinput', input.automationNode.htmlAttributes.id, - 'Current node is not input'); - input.performAction(SwitchAccessMenuAction.KEYBOARD); - - const keyboard = await this.untilFocusIs( - {role: chrome.automation.RoleType.KEYBOARD}); - keyboard.performAction('select'); - - const key = await this.untilFocusIs({instance: KeyboardNode}); - - key.performAction('select'); - - if (input.automationNode.value !== 'q') { - // Wait for the potential value change. - await new Promise(resolve => { - input.automationNode.addEventListener( - chrome.automation.EventType.VALUE_CHANGED, (event) => { - if (event.target.value === 'q') { - resolve(); - } - }); - }); - } - }); + } }); -TEST_F('SwitchAccessItemScanManagerTest', 'DismissVirtualKeyboard', function() { - const website = - `<input type="text" id="testinput"></input><button>ok</button>`; - this.runWithLoadedTree(website, async (rootWebArea) => { - // SA initially focuses this node in Ash Chrome; wait for it first. - await new Promise(resolve => { - chrome.commandLinePrivate.hasSwitch( - 'lacros-chrome-path', async hasLacrosChromePath => { - if (!hasLacrosChromePath) { - await this.untilFocusIs( - {className: 'BrowserNonClientFrameViewChromeOS'}); - } - resolve(); - }); - }); - - // Move to the text field. - Navigator.byItem.moveTo_(this.findNodeById('testinput')); - const input = Navigator.byItem.node_; - assertEquals( - 'testinput', input.automationNode.htmlAttributes.id, - 'Current node is not input'); - input.performAction(SwitchAccessMenuAction.KEYBOARD); - - const keyboard = - await this.untilFocusIs({role: chrome.automation.RoleType.KEYBOARD}); - keyboard.performAction('select'); - - // Grab the key. - const key = await this.untilFocusIs({instance: KeyboardNode}); - - // Simulate a page focusing the ok button. - const okButton = rootWebArea.find({attributes: {name: 'ok'}}); - okButton.focus(); - - // Wait for the keyboard to become invisible and the ok button to be focused - // by automation. - await new Promise(resolve => { - okButton.addEventListener(chrome.automation.EventType.FOCUS, resolve); - }); - await new Promise(resolve => { - keyboard.automationNode.addEventListener( - chrome.automation.EventType.STATE_CHANGED, (event) => { - if (event.target.role === chrome.automation.RoleType.KEYBOARD && - event.target.state.invisible) { +TEST_F( + 'SwitchAccessItemScanManagerTest', 'DismissVirtualKeyboard', + async function() { + const website = + `<input type="text" id="testinput"></input><button>ok</button>`; + const rootWebArea = await this.runWithLoadedTree(website); + // SA initially focuses this node in Ash Chrome; wait for it first. + await new Promise(resolve => { + chrome.commandLinePrivate.hasSwitch( + 'lacros-chrome-path', async hasLacrosChromePath => { + if (!hasLacrosChromePath) { + await this.untilFocusIs( + {className: 'BrowserNonClientFrameViewChromeOS'}); + } resolve(); - } - }); - }); + }); + }); - // We should end up back on the focused button in SA. - const button = - await this.untilFocusIs({role: chrome.automation.RoleType.BUTTON}); - assertEquals('ok', button.automationNode.name); - }); -}); + // Move to the text field. + Navigator.byItem.moveTo_(this.findNodeById('testinput')); + const input = Navigator.byItem.node_; + assertEquals( + 'testinput', input.automationNode.htmlAttributes.id, + 'Current node is not input'); + input.performAction(SwitchAccessMenuAction.KEYBOARD); + + const keyboard = + await this.untilFocusIs({role: chrome.automation.RoleType.KEYBOARD}); + keyboard.performAction('select'); + + // Grab the key. + const key = await this.untilFocusIs({instance: KeyboardNode}); + + // Simulate a page focusing the ok button. + const okButton = rootWebArea.find({attributes: {name: 'ok'}}); + okButton.focus(); + + // Wait for the keyboard to become invisible and the ok button to be + // focused by automation. + await new Promise(resolve => { + okButton.addEventListener(chrome.automation.EventType.FOCUS, resolve); + }); + await new Promise(resolve => { + keyboard.automationNode.addEventListener( + chrome.automation.EventType.STATE_CHANGED, (event) => { + if (event.target.role === chrome.automation.RoleType.KEYBOARD && + event.target.state.invisible) { + resolve(); + } + }); + }); + + // We should end up back on the focused button in SA. + const button = + await this.untilFocusIs({role: chrome.automation.RoleType.BUTTON}); + assertEquals('ok', button.automationNode.name); + }); // TODO(crbug.com/1260231): Test is flaky. TEST_F( 'SwitchAccessItemScanManagerTest', 'DISABLED_ChildrenChangedDoesNotRefresh', - function() { + async function() { const website = ` <div id="slider" role="slider"> <div role="group"><div></div></div> </div> <button>done</button> `; - this.runWithLoadedTree(website, async (rootWebArea) => { - // SA initially focuses this node in Ash Chrome; wait for it first. - await new Promise(resolve => { - chrome.commandLinePrivate.hasSwitch( - 'lacros-chrome-path', async hasLacrosChromePath => { - if (!hasLacrosChromePath) { - await this.untilFocusIs( - {className: 'BrowserNonClientFrameViewChromeOS'}); - } - resolve(); - }); - }); - - // Move to the slider. - Navigator.byItem.moveTo_(this.findNodeById('slider')); - const slider = Navigator.byItem.node_; - assertEquals( - 'slider', slider.automationNode.htmlAttributes.id, - 'Current node is not slider'); - - // Trigger a children changed on the group. - const automationGroup = - rootWebArea.find({role: chrome.automation.RoleType.GROUP}); - assertTrue(!!automationGroup); - const group = Navigator.byItem.group_; - assertTrue(!!group); - const handler = group.childrenChangedHandler_; - assertTrue(!!handler); - - // Fake a children changed event. - handler.eventStack_ = [{ - type: chrome.automation.EventType.CHILDREN_CHANGED, - target: automationGroup - }]; - handler.handleEvent_(); - - // This subtree is not interesting, so it should not have triggered a - // complete refresh of the SA tree. - assertEquals(slider, Navigator.byItem.node_); + const rootWebArea = await this.runWithLoadedTree(website); + // SA initially focuses this node in Ash Chrome; wait for it first. + await new Promise(resolve => { + chrome.commandLinePrivate.hasSwitch( + 'lacros-chrome-path', async hasLacrosChromePath => { + if (!hasLacrosChromePath) { + await this.untilFocusIs( + {className: 'BrowserNonClientFrameViewChromeOS'}); + } + resolve(); + }); }); + + // Move to the slider. + Navigator.byItem.moveTo_(this.findNodeById('slider')); + const slider = Navigator.byItem.node_; + assertEquals( + 'slider', slider.automationNode.htmlAttributes.id, + 'Current node is not slider'); + + // Trigger a children changed on the group. + const automationGroup = + rootWebArea.find({role: chrome.automation.RoleType.GROUP}); + assertTrue(!!automationGroup); + const group = Navigator.byItem.group_; + assertTrue(!!group); + const handler = group.childrenChangedHandler_; + assertTrue(!!handler); + + // Fake a children changed event. + handler.eventStack_ = [{ + type: chrome.automation.EventType.CHILDREN_CHANGED, + target: automationGroup + }]; + handler.handleEvent_(); + + // This subtree is not interesting, so it should not have triggered a + // complete refresh of the SA tree. + assertEquals(slider, Navigator.byItem.node_); }); -TEST_F('SwitchAccessItemScanManagerTest', 'InitialFocus', function() { +TEST_F('SwitchAccessItemScanManagerTest', 'InitialFocus', async function() { const website = `<input></input><button autofocus></button>`; - this.runWithLoadedTree(website, async (rootWebArea) => { - // The button should have initial focus. This ensures we move past the focus - // event below. - const button = - await this.untilFocusIs({role: chrome.automation.RoleType.BUTTON}); + const rootWebArea = await this.runWithLoadedTree(website); + // The button should have initial focus. This ensures we move past the focus + // event below. + const button = + await this.untilFocusIs({role: chrome.automation.RoleType.BUTTON}); - // Build a new ItemScanManager to see what it sets as the initial node. - const desktop = rootWebArea.parent.root; - assertEquals( - chrome.automation.RoleType.DESKTOP, desktop.role, - `Unexpected desktop ${desktop.toString()}`); - const manager = new ItemScanManager(desktop); - assertEquals( - button.automationNode, manager.node_.automationNode, - `Unexpected focus ${manager.node_.debugString()}`); - }); + // Build a new ItemScanManager to see what it sets as the initial node. + const desktop = rootWebArea.parent.root; + assertEquals( + chrome.automation.RoleType.DESKTOP, desktop.role, + `Unexpected desktop ${desktop.toString()}`); + const manager = new ItemScanManager(desktop); + assertEquals( + button.automationNode, manager.node_.automationNode, + `Unexpected focus ${manager.node_.debugString()}`); }); -TEST_F('SwitchAccessItemScanManagerTest', 'SyncFocusToNewWindow', function() { - const website1 = `<button autofocus>one</button>`; - const website2 = `<button autofocus>two</button>`; - this.runWithLoadedTree(website1, async (rootWebArea) => { - // Wait for the first button to get SA focused. - const button1 = await this.untilFocusIs( - {role: chrome.automation.RoleType.BUTTON, name: 'one'}); +TEST_F( + 'SwitchAccessItemScanManagerTest', 'SyncFocusToNewWindow', + async function() { + const website1 = `<button autofocus>one</button>`; + const website2 = `<button autofocus>two</button>`; + await this.runWithLoadedTree(website1); + // Wait for the first button to get SA focused. + const button1 = await this.untilFocusIs( + {role: chrome.automation.RoleType.BUTTON, name: 'one'}); - // Launch a new browser window and load up the second site. - EventGenerator.sendKeyPress(KeyCode.N, {ctrl: true}); - this.runWithLoadedTree(website2, async (rootWebArea) => { + // Launch a new browser window and load up the second site. + EventGenerator.sendKeyPress(KeyCode.N, {ctrl: true}); + await this.runWithLoadedTree(website2); // Wait for the second button to get SA focused. const button2 = await this.untilFocusIs( {role: chrome.automation.RoleType.BUTTON, name: 'two'}); @@ -594,52 +587,49 @@ }); assertEquals(currentFocus, button2.automationNode); }); - }); -}); // TODO(crbug.com/1219067): Unflake. TEST_F( 'SwitchAccessItemScanManagerTest', 'DISABLED_LockScreenBlocksUserSession', - function() { + async function() { const website = `<button autofocus>kitties!</button>`; - this.runWithLoadedTree(website, async (rootWebArea) => { - let button = - await this.untilFocusIs({role: chrome.automation.RoleType.BUTTON}); - assertEquals('kitties!', button.automationNode.name); + await this.runWithLoadedTree(website); + let button = + await this.untilFocusIs({role: chrome.automation.RoleType.BUTTON}); + assertEquals('kitties!', button.automationNode.name); - // Lock the screen. - EventGenerator.sendKeyPress(KeyCode.L, {search: true}); + // Lock the screen. + EventGenerator.sendKeyPress(KeyCode.L, {search: true}); - // Wait for focus to move to the password field. - await this.untilFocusIs({ - role: chrome.automation.RoleType.TEXT_FIELD, - name: 'Password for stub-user@example.com' - }); - - // The button is no longer in the tree because the screen is locked. - const predicate = (node) => node.name === 'kitties!' && - node.role === chrome.automation.RoleType.BUTTON; - assertNotNullNorUndefined( - this.desktop_, 'this.desktop_ is null or undefined.'); - const treeWalker = new AutomationTreeWalker( - this.desktop_, constants.Dir.FORWARD, {visit: predicate}); - const node = treeWalker.next().node; - assertEquals(null, node); - - // Log in again and confirm that the button is back and gets focus - // again. - EventGenerator.sendKeyPress(KeyCode.T); - EventGenerator.sendKeyPress(KeyCode.E); - EventGenerator.sendKeyPress(KeyCode.S); - EventGenerator.sendKeyPress(KeyCode.T); - EventGenerator.sendKeyPress(KeyCode.ZERO); - EventGenerator.sendKeyPress(KeyCode.ZERO); - EventGenerator.sendKeyPress(KeyCode.ZERO); - EventGenerator.sendKeyPress(KeyCode.ZERO); - EventGenerator.sendKeyPress(KeyCode.RETURN); - - button = - await this.untilFocusIs({role: chrome.automation.RoleType.BUTTON}); - assertEquals('kitties!', button.automationNode.name); + // Wait for focus to move to the password field. + await this.untilFocusIs({ + role: chrome.automation.RoleType.TEXT_FIELD, + name: 'Password for stub-user@example.com' }); + + // The button is no longer in the tree because the screen is locked. + const predicate = (node) => node.name === 'kitties!' && + node.role === chrome.automation.RoleType.BUTTON; + assertNotNullNorUndefined( + this.desktop_, 'this.desktop_ is null or undefined.'); + const treeWalker = new AutomationTreeWalker( + this.desktop_, constants.Dir.FORWARD, {visit: predicate}); + const node = treeWalker.next().node; + assertEquals(null, node); + + // Log in again and confirm that the button is back and gets focus + // again. + EventGenerator.sendKeyPress(KeyCode.T); + EventGenerator.sendKeyPress(KeyCode.E); + EventGenerator.sendKeyPress(KeyCode.S); + EventGenerator.sendKeyPress(KeyCode.T); + EventGenerator.sendKeyPress(KeyCode.ZERO); + EventGenerator.sendKeyPress(KeyCode.ZERO); + EventGenerator.sendKeyPress(KeyCode.ZERO); + EventGenerator.sendKeyPress(KeyCode.ZERO); + EventGenerator.sendKeyPress(KeyCode.RETURN); + + button = + await this.untilFocusIs({role: chrome.automation.RoleType.BUTTON}); + assertEquals('kitties!', button.automationNode.name); });
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/basic_node_test.js b/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/basic_node_test.js index c8802f4..a825fd4 100644 --- a/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/basic_node_test.js +++ b/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/basic_node_test.js
@@ -20,7 +20,7 @@ } }; -TEST_F('SwitchAccessBasicNodeTest', 'AsRootNode', function() { +TEST_F('SwitchAccessBasicNodeTest', 'AsRootNode', async function() { const website = `<div aria-label="outer"> <div aria-label="inner"> <input type="range"> @@ -28,30 +28,29 @@ </div> <button></button> </div>`; - this.runWithLoadedTree(website, (rootWebArea) => { - const slider = rootWebArea.find({role: chrome.automation.RoleType.SLIDER}); - const inner = slider.parent; - assertNotEquals(undefined, inner, 'Could not find inner group'); - const outer = inner.parent; - assertNotEquals(undefined, outer, 'Could not find outer group'); + const rootWebArea = await this.runWithLoadedTree(website); + const slider = rootWebArea.find({role: chrome.automation.RoleType.SLIDER}); + const inner = slider.parent; + assertNotEquals(undefined, inner, 'Could not find inner group'); + const outer = inner.parent; + assertNotEquals(undefined, outer, 'Could not find outer group'); - const outerRootNode = BasicRootNode.buildTree(outer, null); - const innerNode = outerRootNode.firstChild; - assertTrue(innerNode.isGroup(), 'Inner group node is not a group'); + const outerRootNode = BasicRootNode.buildTree(outer, null); + const innerNode = outerRootNode.firstChild; + assertTrue(innerNode.isGroup(), 'Inner group node is not a group'); - const innerRootNode = innerNode.asRootNode(); - assertEquals(3, innerRootNode.children.length, 'Expected 3 children'); - const sliderNode = innerRootNode.firstChild; - assertEquals( - chrome.automation.RoleType.SLIDER, sliderNode.role, - 'First child should be a slider'); - assertEquals( - chrome.automation.RoleType.BUTTON, sliderNode.next.role, - 'Second child should be a button'); - assertTrue( - innerRootNode.lastChild instanceof BackButtonNode, - 'Final child should be the back button'); - }); + const innerRootNode = innerNode.asRootNode(); + assertEquals(3, innerRootNode.children.length, 'Expected 3 children'); + const sliderNode = innerRootNode.firstChild; + assertEquals( + chrome.automation.RoleType.SLIDER, sliderNode.role, + 'First child should be a slider'); + assertEquals( + chrome.automation.RoleType.BUTTON, sliderNode.next.role, + 'Second child should be a button'); + assertTrue( + innerRootNode.lastChild instanceof BackButtonNode, + 'Final child should be the back button'); }); TEST_F('SwitchAccessBasicNodeTest', 'Equals', function() { @@ -124,57 +123,56 @@ }); }); -TEST_F('SwitchAccessBasicNodeTest', 'Actions', function() { +TEST_F('SwitchAccessBasicNodeTest', 'Actions', async function() { const website = `<input type="text"> <button></button> <input type="range" min=1 max=5 value=3>`; - this.runWithLoadedTree(website, (rootWebArea) => { - const textField = BasicNode.create( - rootWebArea.find({role: chrome.automation.RoleType.TEXT_FIELD}), - new SARootNode()); + const rootWebArea = await this.runWithLoadedTree(website); + const textField = BasicNode.create( + rootWebArea.find({role: chrome.automation.RoleType.TEXT_FIELD}), + new SARootNode()); - assertEquals( - chrome.automation.RoleType.TEXT_FIELD, textField.role, - 'Text field node is not a text field'); - assertTrue( - textField.hasAction(SwitchAccessMenuAction.KEYBOARD), - 'Text field does not have action KEYBOARD'); - assertTrue( - textField.hasAction(SwitchAccessMenuAction.DICTATION), - 'Text field does not have action DICTATION'); - assertFalse( - textField.hasAction(SwitchAccessMenuAction.SELECT), - 'Text field has action SELECT'); + assertEquals( + chrome.automation.RoleType.TEXT_FIELD, textField.role, + 'Text field node is not a text field'); + assertTrue( + textField.hasAction(SwitchAccessMenuAction.KEYBOARD), + 'Text field does not have action KEYBOARD'); + assertTrue( + textField.hasAction(SwitchAccessMenuAction.DICTATION), + 'Text field does not have action DICTATION'); + assertFalse( + textField.hasAction(SwitchAccessMenuAction.SELECT), + 'Text field has action SELECT'); - const button = BasicNode.create( - rootWebArea.find({role: chrome.automation.RoleType.BUTTON}), - new SARootNode()); + const button = BasicNode.create( + rootWebArea.find({role: chrome.automation.RoleType.BUTTON}), + new SARootNode()); - assertEquals( - chrome.automation.RoleType.BUTTON, button.role, - 'Button node is not a button'); - assertTrue( - button.hasAction(SwitchAccessMenuAction.SELECT), - 'Button does not have action SELECT'); - assertFalse( - button.hasAction(SwitchAccessMenuAction.KEYBOARD), - 'Button has action KEYBOARD'); - assertFalse( - button.hasAction(SwitchAccessMenuAction.DICTATION), - 'Button has action DICTATION'); + assertEquals( + chrome.automation.RoleType.BUTTON, button.role, + 'Button node is not a button'); + assertTrue( + button.hasAction(SwitchAccessMenuAction.SELECT), + 'Button does not have action SELECT'); + assertFalse( + button.hasAction(SwitchAccessMenuAction.KEYBOARD), + 'Button has action KEYBOARD'); + assertFalse( + button.hasAction(SwitchAccessMenuAction.DICTATION), + 'Button has action DICTATION'); - const slider = BasicNode.create( - rootWebArea.find({role: chrome.automation.RoleType.SLIDER}), - new SARootNode()); + const slider = BasicNode.create( + rootWebArea.find({role: chrome.automation.RoleType.SLIDER}), + new SARootNode()); - assertEquals( - chrome.automation.RoleType.SLIDER, slider.role, - 'Slider node is not a slider'); - assertTrue( - slider.hasAction(SwitchAccessMenuAction.INCREMENT), - 'Slider does not have action INCREMENT'); - assertTrue( - slider.hasAction(SwitchAccessMenuAction.DECREMENT), - 'Slider does not have action DECREMENT'); - }); + assertEquals( + chrome.automation.RoleType.SLIDER, slider.role, + 'Slider node is not a slider'); + assertTrue( + slider.hasAction(SwitchAccessMenuAction.INCREMENT), + 'Slider does not have action INCREMENT'); + assertTrue( + slider.hasAction(SwitchAccessMenuAction.DECREMENT), + 'Slider does not have action DECREMENT'); });
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/point_scan_manager_test.js b/chrome/browser/resources/chromeos/accessibility/switch_access/point_scan_manager_test.js index d0aad87..eb34588 100644 --- a/chrome/browser/resources/chromeos/accessibility/switch_access/point_scan_manager_test.js +++ b/chrome/browser/resources/chromeos/accessibility/switch_access/point_scan_manager_test.js
@@ -23,84 +23,89 @@ } }; -TEST_F('SwitchAccessPointScanManagerTest', 'PointScanLeftClick', function() { - const website = '<input type=checkbox style="width: 800px; height: 800px;">'; - this.runWithLoadedTree(website, async (rootWebArea) => { - const checkbox = rootWebArea.find({role: 'checkBox'}); - checkbox.doDefault(); +TEST_F( + 'SwitchAccessPointScanManagerTest', 'PointScanLeftClick', async function() { + const website = + '<input type=checkbox style="width: 800px; height: 800px;">'; + const rootWebArea = await this.runWithLoadedTree(website); + const checkbox = rootWebArea.find({role: 'checkBox'}); + checkbox.doDefault(); - const verifyChecked = checked => resolve => { - const checkedHandler = event => { - assertEquals(event.target.checked, String(checked)); - event.target.removeEventListener( - chrome.automation.EventType.CHECKED_STATE_CHANGED, checkedHandler); - resolve(); - }; - checkbox.addEventListener( - chrome.automation.EventType.CHECKED_STATE_CHANGED, checkedHandler); - }; - await new Promise(verifyChecked(true)); - - SwitchAccess.mode = SAConstants.Mode.POINT_SCAN; - Navigator.byPoint.point_ = {x: 600, y: 600}; - Navigator.byPoint.performMouseAction(SwitchAccessMenuAction.LEFT_CLICK); - await new Promise(verifyChecked(false)); - }); -}); - -TEST_F('SwitchAccessPointScanManagerTest', 'PointScanRightClick', function() { - const website = '<p>Kittens r cute</p>'; - this.runWithLoadedTree(website, async (rootWebArea) => { - const findParams = {role: 'menuItem', attributes: {name: /Back.*/}}; - // Context menu with back button shouldn't exist yet. - const initialMenuItem = rootWebArea.find(findParams); - assertEquals(initialMenuItem, null); - - const menuItemLoaded = () => resolve => { - const observer = treeChange => { - // Wait for the context menu with the back button to show up. - const menuItem = treeChange.target.find(findParams); - if (menuItem !== null) { - chrome.automation.removeTreeChangeObserver(observer); + const verifyChecked = checked => resolve => { + const checkedHandler = event => { + assertEquals(event.target.checked, String(checked)); + event.target.removeEventListener( + chrome.automation.EventType.CHECKED_STATE_CHANGED, + checkedHandler); resolve(); - } + }; + checkbox.addEventListener( + chrome.automation.EventType.CHECKED_STATE_CHANGED, checkedHandler); }; - chrome.automation.addTreeChangeObserver('allTreeChanges', observer); - }; + await new Promise(verifyChecked(true)); - SwitchAccess.mode = SAConstants.Mode.POINT_SCAN; - Navigator.byPoint.point_ = {x: 400, y: 400}; - Navigator.byPoint.performMouseAction(SwitchAccessMenuAction.RIGHT_CLICK); - await new Promise(menuItemLoaded()); - }); -}); + SwitchAccess.mode = SAConstants.Mode.POINT_SCAN; + Navigator.byPoint.point_ = {x: 600, y: 600}; + Navigator.byPoint.performMouseAction(SwitchAccessMenuAction.LEFT_CLICK); + await new Promise(verifyChecked(false)); + }); + +TEST_F( + 'SwitchAccessPointScanManagerTest', 'PointScanRightClick', + async function() { + const website = '<p>Kittens r cute</p>'; + const rootWebArea = await this.runWithLoadedTree(website); + const findParams = {role: 'menuItem', attributes: {name: /Back.*/}}; + // Context menu with back button shouldn't exist yet. + const initialMenuItem = rootWebArea.find(findParams); + assertEquals(initialMenuItem, null); + + const menuItemLoaded = () => resolve => { + const observer = treeChange => { + // Wait for the context menu with the back button to show up. + const menuItem = treeChange.target.find(findParams); + if (menuItem !== null) { + chrome.automation.removeTreeChangeObserver(observer); + resolve(); + } + }; + chrome.automation.addTreeChangeObserver('allTreeChanges', observer); + }; + + SwitchAccess.mode = SAConstants.Mode.POINT_SCAN; + Navigator.byPoint.point_ = {x: 400, y: 400}; + Navigator.byPoint.performMouseAction(SwitchAccessMenuAction.RIGHT_CLICK); + await new Promise(menuItemLoaded()); + }); // Verifies that chrome.accessibilityPrivate.setFocusRings() is not called when // point scanning is running. -TEST_F('SwitchAccessPointScanManagerTest', 'PointScanNoFocusRings', function() { - const sleep = () => { - return new Promise(resolve => setTimeout(resolve, 2 * 1000)); - }; +TEST_F( + 'SwitchAccessPointScanManagerTest', 'PointScanNoFocusRings', + async function() { + const sleep = () => { + return new Promise(resolve => setTimeout(resolve, 2 * 1000)); + }; - const site = '<button>Test</button>'; - this.runWithLoadedTree(site, async (rootWebArea) => { - let setFocusRingsCallCount = 0; - // Mock this API to track how many times it's called. - chrome.accessibilityPrivate.setFocusRings = (focusRings) => { - setFocusRingsCallCount += 1; - }; - assertEquals(0, setFocusRingsCallCount); - Navigator.byPoint.start(); - // When point scanning starts, setFocusRings() gets called once to clear - // the focus rings. - assertEquals(1, setFocusRingsCallCount); - // Simulate the page focusing the button. - const button = rootWebArea.find({role: chrome.automation.RoleType.BUTTON}); - assertNotNullNorUndefined(button); - button.focus(); - // Allow point scanning to run for 2 seconds and ensure no extra calls to - // setFocusRings(). - await sleep(); - assertEquals(1, setFocusRingsCallCount); - }); -}); + const site = '<button>Test</button>'; + const rootWebArea = await this.runWithLoadedTree(site); + let setFocusRingsCallCount = 0; + // Mock this API to track how many times it's called. + chrome.accessibilityPrivate.setFocusRings = (focusRings) => { + setFocusRingsCallCount += 1; + }; + assertEquals(0, setFocusRingsCallCount); + Navigator.byPoint.start(); + // When point scanning starts, setFocusRings() gets called once to clear + // the focus rings. + assertEquals(1, setFocusRingsCallCount); + // Simulate the page focusing the button. + const button = + rootWebArea.find({role: chrome.automation.RoleType.BUTTON}); + assertNotNullNorUndefined(button); + button.focus(); + // Allow point scanning to run for 2 seconds and ensure no extra calls to + // setFocusRings(). + await sleep(); + assertEquals(1, setFocusRingsCallCount); + });
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access_predicate_test.js b/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access_predicate_test.js index fa359d6..4567d90 100644 --- a/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access_predicate_test.js +++ b/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access_predicate_test.js
@@ -89,154 +89,151 @@ }; } -TEST_F('SwitchAccessPredicateTest', 'IsInteresting', function() { - this.runWithLoadedTree(testWebsite(), (loadedPage) => { - const t = getTree(loadedPage); - const cache = new SACache(); +TEST_F('SwitchAccessPredicateTest', 'IsInteresting', async function() { + const loadedPage = await this.runWithLoadedTree(testWebsite()); + const t = getTree(loadedPage); + const cache = new SACache(); - // The scope is only used to verify the locations are not the same, and - // since the buildTree function depends on isInteresting, pass in null - // for the scope. - assertTrue( - SwitchAccessPredicate.isInteresting(t.root, null, cache), - 'Root should be interesting'); - assertTrue( - SwitchAccessPredicate.isInteresting(t.upper1, null, cache), - 'Upper1 should be interesting'); - assertTrue( - SwitchAccessPredicate.isInteresting(t.upper2, null, cache), - 'Upper2 should be interesting'); - assertTrue( - SwitchAccessPredicate.isInteresting(t.lower1, null, cache), - 'Lower1 should be interesting'); - assertFalse( - SwitchAccessPredicate.isInteresting(t.lower2, null, cache), - 'Lower2 should not be interesting'); - assertFalse( - SwitchAccessPredicate.isInteresting(t.lower3, null, cache), - 'Lower3 should not be interesting'); - assertTrue( - SwitchAccessPredicate.isInteresting(t.leaf1, null, cache), - 'Leaf1 should be interesting'); - assertFalse( - SwitchAccessPredicate.isInteresting(t.leaf2, null, cache), - 'Leaf2 should not be interesting'); - assertTrue( - SwitchAccessPredicate.isInteresting(t.leaf3, null, cache), - 'Leaf3 should be interesting'); - assertFalse( - SwitchAccessPredicate.isInteresting(t.leaf4, null, cache), - 'Leaf4 should not be interesting'); - assertTrue( - SwitchAccessPredicate.isInteresting(t.leaf5, null, cache), - 'Leaf5 should be interesting'); - assertFalse( - SwitchAccessPredicate.isInteresting(t.leaf6, null, cache), - 'Leaf6 should not be interesting'); - assertFalse( - SwitchAccessPredicate.isInteresting(t.leaf7, null, cache), - 'Leaf7 should not be interesting'); - }); + // The scope is only used to verify the locations are not the same, and + // since the buildTree function depends on isInteresting, pass in null + // for the scope. + assertTrue( + SwitchAccessPredicate.isInteresting(t.root, null, cache), + 'Root should be interesting'); + assertTrue( + SwitchAccessPredicate.isInteresting(t.upper1, null, cache), + 'Upper1 should be interesting'); + assertTrue( + SwitchAccessPredicate.isInteresting(t.upper2, null, cache), + 'Upper2 should be interesting'); + assertTrue( + SwitchAccessPredicate.isInteresting(t.lower1, null, cache), + 'Lower1 should be interesting'); + assertFalse( + SwitchAccessPredicate.isInteresting(t.lower2, null, cache), + 'Lower2 should not be interesting'); + assertFalse( + SwitchAccessPredicate.isInteresting(t.lower3, null, cache), + 'Lower3 should not be interesting'); + assertTrue( + SwitchAccessPredicate.isInteresting(t.leaf1, null, cache), + 'Leaf1 should be interesting'); + assertFalse( + SwitchAccessPredicate.isInteresting(t.leaf2, null, cache), + 'Leaf2 should not be interesting'); + assertTrue( + SwitchAccessPredicate.isInteresting(t.leaf3, null, cache), + 'Leaf3 should be interesting'); + assertFalse( + SwitchAccessPredicate.isInteresting(t.leaf4, null, cache), + 'Leaf4 should not be interesting'); + assertTrue( + SwitchAccessPredicate.isInteresting(t.leaf5, null, cache), + 'Leaf5 should be interesting'); + assertFalse( + SwitchAccessPredicate.isInteresting(t.leaf6, null, cache), + 'Leaf6 should not be interesting'); + assertFalse( + SwitchAccessPredicate.isInteresting(t.leaf7, null, cache), + 'Leaf7 should not be interesting'); }); -TEST_F('SwitchAccessPredicateTest', 'IsGroup', function() { - this.runWithLoadedTree(testWebsite(), (loadedPage) => { - const t = getTree(loadedPage); - const cache = new SACache(); +TEST_F('SwitchAccessPredicateTest', 'IsGroup', async function() { + const loadedPage = await this.runWithLoadedTree(testWebsite()); + const t = getTree(loadedPage); + const cache = new SACache(); - // The scope is only used to verify the locations are not the same, and - // since the buildTree function depends on isGroup, pass in null for - // the scope. - assertTrue( - SwitchAccessPredicate.isGroup(t.root, null, cache), - 'Root should be a group'); - assertTrue( - SwitchAccessPredicate.isGroup(t.upper1, null, cache), - 'Upper1 should be a group'); - assertFalse( - SwitchAccessPredicate.isGroup(t.upper2, null, cache), - 'Upper2 should not be a group'); - assertTrue( - SwitchAccessPredicate.isGroup(t.lower1, null, cache), - 'Lower1 should be a group'); - assertFalse( - SwitchAccessPredicate.isGroup(t.lower2, null, cache), - 'Lower2 should not be a group'); - assertFalse( - SwitchAccessPredicate.isGroup(t.lower3, null, cache), - 'Lower3 should not be a group'); - assertFalse( - SwitchAccessPredicate.isGroup(t.leaf1, null, cache), - 'Leaf1 should not be a group'); - assertFalse( - SwitchAccessPredicate.isGroup(t.leaf2, null, cache), - 'Leaf2 should not be a group'); - assertFalse( - SwitchAccessPredicate.isGroup(t.leaf3, null, cache), - 'Leaf3 should not be a group'); - assertFalse( - SwitchAccessPredicate.isGroup(t.leaf4, null, cache), - 'Leaf4 should not be a group'); - assertFalse( - SwitchAccessPredicate.isGroup(t.leaf5, null, cache), - 'Leaf5 should not be a group'); - assertFalse( - SwitchAccessPredicate.isGroup(t.leaf6, null, cache), - 'Leaf6 should not be a group'); - assertFalse( - SwitchAccessPredicate.isGroup(t.leaf7, null, cache), - 'Leaf7 should not be a group'); - }); + // The scope is only used to verify the locations are not the same, and + // since the buildTree function depends on isGroup, pass in null for + // the scope. + assertTrue( + SwitchAccessPredicate.isGroup(t.root, null, cache), + 'Root should be a group'); + assertTrue( + SwitchAccessPredicate.isGroup(t.upper1, null, cache), + 'Upper1 should be a group'); + assertFalse( + SwitchAccessPredicate.isGroup(t.upper2, null, cache), + 'Upper2 should not be a group'); + assertTrue( + SwitchAccessPredicate.isGroup(t.lower1, null, cache), + 'Lower1 should be a group'); + assertFalse( + SwitchAccessPredicate.isGroup(t.lower2, null, cache), + 'Lower2 should not be a group'); + assertFalse( + SwitchAccessPredicate.isGroup(t.lower3, null, cache), + 'Lower3 should not be a group'); + assertFalse( + SwitchAccessPredicate.isGroup(t.leaf1, null, cache), + 'Leaf1 should not be a group'); + assertFalse( + SwitchAccessPredicate.isGroup(t.leaf2, null, cache), + 'Leaf2 should not be a group'); + assertFalse( + SwitchAccessPredicate.isGroup(t.leaf3, null, cache), + 'Leaf3 should not be a group'); + assertFalse( + SwitchAccessPredicate.isGroup(t.leaf4, null, cache), + 'Leaf4 should not be a group'); + assertFalse( + SwitchAccessPredicate.isGroup(t.leaf5, null, cache), + 'Leaf5 should not be a group'); + assertFalse( + SwitchAccessPredicate.isGroup(t.leaf6, null, cache), + 'Leaf6 should not be a group'); + assertFalse( + SwitchAccessPredicate.isGroup(t.leaf7, null, cache), + 'Leaf7 should not be a group'); }); -TEST_F('SwitchAccessPredicateTest', 'IsInterestingSubtree', function() { - this.runWithLoadedTree(testWebsite(), (loadedPage) => { - const t = getTree(loadedPage); - const cache = new SACache(); +TEST_F('SwitchAccessPredicateTest', 'IsInterestingSubtree', async function() { + const loadedPage = await this.runWithLoadedTree(testWebsite()); + const t = getTree(loadedPage); + const cache = new SACache(); - assertTrue( - SwitchAccessPredicate.isInterestingSubtree(t.root, cache), - 'Root should be an interesting subtree'); - assertTrue( - SwitchAccessPredicate.isInterestingSubtree(t.upper1, cache), - 'Upper1 should be an interesting subtree'); - assertTrue( - SwitchAccessPredicate.isInterestingSubtree(t.upper2, cache), - 'Upper2 should be an interesting subtree'); - assertTrue( - SwitchAccessPredicate.isInterestingSubtree(t.lower1, cache), - 'Lower1 should be an interesting subtree'); - assertTrue( - SwitchAccessPredicate.isInterestingSubtree(t.lower2, cache), - 'Lower2 should be an interesting subtree'); - assertFalse( - SwitchAccessPredicate.isInterestingSubtree(t.lower3, cache), - 'Lower3 should not be an interesting subtree'); - assertTrue( - SwitchAccessPredicate.isInterestingSubtree(t.leaf1, cache), - 'Leaf1 should be an interesting subtree'); - assertFalse( - SwitchAccessPredicate.isInterestingSubtree(t.leaf2, cache), - 'Leaf2 should not be an interesting subtree'); - assertTrue( - SwitchAccessPredicate.isInterestingSubtree(t.leaf3, cache), - 'Leaf3 should be an interesting subtree'); - assertFalse( - SwitchAccessPredicate.isInterestingSubtree(t.leaf4, cache), - 'Leaf4 should not be an interesting subtree'); - assertTrue( - SwitchAccessPredicate.isInterestingSubtree(t.leaf5, cache), - 'Leaf5 should be an interesting subtree'); - assertFalse( - SwitchAccessPredicate.isInterestingSubtree(t.leaf6, cache), - 'Leaf6 should not be an interesting subtree'); - assertFalse( - SwitchAccessPredicate.isInterestingSubtree(t.leaf7, cache), - 'Leaf7 should not be an interesting subtree'); - }); + assertTrue( + SwitchAccessPredicate.isInterestingSubtree(t.root, cache), + 'Root should be an interesting subtree'); + assertTrue( + SwitchAccessPredicate.isInterestingSubtree(t.upper1, cache), + 'Upper1 should be an interesting subtree'); + assertTrue( + SwitchAccessPredicate.isInterestingSubtree(t.upper2, cache), + 'Upper2 should be an interesting subtree'); + assertTrue( + SwitchAccessPredicate.isInterestingSubtree(t.lower1, cache), + 'Lower1 should be an interesting subtree'); + assertTrue( + SwitchAccessPredicate.isInterestingSubtree(t.lower2, cache), + 'Lower2 should be an interesting subtree'); + assertFalse( + SwitchAccessPredicate.isInterestingSubtree(t.lower3, cache), + 'Lower3 should not be an interesting subtree'); + assertTrue( + SwitchAccessPredicate.isInterestingSubtree(t.leaf1, cache), + 'Leaf1 should be an interesting subtree'); + assertFalse( + SwitchAccessPredicate.isInterestingSubtree(t.leaf2, cache), + 'Leaf2 should not be an interesting subtree'); + assertTrue( + SwitchAccessPredicate.isInterestingSubtree(t.leaf3, cache), + 'Leaf3 should be an interesting subtree'); + assertFalse( + SwitchAccessPredicate.isInterestingSubtree(t.leaf4, cache), + 'Leaf4 should not be an interesting subtree'); + assertTrue( + SwitchAccessPredicate.isInterestingSubtree(t.leaf5, cache), + 'Leaf5 should be an interesting subtree'); + assertFalse( + SwitchAccessPredicate.isInterestingSubtree(t.leaf6, cache), + 'Leaf6 should not be an interesting subtree'); + assertFalse( + SwitchAccessPredicate.isInterestingSubtree(t.leaf7, cache), + 'Leaf7 should not be an interesting subtree'); }); -TEST_F('SwitchAccessPredicateTest', 'IsActionable', function() { +TEST_F('SwitchAccessPredicateTest', 'IsActionable', async function() { const treeString = `<button style="position:absolute; top:-100px;">offscreen</button> <button disabled>disabled</button> @@ -246,62 +243,63 @@ <input type="range" aria-label="slider" value=5 min=0 max=10> <div id="clickable" role="listitem" onclick="2+2"></div> <div id="div1"><p>p1</p></div>`; - this.runWithLoadedTree(treeString, (loadedPage) => { - const cache = new SACache(); + await this.runWithLoadedTree(treeString); + const cache = new SACache(); - const offscreenButton = this.findNodeByNameAndRole('offscreen', 'button'); - assertFalse( - SwitchAccessPredicate.isActionable(offscreenButton, cache), - 'Offscreen objects should not be actionable'); + const offscreenButton = this.findNodeByNameAndRole('offscreen', 'button'); + assertFalse( + SwitchAccessPredicate.isActionable(offscreenButton, cache), + 'Offscreen objects should not be actionable'); - const disabledButton = this.findNodeByNameAndRole('disabled', 'button'); - assertFalse( - SwitchAccessPredicate.isActionable(disabledButton, cache), - 'Disabled objects should not be actionable'); + const disabledButton = this.findNodeByNameAndRole('disabled', 'button'); + assertFalse( + SwitchAccessPredicate.isActionable(disabledButton, cache), + 'Disabled objects should not be actionable'); - assertFalse( - SwitchAccessPredicate.isActionable(loadedPage, cache), - 'Root web area should not be directly actionable'); + assertFalse( + SwitchAccessPredicate.isActionable(loadedPage, cache), + 'Root web area should not be directly actionable'); - const link1 = this.findNodeByNameAndRole('link1', 'link'); - assertTrue( - SwitchAccessPredicate.isActionable(link1, cache), - 'Links should be actionable'); + const link1 = this.findNodeByNameAndRole('link1', 'link'); + assertTrue( + SwitchAccessPredicate.isActionable(link1, cache), + 'Links should be actionable'); - const input1 = this.findNodeByNameAndRole('input1', 'textField'); - assertTrue( - SwitchAccessPredicate.isActionable(input1, cache), - 'Inputs should be actionable'); + const input1 = this.findNodeByNameAndRole('input1', 'textField'); + assertTrue( + SwitchAccessPredicate.isActionable(input1, cache), + 'Inputs should be actionable'); - const button3 = this.findNodeByNameAndRole('button3', 'button'); - assertTrue( - SwitchAccessPredicate.isActionable(button3, cache), - 'Buttons should be actionable'); + const button3 = this.findNodeByNameAndRole('button3', 'button'); + assertTrue( + SwitchAccessPredicate.isActionable(button3, cache), + 'Buttons should be actionable'); - const slider = this.findNodeByNameAndRole('slider', 'slider'); - assertTrue( - SwitchAccessPredicate.isActionable(slider, cache), - 'Sliders should be actionable'); + const slider = this.findNodeByNameAndRole('slider', 'slider'); + assertTrue( + SwitchAccessPredicate.isActionable(slider, cache), + 'Sliders should be actionable'); - const clickable = this.findNodeById('clickable'); - assertTrue( - SwitchAccessPredicate.isActionable(clickable, cache), - 'Clickable list items should be actionable'); + const clickable = this.findNodeById('clickable'); + assertTrue( + SwitchAccessPredicate.isActionable(clickable, cache), + 'Clickable list items should be actionable'); - const div1 = this.findNodeById('div1'); - assertFalse( - SwitchAccessPredicate.isActionable(div1, cache), - 'Divs should not generally be actionable'); + const div1 = this.findNodeById('div1'); + assertFalse( + SwitchAccessPredicate.isActionable(div1, cache), + 'Divs should not generally be actionable'); - const p1 = this.findNodeByNameAndRole('p1', 'staticText'); - assertFalse( - SwitchAccessPredicate.isActionable(p1, cache), - 'Static text should not generally be actionable'); - }); + const p1 = this.findNodeByNameAndRole('p1', 'staticText'); + assertFalse( + SwitchAccessPredicate.isActionable(p1, cache), + 'Static text should not generally be actionable'); }); -TEST_F('SwitchAccessPredicateTest', 'IsActionableFocusableElements', function() { - const treeString = `<div id="noChildren" tabindex=0></div> +TEST_F( + 'SwitchAccessPredicateTest', 'IsActionableFocusableElements', + async function() { + const treeString = `<div id="noChildren" tabindex=0></div> <div id="oneInterestingChild" tabindex=0> <div> <div> @@ -322,163 +320,158 @@ <p>p2</p> <p>p3</p> </div>`; - this.runWithLoadedTree(treeString, (loadedPage) => { - const cache = new SACache(); + await this.runWithLoadedTree(treeString); + const cache = new SACache(); - const noChildren = this.findNodeById('noChildren'); - assertTrue( - SwitchAccessPredicate.isActionable(noChildren, cache), - 'Focusable element with no children should be actionable'); + const noChildren = this.findNodeById('noChildren'); + assertTrue( + SwitchAccessPredicate.isActionable(noChildren, cache), + 'Focusable element with no children should be actionable'); - const oneInterestingChild = this.findNodeById('oneInterestingChild'); - assertFalse( - SwitchAccessPredicate.isActionable(oneInterestingChild, cache), - 'Focusable element with an interesting child should not be actionable'); + const oneInterestingChild = this.findNodeById('oneInterestingChild'); + assertFalse( + SwitchAccessPredicate.isActionable(oneInterestingChild, cache), + 'Focusable element with an interesting child should not be actionable'); - const interestingChildren = this.findNodeById('interestingChildren'); - assertFalse( - SwitchAccessPredicate.isActionable(interestingChildren, cache), - 'Focusable element with interesting children should not be actionable'); + const interestingChildren = this.findNodeById('interestingChildren'); + assertFalse( + SwitchAccessPredicate.isActionable(interestingChildren, cache), + 'Focusable element with interesting children should not be ' + + 'actionable'); - const oneUninterestingChild = this.findNodeById('oneUninterestingChild'); - assertTrue( - SwitchAccessPredicate.isActionable(oneUninterestingChild, cache), - 'Focusable element with one uninteresting child should be actionable'); + const oneUninterestingChild = this.findNodeById('oneUninterestingChild'); + assertTrue( + SwitchAccessPredicate.isActionable(oneUninterestingChild, cache), + 'Focusable element with one uninteresting child should be ' + + 'actionable'); - const uninterestingChildren = this.findNodeById('uninterestingChildren'); - assertTrue( - SwitchAccessPredicate.isActionable(uninterestingChildren, cache), - 'Focusable element with uninteresting children should be actionable'); - }); + const uninterestingChildren = this.findNodeById('uninterestingChildren'); + assertTrue( + SwitchAccessPredicate.isActionable(uninterestingChildren, cache), + 'Focusable element with uninteresting children should be actionable'); + }); + +TEST_F('SwitchAccessPredicateTest', 'LeafPredicate', async function() { + const loadedPage = await this.runWithLoadedTree(testWebsite()); + const t = getTree(loadedPage); + const cache = new SACache(); + + // Start with root as scope + let leaf = SwitchAccessPredicate.leaf(t.root, cache); + assertFalse(leaf(t.root), 'Root should not be a leaf node'); + assertTrue(leaf(t.upper1), 'Upper1 should be a leaf node for root tree'); + assertTrue(leaf(t.upper2), 'Upper2 should be a leaf node for root tree'); + + // Set upper1 as scope + leaf = SwitchAccessPredicate.leaf(t.upper1, cache); + assertFalse(leaf(t.upper1), 'Upper1 should not be a leaf for upper1 tree'); + assertTrue(leaf(t.lower1), 'Lower1 should be a leaf for upper1 tree'); + assertTrue(leaf(t.leaf4), 'leaf4 should be a leaf for upper1 tree'); + assertTrue(leaf(t.leaf5), 'leaf5 should be a leaf for upper1 tree'); + + // Set lower1 as scope + leaf = SwitchAccessPredicate.leaf(t.lower1, cache); + assertFalse(leaf(t.lower1), 'Lower1 should not be a leaf for lower1 tree'); + assertTrue(leaf(t.leaf1), 'Leaf1 should be a leaf for lower1 tree'); + assertTrue(leaf(t.leaf2), 'Leaf2 should be a leaf for lower1 tree'); + assertTrue(leaf(t.leaf3), 'Leaf3 should be a leaf for lower1 tree'); }); -TEST_F('SwitchAccessPredicateTest', 'LeafPredicate', function() { - this.runWithLoadedTree(testWebsite(), (loadedPage) => { - const t = getTree(loadedPage); - const cache = new SACache(); +TEST_F('SwitchAccessPredicateTest', 'RootPredicate', async function() { + const loadedPage = await this.runWithLoadedTree(testWebsite()); + const t = getTree(loadedPage); - // Start with root as scope - let leaf = SwitchAccessPredicate.leaf(t.root, cache); - assertFalse(leaf(t.root), 'Root should not be a leaf node'); - assertTrue(leaf(t.upper1), 'Upper1 should be a leaf node for root tree'); - assertTrue(leaf(t.upper2), 'Upper2 should be a leaf node for root tree'); + // Start with root as scope + let root = SwitchAccessPredicate.root(t.root); + assertTrue(root(t.root), 'Root should be a root of the root tree'); + assertFalse(root(t.upper1), 'Upper1 should not be a root of the root tree'); + assertFalse(root(t.upper2), 'Upper2 should not be a root of the root tree'); - // Set upper1 as scope - leaf = SwitchAccessPredicate.leaf(t.upper1, cache); - assertFalse(leaf(t.upper1), 'Upper1 should not be a leaf for upper1 tree'); - assertTrue(leaf(t.lower1), 'Lower1 should be a leaf for upper1 tree'); - assertTrue(leaf(t.leaf4), 'leaf4 should be a leaf for upper1 tree'); - assertTrue(leaf(t.leaf5), 'leaf5 should be a leaf for upper1 tree'); + // Set upper1 as scope + root = SwitchAccessPredicate.root(t.upper1); + assertTrue(root(t.upper1), 'Upper1 should be a root of the upper1 tree'); + assertFalse(root(t.lower1), 'Lower1 should not be a root of the upper1 tree'); + assertFalse(root(t.lower2), 'Lower2 should not be a root of the upper1 tree'); - // Set lower1 as scope - leaf = SwitchAccessPredicate.leaf(t.lower1, cache); - assertFalse(leaf(t.lower1), 'Lower1 should not be a leaf for lower1 tree'); - assertTrue(leaf(t.leaf1), 'Leaf1 should be a leaf for lower1 tree'); - assertTrue(leaf(t.leaf2), 'Leaf2 should be a leaf for lower1 tree'); - assertTrue(leaf(t.leaf3), 'Leaf3 should be a leaf for lower1 tree'); - }); + // Set lower1 as scope + root = SwitchAccessPredicate.root(t.lower1); + assertTrue(root(t.lower1), 'Lower1 should be a root of the lower1 tree'); + assertFalse(root(t.leaf1), 'Leaf1 should not be a root of the lower1 tree'); + assertFalse(root(t.leaf2), 'Leaf2 should not be a root of the lower1 tree'); + assertFalse(root(t.leaf3), 'Leaf3 should not be a root of the lower1 tree'); }); -TEST_F('SwitchAccessPredicateTest', 'RootPredicate', function() { - this.runWithLoadedTree(testWebsite(), (loadedPage) => { - const t = getTree(loadedPage); +TEST_F('SwitchAccessPredicateTest', 'VisitPredicate', async function() { + const loadedPage = await this.runWithLoadedTree(testWebsite()); + const t = getTree(loadedPage); + const cache = new SACache(); - // Start with root as scope - let root = SwitchAccessPredicate.root(t.root); - assertTrue(root(t.root), 'Root should be a root of the root tree'); - assertFalse(root(t.upper1), 'Upper1 should not be a root of the root tree'); - assertFalse(root(t.upper2), 'Upper2 should not be a root of the root tree'); + // Start with root as scope + let visit = SwitchAccessPredicate.visit(t.root, cache); + assertTrue(visit(t.root), 'Root should be visited in root tree'); + assertTrue(visit(t.upper1), 'Upper1 should be visited in root tree'); + assertTrue(visit(t.upper2), 'Upper2 should be visited in root tree'); - // Set upper1 as scope - root = SwitchAccessPredicate.root(t.upper1); - assertTrue(root(t.upper1), 'Upper1 should be a root of the upper1 tree'); - assertFalse( - root(t.lower1), 'Lower1 should not be a root of the upper1 tree'); - assertFalse( - root(t.lower2), 'Lower2 should not be a root of the upper1 tree'); + // Set upper1 as scope + visit = SwitchAccessPredicate.visit(t.upper1, cache); + assertTrue(visit(t.upper1), 'Upper1 should be visited in upper1 tree'); + assertTrue(visit(t.lower1), 'Lower1 should be visited in upper1 tree'); + assertFalse(visit(t.lower2), 'Lower2 should not be visited in upper1 tree'); + assertFalse(visit(t.leaf4), 'Leaf4 should not be visited in upper1 tree'); + assertTrue(visit(t.leaf5), 'Leaf5 should be visited in upper1 tree'); - // Set lower1 as scope - root = SwitchAccessPredicate.root(t.lower1); - assertTrue(root(t.lower1), 'Lower1 should be a root of the lower1 tree'); - assertFalse(root(t.leaf1), 'Leaf1 should not be a root of the lower1 tree'); - assertFalse(root(t.leaf2), 'Leaf2 should not be a root of the lower1 tree'); - assertFalse(root(t.leaf3), 'Leaf3 should not be a root of the lower1 tree'); - }); + // Set lower1 as scope + visit = SwitchAccessPredicate.visit(t.lower1, cache); + assertTrue(visit(t.lower1), 'Lower1 should be visited in lower1 tree'); + assertTrue(visit(t.leaf1), 'Leaf1 should be visited in lower1 tree'); + assertFalse(visit(t.leaf2), 'Leaf2 should not be visited in lower1 tree'); + assertTrue(visit(t.leaf3), 'Leaf3 should be visited in lower1 tree'); + + // An uninteresting subtree should return false, regardless of scope + assertFalse(visit(t.lower3), 'Lower3 should not be visited in lower1 tree'); + assertFalse(visit(t.leaf6), 'Leaf6 should not be visited in lower1 tree'); + assertFalse(visit(t.leaf7), 'Leaf7 should not be visited in lower1 tree'); }); -TEST_F('SwitchAccessPredicateTest', 'VisitPredicate', function() { - this.runWithLoadedTree(testWebsite(), (loadedPage) => { - const t = getTree(loadedPage); - const cache = new SACache(); +TEST_F('SwitchAccessPredicateTest', 'Cache', async function() { + const loadedPage = await this.runWithLoadedTree(testWebsite()); + const t = getTree(loadedPage); + const cache = new SACache(); - // Start with root as scope - let visit = SwitchAccessPredicate.visit(t.root, cache); - assertTrue(visit(t.root), 'Root should be visited in root tree'); - assertTrue(visit(t.upper1), 'Upper1 should be visited in root tree'); - assertTrue(visit(t.upper2), 'Upper2 should be visited in root tree'); - - // Set upper1 as scope - visit = SwitchAccessPredicate.visit(t.upper1, cache); - assertTrue(visit(t.upper1), 'Upper1 should be visited in upper1 tree'); - assertTrue(visit(t.lower1), 'Lower1 should be visited in upper1 tree'); - assertFalse(visit(t.lower2), 'Lower2 should not be visited in upper1 tree'); - assertFalse(visit(t.leaf4), 'Leaf4 should not be visited in upper1 tree'); - assertTrue(visit(t.leaf5), 'Leaf5 should be visited in upper1 tree'); - - // Set lower1 as scope - visit = SwitchAccessPredicate.visit(t.lower1, cache); - assertTrue(visit(t.lower1), 'Lower1 should be visited in lower1 tree'); - assertTrue(visit(t.leaf1), 'Leaf1 should be visited in lower1 tree'); - assertFalse(visit(t.leaf2), 'Leaf2 should not be visited in lower1 tree'); - assertTrue(visit(t.leaf3), 'Leaf3 should be visited in lower1 tree'); - - // An uninteresting subtree should return false, regardless of scope - assertFalse(visit(t.lower3), 'Lower3 should not be visited in lower1 tree'); - assertFalse(visit(t.leaf6), 'Leaf6 should not be visited in lower1 tree'); - assertFalse(visit(t.leaf7), 'Leaf7 should not be visited in lower1 tree'); - }); -}); - -TEST_F('SwitchAccessPredicateTest', 'Cache', function() { - this.runWithLoadedTree(testWebsite(), (loadedPage) => { - const t = getTree(loadedPage); - const cache = new SACache(); - - let locationAccessCount = 0; - class TestRoot extends SARootNode { - /** @override */ - get location() { - locationAccessCount++; - return null; - } + let locationAccessCount = 0; + class TestRoot extends SARootNode { + /** @override */ + get location() { + locationAccessCount++; + return null; } - const group = new TestRoot(t.root); + } + const group = new TestRoot(t.root); - assertTrue( - SwitchAccessPredicate.isGroup(t.root, group, cache), - 'Root should be a group'); - assertEquals( - locationAccessCount, 1, - 'Location should have been accessed to calculate isGroup'); - assertTrue( - SwitchAccessPredicate.isGroup(t.root, group, cache), - 'isGroup value should not change'); - assertEquals( - locationAccessCount, 1, - 'Cache should have been used, avoiding second location access'); + assertTrue( + SwitchAccessPredicate.isGroup(t.root, group, cache), + 'Root should be a group'); + assertEquals( + locationAccessCount, 1, + 'Location should have been accessed to calculate isGroup'); + assertTrue( + SwitchAccessPredicate.isGroup(t.root, group, cache), + 'isGroup value should not change'); + assertEquals( + locationAccessCount, 1, + 'Cache should have been used, avoiding second location access'); - locationAccessCount = 0; - assertFalse( - SwitchAccessPredicate.isGroup(t.leaf1, group, cache), - 'Leaf should not be a group'); - assertEquals( - locationAccessCount, 1, - 'Location should have been accessed to calculate isGroup'); - assertFalse( - SwitchAccessPredicate.isGroup(t.leaf1, group, cache), - 'isGroup value should not change'); - assertEquals( - locationAccessCount, 1, - 'Cache should have been used, avoiding second location access'); - }); + locationAccessCount = 0; + assertFalse( + SwitchAccessPredicate.isGroup(t.leaf1, group, cache), + 'Leaf should not be a group'); + assertEquals( + locationAccessCount, 1, + 'Location should have been accessed to calculate isGroup'); + assertFalse( + SwitchAccessPredicate.isGroup(t.leaf1, group, cache), + 'isGroup value should not change'); + assertEquals( + locationAccessCount, 1, + 'Cache should have been used, avoiding second location access'); });
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/test_utility.js b/chrome/browser/resources/chromeos/accessibility/switch_access/test_utility.js index dc8a8ee9..cc9aa05 100644 --- a/chrome/browser/resources/chromeos/accessibility/switch_access/test_utility.js +++ b/chrome/browser/resources/chromeos/accessibility/switch_access/test_utility.js
@@ -55,7 +55,7 @@ chrome.accessibilityPrivate.SwitchAccessCommand.SELECT); }, - /** Only call from inside runWithLoadedTree() */ + /** Only call after runWithLoadedTree() */ startFocusInside(rootWebArea) { if (!rootWebArea) { throw new Error('Web root node is undefined');
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/text_navigation_manager_test.js b/chrome/browser/resources/chromeos/accessibility/switch_access/text_navigation_manager_test.js index 23d35a0..a11625ef 100644 --- a/chrome/browser/resources/chromeos/accessibility/switch_access/text_navigation_manager_test.js +++ b/chrome/browser/resources/chromeos/accessibility/switch_access/text_navigation_manager_test.js
@@ -23,7 +23,7 @@ * executes the specified text navigation action. Upon detecting the * text navigation action, the node will verify that the action correctly * changed the index of the text caret. - * @param {!SwitchAccessE2ETest} testHelper + * @param {!SwitchAccessE2ETest} testFixture * @param {{content: string, * initialIndex: number, * targetIndex: number, @@ -32,7 +32,7 @@ * cols: (number || undefined), * wrap: (string || undefined)}} textParams */ -function runTextNavigationTest(testHelper, textParams) { +async function runTextNavigationTest(testFixture, textParams) { // Required parameters. const textContent = textParams.content; const initialTextIndex = textParams.initialIndex; @@ -47,16 +47,15 @@ const website = generateWebsiteWithTextArea( textId, textContent, initialTextIndex, textCols, textWrap); - testHelper.runWithLoadedTree(website, function(rootWebArea) { - const inputNode = this.findNodeById(textId); - assertNotEquals(inputNode, null); + await testFixture.runWithLoadedTree(website); + const inputNode = this.findNodeById(textId); + assertNotEquals(inputNode, null); - setUpCursorChangeListener( - testHelper, inputNode, initialTextIndex, targetTextIndex, - targetTextIndex); + setUpCursorChangeListener( + testFixture, inputNode, initialTextIndex, targetTextIndex, + targetTextIndex); - textNavigationAction(); - }); + textNavigationAction(); } /** @@ -81,10 +80,10 @@ * in the text area (optional). -wrap: the wrap attribute ("hard" or "soft") of * the text area (optional). * - * @param {!SwitchAccessE2ETest} testHelper + * @param {!SwitchAccessE2ETest} testFixture * @param {selectionTextParams} textParams, */ -function runTextSelectionTest(testHelper, textParams) { +async function runTextSelectionTest(testFixture, textParams) { // Required parameters. const textContent = textParams.content; const initialTextIndex = textParams.initialIndex; @@ -106,25 +105,24 @@ navigationTargetIndex = targetTextStartIndex; } - testHelper.runWithLoadedTree(website, function(rootWebArea) { - const inputNode = this.findNodeById(textId); - assertNotEquals(inputNode, null); - checkNodeIsFocused(inputNode); - const callback = testHelper.newCallback(function() { - setUpCursorChangeListener( - testHelper, inputNode, targetTextEndIndex, targetTextStartIndex, - targetTextEndIndex); - testHelper.textNavigationManager.saveSelectEnd(); - }); - - testHelper.textNavigationManager.saveSelectStart(); - + await testFixture.runWithLoadedTree(website); + const inputNode = this.findNodeById(textId); + assertNotEquals(inputNode, null); + checkNodeIsFocused(inputNode); + const callback = testFixture.newCallback(function() { setUpCursorChangeListener( - testHelper, inputNode, initialTextIndex, navigationTargetIndex, - navigationTargetIndex, callback); - - textNavigationAction(); + testFixture, inputNode, targetTextEndIndex, targetTextStartIndex, + targetTextEndIndex); + testFixture.textNavigationManager.saveSelectEnd(); }); + + testFixture.textNavigationManager.saveSelectStart(); + + setUpCursorChangeListener( + testFixture, inputNode, initialTextIndex, navigationTargetIndex, + navigationTargetIndex, callback); + + textNavigationAction(); } /** @@ -170,7 +168,7 @@ * change from the text navigation action). Also assumes that * the text navigation and selection actions directly changes the text caret * to the correct index (with no intermediate movements). - * @param {!SwitchAccessE2ETest} testHelper + * @param {!SwitchAccessE2ETest} testFixture * @param {!AutomationNode} inputNode * @param {number} initialTextIndex * @param {number} targetTextStartIndex @@ -178,7 +176,7 @@ * @param {function() || undefined} callback */ function setUpCursorChangeListener( - testHelper, inputNode, initialTextIndex, targetTextStartIndex, + testFixture, inputNode, initialTextIndex, targetTextStartIndex, targetTextEndIndex, callback) { // Ensures that the text index has changed before checking the new index. const checkActionFinished = function(tab) { @@ -192,7 +190,7 @@ }; // Test will not exit until this check is called. - const checkTextIndex = testHelper.newCallback(function() { + const checkTextIndex = testFixture.newCallback(function() { assertEquals(inputNode.textSelStart, targetTextStartIndex); assertEquals(inputNode.textSelEnd, targetTextEndIndex); // If there's a callback then this is the navigation listener for a @@ -212,8 +210,8 @@ // TODO(crbug.com/1268230): Re-enable test. TEST_F( 'SwitchAccessTextNavigationManagerTest', 'DISABLED_JumpToBeginning', - function() { - runTextNavigationTest(this, { + async function() { + await runTextNavigationTest(this, { content: 'hi there', initialIndex: 6, targetIndex: 0, @@ -225,8 +223,9 @@ // TODO(crbug.com/1268230): Re-enable test. TEST_F( - 'SwitchAccessTextNavigationManagerTest', 'DISABLED_JumpToEnd', function() { - runTextNavigationTest(this, { + 'SwitchAccessTextNavigationManagerTest', 'DISABLED_JumpToEnd', + async function() { + await runTextNavigationTest(this, { content: 'hi there', initialIndex: 3, targetIndex: 8, @@ -239,8 +238,8 @@ // TODO(crbug.com/1177096) Renable test TEST_F( 'SwitchAccessTextNavigationManagerTest', 'DISABLED_MoveBackwardOneChar', - function() { - runTextNavigationTest(this, { + async function() { + await runTextNavigationTest(this, { content: 'parrots!', initialIndex: 7, targetIndex: 6, @@ -253,8 +252,8 @@ // TODO(crbug.com/1268230): Re-enable test. TEST_F( 'SwitchAccessTextNavigationManagerTest', 'DISABLED_MoveBackwardOneWord', - function() { - runTextNavigationTest(this, { + async function() { + await runTextNavigationTest(this, { content: 'more parrots!', initialIndex: 5, targetIndex: 0, @@ -267,8 +266,8 @@ // TODO(crbug.com/1268230): Re-enable test. TEST_F( 'SwitchAccessTextNavigationManagerTest', 'DISABLED_MoveForwardOneChar', - function() { - runTextNavigationTest(this, { + async function() { + await runTextNavigationTest(this, { content: 'hello', initialIndex: 0, targetIndex: 1, @@ -281,8 +280,8 @@ // TODO(crbug.com/1268230): Re-enable test. TEST_F( 'SwitchAccessTextNavigationManagerTest', 'DISABLED_MoveForwardOneWord', - function() { - runTextNavigationTest(this, { + async function() { + await runTextNavigationTest(this, { content: 'more parrots!', initialIndex: 4, targetIndex: 12, @@ -295,8 +294,8 @@ // TODO(crbug.com/1268230): Re-enable test. TEST_F( 'SwitchAccessTextNavigationManagerTest', 'DISABLED_MoveUpOneLine', - function() { - runTextNavigationTest(this, { + async function() { + await runTextNavigationTest(this, { content: 'more parrots!', initialIndex: 7, targetIndex: 2, @@ -311,8 +310,8 @@ // TODO(crbug.com/1268230): Re-enable test. TEST_F( 'SwitchAccessTextNavigationManagerTest', 'DISABLED_MoveDownOneLine', - function() { - runTextNavigationTest(this, { + async function() { + await runTextNavigationTest(this, { content: 'more parrots!', initialIndex: 3, targetIndex: 8, @@ -331,19 +330,18 @@ */ TEST_F( 'SwitchAccessTextNavigationManagerTest', 'DISABLED_SelectStart', - function() { + async function() { const website = generateWebsiteWithTextArea('test', 'test123', 3, 20, 'hard'); - this.runWithLoadedTree(website, function(rootWebArea) { - const inputNode = this.findNodeById('test'); - assertNotEquals(inputNode, null); - checkNodeIsFocused(inputNode); + await this.runWithLoadedTree(website); + const inputNode = this.findNodeById('test'); + assertNotEquals(inputNode, null); + checkNodeIsFocused(inputNode); - this.textNavigationManager.saveSelectStart(); - const startIndex = this.textNavigationManager.selectionStartIndex_; - assertEquals(startIndex, 3); - }); + this.textNavigationManager.saveSelectStart(); + const startIndex = this.textNavigationManager.selectionStartIndex_; + assertEquals(startIndex, 3); }); /** @@ -352,23 +350,23 @@ * bounds */ TEST_F( - 'SwitchAccessTextNavigationManagerTest', 'DISABLED_SelectEnd', function() { + 'SwitchAccessTextNavigationManagerTest', 'DISABLED_SelectEnd', + async function() { const website = generateWebsiteWithTextArea('test', 'test 123', 6, 20, 'hard'); - this.runWithLoadedTree(website, function(rootWebArea) { - const inputNode = this.findNodeById('test'); - assertNotEquals(inputNode, null); - checkNodeIsFocused(inputNode); + await this.runWithLoadedTree(website); + const inputNode = this.findNodeById('test'); + assertNotEquals(inputNode, null); + checkNodeIsFocused(inputNode); - const startIndex = 3; - this.textNavigationManager.selectionStartIndex_ = startIndex; - this.textNavigationManager.selectionStartObject_ = inputNode; - this.textNavigationManager.saveSelectEnd(); - const endIndex = inputNode.textSelEnd; - assertEquals(6, endIndex); - }); + const startIndex = 3; + this.textNavigationManager.selectionStartIndex_ = startIndex; + this.textNavigationManager.selectionStartObject_ = inputNode; + this.textNavigationManager.saveSelectEnd(); + const endIndex = inputNode.textSelEnd; + assertEquals(6, endIndex); }); /** @@ -377,8 +375,8 @@ */ TEST_F( 'SwitchAccessTextNavigationManagerTest', 'DISABLED_SelectCharacter', - function() { - runTextSelectionTest(this, { + async function() { + await runTextSelectionTest(this, { content: 'hello world!', initialIndex: 0, targetStartIndex: 0, @@ -397,8 +395,8 @@ */ TEST_F( 'SwitchAccessTextNavigationManagerTest', 'DISABLED_SelectWordBackward', - function() { - runTextSelectionTest(this, { + async function() { + await runTextSelectionTest(this, { content: 'hello world!', initialIndex: 5, targetStartIndex: 0,
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_browser_proxy.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_browser_proxy.js index 03ce440..e434508b 100644 --- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_browser_proxy.js +++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_browser_proxy.js
@@ -92,6 +92,16 @@ * Cancels the phone hub apps access setup flow. */ cancelAppsSetup() {} + + /** + * Attempts the phone hub combined feature access setup flow. + */ + attemptCombinedFeatureSetup(cameraRoll, notifications) {} + + /** + * Cancels the phone hub combined feature access setup flow. + */ + cancelCombinedFeatureSetup() {} } /** @@ -168,6 +178,16 @@ cancelAppsSetup() { chrome.send('cancelAppsSetup'); } + + /** @override */ + attemptCombinedFeatureSetup(cameraRoll, notifications) { + chrome.send('attemptCombinedFeatureSetup', [cameraRoll, notifications]); + } + + /** @override */ + cancelCombinedFeatureSetup() { + chrome.send('cancelCombinedFeatureSetup'); + } } addSingletonGetter(MultiDeviceBrowserProxyImpl);
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_constants.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_constants.js index 625e6ac..6999cbe 100644 --- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_constants.js +++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_constants.js
@@ -141,7 +141,8 @@ * isNearbyShareDisallowedByPolicy: boolean, * isPhoneHubAppsAccessGranted: boolean, * isPhoneHubPermissionsDialogSupported: boolean, - * isCameraRollFilePermissionGranted: boolean + * isCameraRollFilePermissionGranted: boolean, + * isPhoneHubFeatureCombinedSetupSupported: boolean * }} */ export let MultiDevicePageContentData;
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.html index 96c37f5..32dbd4b 100644 --- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.html +++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.html
@@ -210,6 +210,8 @@ pageContentData)]]" show-app-streaming="[[isPhoneHubAppsSetupRequired( pageContentData)]]" + combined-setup-supported="[[isCombinedSetupSupported_( + pageContentData)]]" on-close="onHidePhonePermissionsSetupDialog_"> </settings-multidevice-permissions-setup-dialog> </template>
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.js index 7de1412..03197644 100644 --- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.js +++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.js
@@ -692,4 +692,14 @@ return loadTimeData.getBoolean('isNearbyShareBackgroundScanningEnabled') && is_hardware_supported; }, + + /** + * Whether the combined setup for Notifications and Camera Roll is supported + * on the connected phone. + * @return {boolean} + * @private + */ + isCombinedSetupSupported_() { + return this.pageContentData.isPhoneHubFeatureCombinedSetupSupported; + }, });
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_permissions_setup_dialog.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_permissions_setup_dialog.js index 01ab2f93..207de80 100644 --- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_permissions_setup_dialog.js +++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_permissions_setup_dialog.js
@@ -52,6 +52,7 @@ SET_LOCKSCREEN: 1, WAIT_FOR_PHONE_NOTIFICATION: 2, WAIT_FOR_PHONE_APPS: 3, + WAIT_FOR_PHONE_COMBINED: 4, }; Polymer({ @@ -171,6 +172,15 @@ computed: 'computeShouldShowDisabledDoneButton_(setupState_)', reflectToAttribute: true, }, + + /** + * Whether the combined setup for Notifications and Camera Roll is supported + * on the connected phone. + */ + combinedSetupSupported: { + type: Boolean, + value: false, + }, }, /** @private {?MultiDeviceBrowserProxy} */ @@ -189,6 +199,9 @@ this.addWebUIListener( 'settings.onAppsAccessSetupStatusChanged', this.onAppsSetupStateChanged_.bind(this)); + this.addWebUIListener( + 'settings.onCombinedAccessSetupStatusChanged', + this.onCombinedSetupStateChanged_.bind(this)); this.$.dialog.showModal(); }, @@ -233,6 +246,36 @@ }, /** + * @param {!PermissionsSetupStatus} setupState + * @private + */ + onCombinedSetupStateChanged_(setupState) { + if (this.flowState_ !== SetupFlowStatus.WAIT_FOR_PHONE_COMBINED) { + return; + } + + this.setupState_ = setupState; + if (this.setupState_ !== PermissionsSetupStatus.COMPLETED_SUCCESSFULLY) { + return; + } + + if (this.showCameraRoll) { + this.browserProxy_.setFeatureEnabledState( + MultiDeviceFeature.PHONE_HUB_CAMERA_ROLL, true); + } + if (this.showNotifications) { + this.browserProxy_.setFeatureEnabledState( + MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS, true); + } + + if (this.showAppStreaming) { + this.browserProxy_.attemptAppsSetup(); + this.flowState_ = SetupFlowStatus.WAIT_FOR_PHONE_APPS; + this.setupState_ = PermissionsSetupStatus.CONNECTION_REQUESTED; + } + }, + + /** * @return {boolean} * @private */ @@ -298,7 +341,13 @@ break; } - if (this.showNotifications) { + if ((this.showCameraRoll || this.showNotifications) && + this.combinedSetupSupported) { + this.browserProxy_.attemptCombinedFeatureSetup( + this.showCameraRoll, this.showNotifications); + this.flowState_ = SetupFlowStatus.WAIT_FOR_PHONE_COMBINED; + this.setupState_ = PermissionsSetupStatus.CONNECTION_REQUESTED; + } else if (this.showNotifications && !this.combinedSetupSupported) { this.browserProxy_.attemptNotificationSetup(); this.flowState_ = SetupFlowStatus.WAIT_FOR_PHONE_NOTIFICATION; this.setupState_ = PermissionsSetupStatus.CONNECTION_REQUESTED; @@ -306,11 +355,6 @@ this.browserProxy_.attemptAppsSetup(); this.flowState_ = SetupFlowStatus.WAIT_FOR_PHONE_APPS; this.setupState_ = PermissionsSetupStatus.CONNECTION_REQUESTED; - } else if (this.showCameraRoll) { - // Camera Roll setup is not implemented yet - // Dialog fails if Camera Roll is only feature needing setup - this.flowState_ = SetupFlowStatus.WAIT_FOR_PHONE_NOTIFICATION; - this.setupState_ = PermissionsSetupStatus.TIMED_OUT_CONNECTING; } }, @@ -320,6 +364,8 @@ this.browserProxy_.cancelNotificationSetup(); } else if (this.flowState_ === SetupFlowStatus.WAIT_FOR_PHONE_APPS) { this.browserProxy_.cancelAppsSetup(); + } else if (this.flowState_ === SetupFlowStatus.WAIT_FOR_PHONE_COMBINED) { + this.browserProxy_.cancelCombinedFeatureSetup(); } this.$.dialog.close(); },
diff --git a/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_app.html b/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_app.html index 2ab226d..415076b 100644 --- a/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_app.html +++ b/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_app.html
@@ -34,6 +34,7 @@ } .action-container { + align-items: flex-end; bottom: 0; box-sizing: border-box; position: absolute; @@ -159,6 +160,11 @@ font-weight: normal; } + #buttonsContainer cr-checkbox { + font-size: 12px; + padding-inline-end: 8px; + } + @media (prefers-color-scheme: dark) { .work-badge { border-color: var(--md-background-color); @@ -211,10 +217,16 @@ </template> </div> <div id="buttonsContainer" class="action-container"> + <template is="dom-if" if="[[showLinkDataCheckbox_]]"> + <cr-checkbox id="linkData" checked="{{linkData_}}"> + <div>$i18n{linkDataText}</div> + </cr-checkbox> + </template> <cr-button id="proceedButton" class="action-button" on-click="onProceed_" autofocus$="[[isModalDialog_]]" disabled="[[disableProceedButton_]]"> [[proceedLabel_]] </cr-button> + </template> <cr-button id="cancelButton" on-click="onCancel_"> $i18n{cancelLabel} </cr-button>
diff --git a/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_app.ts b/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_app.ts index a6e2838..ecfd5f4e 100644 --- a/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_app.ts +++ b/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_app.ts
@@ -3,6 +3,7 @@ // found in the LICENSE file. import 'chrome://resources/cr_elements/cr_button/cr_button.m.js'; +import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js'; import 'chrome://resources/cr_elements/shared_vars_css.m.js'; import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js'; import 'chrome://resources/cr_elements/icons.m.js'; @@ -11,6 +12,7 @@ import './signin_vars_css.js'; import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.m.js'; +import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js'; import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.js'; import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; @@ -39,7 +41,7 @@ } const EnterpriseProfileWelcomeAppElementBase = - WebUIListenerMixin(PolymerElement); + WebUIListenerMixin(I18nMixin(PolymerElement)); export class EnterpriseProfileWelcomeAppElement extends EnterpriseProfileWelcomeAppElementBase { @@ -76,12 +78,27 @@ } }, + showLinkDataCheckbox_: { + type: String, + reflectToAttribute: true, + value() { + return loadTimeData.getBoolean('showLinkDataCheckbox'); + } + }, + /** The label for the button to proceed with the flow */ proceedLabel_: String, disableProceedButton_: { type: Boolean, value: false, + }, + + linkData_: { + type: Boolean, + reflectToAttribute: true, + value: false, + observer: 'linkDataChanged_' } }; } @@ -93,6 +110,8 @@ private isModalDialog_: boolean; private proceedLabel_: string; private disableProceedButton_: boolean; + private linkData_: boolean; + private defaultProceedLabel_: string; private enterpriseProfileWelcomeBrowserProxy_: EnterpriseProfileWelcomeBrowserProxy = EnterpriseProfileWelcomeBrowserProxyImpl.getInstance(); @@ -107,10 +126,15 @@ info => this.setProfileInfo_(info)); } + private linkDataChanged_(linkData: boolean) { + this.proceedLabel_ = linkData ? this.i18n('proceedAlternateLabel') : + this.defaultProceedLabel_; + } + /** Called when the proceed button is clicked. */ private onProceed_() { this.disableProceedButton_ = true; - this.enterpriseProfileWelcomeBrowserProxy_.proceed(); + this.enterpriseProfileWelcomeBrowserProxy_.proceed(this.linkData_); } /** Called when the cancel button is clicked. */ @@ -124,7 +148,8 @@ this.showEnterpriseBadge_ = info.showEnterpriseBadge; this.enterpriseTitle_ = info.enterpriseTitle; this.enterpriseInfo_ = info.enterpriseInfo; - this.proceedLabel_ = info.proceedLabel; + this.defaultProceedLabel_ = info.proceedLabel; + this.proceedLabel_ = this.defaultProceedLabel_; } }
diff --git a/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_browser_proxy.ts b/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_browser_proxy.ts index da31d576..3003f38 100644 --- a/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_browser_proxy.ts +++ b/chrome/browser/resources/signin/enterprise_profile_welcome/enterprise_profile_welcome_browser_proxy.ts
@@ -28,7 +28,7 @@ /** * Called when the user clicks the proceed button. */ - proceed(): void; + proceed(linkData: boolean): void; /** * Called when the user clicks the cancel button. @@ -46,8 +46,8 @@ chrome.send('initializedWithSize', [height]); } - proceed() { - chrome.send('proceed'); + proceed(linkData: boolean) { + chrome.send('proceed', [linkData]); } cancel() {
diff --git a/chrome/browser/site_isolation/origin_agent_cluster_browsertest.cc b/chrome/browser/site_isolation/origin_agent_cluster_browsertest.cc index 94b8bdd..c43a0e2 100644 --- a/chrome/browser/site_isolation/origin_agent_cluster_browsertest.cc +++ b/chrome/browser/site_isolation/origin_agent_cluster_browsertest.cc
@@ -13,6 +13,7 @@ #include "chrome/test/base/ui_test_utils.h" #include "components/network_session_configurator/common/network_switches.h" #include "components/page_load_metrics/browser/page_load_metrics_test_waiter.h" +#include "components/variations/active_field_trials.h" #include "content/public/browser/web_contents.h" #include "content/public/common/content_features.h" #include "content/public/common/content_switches.h" @@ -219,6 +220,28 @@ } IN_PROC_BROWSER_TEST_F(OriginAgentClusterBrowserTest, + SyntheticTrialActivation) { + const std::string kSyntheticTrialName = + "ProcessIsolatedOriginAgentClusterActive"; + const std::string kSyntheticTrialGroup = "Enabled"; + + GURL start_url(https_server()->GetURL("foo.com", "/iframe.html")); + GURL origin_keyed_url( + https_server()->GetURL("origin-keyed.foo.com", "/origin_key_me")); + + content::WebContents* web_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), start_url)); + // We won't have an active synthetic trial until we navigate to + // `origin_keyed_url`. + EXPECT_FALSE(variations::HasSyntheticTrial(kSyntheticTrialName)); + EXPECT_TRUE(NavigateIframeToURL(web_contents, "test", origin_keyed_url)); + EXPECT_TRUE(variations::IsInSyntheticTrialGroup(kSyntheticTrialName, + kSyntheticTrialGroup)); +} + +IN_PROC_BROWSER_TEST_F(OriginAgentClusterBrowserTest, ProcessCountMetricsSimple) { GURL start_url(https_server()->GetURL("foo.com", "/iframe.html")); GURL origin_keyed_url(
diff --git a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/PersistedTabData.java b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/PersistedTabData.java index eeaaff5..c9d7cd9 100644 --- a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/PersistedTabData.java +++ b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/PersistedTabData.java
@@ -109,7 +109,7 @@ if (persistedTabDataFromTab.needsUpdate()) { tabDataCreator.onResult((tabData) -> { if (tab.isDestroyed()) { - PostTask.runOrPostTask( + PostTask.postTask( UiThreadTaskTraits.DEFAULT, () -> { callback.onResult(null); }); return; } @@ -117,11 +117,11 @@ if (tabData != null) { setUserData(tab, clazz, tabData); } - PostTask.runOrPostTask( + PostTask.postTask( UiThreadTaskTraits.DEFAULT, () -> { callback.onResult(tabData); }); }); } else { - PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, + PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> { callback.onResult(persistedTabDataFromTab); }); } return; @@ -179,15 +179,14 @@ } private static <T extends PersistedTabData> void onPersistedTabDataResult( - T persistedTabData, Tab tab, Class<T> clazz, String key) { - if (tab.isDestroyed()) { - persistedTabData = null; - } + T pPersistedTabData, Tab tab, Class<T> clazz, String key) { + final T persistedTabData = tab.isDestroyed() ? null : pPersistedTabData; if (persistedTabData != null) { setUserData(tab, clazz, persistedTabData); } for (Callback cachedCallback : sCachedCallbacks.get(key)) { - cachedCallback.onResult(persistedTabData); + PostTask.postTask( + UiThreadTaskTraits.DEFAULT, () -> cachedCallback.onResult(persistedTabData)); } sCachedCallbacks.remove(key); }
diff --git a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabData.java b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabData.java index 2b28f0d..0a5d61d 100644 --- a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabData.java +++ b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabData.java
@@ -451,8 +451,7 @@ @DelayedInitMethod int delayedInitMethod = getDelayedInitMethod(); if (delayedInitMethod == DelayedInitMethod.EMPTY_RESPONSES_UNTIL_INIT) { - PostTask.runOrPostTask( - UiThreadTaskTraits.DEFAULT, () -> { callback.onResult(null); }); + PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> { callback.onResult(null); }); } else if (delayedInitMethod == DelayedInitMethod.DELAY_RESPONSES_UNTIL_INIT) { sShoppingDataRequests.add(new ShoppingDataRequest(tab, callback)); } else { @@ -466,7 +465,7 @@ // Shopping related data is not available for incognito or Custom Tabs. For example, // for incognito Tabs it is not possible to call a backend service with the user's URL. if (tab.isIncognito() || tab.isCustomTab()) { - PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> { callback.onResult(null); }); + PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> { callback.onResult(null); }); return; } PersistedTabData.from(tab, @@ -477,7 +476,7 @@ ShoppingPersistedTabData.from(tab); PostTask.postTask(TaskTraits.USER_BLOCKING_MAY_BLOCK, () -> { shoppingPersistedTabData.deserializeAndLog(data); - PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, + PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> { factoryCallback.onResult(shoppingPersistedTabData); }); }); }); @@ -487,8 +486,7 @@ if (tab.isDestroyed() || getTimeSinceTabLastOpenedMs(tab) > TimeUnit.SECONDS.toMillis(getStaleTabThresholdSeconds())) { - PostTask.runOrPostTask( - UiThreadTaskTraits.DEFAULT, () -> supplierCallback.onResult(null)); + supplierCallback.onResult(null); return; } PriceDataSnapshot previous = PersistedTabData.from(tab, USER_DATA_KEY) == null @@ -496,10 +494,7 @@ : new PriceDataSnapshot(PersistedTabData.from(tab, USER_DATA_KEY)); ShoppingPersistedTabData.isShoppingPage(tab.getUrl(), (isShoppingPage) -> { if (!isShoppingPage) { - PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, - () - -> supplierCallback.onResult( - getEmptyShoppingPersistedTabData(tab))); + supplierCallback.onResult(getEmptyShoppingPersistedTabData(tab)); return; } @@ -510,9 +505,7 @@ HintsProto.OptimizationType.PRICE_TRACKING, (decision, metadata) -> { if (tab.isDestroyed()) { - PostTask.runOrPostTask( - UiThreadTaskTraits.DEFAULT, - () -> supplierCallback.onResult(null)); + supplierCallback.onResult(null); return; } if (decision != OptimizationGuideDecision.TRUE) { @@ -520,9 +513,7 @@ getEmptyShoppingPersistedTabData(tab); res.logPriceDropMetrics( METRICS_IDENTIFIER_PREFIX); - PostTask.runOrPostTask( - UiThreadTaskTraits.DEFAULT, - () -> supplierCallback.onResult(res)); + supplierCallback.onResult(res); return; } try { @@ -535,9 +526,7 @@ tab, priceTrackingDataProto, previous); sptd.logPriceDropMetrics( METRICS_IDENTIFIER_PREFIX); - PostTask.runOrPostTask( - UiThreadTaskTraits.DEFAULT, - () -> supplierCallback.onResult(sptd)); + supplierCallback.onResult(sptd); } catch (InvalidProtocolBufferException e) { Log.i(TAG, String.format(Locale.US, @@ -547,18 +536,13 @@ + "DataProto. " + "Details %s.", e)); - PostTask.runOrPostTask( - UiThreadTaskTraits.DEFAULT, - () -> supplierCallback.onResult(null)); + supplierCallback.onResult(null); } }); } else { sPageAnnotationsServiceFactory.getForLastUsedProfile().getAnnotations( tab.getUrl(), (result) -> { - PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, - () - -> supplierCallback.onResult( - build(tab, result, previous))); + supplierCallback.onResult(build(tab, result, previous)); }); } }); @@ -1136,7 +1120,7 @@ // If Tab was destroyed we should just return null and not try and // create and associate {@link ShoppingPersistedTabData} with a // destroyed {@link Tab}. - PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, + PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> { shoppingDataRequest.callback.onResult(null); }); processNextItemOnQueue(); return;
diff --git a/chrome/browser/tab_contents/navigation_metrics_recorder.cc b/chrome/browser/tab_contents/navigation_metrics_recorder.cc index 31c83ad..e86429e 100644 --- a/chrome/browser/tab_contents/navigation_metrics_recorder.cc +++ b/chrome/browser/tab_contents/navigation_metrics_recorder.cc
@@ -76,14 +76,19 @@ // process and register a synthetic field trial if so. Note that this needs // to go before the IsInPrimaryMainFrame() check, as we want to register // navigations to isolated sites from both main frames and subframes. + auto* site_instance = + navigation_handle->GetRenderFrameHost()->GetSiteInstance(); if (is_synthetic_isolation_trial_enabled_ && - navigation_handle->GetRenderFrameHost() - ->GetSiteInstance() - ->RequiresDedicatedProcess()) { + site_instance->RequiresDedicatedProcess()) { ChromeMetricsServiceAccessor::RegisterSyntheticFieldTrial( "SiteIsolationActive", "Enabled"); } + if (site_instance->RequiresOriginKeyedProcess()) { + ChromeMetricsServiceAccessor::RegisterSyntheticFieldTrial( + "ProcessIsolatedOriginAgentClusterActive", "Enabled"); + } + // Also register a synthetic field trial when we encounter a navigation to an // OOPIF. if (is_synthetic_isolation_trial_enabled_ &&
diff --git a/chrome/browser/ui/app_list/app_list_syncable_service.cc b/chrome/browser/ui/app_list/app_list_syncable_service.cc index be76c38..5aeea46 100644 --- a/chrome/browser/ui/app_list/app_list_syncable_service.cc +++ b/chrome/browser/ui/app_list/app_list_syncable_service.cc
@@ -180,32 +180,32 @@ dict_item = pref_update->SetKey(sync_item->item_id, base::Value(base::Value::Type::DICTIONARY)); } - - dict_item->SetKey(kNameKey, base::Value(sync_item->item_name)); - dict_item->SetKey(kParentIdKey, base::Value(sync_item->parent_id)); - dict_item->SetKey(kPositionKey, - base::Value(sync_item->item_ordinal.IsValid() - ? sync_item->item_ordinal.ToInternalValue() - : std::string())); - dict_item->SetKey( + base::Value::Dict& dict_item_dict = dict_item->GetDict(); + dict_item_dict.Set(kNameKey, base::Value(sync_item->item_name)); + dict_item_dict.Set(kParentIdKey, base::Value(sync_item->parent_id)); + dict_item_dict.Set(kPositionKey, + base::Value(sync_item->item_ordinal.IsValid() + ? sync_item->item_ordinal.ToInternalValue() + : std::string())); + dict_item_dict.Set( kPinPositionKey, base::Value(sync_item->item_pin_ordinal.IsValid() ? sync_item->item_pin_ordinal.ToInternalValue() : std::string())); - dict_item->SetKey(kTypeKey, - base::Value(static_cast<int>(sync_item->item_type))); + dict_item_dict.Set(kTypeKey, + base::Value(static_cast<int>(sync_item->item_type))); if (ash::features::IsLauncherItemColorSyncEnabled()) { // Handle the item color. if (sync_item->item_color.IsValid()) { - dict_item->SetKey(kBackgroundColorKey, - base::Value(sync_pb::AppListSpecifics::ColorGroup_Name( - sync_item->item_color.background_color()))); - dict_item->SetKey(kHueKey, base::Value(sync_item->item_color.hue())); - } else if (dict_item->FindKey(kBackgroundColorKey)) { - dict_item->RemoveKey(kBackgroundColorKey); - DCHECK(dict_item->FindKey(kHueKey)); - dict_item->RemoveKey(kHueKey); + dict_item_dict.Set(kBackgroundColorKey, + base::Value(sync_pb::AppListSpecifics::ColorGroup_Name( + sync_item->item_color.background_color()))); + dict_item_dict.Set(kHueKey, base::Value(sync_item->item_color.hue())); + } else if (dict_item_dict.Find(kBackgroundColorKey)) { + dict_item_dict.Remove(kBackgroundColorKey); + DCHECK(dict_item_dict.Find(kHueKey)); + dict_item_dict.Remove(kHueKey); } } } @@ -447,8 +447,8 @@ LOG(ERROR) << "Dictionary not found for " << item.first + "."; continue; } - - absl::optional<int> type = item.second.FindIntKey(kTypeKey); + const base::Value::Dict& item_dict = item.second.GetDict(); + absl::optional<int> type = item_dict.FindInt(kTypeKey); if (!type) { LOG(ERROR) << "Item type is not set in local storage for " << item.second << "."; @@ -459,17 +459,15 @@ item.first, static_cast<sync_pb::AppListSpecifics::AppListItemType>(*type)); - const std::string* maybe_item_name = item.second.FindStringKey(kNameKey); + const std::string* maybe_item_name = item_dict.FindString(kNameKey); if (maybe_item_name) sync_item->item_name = *maybe_item_name; - const std::string* maybe_parent_id = - item.second.FindStringKey(kParentIdKey); + const std::string* maybe_parent_id = item_dict.FindString(kParentIdKey); if (maybe_parent_id) sync_item->parent_id = *maybe_parent_id; - const std::string* position = item.second.FindStringKey(kPositionKey); - const std::string* pin_position = - item.second.FindStringKey(kPinPositionKey); + const std::string* position = item_dict.FindString(kPositionKey); + const std::string* pin_position = item_dict.FindString(kPinPositionKey); if (position && !position->empty()) sync_item->item_ordinal = syncer::StringOrdinal(*position); if (pin_position && !pin_position->empty()) @@ -480,7 +478,7 @@ item.second.FindKey(kBackgroundColorKey)) { // Retrieve the background color. const std::string* background_color_internal_string = - item.second.FindStringKey(kBackgroundColorKey); + item_dict.FindString(kBackgroundColorKey); sync_pb::AppListSpecifics::ColorGroup background_color; sync_pb::AppListSpecifics::ColorGroup_Parse( background_color_internal_string ? *background_color_internal_string @@ -488,9 +486,9 @@ &background_color); // Retrieve the hue. - DCHECK(item.second.FindKey(kHueKey)); + DCHECK(item_dict.Find(kHueKey)); int hue = - item.second.FindIntKey(kHueKey).value_or(ash::IconColor::kHueInvalid); + item_dict.FindInt(kHueKey).value_or(ash::IconColor::kHueInvalid); sync_item->item_color = ash::IconColor(background_color, hue);
diff --git a/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc b/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc index ae1604b..7004db9 100644 --- a/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc +++ b/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc
@@ -115,20 +115,20 @@ DictionaryPrefUpdate update( prefs_, arc::prefs::kArcSetNotificationsEnabledDeferred); base::Value* const dict = update.Get(); - dict->SetKey(app_id, base::Value(enabled)); + dict->GetDict().Set(app_id, base::Value(enabled)); } bool Get(const std::string& app_id) { const base::Value* dict = prefs_->GetDictionary(arc::prefs::kArcSetNotificationsEnabledDeferred); - return dict->FindBoolKey(app_id).value_or(false); + return dict->GetDict().FindBool(app_id).value_or(false); } void Remove(const std::string& app_id) { DictionaryPrefUpdate update( prefs_, arc::prefs::kArcSetNotificationsEnabledDeferred); base::Value* const dict = update.Get(); - dict->RemoveKey(app_id); + dict->GetDict().Remove(app_id); } private: @@ -219,11 +219,11 @@ arc::IsArcPlayStoreEnabledForProfile(profile); } -bool GetInt64FromPref(const base::Value* dict, +bool GetInt64FromPref(const base::Value::Dict* dict, const std::string& key, int64_t* value) { - DCHECK(dict && dict->is_dict()); - const std::string* value_str = dict->FindStringKey(key); + DCHECK(dict); + const std::string* value_str = dict->FindString(key); if (!value_str) { VLOG(2) << "Can't find key in local pref dictionary. Invalid key: " << key << "."; @@ -241,12 +241,12 @@ // Converts |rect| to base::Value, e.g. { 0, 100, 200, 300 }. base::Value RectToValueDict(const gfx::Rect& rect) { - base::Value dict(base::Value::Type::DICTIONARY); - dict.SetIntKey("x", rect.x()); - dict.SetIntKey("y", rect.y()); - dict.SetIntKey("width", rect.width()); - dict.SetIntKey("height", rect.height()); - return dict; + base::Value::Dict dict; + dict.Set("x", rect.x()); + dict.Set("y", rect.y()); + dict.Set("width", rect.width()); + dict.Set("height", rect.height()); + return base::Value(std::move(dict)); } // Gets gfx::Rect from base::Value, e.g. { 0, 100, 200, 300 } returns @@ -255,10 +255,10 @@ absl::optional<gfx::Rect> RectFromDictValue(const base::Value* rect_dict) { if (!rect_dict) return absl::nullopt; - auto x = rect_dict->FindIntKey("x"); - auto y = rect_dict->FindIntKey("y"); - auto width = rect_dict->FindIntKey("width"); - auto height = rect_dict->FindIntKey("height"); + auto x = rect_dict->GetDict().FindInt("x"); + auto y = rect_dict->GetDict().FindInt("y"); + auto width = rect_dict->GetDict().FindInt("width"); + auto height = rect_dict->GetDict().FindInt("height"); if (!x.has_value() || !y.has_value() || !width.has_value() || !height.has_value()) { return absl::nullopt; @@ -268,22 +268,26 @@ base::Value WindowLayoutToDict( const ArcAppListPrefs::WindowLayout& window_layout) { - base::Value dict(base::Value::Type::DICTIONARY); - dict.SetIntKey(kWindowSizeType, static_cast<int32_t>(window_layout.type)); - dict.SetBoolKey(kWindowResizability, window_layout.resizable); + base::Value::Dict dict; + dict.Set(kWindowSizeType, static_cast<int32_t>(window_layout.type)); + dict.Set(kWindowResizability, window_layout.resizable); if (window_layout.bounds.has_value()) - dict.SetKey(kWindowBounds, RectToValueDict(window_layout.bounds.value())); - return dict; + dict.Set(kWindowBounds, RectToValueDict(window_layout.bounds.value())); + + return base::Value(std::move(dict)); } -ArcAppListPrefs::WindowLayout WindowLayoutFromDict(const base::Value* dict) { +ArcAppListPrefs::WindowLayout WindowLayoutFromDict( + const base::Value::Dict* dict) { if (!dict) return ArcAppListPrefs::WindowLayout(); + + const base::Value* window_bounds = dict->Find(kWindowBounds); return ArcAppListPrefs::WindowLayout( static_cast<arc::mojom::WindowSizeType>( - dict->FindIntKey(kWindowSizeType).value_or(0)), - dict->FindBoolKey(kWindowResizability).value_or(true), - RectFromDictValue(dict->FindKey(kWindowBounds))); + dict->FindInt(kWindowSizeType).value_or(0)), + dict->FindBool(kWindowResizability).value_or(true), + RectFromDictValue(window_bounds)); } ArcAppListPrefs::WindowLayout WindowLayoutFromApp( @@ -405,15 +409,13 @@ for (const auto it : apps->DictItems()) { const base::Value& value = it.second; - const base::Value* installed_package_name = - value.FindKeyOfType(kPackageName, base::Value::Type::STRING); - if (!installed_package_name || - installed_package_name->GetString() != package_name) + const std::string* installed_package_name = + value.GetDict().FindString(kPackageName); + if (!installed_package_name || *installed_package_name != package_name) continue; - const base::Value* activity_name = - value.FindKeyOfType(kActivity, base::Value::Type::STRING); - return activity_name ? GetAppId(package_name, activity_name->GetString()) + const std::string* activity_name = value.GetDict().FindString(kActivity); + return activity_name ? GetAppId(package_name, *activity_name) : std::string(); } return std::string(); @@ -717,11 +719,11 @@ if (!packages) return nullptr; - const base::Value* package = packages->FindDictKey(package_name); + const base::Value::Dict* package = packages->GetDict().FindDict(package_name); if (!package) return nullptr; - if (package->FindBoolKey(kUninstalled).value_or(false)) + if (package->FindBool(kUninstalled).value_or(false)) return nullptr; int64_t last_backup_android_id = 0; @@ -731,7 +733,7 @@ GetInt64FromPref(package, kLastBackupAndroidId, &last_backup_android_id); GetInt64FromPref(package, kLastBackupTime, &last_backup_time); - const base::Value* permission_val = package->FindKey(kPermissionStates); + const base::Value* permission_val = package->Find(kPermissionStates); if (permission_val) { const base::DictionaryValue* permission_dict = nullptr; permission_val->GetAsDictionary(&permission_dict); @@ -747,12 +749,12 @@ const base::DictionaryValue* permission_state_dict; if (permission_state.GetAsDictionary(&permission_state_dict)) { - bool granted = - permission_state_dict->FindBoolKey(kPermissionStateGranted) - .value_or(false); - bool managed = - permission_state_dict->FindBoolKey(kPermissionStateManaged) - .value_or(false); + bool granted = permission_state_dict->GetDict() + .FindBool(kPermissionStateGranted) + .value_or(false); + bool managed = permission_state_dict->GetDict() + .FindBool(kPermissionStateManaged) + .value_or(false); arc::mojom::AppPermission permission = static_cast<arc::mojom::AppPermission>(permission_type); permissions.emplace(permission, @@ -764,12 +766,11 @@ } return std::make_unique<PackageInfo>( - package_name, package->FindIntKey(kPackageVersion).value_or(0), + package_name, package->FindInt(kPackageVersion).value_or(0), last_backup_android_id, last_backup_time, - package->FindBoolKey(kShouldSync).value_or(false), - package->FindBoolKey(kSystem).value_or(false), - package->FindBoolKey(kVPNProvider).value_or(false), - std::move(permissions)); + package->FindBool(kShouldSync).value_or(false), + package->FindBool(kSystem).value_or(false), + package->FindBool(kVPNProvider).value_or(false), std::move(permissions)); } bool ArcAppListPrefs::IsPackageInstalled( @@ -830,25 +831,25 @@ const base::Value* apps = prefs_->GetDictionary(arc::prefs::kArcApps); if (!apps) return nullptr; - const base::Value* app = apps->FindDictKey(app_id); - if (!app) + const base::Value::Dict* app_dict = apps->GetDict().FindDict(app_id); + if (!app_dict) return nullptr; bool notifications_enabled = - app->FindBoolKey(kNotificationsEnabled).value_or(true); + app_dict->FindBool(kNotificationsEnabled).value_or(true); auto resize_lock_state = static_cast<arc::mojom::ArcResizeLockState>( - app->FindIntKey(kResizeLockState) + app_dict->FindInt(kResizeLockState) .value_or( static_cast<int32_t>(arc::mojom::ArcResizeLockState::UNDEFINED))); - const bool shortcut = app->FindBoolKey(kShortcut).value_or(false); - const bool launchable = app->FindBoolKey(kLaunchable).value_or(true); + const bool shortcut = app_dict->FindBool(kShortcut).value_or(false); + const bool launchable = app_dict->FindBool(kLaunchable).value_or(true); - const std::string* maybe_name = app->FindStringKey(kName); - const std::string* maybe_package_name = app->FindStringKey(kPackageName); - const std::string* maybe_activity = app->FindStringKey(kActivity); - const std::string* maybe_intent_uri = app->FindStringKey(kIntentUri); + const std::string* maybe_name = app_dict->FindString(kName); + const std::string* maybe_package_name = app_dict->FindString(kPackageName); + const std::string* maybe_activity = app_dict->FindString(kActivity); + const std::string* maybe_intent_uri = app_dict->FindString(kIntentUri); const std::string* maybe_icon_resource_id = - app->FindStringKey(kIconResourceId); + app_dict->FindString(kIconResourceId); std::string name = maybe_name ? *maybe_name : std::string(); std::string package_name = @@ -864,7 +865,7 @@ int64_t last_launch_time_internal = 0; base::Time last_launch_time; - if (GetInt64FromPref(app, kLastLaunchTime, &last_launch_time_internal)) { + if (GetInt64FromPref(app_dict, kLastLaunchTime, &last_launch_time_internal)) { last_launch_time = base::Time::FromInternalValue(last_launch_time_internal); } @@ -873,16 +874,16 @@ notifications_enabled = deferred; WindowLayout window_layout = - WindowLayoutFromDict(app->FindDictKey(kWindowLayout)); + WindowLayoutFromDict(app_dict->FindDict(kWindowLayout)); return std::make_unique<AppInfo>( name, package_name, activity, intent_uri, icon_resource_id, last_launch_time, GetInstallTime(app_id), - app->FindBoolKey(kSticky).value_or(false), notifications_enabled, + app_dict->FindBool(kSticky).value_or(false), notifications_enabled, resize_lock_state, - app->FindBoolKey(kResizeLockNeedsConfirmation).value_or(true), + app_dict->FindBool(kResizeLockNeedsConfirmation).value_or(true), window_layout, ready_apps_.count(app_id) > 0 /* ready */, - app->FindBoolKey(kSuspended).value_or(false), + app_dict->FindBool(kSuspended).value_or(false), launchable && arc::ShouldShowInLauncher(app_id), shortcut, launchable); } @@ -947,7 +948,7 @@ arc::ArcAppScopedPrefUpdate update(prefs_, app_id, arc::prefs::kArcApps); base::Value* app_dict = update.Get(); const std::string string_value = base::NumberToString(time.ToInternalValue()); - app_dict->SetStringKey(kLastLaunchTime, string_value); + app_dict->GetDict().Set(kLastLaunchTime, string_value); for (auto& observer : observer_list_) observer.OnAppLastLaunchTimeUpdated(app_id); @@ -1118,7 +1119,7 @@ arc::ArcAppScopedPrefUpdate update(prefs_, app_id, arc::prefs::kArcApps); base::Value* app_dict = update.Get(); - app_dict->SetIntKey(kResizeLockState, static_cast<int32_t>(state)); + app_dict->GetDict().Set(kResizeLockState, static_cast<int32_t>(state)); NotifyAppStatesChanged(app_id); } @@ -1144,7 +1145,7 @@ arc::ArcAppScopedPrefUpdate update(prefs_, app_id, arc::prefs::kArcApps); base::Value* app_dict = update.Get(); - app_dict->SetBoolKey(kResizeLockNeedsConfirmation, is_needed); + app_dict->GetDict().Set(kResizeLockNeedsConfirmation, is_needed); } int ArcAppListPrefs::GetShowSplashScreenDialogCount() const { @@ -1198,7 +1199,7 @@ } arc::ArcAppScopedPrefUpdate update(prefs_, package_name, arc::prefs::kArcPackages); - return update.Get()->FindKey(key); + return update.Get()->GetDict().Find(key); } void ArcAppListPrefs::SetPackagePrefs(const std::string& package_name, @@ -1210,7 +1211,7 @@ } arc::ArcAppScopedPrefUpdate update(prefs_, package_name, arc::prefs::kArcPackages); - update.Get()->SetKey(key, std::move(value)); + update.Get()->GetDict().Set(key, std::move(value)); } void ArcAppListPrefs::SetDefaultAppsReadyCallback(base::OnceClosure callback) { @@ -1337,30 +1338,28 @@ GetResizeLockNeedsConfirmation(app_id); arc::ArcAppScopedPrefUpdate update(prefs_, app_id, arc::prefs::kArcApps); - base::Value* app_dict = update.Get(); - app_dict->SetStringKey(kName, updated_name); - app_dict->SetStringKey(kPackageName, package_name); - app_dict->SetStringKey(kActivity, activity); - app_dict->SetStringKey(kIntentUri, intent_uri); - app_dict->SetStringKey(kIconResourceId, icon_resource_id); - app_dict->SetBoolKey(kSuspended, suspended); - app_dict->SetBoolKey(kSticky, sticky); - app_dict->SetBoolKey(kNotificationsEnabled, notifications_enabled); - app_dict->SetIntKey(kResizeLockState, - static_cast<int32_t>(resize_lock_state)); - app_dict->SetBoolKey(kResizeLockNeedsConfirmation, - resize_lock_needs_confirmation); - app_dict->SetBoolKey(kShortcut, shortcut); - app_dict->SetBoolKey(kLaunchable, launchable); + base::Value::Dict& app_dict = update.Get()->GetDict(); + app_dict.Set(kName, updated_name); + app_dict.Set(kPackageName, package_name); + app_dict.Set(kActivity, activity); + app_dict.Set(kIntentUri, intent_uri); + app_dict.Set(kIconResourceId, icon_resource_id); + app_dict.Set(kSuspended, suspended); + app_dict.Set(kSticky, sticky); + app_dict.Set(kNotificationsEnabled, notifications_enabled); + app_dict.Set(kResizeLockState, static_cast<int32_t>(resize_lock_state)); + app_dict.Set(kResizeLockNeedsConfirmation, resize_lock_needs_confirmation); + app_dict.Set(kShortcut, shortcut); + app_dict.Set(kLaunchable, launchable); - app_dict->SetKey(kWindowLayout, WindowLayoutToDict(initial_window_layout)); + app_dict.Set(kWindowLayout, WindowLayoutToDict(initial_window_layout)); // Note the install time is the first time the Chrome OS sees the app, not the // actual install time in Android side. if (GetInstallTime(app_id).is_null()) { std::string install_time_str = base::NumberToString(base::Time::Now().ToInternalValue()); - app_dict->SetStringKey(kInstallTime, install_time_str); + app_dict.Set(kInstallTime, install_time_str); } const bool was_disabled = ready_apps_.count(app_id) == 0; @@ -1406,7 +1405,7 @@ // Invalidate app icons in case it was already registered, becomes ready and // icon version is updated. This allows to use previous icons until new // icons are been prepared. - const base::Value* existing_version = app_dict->FindKey(kIconVersion); + const base::Value* existing_version = app_dict.Find(kIconVersion); if (was_tracked && (!existing_version || existing_version->GetInt() != current_icons_version)) { VLOG(1) << "Invalidate icons for " << app_id << " from " @@ -1415,7 +1414,7 @@ InvalidateAppIcons(app_id); } - app_dict->SetKey(kIconVersion, base::Value(current_icons_version)); + app_dict.Set(kIconVersion, base::Value(current_icons_version)); if (arc::IsArcForceCacheAppIcon() && app_id != arc::kPlayStoreAppId) { // Request full set of app icons. @@ -1451,7 +1450,7 @@ // Remove from prefs. DictionaryPrefUpdate update(prefs_, arc::prefs::kArcApps); base::Value* apps = update.Get(); - const bool removed = apps->RemoveKey(app_id); + const bool removed = apps->GetDict().Remove(app_id); DCHECK(removed); // |tracked_apps_| contains apps that are reported externally as available. @@ -1492,38 +1491,37 @@ arc::ArcAppScopedPrefUpdate update(prefs_, package_name, arc::prefs::kArcPackages); - base::Value* package_dict = update.Get(); + base::Value::Dict& package_dict = update.Get()->GetDict(); const std::string id_str = base::NumberToString(package.last_backup_android_id); const std::string time_str = base::NumberToString(package.last_backup_time); - int old_package_version = - package_dict->FindIntKey(kPackageVersion).value_or(-1); - package_dict->SetBoolKey(kShouldSync, package.sync); - package_dict->SetIntKey(kPackageVersion, package.package_version); - package_dict->SetStringKey(kLastBackupAndroidId, id_str); - package_dict->SetStringKey(kLastBackupTime, time_str); - package_dict->SetBoolKey(kSystem, package.system); - package_dict->SetBoolKey(kUninstalled, false); - package_dict->SetBoolKey(kVPNProvider, package.vpn_provider); + int old_package_version = package_dict.FindInt(kPackageVersion).value_or(-1); + package_dict.Set(kShouldSync, package.sync); + package_dict.Set(kPackageVersion, package.package_version); + package_dict.Set(kLastBackupAndroidId, id_str); + package_dict.Set(kLastBackupTime, time_str); + package_dict.Set(kSystem, package.system); + package_dict.Set(kUninstalled, false); + package_dict.Set(kVPNProvider, package.vpn_provider); base::DictionaryValue permissions_dict; if (package.permission_states.has_value()) { // Support new format for (const auto& permission : package.permission_states.value()) { base::DictionaryValue permission_state_dict; - permission_state_dict.SetBoolKey(kPermissionStateGranted, - permission.second->granted); - permission_state_dict.SetBoolKey(kPermissionStateManaged, - permission.second->managed); - permissions_dict.SetKey( + permission_state_dict.GetDict().Set(kPermissionStateGranted, + permission.second->granted); + permission_state_dict.GetDict().Set(kPermissionStateManaged, + permission.second->managed); + permissions_dict.GetDict().Set( base::NumberToString(static_cast<int64_t>(permission.first)), std::move(permission_state_dict)); } - package_dict->SetKey(kPermissionStates, std::move(permissions_dict)); + package_dict.Set(kPermissionStates, std::move(permissions_dict)); } else { // Remove kPermissionStates from dict if there are no permissions. - package_dict->RemoveKey(kPermissionStates); + package_dict.Remove(kPermissionStates); } if (old_package_version == -1 || @@ -1537,7 +1535,8 @@ void ArcAppListPrefs::RemovePackageFromPrefs(const std::string& package_name) { DictionaryPrefUpdate(prefs_, arc::prefs::kArcPackages) .Get() - ->RemoveKey(package_name); + ->GetDict() + .Remove(package_name); } void ArcAppListPrefs::OnAppListRefreshed( @@ -1702,7 +1701,7 @@ if (shelf_controller) { int pin_index = shelf_controller->PinnedItemIndexByAppID(*apps_to_remove.begin()); - package_dict->SetIntKey(kPinIndex, pin_index); + package_dict->GetDict().Set(kPinIndex, pin_index); } } @@ -1735,14 +1734,15 @@ } const std::string* installed_package_name = - app.second.FindStringKey(kPackageName); + app.second.GetDict().FindString(kPackageName); const std::string* installed_intent_uri = - app.second.FindStringKey(kIntentUri); + app.second.GetDict().FindString(kIntentUri); if (!installed_package_name || !installed_intent_uri) { VLOG(2) << "Failed to extract information for " << app.first << "."; continue; } - const bool shortcut = app.second.FindBoolKey(kShortcut).value_or(false); + const bool shortcut = + app.second.GetDict().FindBool(kShortcut).value_or(false); if (!shortcut || *installed_package_name != package_name || *installed_intent_uri != intent_uri) { continue; @@ -1777,7 +1777,8 @@ continue; } - const std::string* app_package = app.second.FindStringKey(kPackageName); + const std::string* app_package = + app.second.GetDict().FindString(kPackageName); if (!app_package) { LOG(ERROR) << "App is malformed: " << app.first; continue; @@ -1787,13 +1788,13 @@ continue; if (!include_shortcuts) { - if (app.second.FindBoolKey(kShortcut).value_or(false)) + if (app.second.GetDict().FindBool(kShortcut).value_or(false)) continue; } if (include_only_launchable_apps) { // Filter out non-lauchable apps. - if (!app.second.FindBoolKey(kLaunchable).value_or(false)) + if (!app.second.GetDict().FindBool(kLaunchable).value_or(false)) continue; } @@ -1918,7 +1919,7 @@ continue; } const std::string* app_package_name = - app.second.FindStringKey(kPackageName); + app.second.GetDict().FindString(kPackageName); if (!app_package_name) { NOTREACHED(); continue; @@ -1928,7 +1929,7 @@ } arc::ArcAppScopedPrefUpdate update(prefs_, app.first, arc::prefs::kArcApps); base::Value* updating_app_dict = update.Get(); - updating_app_dict->SetBoolKey(kNotificationsEnabled, enabled); + updating_app_dict->GetDict().Set(kNotificationsEnabled, enabled); } for (auto& observer : observer_list_) observer.OnNotificationsEnabledChanged(package_name, enabled); @@ -2020,7 +2021,7 @@ } const bool uninstalled = - package.second.FindBoolKey(kUninstalled).value_or(false); + package.second.GetDict().FindBool(kUninstalled).value_or(false); if (installed != !uninstalled) continue; @@ -2039,7 +2040,7 @@ if (!app) return base::Time(); - const std::string* install_time_str = app->FindStringKey(kInstallTime); + const std::string* install_time_str = app->GetDict().FindString(kInstallTime); if (!install_time_str) return base::Time();
diff --git a/chrome/browser/ui/app_list/arc/arc_default_app_list.cc b/chrome/browser/ui/app_list/arc/arc_default_app_list.cc index 73ac703..e8e7446 100644 --- a/chrome/browser/ui/app_list/arc/arc_default_app_list.cc +++ b/chrome/browser/ui/app_list/arc/arc_default_app_list.cc
@@ -6,6 +6,9 @@ #include <string.h> +#include <utility> +#include <vector> + #include "ash/components/arc/arc_util.h" #include "base/barrier_closure.h" #include "base/bind.h" @@ -87,12 +90,10 @@ base::Value app_info = base::Value::FromUniquePtrValue(std::move(app_info_ptr)); - CHECK(app_info.is_dict()); - - auto* name = app_info.FindStringKey(kName); - auto* package_name = app_info.FindStringKey(kPackageName); - auto* activity = app_info.FindStringKey(kActivity); - auto* app_path = app_info.FindStringKey(kAppPath); + auto* name = app_info.GetDict().FindString(kName); + auto* package_name = app_info.GetDict().FindString(kPackageName); + auto* activity = app_info.GetDict().FindString(kActivity); + auto* app_path = app_info.GetDict().FindString(kAppPath); bool oem = app_info.FindBoolPath(kOem).value_or(false); if (!name || !package_name || !activity || !app_path || name->empty() || @@ -328,7 +329,8 @@ // Store hidden flag. arc::ArcAppScopedPrefUpdate(profile_->GetPrefs(), app_id, kDefaultApps) .Get() - ->SetBoolKey(kHidden, hidden); + ->GetDict() + .Set(kHidden, hidden); } void ArcDefaultAppList::SetAppsHiddenForPackage(
diff --git a/chrome/browser/ui/app_list/search/files/item_suggest_cache.cc b/chrome/browser/ui/app_list/search/files/item_suggest_cache.cc index fd74950..9b59e0f 100644 --- a/chrome/browser/ui/app_list/search/files/item_suggest_cache.cc +++ b/chrome/browser/ui/app_list/search/files/item_suggest_cache.cc
@@ -108,7 +108,7 @@ const std::string& key) { if (!value->is_dict()) return absl::nullopt; - const std::string* field = value->FindStringKey(key); + const std::string* field = value->GetDict().FindString(key); if (!field) return absl::nullopt; return *field;
diff --git a/chrome/browser/ui/ash/chrome_shell_delegate.cc b/chrome/browser/ui/ash/chrome_shell_delegate.cc index 6900baa..fd19a4b6 100644 --- a/chrome/browser/ui/ash/chrome_shell_delegate.cc +++ b/chrome/browser/ui/ash/chrome_shell_delegate.cc
@@ -132,7 +132,7 @@ } scoped_refptr<network::SharedURLLoaderFactory> -ChromeShellDelegate::GetGeolocationSharedURLLoaderFactory() const { +ChromeShellDelegate::GetGeolocationUrlLoaderFactory() const { return g_browser_process->shared_url_loader_factory(); }
diff --git a/chrome/browser/ui/ash/chrome_shell_delegate.h b/chrome/browser/ui/ash/chrome_shell_delegate.h index 57cb4704..efd71ee 100644 --- a/chrome/browser/ui/ash/chrome_shell_delegate.h +++ b/chrome/browser/ui/ash/chrome_shell_delegate.h
@@ -33,7 +33,7 @@ std::unique_ptr<ash::DesksTemplatesDelegate> CreateDesksTemplatesDelegate() const override; scoped_refptr<network::SharedURLLoaderFactory> - GetGeolocationSharedURLLoaderFactory() const override; + GetGeolocationUrlLoaderFactory() const override; void OpenKeyboardShortcutHelpPage() const override; bool CanGoBack(gfx::NativeWindow window) const override; void SetTabScrubberChromeOSEnabled(bool enabled) override;
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_util.cc b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_util.cc index 85c4771..55c0fd1 100644 --- a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_util.cc +++ b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_util.cc
@@ -133,8 +133,8 @@ if (!policy_dict_entry.is_dict()) return AppListControllerDelegate::PIN_EDITABLE; - const std::string* policy_entry = - policy_dict_entry.FindStringKey(ChromeShelfPrefs::kPinnedAppsPrefAppIDKey); + const std::string* policy_entry = policy_dict_entry.GetDict().FindString( + ChromeShelfPrefs::kPinnedAppsPrefAppIDKey); if (!policy_entry) return AppListControllerDelegate::PIN_EDITABLE;
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_prefs.cc b/chrome/browser/ui/ash/shelf/chrome_shelf_prefs.cc index 30c82f4c..b1516ede 100644 --- a/chrome/browser/ui/ash/shelf/chrome_shelf_prefs.cc +++ b/chrome/browser/ui/ash/shelf/chrome_shelf_prefs.cc
@@ -235,7 +235,7 @@ for (const auto& policy_dict_entry : policy_apps->GetListDeprecated()) { const std::string* policy_entry = policy_dict_entry.is_dict() - ? policy_dict_entry.FindStringKey( + ? policy_dict_entry.GetDict().FindString( ChromeShelfPrefs::kPinnedAppsPrefAppIDKey) : nullptr;
diff --git a/chrome/browser/ui/ash/thumbnail_loader.cc b/chrome/browser/ui/ash/thumbnail_loader.cc index f091835..732641f0 100644 --- a/chrome/browser/ui/ash/thumbnail_loader.cc +++ b/chrome/browser/ui/ash/thumbnail_loader.cc
@@ -4,6 +4,9 @@ #include "chrome/browser/ui/ash/thumbnail_loader.h" +#include <algorithm> +#include <utility> + #include "ash/public/cpp/image_downloader.h" #include "base/bind.h" #include "base/callback_helpers.h" @@ -195,8 +198,8 @@ } const std::string* received_request_id = - result.value->FindStringKey("taskId"); - const std::string* data = result.value->FindStringKey("data"); + result.value->GetDict().FindString("taskId"); + const std::string* data = result.value->GetDict().FindString("data"); if (!data || !received_request_id || *received_request_id != request_id) { std::move(callback).Run(""); @@ -401,15 +404,16 @@ // Generate an image loader request. The request type is defined in // ui/file_manager/image_loader/load_image_request.js. base::Value request_value(base::Value::Type::DICTIONARY); - request_value.SetKey("taskId", base::Value(request_id.ToString())); - request_value.SetKey("url", base::Value(thumbnail_url.spec())); - request_value.SetKey("timestamp", base::TimeToValue(file_info.last_modified)); + base::Value::Dict& request_dict = request_value.GetDict(); + request_dict.Set("taskId", base::Value(request_id.ToString())); + request_dict.Set("url", base::Value(thumbnail_url.spec())); + request_dict.Set("timestamp", base::TimeToValue(file_info.last_modified)); // TODO(crbug.com/2650014) : Add an arg to set this to false for sharesheet. - request_value.SetBoolKey("cache", true); - request_value.SetBoolKey("crop", true); - request_value.SetKey("priority", base::Value(1)); - request_value.SetKey("width", base::Value(size)); - request_value.SetKey("height", base::Value(size)); + request_dict.Set("cache", true); + request_dict.Set("crop", true); + request_dict.Set("priority", base::Value(1)); + request_dict.Set("width", base::Value(size)); + request_dict.Set("height", base::Value(size)); std::string request_message; base::JSONWriter::Write(request_value, &request_message);
diff --git a/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc b/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc index 880d24f0..53fc6aa 100644 --- a/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc +++ b/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc
@@ -4,7 +4,10 @@ #include "chrome/browser/ui/ash/wallpaper_controller_client_impl.h" +#include <algorithm> +#include <string> #include <utility> +#include <vector> #include "ash/components/cryptohome/system_salt_getter.h" #include "ash/components/settings/cros_settings_names.h" @@ -183,7 +186,8 @@ return std::string(); const auto* daily_refresh_info_string = - read_result.settings().FindStringKey(kChromeAppDailyRefreshInfoPref); + read_result.settings().GetDict().FindString( + kChromeAppDailyRefreshInfoPref); if (!daily_refresh_info_string) return std::string(); @@ -195,7 +199,7 @@ return std::string(); const auto* collection_id = - daily_refresh_info->FindStringKey(kChromeAppCollectionId); + daily_refresh_info->GetDict().FindString(kChromeAppCollectionId); if (!collection_id) return std::string();
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_utils.cc b/chrome/browser/ui/autofill/autofill_popup_controller_utils.cc index e13e0b2..9aebffb 100644 --- a/chrome/browser/ui/autofill/autofill_popup_controller_utils.cc +++ b/chrome/browser/ui/autofill/autofill_popup_controller_utils.cc
@@ -33,7 +33,6 @@ {kTroyCard, IDR_AUTOFILL_CC_TROY}, {kUnionPay, IDR_AUTOFILL_CC_UNIONPAY}, {kVisaCard, IDR_AUTOFILL_CC_VISA}, - {kGoogleIssuedCard, IDR_AUTOFILL_GOOGLE_ISSUED_CARD}, #if BUILDFLAG(IS_ANDROID) {"httpWarning", IDR_ANDROID_AUTOFILL_HTTP_WARNING}, {"httpsInvalid", IDR_ANDROID_AUTOFILL_HTTPS_INVALID_WARNING},
diff --git a/chrome/browser/ui/color/chrome_color_id.h b/chrome/browser/ui/color/chrome_color_id.h index 2bca4c2..7b4a48f 100644 --- a/chrome/browser/ui/color/chrome_color_id.h +++ b/chrome/browser/ui/color/chrome_color_id.h
@@ -52,6 +52,8 @@ E_CPONLY(kColorDesktopMediaTabListBorder) \ E_CPONLY(kColorDesktopMediaTabListPreviewBackground) \ /* Download shelf colors. */ \ + E_CPONLY(kColorDownloadItemProgressRingBackground) \ + E_CPONLY(kColorDownloadItemProgressRingForeground) \ E(kColorDownloadShelfBackground, ThemeProperties::COLOR_DOWNLOAD_SHELF) \ E(kColorDownloadShelfButtonBackground, \ ThemeProperties::COLOR_DOWNLOAD_SHELF_BUTTON_BACKGROUND) \
diff --git a/chrome/browser/ui/color/chrome_color_mixer.cc b/chrome/browser/ui/color/chrome_color_mixer.cc index ef17095..e2270ebb 100644 --- a/chrome/browser/ui/color/chrome_color_mixer.cc +++ b/chrome/browser/ui/color/chrome_color_mixer.cc
@@ -226,6 +226,9 @@ mixer[kColorCapturedTabContentsBorder] = {ui::kColorAccent}; mixer[kColorDesktopMediaTabListBorder] = {ui::kColorMidground}; mixer[kColorDesktopMediaTabListPreviewBackground] = {ui::kColorMidground}; + mixer[kColorDownloadItemProgressRingBackground] = { + ui::SetAlpha(kColorDownloadItemProgressRingForeground, 0x33)}; + mixer[kColorDownloadItemProgressRingForeground] = {ui::kColorThrobber}; mixer[kColorDownloadShelfBackground] = {kColorToolbar}; mixer[kColorDownloadShelfButtonBackground] = {kColorDownloadShelfBackground}; mixer[kColorDownloadShelfButtonText] =
diff --git a/chrome/browser/ui/global_media_controls/cast_media_notification_item.cc b/chrome/browser/ui/global_media_controls/cast_media_notification_item.cc index 97c45118..a8d4d499 100644 --- a/chrome/browser/ui/global_media_controls/cast_media_notification_item.cc +++ b/chrome/browser/ui/global_media_controls/cast_media_notification_item.cc
@@ -155,7 +155,7 @@ profile_(profile), session_controller_(std::move(session_controller)), media_route_id_(route.media_route_id()), - is_local_presentation_(route.is_local_presentation()), + route_is_local_(route.is_local()), image_downloader_( profile, base::BindRepeating(&CastMediaNotificationItem::ImageChanged,
diff --git a/chrome/browser/ui/global_media_controls/cast_media_notification_item.h b/chrome/browser/ui/global_media_controls/cast_media_notification_item.h index c2290cfe..6734ada 100644 --- a/chrome/browser/ui/global_media_controls/cast_media_notification_item.h +++ b/chrome/browser/ui/global_media_controls/cast_media_notification_item.h
@@ -78,7 +78,7 @@ } Profile* profile() { return profile_; } bool is_active() const { return is_active_; } - bool is_local_presentation() const { return is_local_presentation_; } + bool route_is_local() const { return route_is_local_; } base::WeakPtr<CastMediaNotificationItem> GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); @@ -139,7 +139,8 @@ std::unique_ptr<CastMediaSessionController> session_controller_; const media_router::MediaRoute::Id media_route_id_; - const bool is_local_presentation_; + // True if the route is started from the |profile_| on the current device. + const bool route_is_local_; ImageDownloader image_downloader_; media_session::MediaMetadata metadata_; std::vector<media_session::mojom::MediaSessionAction> actions_;
diff --git a/chrome/browser/ui/global_media_controls/cast_media_notification_producer.cc b/chrome/browser/ui/global_media_controls/cast_media_notification_producer.cc index 9432c69..d90b2f3f 100644 --- a/chrome/browser/ui/global_media_controls/cast_media_notification_producer.cc +++ b/chrome/browser/ui/global_media_controls/cast_media_notification_producer.cc
@@ -14,10 +14,15 @@ #include "components/media_message_center/media_notification_util.h" #include "components/media_router/browser/media_router.h" #include "components/media_router/browser/media_router_factory.h" +#include "components/media_router/common/pref_names.h" #include "components/media_router/common/providers/cast/cast_media_source.h" +#include "components/prefs/pref_service.h" namespace { +// Returns false if a notification item shouldn't be created for |route|. +// If a route should be hidden, it's not possible to create an item +// for this route until the next time |OnModuleUpdated()| is called. bool ShouldHideNotification(const raw_ptr<Profile> profile, const media_router::MediaRoute& route) { // TODO(crbug.com/1195382): Display multizone group route. @@ -26,16 +31,18 @@ } if (media_router::GlobalMediaControlsCastStartStopEnabled(profile)) { - // Hide a route if it's not for display or it's a mirroring route. + // Hide a route if it's a mirroring route. if (route.media_source().IsTabMirroringSource() || route.media_source().IsDesktopMirroringSource() || route.media_source().IsLocalFileSource()) return true; } else if (route.controller_type() != media_router::RouteControllerType::kGeneric) { + // Hide a route if it doesn't have a generic controller (play, pause etc.). return true; } + // Skip the multizone member check if it's a DIAL route. if (!route.media_source().IsCastPresentationUrl()) { return false; } @@ -83,11 +90,30 @@ } std::set<std::string> -CastMediaNotificationProducer::GetActiveControllableItemIds() { +CastMediaNotificationProducer::GetActiveControllableItemIds() const { std::set<std::string> ids; for (const auto& item : items_) { - if (item.second.is_active()) - ids.insert(item.first); + if (!item.second.is_active()) + continue; + +// kMediaRouterShowCastSessionsStartedByOtherDevices is not registered on +// Android nor ChromeOS. +// // TODO(crbug.com/1308053): Enable it on ChromeOS once Cast+GMC ships. +#if !BUILDFLAG(IS_CHROMEOS) + // The non-local Cast session filter should not be put in + // |ShouldHideNotification()| because it's used to determine if an item + // should be created. It's possible that users later change the pref to + // show all Cast sessions. + if (media_router::GlobalMediaControlsCastStartStopEnabled(profile_) && + !this->profile_->GetPrefs()->GetBoolean( + media_router::prefs:: + kMediaRouterShowCastSessionsStartedByOtherDevices) && + !item.second.route_is_local()) { + continue; + } +#endif + + ids.insert(item.first); } return ids; } @@ -170,17 +196,15 @@ } size_t CastMediaNotificationProducer::GetActiveItemCount() const { - return std::count_if(items_.begin(), items_.end(), [](const auto& item) { - return item.second.is_active(); - }); + return GetActiveControllableItemIds().size(); } bool CastMediaNotificationProducer::HasActiveItems() const { - return GetActiveItemCount() != 0; + return !GetActiveControllableItemIds().empty(); } bool CastMediaNotificationProducer::HasLocalMediaRoute() const { return std::find_if(items_.begin(), items_.end(), [](const auto& item) { - return item.second.is_local_presentation(); + return item.second.route_is_local(); }) != items_.end(); }
diff --git a/chrome/browser/ui/global_media_controls/cast_media_notification_producer.h b/chrome/browser/ui/global_media_controls/cast_media_notification_producer.h index fb57b5c4..687f5ca 100644 --- a/chrome/browser/ui/global_media_controls/cast_media_notification_producer.h +++ b/chrome/browser/ui/global_media_controls/cast_media_notification_producer.h
@@ -48,7 +48,7 @@ // global_media_controls::MediaItemProducer: base::WeakPtr<media_message_center::MediaNotificationItem> GetMediaItem( const std::string& id) override; - std::set<std::string> GetActiveControllableItemIds() override; + std::set<std::string> GetActiveControllableItemIds() const override; bool HasFrozenItems() override; void OnItemShown(const std::string& id, global_media_controls::MediaItemUI* item_ui) override;
diff --git a/chrome/browser/ui/global_media_controls/cast_media_notification_producer_unittest.cc b/chrome/browser/ui/global_media_controls/cast_media_notification_producer_unittest.cc index 817d86bf..1ac8de4 100644 --- a/chrome/browser/ui/global_media_controls/cast_media_notification_producer_unittest.cc +++ b/chrome/browser/ui/global_media_controls/cast_media_notification_producer_unittest.cc
@@ -13,6 +13,8 @@ #include "components/media_message_center/mock_media_notification_view.h" #include "components/media_router/browser/test/mock_media_router.h" #include "components/media_router/common/media_route.h" +#include "components/media_router/common/pref_names.h" +#include "components/sync_preferences/testing_pref_service_syncable.h" #include "content/public/test/browser_task_environment.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -51,6 +53,8 @@ void TearDown() override { notification_producer_.reset(); } + TestingProfile* profile() { return &profile_; } + protected: content::BrowserTaskEnvironment task_environment_; TestingProfile profile_; @@ -141,4 +145,21 @@ {mirroring_route, multizone_member_route, connecting_route}); EXPECT_EQ(0u, notification_producer_->GetActiveItemCount()); } + +TEST_F(CastMediaNotificationProducerTest, NonLocalRoutesWithoutNotifications) { + MediaRoute non_local_route = CreateRoute("non-local-route"); + non_local_route.set_local(false); + sync_preferences::TestingPrefServiceSyncable* pref_service = + profile()->GetTestingPrefService(); + + notification_producer_->OnRoutesUpdated({non_local_route}); + EXPECT_EQ(1u, notification_producer_->GetActiveItemCount()); + + // There is no need to call |OnRouteUpdated()| here because this is a + // client-side change. + pref_service->SetBoolean( + media_router::prefs::kMediaRouterShowCastSessionsStartedByOtherDevices, + false); + EXPECT_EQ(0u, notification_producer_->GetActiveItemCount()); +} #endif // BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/browser/ui/global_media_controls/presentation_request_notification_producer.cc b/chrome/browser/ui/global_media_controls/presentation_request_notification_producer.cc index 778d85e..422c255 100644 --- a/chrome/browser/ui/global_media_controls/presentation_request_notification_producer.cc +++ b/chrome/browser/ui/global_media_controls/presentation_request_notification_producer.cc
@@ -100,7 +100,7 @@ } std::set<std::string> -PresentationRequestNotificationProducer::GetActiveControllableItemIds() { +PresentationRequestNotificationProducer::GetActiveControllableItemIds() const { return (item_ && !should_hide_) ? std::set<std::string>({item_->id()}) : std::set<std::string>(); }
diff --git a/chrome/browser/ui/global_media_controls/presentation_request_notification_producer.h b/chrome/browser/ui/global_media_controls/presentation_request_notification_producer.h index 6d898f9f..af18401 100644 --- a/chrome/browser/ui/global_media_controls/presentation_request_notification_producer.h +++ b/chrome/browser/ui/global_media_controls/presentation_request_notification_producer.h
@@ -65,7 +65,7 @@ base::WeakPtr<media_message_center::MediaNotificationItem> GetMediaItem( const std::string& id) override; // Returns the supplemental notification's id if it should be shown. - std::set<std::string> GetActiveControllableItemIds() override; + std::set<std::string> GetActiveControllableItemIds() const override; bool HasFrozenItems() override; void OnItemShown(const std::string& id, global_media_controls::MediaItemUI* item_ui) override;
diff --git a/chrome/browser/ui/media_router/media_router_ui.cc b/chrome/browser/ui/media_router/media_router_ui.cc index 76e837a..8eff3c13 100644 --- a/chrome/browser/ui/media_router/media_router_ui.cc +++ b/chrome/browser/ui/media_router/media_router_ui.cc
@@ -126,8 +126,12 @@ // If |start_presentation_context_| still exists, then it means presentation // route request was never attempted. if (start_presentation_context_) { + std::vector<MediaSinkWithCastModes> sinks; + if (query_result_manager_.get()) { + sinks = query_result_manager_->GetSinksWithCastModes(); + } bool presentation_sinks_available = std::any_of( - sinks_.begin(), sinks_.end(), [](const MediaSinkWithCastModes& sink) { + sinks.begin(), sinks.end(), [](const MediaSinkWithCastModes& sink) { return base::Contains(sink.cast_modes, MediaCastMode::PRESENTATION); }); if (presentation_sinks_available) { @@ -188,11 +192,6 @@ presentation_manager_->HasDefaultPresentationRequest()) { OnDefaultPresentationChanged( &presentation_manager_->GetDefaultPresentationRequest()); - } else { - // Register for MediaRoute updates without a media source. - routes_observer_ = std::make_unique<UIMediaRoutesObserver>( - GetMediaRouter(), base::BindRepeating(&MediaRouterUI::OnRoutesUpdated, - base::Unretained(this))); } } @@ -424,6 +423,10 @@ initiator_, base::BindRepeating(&MediaRouterUI::UpdateSinks, base::Unretained(this))); + routes_observer_ = std::make_unique<UIMediaRoutesObserver>( + GetMediaRouter(), base::BindRepeating(&MediaRouterUI::OnRoutesUpdated, + base::Unretained(this))); + StartObservingIssues(); } @@ -460,10 +463,6 @@ query_result_manager_->SetSourcesForCastMode( MediaCastMode::PRESENTATION, sources, presentation_request_->frame_origin); - // Register for MediaRoute updates. - routes_observer_ = std::make_unique<UIMediaRoutesObserver>( - GetMediaRouter(), base::BindRepeating(&MediaRouterUI::OnRoutesUpdated, - base::Unretained(this))); UpdateModelHeader(); } @@ -471,11 +470,6 @@ presentation_request_.reset(); query_result_manager_->RemoveSourcesForCastMode(MediaCastMode::PRESENTATION); - // Register for MediaRoute updates. - routes_observer_ = std::make_unique<UIMediaRoutesObserver>( - GetMediaRouter(), base::BindRepeating(&MediaRouterUI::OnRoutesUpdated, - base::Unretained(this))); - UpdateModelHeader(); }
diff --git a/chrome/browser/ui/media_router/query_result_manager.cc b/chrome/browser/ui/media_router/query_result_manager.cc index 28472a9..a6905d3 100644 --- a/chrome/browser/ui/media_router/query_result_manager.cc +++ b/chrome/browser/ui/media_router/query_result_manager.cc
@@ -159,6 +159,22 @@ : cast_mode_it->second; } +std::vector<MediaSinkWithCastModes> QueryResultManager::GetSinksWithCastModes() + const { + std::vector<MediaSinkWithCastModes> sinks; + for (const auto& sink_pair : sinks_with_sources_) { + MediaSinkWithCastModes sink_with_cast_modes(sink_pair.second.sink()); + sink_with_cast_modes.cast_modes = sink_pair.second.GetCastModes(); + sinks.push_back(sink_with_cast_modes); + } + for (const auto& sink : all_sinks_) { + if (!base::Contains(sinks_with_sources_, sink.id())) + sinks.push_back(MediaSinkWithCastModes(sink)); + } + + return sinks; +} + void QueryResultManager::RemoveOldSourcesForCastMode( MediaCastMode cast_mode, const std::vector<MediaSource>& new_sources) { @@ -260,16 +276,7 @@ } void QueryResultManager::NotifyOnResultsUpdated() { - std::vector<MediaSinkWithCastModes> sinks; - for (const auto& sink_pair : sinks_with_sources_) { - MediaSinkWithCastModes sink_with_cast_modes(sink_pair.second.sink()); - sink_with_cast_modes.cast_modes = sink_pair.second.GetCastModes(); - sinks.push_back(sink_with_cast_modes); - } - for (const auto& sink : all_sinks_) { - if (!base::Contains(sinks_with_sources_, sink.id())) - sinks.push_back(MediaSinkWithCastModes(sink)); - } + std::vector<MediaSinkWithCastModes> sinks = GetSinksWithCastModes(); for (QueryResultManager::Observer& observer : observers_) observer.OnResultsUpdated(sinks); }
diff --git a/chrome/browser/ui/media_router/query_result_manager.h b/chrome/browser/ui/media_router/query_result_manager.h index 5e473db9..fc49354 100644 --- a/chrome/browser/ui/media_router/query_result_manager.h +++ b/chrome/browser/ui/media_router/query_result_manager.h
@@ -114,6 +114,9 @@ // vector if there is none. std::vector<MediaSource> GetSourcesForCastMode(MediaCastMode cast_mode) const; + // Returns all of the currently known sinks with the cast modes they support + std::vector<MediaSinkWithCastModes> GetSinksWithCastModes() const; + private: class MediaSourceMediaSinksObserver; class AnyMediaSinksObserver;
diff --git a/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.cc b/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.cc index 744b4a3..ac4e11a 100644 --- a/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.cc +++ b/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.cc
@@ -17,6 +17,7 @@ #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/webui/signin/signin_utils.h" #include "components/signin/public/base/signin_metrics.h" #include "components/signin/public/identity_manager/identity_manager.h" #include "google_apis/gaia/core_account_id.h" @@ -63,19 +64,29 @@ void ShowEnterpriseProfileInterceptionDialog(const AccountInfo& account_info, SkColor profile_color) { browser_->signin_view_controller()->ShowModalEnterpriseConfirmationDialog( - account_info, profile_color, + account_info, /*force_new_profile=*/true, profile_color, base::BindOnce(&ForcedEnterpriseSigninInterceptionHandle:: OnEnterpriseInterceptionDialogClosed, base::Unretained(this))); } - void OnEnterpriseInterceptionDialogClosed(bool create_profile) { - if (!create_profile) - browser_->signin_view_controller()->CloseModalSignin(); - std::move(callback_).Run(create_profile - ? SigninInterceptionResult::kAccepted - : SigninInterceptionResult::kDeclined); + void OnEnterpriseInterceptionDialogClosed(signin::SigninChoice choice) { + switch (choice) { + case signin::SIGNIN_CHOICE_NEW_PROFILE: + std::move(callback_).Run(SigninInterceptionResult::kAccepted); + break; + case signin::SIGNIN_CHOICE_CANCEL: + browser_->signin_view_controller()->CloseModalSignin(); + std::move(callback_).Run(SigninInterceptionResult::kDeclined); + break; + case signin::SIGNIN_CHOICE_CONTINUE: + case signin::SIGNIN_CHOICE_SIZE: + default: + NOTREACHED(); + break; + } } + raw_ptr<Browser> browser_; base::OnceCallback<void(SigninInterceptionResult)> callback_; };
diff --git a/chrome/browser/ui/signin_view_controller.cc b/chrome/browser/ui/signin_view_controller.cc index da8579c4..52f48996 100644 --- a/chrome/browser/ui/signin_view_controller.cc +++ b/chrome/browser/ui/signin_view_controller.cc
@@ -20,6 +20,7 @@ #include "chrome/browser/ui/signin_modal_dialog.h" #include "chrome/browser/ui/signin_modal_dialog_impl.h" #include "chrome/browser/ui/signin_view_controller_delegate.h" +#include "chrome/browser/ui/webui/signin/signin_utils.h" #include "components/signin/public/base/consent_level.h" #include "components/signin/public/base/signin_buildflags.h" #include "components/signin/public/identity_manager/account_info.h" @@ -256,18 +257,16 @@ void SigninViewController::ShowModalEnterpriseConfirmationDialog( const AccountInfo& account_info, + bool force_new_profile, SkColor profile_color, - base::OnceCallback<void(bool)> callback) { + signin::SigninChoiceCallback callback) { #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \ BUILDFLAG(IS_CHROMEOS_LACROS) CloseModalSignin(); dialog_ = std::make_unique<SigninModalDialogImpl>( SigninViewControllerDelegate::CreateEnterpriseConfirmationDelegate( - browser_, account_info, profile_color, - base::BindOnce( - [](Browser* browser, base::OnceCallback<void(bool)> callback, - bool result) { std::move(callback).Run(result); }, - base::Unretained(browser_), std::move(callback))), + browser_, account_info, force_new_profile, profile_color, + std::move(callback)), GetOnModalDialogClosedCallback()); chrome::RecordDialogCreation( chrome::DialogIdentifier::SIGNIN_ENTERPRISE_INTERCEPTION);
diff --git a/chrome/browser/ui/signin_view_controller.h b/chrome/browser/ui/signin_view_controller.h index 3549238..25e3fcc 100644 --- a/chrome/browser/ui/signin_view_controller.h +++ b/chrome/browser/ui/signin_view_controller.h
@@ -14,6 +14,7 @@ #include "build/build_config.h" #include "chrome/browser/ui/profile_chooser_constants.h" #include "chrome/browser/ui/signin_modal_dialog.h" +#include "chrome/browser/ui/webui/signin/signin_utils.h" #include "components/signin/public/base/signin_buildflags.h" #include "third_party/skia/include/core/SkColor.h" #include "url/gurl.h" @@ -143,8 +144,9 @@ // on the dialog. void ShowModalEnterpriseConfirmationDialog( const AccountInfo& account_info, + bool force_new_profile, SkColor profile_color, - base::OnceCallback<void(bool)> callback); + signin::SigninChoiceCallback callback); // Shows the modal sign-in error dialog as a browser-modal dialog on top of // the |browser_|'s window.
diff --git a/chrome/browser/ui/signin_view_controller_delegate.h b/chrome/browser/ui/signin_view_controller_delegate.h index 38860ee7..f3213d5f 100644 --- a/chrome/browser/ui/signin_view_controller_delegate.h +++ b/chrome/browser/ui/signin_view_controller_delegate.h
@@ -10,6 +10,7 @@ #include "base/observer_list_types.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" +#include "chrome/browser/ui/webui/signin/signin_utils.h" #include "components/signin/public/base/signin_buildflags.h" #include "third_party/skia/include/core/SkColor.h" @@ -81,8 +82,9 @@ static SigninViewControllerDelegate* CreateEnterpriseConfirmationDelegate( Browser* browser, const AccountInfo& account_info, + bool force_new_profile, SkColor profile_color, - base::OnceCallback<void(bool)> callback); + signin::SigninChoiceCallback callback); #endif void AddObserver(Observer* observer);
diff --git a/chrome/browser/ui/signin_view_controller_interactive_uitest.cc b/chrome/browser/ui/signin_view_controller_interactive_uitest.cc index c11a1bcd..92f02704 100644 --- a/chrome/browser/ui/signin_view_controller_interactive_uitest.cc +++ b/chrome/browser/ui/signin_view_controller_interactive_uitest.cc
@@ -205,13 +205,14 @@ content::TestNavigationObserver content_observer( GURL("chrome://enterprise-profile-welcome/")); content_observer.StartWatchingNewWebContents(); - bool result; + signin::SigninChoice result; browser()->signin_view_controller()->ShowModalEnterpriseConfirmationDialog( - account_info, SK_ColorWHITE, + account_info, /*force_new_profile=*/true, SK_ColorWHITE, base::BindOnce( - [](Browser* browser, bool* result, bool create) { + [](Browser* browser, signin::SigninChoice* result, + signin::SigninChoice choice) { browser->signin_view_controller()->CloseModalSignin(); - *result = create; + *result = choice; }, browser(), &result)); EXPECT_TRUE(browser()->signin_view_controller()->ShowsModalDialog()); @@ -227,6 +228,6 @@ /*command=*/false)); dialog_destroyed_watcher.Wait(); - EXPECT_TRUE(result); + EXPECT_EQ(result, signin::SigninChoice::SIGNIN_CHOICE_NEW_PROFILE); EXPECT_FALSE(browser()->signin_view_controller()->ShowsModalDialog()); }
diff --git a/chrome/browser/ui/task_manager/task_manager_table_model.cc b/chrome/browser/ui/task_manager/task_manager_table_model.cc index e2f4d0c..710bee3 100644 --- a/chrome/browser/ui/task_manager/task_manager_table_model.cc +++ b/chrome/browser/ui/task_manager/task_manager_table_model.cc
@@ -6,6 +6,9 @@ #include <stddef.h> +#include <string> +#include <vector> + #include "base/command_line.h" #include "base/i18n/number_formatting.h" #include "base/i18n/rtl.h" @@ -826,9 +829,9 @@ // Do a best effort of retrieving the correct settings from the local state. // Use the default settings of the value if it fails to be retrieved. const std::string* sorted_col_id = - dictionary->FindStringKey(kSortColumnIdKey); + dictionary->GetDict().FindString(kSortColumnIdKey); bool sort_is_ascending = - dictionary->FindBoolKey(kSortIsAscendingKey).value_or(true); + dictionary->GetDict().FindBool(kSortIsAscendingKey).value_or(true); int current_visible_column_index = 0; for (size_t i = 0; i < kColumnsSize; ++i) { @@ -874,13 +877,14 @@ // Store the current sort status to be restored again at startup. if (!table_view_delegate_->IsTableSorted()) { - dict_update->SetStringKey(kSortColumnIdKey, ""); + dict_update->GetDict().Set(kSortColumnIdKey, ""); } else { const auto& sort_descriptor = table_view_delegate_->GetSortDescriptor(); - dict_update->SetStringKey( + dict_update->GetDict().Set( kSortColumnIdKey, GetColumnIdAsString(sort_descriptor.sorted_column_id)); - dict_update->SetBoolKey(kSortIsAscendingKey, sort_descriptor.is_ascending); + dict_update->GetDict().Set(kSortIsAscendingKey, + sort_descriptor.is_ascending); } }
diff --git a/chrome/browser/ui/views/download/download_item_view.cc b/chrome/browser/ui/views/download/download_item_view.cc index 118f311..8677c236 100644 --- a/chrome/browser/ui/views/download/download_item_view.cc +++ b/chrome/browser/ui/views/download/download_item_view.cc
@@ -1061,8 +1061,6 @@ const gfx::RectF& bounds, const base::TimeDelta& indeterminate_progress_time, int percent_done) const { - const SkColor color = GetColorProvider()->GetColor(ui::kColorThrobber); - // Calculate progress. SkScalar start_pos = SkIntToScalar(270); // 12 o'clock SkScalar sweep_angle = SkDoubleToScalar(360 * percent_done / 100.0); @@ -1074,9 +1072,12 @@ sweep_angle = SkIntToScalar(50); } - views::DrawProgressRing(canvas, gfx::RectFToSkRect(bounds), - SkColorSetA(color, 0x33), color, - /*stroke_width=*/1.7f, start_pos, sweep_angle); + const auto* color_provider = GetColorProvider(); + views::DrawProgressRing( + canvas, gfx::RectFToSkRect(bounds), + color_provider->GetColor(kColorDownloadItemProgressRingBackground), + color_provider->GetColor(kColorDownloadItemProgressRingForeground), + /*stroke_width=*/1.7f, start_pos, sweep_angle); } ui::ImageModel DownloadItemView::GetIcon() const {
diff --git a/chrome/browser/ui/views/global_media_controls/media_toolbar_button_contextual_menu.cc b/chrome/browser/ui/views/global_media_controls/media_toolbar_button_contextual_menu.cc index 2c817c8..f093a6f 100644 --- a/chrome/browser/ui/views/global_media_controls/media_toolbar_button_contextual_menu.cc +++ b/chrome/browser/ui/views/global_media_controls/media_toolbar_button_contextual_menu.cc
@@ -9,37 +9,47 @@ #include "chrome/browser/media/router/media_router_feature.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/global_media_controls/media_notification_service.h" +#include "chrome/browser/ui/global_media_controls/media_notification_service_factory.h" #include "chrome/browser/ui/singleton_tabs.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" #include "chrome/grit/generated_resources.h" +#include "components/global_media_controls/public/media_item_manager.h" +#include "components/media_router/common/pref_names.h" #include "components/prefs/pref_service.h" +namespace { +global_media_controls::MediaItemManager* GetItemManagerFromBrowser( + Browser* browser) { + return MediaNotificationServiceFactory::GetForProfile(browser->profile()) + ->media_item_manager(); +} +} // namespace + std::unique_ptr<MediaToolbarButtonContextualMenu> MediaToolbarButtonContextualMenu::Create(Browser* browser) { -#if BUILDFLAG(GOOGLE_CHROME_BRANDING) if (media_router::GlobalMediaControlsCastStartStopEnabled( browser->profile())) { return std::make_unique<MediaToolbarButtonContextualMenu>(browser); } -#endif return nullptr; } MediaToolbarButtonContextualMenu::MediaToolbarButtonContextualMenu( Browser* browser) -#if BUILDFLAG(GOOGLE_CHROME_BRANDING) - : browser_(browser) -#endif -{ -} + : browser_(browser), item_manager_(GetItemManagerFromBrowser(browser_)) {} MediaToolbarButtonContextualMenu::~MediaToolbarButtonContextualMenu() = default; std::unique_ptr<ui::SimpleMenuModel> MediaToolbarButtonContextualMenu::CreateMenuModel() { -#if BUILDFLAG(GOOGLE_CHROME_BRANDING) auto menu_model = std::make_unique<ui::SimpleMenuModel>(this); + menu_model->AddCheckItemWithStringId( + IDC_MEDIA_TOOLBAR_CONTEXT_SHOW_OTHER_SESSIONS, + IDS_MEDIA_TOOLBAR_CONTEXT_SHOW_OTHER_SESSIONS); + +#if BUILDFLAG(GOOGLE_CHROME_BRANDING) if (!browser_->profile()->IsOffTheRecord() && browser_->profile()->GetPrefs()->GetBoolean( prefs::kUserFeedbackAllowed)) { @@ -47,15 +57,29 @@ IDC_MEDIA_TOOLBAR_CONTEXT_REPORT_CAST_ISSUE, IDS_MEDIA_TOOLBAR_CONTEXT_REPORT_CAST_ISSUE); } - return menu_model; -#else - return nullptr; #endif + return menu_model; +} + +bool MediaToolbarButtonContextualMenu::IsCommandIdChecked( + int command_id) const { + PrefService* pref_service = browser_->profile()->GetPrefs(); + switch (command_id) { + case IDC_MEDIA_TOOLBAR_CONTEXT_SHOW_OTHER_SESSIONS: + return pref_service->GetBoolean( + media_router::prefs:: + kMediaRouterShowCastSessionsStartedByOtherDevices); + default: + return false; + } } void MediaToolbarButtonContextualMenu::ExecuteCommand(int command_id, int event_flags) { switch (command_id) { + case IDC_MEDIA_TOOLBAR_CONTEXT_SHOW_OTHER_SESSIONS: + ToggleShowOtherSessions(); + break; #if BUILDFLAG(GOOGLE_CHROME_BRANDING) case IDC_MEDIA_TOOLBAR_CONTEXT_REPORT_CAST_ISSUE: ReportIssue(); @@ -66,6 +90,21 @@ } } +void MediaToolbarButtonContextualMenu::MenuClosed(ui::SimpleMenuModel* source) { + if (item_manager_) { + item_manager_->OnItemsChanged(); + } +} + +void MediaToolbarButtonContextualMenu::ToggleShowOtherSessions() { + PrefService* pref_service = browser_->profile()->GetPrefs(); + pref_service->SetBoolean( + media_router::prefs::kMediaRouterShowCastSessionsStartedByOtherDevices, + !pref_service->GetBoolean( + media_router::prefs:: + kMediaRouterShowCastSessionsStartedByOtherDevices)); +} + #if BUILDFLAG(GOOGLE_CHROME_BRANDING) void MediaToolbarButtonContextualMenu::ReportIssue() { ShowSingletonTab(
diff --git a/chrome/browser/ui/views/global_media_controls/media_toolbar_button_contextual_menu.h b/chrome/browser/ui/views/global_media_controls/media_toolbar_button_contextual_menu.h index bfc9844..384643e 100644 --- a/chrome/browser/ui/views/global_media_controls/media_toolbar_button_contextual_menu.h +++ b/chrome/browser/ui/views/global_media_controls/media_toolbar_button_contextual_menu.h
@@ -10,11 +10,13 @@ #include "ui/base/models/simple_menu_model.h" class Browser; +namespace global_media_controls { +class MediaItemManager; +} -// The contextual menu of the media toolbar button has only one item, which is -// to open the Cast feedback page. So this class should be instantiated when: -// (1) It is a Chrome branded build. -// (2) GlobalMediaControlsCastStartStop is enabled. +// The contextual menu of the media toolbar button has two items, both of which +// are related to Cast. So this class should be instantiated only when +// GlobalMediaControlsCastStartStop is enabled. class MediaToolbarButtonContextualMenu : public ui::SimpleMenuModel::Delegate { public: static std::unique_ptr<MediaToolbarButtonContextualMenu> Create( @@ -32,13 +34,18 @@ friend class MediaToolbarButtonContextualMenuTest; // ui::SimpleMenuModel::Delegate: + bool IsCommandIdChecked(int command_id) const override; void ExecuteCommand(int command_id, int event_flags) override; + void MenuClosed(ui::SimpleMenuModel* source) override; + + void ToggleShowOtherSessions(); #if BUILDFLAG(GOOGLE_CHROME_BRANDING) // Opens the Cast feedback page. void ReportIssue(); +#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING) const raw_ptr<Browser> browser_; -#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING) + const raw_ptr<global_media_controls::MediaItemManager> item_manager_; }; #endif // CHROME_BROWSER_UI_VIEWS_GLOBAL_MEDIA_CONTROLS_MEDIA_TOOLBAR_BUTTON_CONTEXTUAL_MENU_H_
diff --git a/chrome/browser/ui/views/global_media_controls/media_toolbar_button_contextual_menu_unittest.cc b/chrome/browser/ui/views/global_media_controls/media_toolbar_button_contextual_menu_unittest.cc index e7d68925..68d1d33 100644 --- a/chrome/browser/ui/views/global_media_controls/media_toolbar_button_contextual_menu_unittest.cc +++ b/chrome/browser/ui/views/global_media_controls/media_toolbar_button_contextual_menu_unittest.cc
@@ -12,6 +12,7 @@ #include "chrome/test/base/browser_with_test_window_test.h" #include "chrome/test/base/menu_model_test.h" #include "components/media_router/browser/test/mock_media_router.h" +#include "components/media_router/common/pref_names.h" class MediaToolbarButtonContextualMenuTest : public MenuModelTest, public BrowserWithTestWindowTest { @@ -29,6 +30,15 @@ menu_ = MediaToolbarButtonContextualMenu::Create(browser()); } + void ExecuteToggleOtherSessionCommand() { + menu_->ExecuteCommand(IDC_MEDIA_TOOLBAR_CONTEXT_SHOW_OTHER_SESSIONS, 0); + } + + bool IsOtherSessionItemChecked() { + return menu_->IsCommandIdChecked( + IDC_MEDIA_TOOLBAR_CONTEXT_SHOW_OTHER_SESSIONS); + } + #if BUILDFLAG(GOOGLE_CHROME_BRANDING) void ExecuteReportIssueCommand() { menu_->ExecuteCommand(IDC_MEDIA_TOOLBAR_CONTEXT_REPORT_CAST_ISSUE, 0); @@ -40,22 +50,44 @@ base::test::ScopedFeatureList feature_list_; }; -#if BUILDFLAG(GOOGLE_CHROME_BRANDING) TEST_F(MediaToolbarButtonContextualMenuTest, ShowMenu) { auto menu = MediaToolbarButtonContextualMenu::Create(browser()); auto model = menu->CreateMenuModel(); - EXPECT_EQ(model->GetItemCount(), 1); - EXPECT_EQ(model->GetCommandIdAt(0), +#if BUILDFLAG(GOOGLE_CHROME_BRANDING) + EXPECT_EQ(model->GetItemCount(), 2); + EXPECT_EQ(model->GetCommandIdAt(1), IDC_MEDIA_TOOLBAR_CONTEXT_REPORT_CAST_ISSUE); -} #else -TEST_F(MediaToolbarButtonContextualMenuTest, DoNotShowMenu) { - EXPECT_FALSE(MediaToolbarButtonContextualMenu::Create(browser())); + EXPECT_EQ(model->GetItemCount(), 1); +#endif + EXPECT_EQ(model->GetCommandIdAt(0), + IDC_MEDIA_TOOLBAR_CONTEXT_SHOW_OTHER_SESSIONS); +} + +// The kMediaRouterShowCastSessionsStartedByOtherDevices pref is not registered +// on ChromeOS nor Android. +#if !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID) +TEST_F(MediaToolbarButtonContextualMenuTest, ToggleOtherSessionsItem) { + PrefService* pref_service = browser()->profile()->GetPrefs(); + pref_service->SetBoolean( + media_router::prefs::kMediaRouterShowCastSessionsStartedByOtherDevices, + false); + EXPECT_FALSE(IsOtherSessionItemChecked()); + + ExecuteToggleOtherSessionCommand(); + EXPECT_TRUE(IsOtherSessionItemChecked()); + EXPECT_TRUE(pref_service->GetBoolean( + media_router::prefs::kMediaRouterShowCastSessionsStartedByOtherDevices)); + + ExecuteToggleOtherSessionCommand(); + EXPECT_FALSE(IsOtherSessionItemChecked()); + EXPECT_FALSE(pref_service->GetBoolean( + media_router::prefs::kMediaRouterShowCastSessionsStartedByOtherDevices)); } #endif #if BUILDFLAG(GOOGLE_CHROME_BRANDING) -TEST_F(MediaToolbarButtonContextualMenuTest, ExecuteCommand) { +TEST_F(MediaToolbarButtonContextualMenuTest, ExecuteReportIssueCommand) { ExecuteReportIssueCommand(); EXPECT_EQ(browser()->tab_strip_model()->GetWebContentsAt(0)->GetURL(), GURL("chrome://cast-feedback"));
diff --git a/chrome/browser/ui/views/menu_item_view_interactive_uitest.cc b/chrome/browser/ui/views/menu_item_view_interactive_uitest.cc index 5d024d3..fc759e5 100644 --- a/chrome/browser/ui/views/menu_item_view_interactive_uitest.cc +++ b/chrome/browser/ui/views/menu_item_view_interactive_uitest.cc
@@ -58,15 +58,7 @@ // If this flakes, disable and log details in http://crbug.com/523255. VIEW_TEST(MenuItemViewTestBasic0, SelectItem0) VIEW_TEST(MenuItemViewTestBasic1, SelectItem1) - -// If this flakes, disable and log details in http://crbug.com/523255. -// Flake on Linux Tests (Wayland) builder. see http://crbug.com/523255. -#if BUILDFLAG(IS_LINUX) -#define MAYBE_SelectItem2 DISABLED_SelectItem2 -#else -#define MAYBE_SelectItem2 SelectItem2 -#endif -VIEW_TEST(MenuItemViewTestBasic2, MAYBE_SelectItem2) +VIEW_TEST(MenuItemViewTestBasic2, SelectItem2) // Test class for inserting a menu item while the menu is open. template <int INSERT_INDEX, int SELECT_INDEX>
diff --git a/chrome/browser/ui/views/profiles/profile_picker_signed_in_flow_controller.cc b/chrome/browser/ui/views/profiles/profile_picker_signed_in_flow_controller.cc index 69beea1..c59e8f2 100644 --- a/chrome/browser/ui/views/profiles/profile_picker_signed_in_flow_controller.cc +++ b/chrome/browser/ui/views/profiles/profile_picker_signed_in_flow_controller.cc
@@ -78,7 +78,7 @@ void ProfilePickerSignedInFlowController::SwitchToEnterpriseProfileWelcome( EnterpriseProfileWelcomeUI::ScreenType type, - base::OnceCallback<void(bool)> proceed_callback) { + signin::SigninChoiceCallback proceed_callback) { DCHECK(IsInitialized()); host_->ShowScreen(contents(), GURL(chrome::kChromeUIEnterpriseProfileWelcomeURL), @@ -150,7 +150,7 @@ void ProfilePickerSignedInFlowController:: SwitchToEnterpriseProfileWelcomeFinished( EnterpriseProfileWelcomeUI::ScreenType type, - base::OnceCallback<void(bool)> proceed_callback) { + signin::SigninChoiceCallback proceed_callback) { DCHECK(IsInitialized()); // Initialize the WebUI page once we know it's committed. EnterpriseProfileWelcomeUI* enterprise_profile_welcome_ui = @@ -163,7 +163,8 @@ /*browser=*/nullptr, type, IdentityManagerFactory::GetForProfile(profile_) ->FindExtendedAccountInfoByEmailAddress(email_), - GetProfileColor(), std::move(proceed_callback)); + /*force_new_profile_=*/true, GetProfileColor(), + std::move(proceed_callback)); } bool ProfilePickerSignedInFlowController::IsInitialized() const {
diff --git a/chrome/browser/ui/views/profiles/profile_picker_signed_in_flow_controller.h b/chrome/browser/ui/views/profiles/profile_picker_signed_in_flow_controller.h index 0a6b50df..33c0b06 100644 --- a/chrome/browser/ui/views/profiles/profile_picker_signed_in_flow_controller.h +++ b/chrome/browser/ui/views/profiles/profile_picker_signed_in_flow_controller.h
@@ -61,7 +61,7 @@ // screen. void SwitchToEnterpriseProfileWelcome( EnterpriseProfileWelcomeUI::ScreenType type, - base::OnceCallback<void(bool)> proceed_callback); + signin::SigninChoiceCallback proceed_callback); // When the sign-in flow cannot be completed because another profile at // `profile_path` is already syncing with a chosen account, shows the profile @@ -94,7 +94,7 @@ void SwitchToSyncConfirmationFinished(); void SwitchToEnterpriseProfileWelcomeFinished( EnterpriseProfileWelcomeUI::ScreenType type, - base::OnceCallback<void(bool)> proceed_callback); + signin::SigninChoiceCallback proceed_callback); // Returns whether the flow is initialized (i.e. whether `Init()` has been // called).
diff --git a/chrome/browser/ui/views/profiles/profile_picker_turn_sync_on_delegate.cc b/chrome/browser/ui/views/profiles/profile_picker_turn_sync_on_delegate.cc index 468d38482..df6e5c4 100644 --- a/chrome/browser/ui/views/profiles/profile_picker_turn_sync_on_delegate.cc +++ b/chrome/browser/ui/views/profiles/profile_picker_turn_sync_on_delegate.cc
@@ -224,8 +224,8 @@ void ProfilePickerTurnSyncOnDelegate::OnEnterpriseWelcomeClosed( EnterpriseProfileWelcomeUI::ScreenType type, - bool proceed) { - if (!proceed) { + signin::SigninChoice choice) { + if (choice == signin::SIGNIN_CHOICE_CANCEL) { LogOutcome(ProfileMetrics::ProfileSignedInFlowOutcome:: kAbortedOnEnterpriseWelcome); // The callback provided by TurnSyncOnHelper must be called, UI_CLOSED @@ -237,6 +237,8 @@ return; } + DCHECK_EQ(choice, signin::SIGNIN_CHOICE_NEW_PROFILE); + switch (type) { case EnterpriseProfileWelcomeUI::ScreenType::kEntepriseAccountSyncEnabled: ShowSyncConfirmationScreen();
diff --git a/chrome/browser/ui/views/profiles/profile_picker_turn_sync_on_delegate.h b/chrome/browser/ui/views/profiles/profile_picker_turn_sync_on_delegate.h index 8c62234..98d24018 100644 --- a/chrome/browser/ui/views/profiles/profile_picker_turn_sync_on_delegate.h +++ b/chrome/browser/ui/views/profiles/profile_picker_turn_sync_on_delegate.h
@@ -67,7 +67,7 @@ // Shows the enterprise welcome screen. void ShowEnterpriseWelcome(EnterpriseProfileWelcomeUI::ScreenType type); void OnEnterpriseWelcomeClosed(EnterpriseProfileWelcomeUI::ScreenType type, - bool proceed); + signin::SigninChoice choice); // Reports metric with the outcome of the turn-sync-on flow. void LogOutcome(ProfileMetrics::ProfileSignedInFlowOutcome outcome);
diff --git a/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc b/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc index 3700e7f..3e502f9d 100644 --- a/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc +++ b/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
@@ -53,6 +53,7 @@ #include "chrome/browser/ui/webui/signin/profile_picker_handler.h" #include "chrome/browser/ui/webui/signin/profile_picker_ui.h" #include "chrome/browser/ui/webui/signin/signin_url_utils.h" +#include "chrome/browser/ui/webui/signin/signin_utils.h" #include "chrome/common/pref_names.h" #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/profile_deletion_observer.h" @@ -1270,7 +1271,7 @@ void ExpectEnterpriseScreenTypeAndProceed( EnterpriseProfileWelcomeUI::ScreenType expected_type, - bool proceed) { + signin::SigninChoice choice) { EnterpriseProfileWelcomeHandler* handler = web_contents() ->GetWebUI() @@ -1280,7 +1281,7 @@ EXPECT_EQ(handler->GetTypeForTesting(), expected_type); // Simulate clicking on the next button. - handler->CallProceedCallbackForTesting(proceed); + handler->CallProceedCallbackForTesting(choice); } }; @@ -1299,7 +1300,7 @@ ExpectEnterpriseScreenTypeAndProceed( /*expected_type=*/EnterpriseProfileWelcomeUI::ScreenType:: kEntepriseAccountSyncEnabled, - /*proceed=*/true); + /*choice=*/signin::SIGNIN_CHOICE_NEW_PROFILE); WaitForLoadStop(GetSyncConfirmationURL()); // Simulate finishing the flow with "No, thanks". @@ -1356,7 +1357,7 @@ ExpectEnterpriseScreenTypeAndProceed( /*expected_type=*/EnterpriseProfileWelcomeUI::ScreenType:: kConsumerAccountSyncDisabled, - /*proceed=*/true); + /*choice=*/signin::SIGNIN_CHOICE_NEW_PROFILE); Browser* new_browser = BrowserAddedWaiter(2u).Wait(); WaitForLoadStop(GURL("chrome://newtab/"), @@ -1400,7 +1401,7 @@ ExpectEnterpriseScreenTypeAndProceed( /*expected_type=*/EnterpriseProfileWelcomeUI::ScreenType:: kEntepriseAccountSyncEnabled, - /*proceed=*/true); + /*choice=*/signin::SIGNIN_CHOICE_NEW_PROFILE); WaitForLoadStop(GetSyncConfirmationURL()); // Simulate finishing the flow with "Configure sync". @@ -1454,7 +1455,7 @@ ExpectEnterpriseScreenTypeAndProceed( /*expected_type=*/EnterpriseProfileWelcomeUI::ScreenType:: kEntepriseAccountSyncEnabled, - /*proceed=*/false); + /*choice=*/signin::SIGNIN_CHOICE_CANCEL); // As the profile creation flow was opened directly, the window is closed now. WaitForPickerClosed();
diff --git a/chrome/browser/ui/views/profiles/signin_view_controller_delegate_views.cc b/chrome/browser/ui/views/profiles/signin_view_controller_delegate_views.cc index ee8d372..22b29eb 100644 --- a/chrome/browser/ui/views/profiles/signin_view_controller_delegate_views.cc +++ b/chrome/browser/ui/views/profiles/signin_view_controller_delegate_views.cc
@@ -19,6 +19,7 @@ #include "chrome/browser/ui/views/frame/browser_view.h" #include "chrome/browser/ui/webui/signin/profile_customization_ui.h" #include "chrome/browser/ui/webui/signin/signin_url_utils.h" +#include "chrome/browser/ui/webui/signin/signin_utils.h" #include "chrome/browser/ui/webui/signin/sync_confirmation_ui.h" #include "chrome/common/url_constants.h" #include "chrome/common/webui_url_constants.h" @@ -143,8 +144,9 @@ SigninViewControllerDelegateViews::CreateEnterpriseConfirmationWebView( Browser* browser, const AccountInfo& account_info, + bool force_new_profile, SkColor profile_color, - base::OnceCallback<void(bool)> callback) { + signin::SigninChoiceCallback callback) { std::unique_ptr<views::WebView> web_view = CreateDialogWebView( browser, GURL(chrome::kChromeUIEnterpriseProfileWelcomeURL), kSyncConfirmationDialogHeight, kSyncConfirmationDialogWidth, @@ -159,7 +161,7 @@ web_dialog_ui->Initialize( browser, EnterpriseProfileWelcomeUI::ScreenType::kEnterpriseAccountCreation, - account_info, profile_color, std::move(callback)); + account_info, force_new_profile, profile_color, std::move(callback)); return web_view; } @@ -420,11 +422,13 @@ SigninViewControllerDelegate::CreateEnterpriseConfirmationDelegate( Browser* browser, const AccountInfo& account_info, + bool force_new_profile, SkColor profile_color, - base::OnceCallback<void(bool)> callback) { + signin::SigninChoiceCallback callback) { return new SigninViewControllerDelegateViews( SigninViewControllerDelegateViews::CreateEnterpriseConfirmationWebView( - browser, account_info, profile_color, std::move(callback)), + browser, account_info, force_new_profile, profile_color, + std::move(callback)), browser, ui::MODAL_TYPE_WINDOW, true, false); } #endif
diff --git a/chrome/browser/ui/views/profiles/signin_view_controller_delegate_views.h b/chrome/browser/ui/views/profiles/signin_view_controller_delegate_views.h index 99583697..4e21a7f 100644 --- a/chrome/browser/ui/views/profiles/signin_view_controller_delegate_views.h +++ b/chrome/browser/ui/views/profiles/signin_view_controller_delegate_views.h
@@ -14,6 +14,7 @@ #include "chrome/browser/ui/profile_chooser_constants.h" #include "chrome/browser/ui/signin_view_controller_delegate.h" #include "chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.h" +#include "chrome/browser/ui/webui/signin/signin_utils.h" #include "components/signin/public/base/signin_buildflags.h" #include "content/public/browser/web_contents_delegate.h" #include "third_party/skia/include/core/SkColor.h" @@ -75,8 +76,9 @@ static std::unique_ptr<views::WebView> CreateEnterpriseConfirmationWebView( Browser* browser, const AccountInfo& account_info, + bool force_new_profile, SkColor profile_color, - base::OnceCallback<void(bool)> callback); + signin::SigninChoiceCallback callback); #endif // views::DialogDelegateView:
diff --git a/chrome/browser/ui/views/web_apps/force_installed_deprecated_apps_dialog_view.cc b/chrome/browser/ui/views/web_apps/force_installed_deprecated_apps_dialog_view.cc index 72602e3..57dd9109 100644 --- a/chrome/browser/ui/views/web_apps/force_installed_deprecated_apps_dialog_view.cc +++ b/chrome/browser/ui/views/web_apps/force_installed_deprecated_apps_dialog_view.cc
@@ -14,6 +14,7 @@ #include "ui/base/window_open_disposition.h" #include "ui/views/controls/link.h" #include "ui/views/controls/styled_label.h" +#include "ui/views/layout/box_layout.h" #include "ui/views/layout/layout_provider.h" #include "ui/views/window/dialog_delegate.h" @@ -47,34 +48,31 @@ const extensions::Extension* extension = extensions::ExtensionRegistry::Get(browser_context) ->GetInstalledExtension(app_id_); - SetUseDefaultFillLayout(true); - auto* info_label = AddChildView(std::make_unique<views::StyledLabel>()); + SetLayoutManager(std::make_unique<views::BoxLayout>( + views::BoxLayout::Orientation::kVertical, gfx::Insets(), + ChromeLayoutProvider::Get()->GetDistanceMetric( + views::DISTANCE_RELATED_CONTROL_VERTICAL))); + auto* info_label = AddChildView(std::make_unique<views::Label>( + l10n_util::GetStringFUTF16(IDS_FORCE_INSTALLED_DEPRECATED_APPS_CONTENT, + base::UTF8ToUTF16(extension->name())))); + info_label->SetMultiLine(true); + info_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); - std::vector<size_t> offsets; - std::u16string link_text = - l10n_util::GetStringUTF16(IDS_DEPRECATED_APPS_LEARN_MORE); - std::u16string info_text = - l10n_util::GetStringUTF16(IDS_FORCE_INSTALLED_DEPRECATED_APPS_CONTENT); - std::u16string label_text = l10n_util::FormatString( - info_text, {base::UTF8ToUTF16(extension->name()), link_text}, &offsets); - - const size_t offset = offsets.back(); - - auto link_style = - views::StyledLabel::RangeStyleInfo::CreateForLink(base::BindRepeating( - [](content::WebContents* web_contents, const ui::Event& event) { - web_contents->OpenURL(content::OpenURLParams( - GURL(chrome::kChromeAppsDeprecationLearnMoreURL), - content::Referrer(), - ui::DispositionFromEventFlags( - event.flags(), WindowOpenDisposition::NEW_FOREGROUND_TAB), - ui::PAGE_TRANSITION_LINK, /*is_renderer_initiated=*/false)); - }, - web_contents_)); - link_style.disable_line_wrapping = true; - info_label->SetText(label_text); - info_label->AddStyleRange(gfx::Range(offset, offset + link_text.length()), - link_style); + auto* learn_more = AddChildView(std::make_unique<views::Link>( + l10n_util::GetStringUTF16(IDS_DEPRECATED_APPS_LEARN_MORE))); + learn_more->SetCallback(base::BindRepeating( + [](content::WebContents* web_contents, const ui::Event& event) { + web_contents->OpenURL(content::OpenURLParams( + GURL(chrome::kChromeAppsDeprecationLearnMoreURL), + content::Referrer(), + ui::DispositionFromEventFlags( + event.flags(), WindowOpenDisposition::NEW_FOREGROUND_TAB), + ui::PAGE_TRANSITION_LINK, /*is_renderer_initiated=*/false)); + }, + web_contents_)); + learn_more->SetAccessibleName(l10n_util::GetStringUTF16( + IDS_FORCE_INSTALLED_DEPRECATED_APPS_LEARN_MORE_AX_LABEL)); + learn_more->SetHorizontalAlignment(gfx::ALIGN_LEFT); } BEGIN_METADATA(ForceInstalledDeprecatedAppsDialogView, views::View)
diff --git a/chrome/browser/ui/webui/access_code_cast/access_code_cast_handler.cc b/chrome/browser/ui/webui/access_code_cast/access_code_cast_handler.cc index d351b0e..6bcd801 100644 --- a/chrome/browser/ui/webui/access_code_cast/access_code_cast_handler.cc +++ b/chrome/browser/ui/webui/access_code_cast/access_code_cast_handler.cc
@@ -153,8 +153,15 @@ // If |start_presentation_context_| still exists, then it means presentation // route request was never attempted. if (start_presentation_context_) { - if (sink_id_ && - base::Contains(supported_cast_modes_, MediaCastMode::PRESENTATION)) { + std::vector<MediaSinkWithCastModes> sinks; + if (query_result_manager_.get()) { + sinks = query_result_manager_->GetSinksWithCastModes(); + } + bool presentation_sinks_available = std::any_of( + sinks.begin(), sinks.end(), [](const MediaSinkWithCastModes& sink) { + return base::Contains(sink.cast_modes, MediaCastMode::PRESENTATION); + }); + if (presentation_sinks_available) { start_presentation_context_->InvokeErrorCallback( blink::mojom::PresentationError(blink::mojom::PresentationErrorType:: PRESENTATION_REQUEST_CANCELLED,
diff --git a/chrome/browser/ui/webui/access_code_cast/access_code_cast_handler.h b/chrome/browser/ui/webui/access_code_cast/access_code_cast_handler.h index 4b80259..a07fd5e 100644 --- a/chrome/browser/ui/webui/access_code_cast/access_code_cast_handler.h +++ b/chrome/browser/ui/webui/access_code_cast/access_code_cast_handler.h
@@ -150,8 +150,6 @@ // The id of the media sink discovered from the access code; absl::optional<MediaSink::Id> sink_id_; - // Set of cast modes supported by the discovered sink; - media_router::CastModeSet supported_cast_modes_; // Monitors and reports sink availability. std::unique_ptr<QueryResultManager> query_result_manager_;
diff --git a/chrome/browser/ui/webui/access_code_cast/access_code_cast_ui.cc b/chrome/browser/ui/webui/access_code_cast/access_code_cast_ui.cc index 9a88da8..43adbbf8 100644 --- a/chrome/browser/ui/webui/access_code_cast/access_code_cast_ui.cc +++ b/chrome/browser/ui/webui/access_code_cast/access_code_cast_ui.cc
@@ -34,13 +34,11 @@ using media_router::AccessCodeCastHandler; -// Creates default params for showing AccessCodeCastDialog in ChromeOS +// Creates default params for showing AccessCodeCastDialog views::Widget::InitParams CreateParams() { views::Widget::InitParams params; params.remove_standard_frame = true; params.corner_radius = 12; - // Dialog frame view has its own shadow. - params.shadow_type = views::Widget::InitParams::ShadowType::kNone; params.type = views::Widget::InitParams::Type::TYPE_BUBBLE; // Make sure the dialog border is rendered correctly params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
diff --git a/chrome/browser/ui/webui/chromeos/login/l10n_util.cc b/chrome/browser/ui/webui/chromeos/login/l10n_util.cc index cdede97..5243d78 100644 --- a/chrome/browser/ui/webui/chromeos/login/l10n_util.cc +++ b/chrome/browser/ui/webui/chromeos/login/l10n_util.cc
@@ -52,18 +52,16 @@ constexpr char16_t kMostRelevantLanguagesDivider16[] = u"MOST_RELEVANT_LANGUAGES_DIVIDER"; -std::unique_ptr<base::DictionaryValue> CreateInputMethodsEntry( +base::Value CreateInputMethodsEntry( const input_method::InputMethodDescriptor& method, const std::string selected, input_method::InputMethodUtil* util) { const std::string& ime_id = method.id(); - std::unique_ptr<base::DictionaryValue> input_method( - new base::DictionaryValue); - input_method->GetDict().Set("value", ime_id); - input_method->GetDict().Set("title", - util->GetInputMethodLongNameStripped(method)); - input_method->GetDict().Set("selected", ime_id == selected); - return input_method; + base::Value::Dict input_method; + input_method.Set("value", ime_id); + input_method.Set("title", util->GetInputMethodLongNameStripped(method)); + input_method.Set("selected", ime_id == selected); + return base::Value(std::move(input_method)); } // Returns true if element was inserted. @@ -74,14 +72,13 @@ } void AddOptgroupOtherLayouts(base::ListValue* input_methods_list) { - std::unique_ptr<base::DictionaryValue> optgroup(new base::DictionaryValue); - optgroup->GetDict().Set( - "optionGroupName", - l10n_util::GetStringUTF16(IDS_OOBE_OTHER_KEYBOARD_LAYOUTS)); - input_methods_list->Append(std::move(optgroup)); + base::Value::Dict optgroup; + optgroup.Set("optionGroupName", + l10n_util::GetStringUTF16(IDS_OOBE_OTHER_KEYBOARD_LAYOUTS)); + input_methods_list->Append(base::Value(std::move(optgroup))); } -std::unique_ptr<base::DictionaryValue> CreateLanguageEntry( +base::Value CreateLanguageEntry( const std::string& language_code, const std::u16string& language_display_name, const std::u16string& language_native_display_name) { @@ -92,14 +89,14 @@ const bool has_rtl_chars = base::i18n::StringContainsStrongRTLChars(display_name); - const std::string directionality = has_rtl_chars ? "rtl" : "ltr"; + const char* directionality = has_rtl_chars ? "rtl" : "ltr"; - auto dictionary = std::make_unique<base::DictionaryValue>(); - dictionary->GetDict().Set("code", language_code); - dictionary->GetDict().Set("displayName", language_display_name); - dictionary->GetDict().Set("textDirection", directionality); - dictionary->GetDict().Set("nativeDisplayName", language_native_display_name); - return dictionary; + base::Value::Dict dictionary; + dictionary.Set("code", language_code); + dictionary.Set("displayName", language_display_name); + dictionary.Set("textDirection", directionality); + dictionary.Set("nativeDisplayName", language_native_display_name); + return base::Value(std::move(dictionary)); } // Gets the list of languages with `descriptors` based on `base_language_codes`. @@ -270,9 +267,9 @@ std::u16string display_name(out_display_names[i]); if (insert_divider && display_name == divider16) { // Insert divider. - auto dictionary = std::make_unique<base::DictionaryValue>(); - dictionary->GetDict().Set("code", kMostRelevantLanguagesDivider); - language_list->Append(std::move(dictionary)); + base::Value::Dict dictionary; + dictionary.Set("code", kMostRelevantLanguagesDivider); + language_list->Append(base::Value(std::move(dictionary))); continue; }
diff --git a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.cc b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.cc index b31dbdb9..e14dac2 100644 --- a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.cc +++ b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.cc
@@ -66,6 +66,8 @@ "isPhoneHubPermissionsDialogSupported"; const char kIsCameraRollFilePermissionGranted[] = "isCameraRollFilePermissionGranted"; +const char kIsPhoneHubFeatureCombinedSetupSupported[] = + "isPhoneHubFeatureCombinedSetupSupported"; constexpr char kAndroidSmsInfoOriginKey[] = "origin"; constexpr char kAndroidSmsInfoEnabledKey[] = "enabled"; @@ -161,6 +163,15 @@ "cancelAppsSetup", base::BindRepeating(&MultideviceHandler::HandleCancelAppsSetup, base::Unretained(this))); + web_ui()->RegisterMessageCallback( + "attemptCombinedFeatureSetup", + base::BindRepeating( + &MultideviceHandler::HandleAttemptCombinedFeatureSetup, + base::Unretained(this))); + web_ui()->RegisterMessageCallback( + "cancelCombinedFeatureSetup", + base::BindRepeating(&MultideviceHandler::HandleCancelCombinedFeatureSetup, + base::Unretained(this))); } void MultideviceHandler::OnJavascriptAllowed() { @@ -274,6 +285,10 @@ UpdatePageContent(); } +void MultideviceHandler::OnFeatureSetupRequestSupportedChanged() { + UpdatePageContent(); +} + void MultideviceHandler::OnPairingStateChanged() { UpdatePageContent(); NotifyAndroidSmsInfoChange(); @@ -516,7 +531,60 @@ apps_access_operation_.reset(); } -void MultideviceHandler::OnStatusChange( +void MultideviceHandler::HandleAttemptCombinedFeatureSetup( + const base::Value::List& args) { + bool camera_roll = false; + if (args[0].is_bool()) + camera_roll = args[0].GetBool(); + bool notifications = false; + if (args[1].is_bool()) + notifications = args[1].GetBool(); + + DCHECK(features::IsPhoneHubEnabled()); + DCHECK(!combined_access_operation_); + + if (!multidevice_feature_access_manager_->GetFeatureSetupRequestSupported()) { + PA_LOG(WARNING) << "Cannot request combined access setup flow; " + << "FeatureSetupRequest is not supported by the phone."; + return; + } + + phonehub::MultideviceFeatureAccessManager::AccessStatus + notification_access_status = + multidevice_feature_access_manager_->GetNotificationAccessStatus(); + phonehub::MultideviceFeatureAccessManager::AccessStatus + camera_roll_access_status = + multidevice_feature_access_manager_->GetCameraRollAccessStatus(); + if (camera_roll_access_status != phonehub::MultideviceFeatureAccessManager:: + AccessStatus::kAvailableButNotGranted && + camera_roll) { + PA_LOG(WARNING) << "Cannot request combined access setup flow; current " + << "Camera Roll status: " << camera_roll_access_status; + return; + } + if (notification_access_status != phonehub::MultideviceFeatureAccessManager:: + AccessStatus::kAvailableButNotGranted && + notifications) { + PA_LOG(WARNING) << "Cannot request combined access setup flow; current " + << "Notification status: " << notification_access_status; + return; + } + + combined_access_operation_ = + multidevice_feature_access_manager_->AttemptCombinedFeatureSetup( + camera_roll, notifications, /*delegate=*/this); + DCHECK(combined_access_operation_); +} + +void MultideviceHandler::HandleCancelCombinedFeatureSetup( + const base::Value::List& args) { + DCHECK(features::IsPhoneHubEnabled()); + DCHECK(combined_access_operation_); + + combined_access_operation_.reset(); +} + +void MultideviceHandler::OnNotificationStatusChange( phonehub::NotificationAccessSetupOperation::Status new_status) { FireWebUIListener("settings.onNotificationAccessSetupStatusChanged", base::Value(static_cast<int32_t>(new_status))); @@ -534,6 +602,15 @@ apps_access_operation_.reset(); } +void MultideviceHandler::OnCombinedStatusChange( + phonehub::CombinedAccessSetupOperation::Status new_status) { + FireWebUIListener("settings.onCombinedAccessSetupStatusChanged", + base::Value(static_cast<int32_t>(new_status))); + + if (phonehub::CombinedAccessSetupOperation::IsFinalStatus(new_status)) + combined_access_operation_.reset(); +} + void MultideviceHandler::OnSetFeatureStateEnabledResult( const std::string& js_callback_id, bool success) { @@ -677,6 +754,13 @@ kIsPhoneHubPermissionsDialogSupported, is_phone_hub_permissions_dialog_supported); + page_content_dictionary->SetBoolKey( + kIsPhoneHubFeatureCombinedSetupSupported, + multidevice_feature_access_manager_ + ? multidevice_feature_access_manager_ + ->GetFeatureSetupRequestSupported() + : false); + return page_content_dictionary; }
diff --git a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.h b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.h index babc030c..8a312d6 100644 --- a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.h +++ b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.h
@@ -7,6 +7,7 @@ #include "ash/components/multidevice/remote_device_ref.h" #include "ash/components/phonehub/camera_roll_manager.h" +#include "ash/components/phonehub/combined_access_setup_operation.h" #include "ash/components/phonehub/multidevice_feature_access_manager.h" #include "ash/components/phonehub/notification_access_setup_operation.h" #include "ash/services/multidevice_setup/public/cpp/multidevice_setup_client.h" @@ -38,7 +39,8 @@ public phonehub::NotificationAccessSetupOperation::Delegate, public ash::eche_app::AppsAccessManager::Observer, public ash::eche_app::AppsAccessSetupOperation::Delegate, - public ash::phonehub::CameraRollManager::Observer { + public ash::phonehub::CameraRollManager::Observer, + public phonehub::CombinedAccessSetupOperation::Delegate { public: MultideviceHandler( PrefService* prefs, @@ -74,16 +76,21 @@ feature_states_map) override; // NotificationAccessSetupOperation::Delegate: - void OnStatusChange( + void OnNotificationStatusChange( phonehub::NotificationAccessSetupOperation::Status new_status) override; // ash::eche_app::AppsAccessSetupOperation::Delegate: void OnAppsStatusChange( ash::eche_app::AppsAccessSetupOperation::Status new_status) override; + // CombinedAccessSetupOperation::Delegate: + void OnCombinedStatusChange( + phonehub::CombinedAccessSetupOperation::Status new_status) override; + // phonehub::MultideviceFeatureAccessManager::Observer: void OnNotificationAccessChanged() override; void OnCameraRollAccessChanged() override; + void OnFeatureSetupRequestSupportedChanged() override; // multidevice_setup::AndroidSmsPairingStateTracker::Observer: void OnPairingStateChanged() override; @@ -118,6 +125,8 @@ void HandleCancelNotificationSetup(const base::Value::List& args); void HandleAttemptAppsSetup(const base::Value::List& args); void HandleCancelAppsSetup(const base::Value::List& args); + void HandleAttemptCombinedFeatureSetup(const base::Value::List& args); + void HandleCancelCombinedFeatureSetup(const base::Value::List& args); void OnSetFeatureStateEnabledResult(const std::string& js_callback_id, bool success); @@ -155,6 +164,8 @@ multidevice_feature_access_manager_; std::unique_ptr<phonehub::NotificationAccessSetupOperation> notification_access_operation_; + std::unique_ptr<phonehub::CombinedAccessSetupOperation> + combined_access_operation_; multidevice_setup::AndroidSmsPairingStateTracker* android_sms_pairing_state_tracker_;
diff --git a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler_unittest.cc b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler_unittest.cc index f444464..ee262e9d 100644 --- a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler_unittest.cc +++ b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler_unittest.cc
@@ -102,7 +102,8 @@ bool expected_is_nearby_share_disallowed_by_policy_, bool expected_is_phone_hub_apps_access_granted_, bool expected_is_camera_roll_file_permission_granted_, - bool expected_is_camera_roll_access_status_granted_) { + bool expected_is_camera_roll_access_status_granted_, + bool expected_is_feature_setup_request_supported_) { const base::DictionaryValue* page_content_dict; EXPECT_TRUE(value->GetAsDictionary(&page_content_dict)); @@ -200,6 +201,10 @@ EXPECT_THAT(page_content_dict->FindIntKey("cameraRollAccessStatus"), Optional(expected_is_camera_roll_access_status_granted_ ? 2 : 1)); + + EXPECT_THAT( + page_content_dict->FindBoolKey("isPhoneHubFeatureCombinedSetupSupported"), + Optional(expected_is_feature_setup_request_supported_)); } } // namespace @@ -265,6 +270,21 @@ {}); } + void SetUpHandlerWithEmptyManagers() { + handler_.reset(); + test_web_ui_.reset(); + handler_ = std::make_unique<TestMultideviceHandler>( + prefs_.get(), fake_multidevice_setup_client_.get(), nullptr, nullptr, + nullptr, nullptr, nullptr); + + test_web_ui_ = std::make_unique<content::TestWebUI>(); + test_web_ui_->set_web_contents(test_web_contents_.get()); + handler_->set_web_ui(test_web_ui_.get()); + + handler_->RegisterMessages(); + handler_->AllowJavascript(); + } + void CallGetPageContentData() { size_t call_data_count_before_call = test_web_ui()->call_data().size(); @@ -351,6 +371,26 @@ test_web_ui()->HandleReceivedMessage("cancelAppsSetup", &empty_args); } + void CallAttemptCameraRollSetup(bool has_camera_roll_access_been_granted) { + fake_multidevice_feature_access_manager() + ->SetCameraRollAccessStatusInternal( + has_camera_roll_access_been_granted + ? phonehub::MultideviceFeatureAccessManager::AccessStatus:: + kAccessGranted + : phonehub::MultideviceFeatureAccessManager::AccessStatus:: + kAvailableButNotGranted); + base::ListValue args; + args.Append(/*camera_roll=*/true); + args.Append(/*notifications=*/false); + test_web_ui()->HandleReceivedMessage("attemptCombinedFeatureSetup", &args); + } + + void CallCancelCameraRollSetup() { + base::ListValue empty_args; + test_web_ui()->HandleReceivedMessage("cancelCombinedFeatureSetup", + &empty_args); + } + void SimulateHostStatusUpdate( multidevice_setup::mojom::HostStatus host_status, const absl::optional<multidevice::RemoteDeviceRef>& host_device) { @@ -608,7 +648,7 @@ bool IsNotificationAccessSetupOperationInProgress() { return fake_multidevice_feature_access_manager() - ->IsSetupOperationInProgress(); + ->IsNotificationSetupOperationInProgress(); } void SimulateAppsOptInStatusChange( @@ -637,12 +677,41 @@ return fake_apps_access_manager()->IsSetupOperationInProgress(); } + void SimulateCameraRollOptInStatusChange( + phonehub::CombinedAccessSetupOperation::Status status) { + size_t call_data_count_before_call = test_web_ui()->call_data().size(); + + fake_multidevice_feature_access_manager()->SetCombinedSetupOperationStatus( + status); + + bool completed_successfully = + status == + phonehub::CombinedAccessSetupOperation::Status::kCompletedSuccessfully; + if (completed_successfully) + call_data_count_before_call++; + + EXPECT_EQ(call_data_count_before_call + 1u, + test_web_ui()->call_data().size()); + const content::TestWebUI::CallData& call_data = + CallDataAtIndex(call_data_count_before_call); + EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name()); + EXPECT_EQ("settings.onCombinedAccessSetupStatusChanged", + call_data.arg1()->GetString()); + EXPECT_EQ(call_data.arg2()->GetInt(), static_cast<int32_t>(status)); + } + + bool IsCameraRollAccessSetupOperationInProgress() { + return fake_multidevice_feature_access_manager() + ->IsCombinedSetupOperationInProgress(); + } + const multidevice::RemoteDeviceRef test_device_; bool expected_is_nearby_share_disallowed_by_policy_ = false; bool expected_is_phone_hub_apps_access_granted_ = false; bool expected_is_camera_roll_file_permission_granted_ = true; bool expected_is_camera_roll_access_status_granted_ = false; + bool expected_is_feature_setup_request_supported_ = false; private: void VerifyPageContent(const base::Value* value) { @@ -653,7 +722,8 @@ expected_is_nearby_share_disallowed_by_policy_, expected_is_phone_hub_apps_access_granted_, expected_is_camera_roll_file_permission_granted_, - expected_is_camera_roll_access_status_granted_); + expected_is_camera_roll_access_status_granted_, + expected_is_feature_setup_request_supported_); } content::BrowserTaskEnvironment task_environment_; @@ -684,6 +754,15 @@ base::test::ScopedFeatureList scoped_feature_list_; }; +TEST_F(MultideviceHandlerTest, PageContentDataRequestedWithNullManagers) { + SetUpHandlerWithEmptyManagers(); + + base::Value args(base::Value::Type::LIST); + args.Append("handlerFunctionName"); + test_web_ui()->HandleReceivedMessage("getPageContentData", + &base::Value::AsListValue(args)); +} + TEST_F(MultideviceHandlerTest, NotificationSetupFlow) { using Status = phonehub::NotificationAccessSetupOperation::Status; @@ -782,6 +861,57 @@ EXPECT_FALSE(IsAppsAccessSetupOperationInProgress()); } +TEST_F(MultideviceHandlerTest, CameraRollSetupFlow) { + using Status = phonehub::CombinedAccessSetupOperation::Status; + fake_multidevice_feature_access_manager() + ->SetFeatureSetupRequestSupportedInternal(true); + + // Simulate success flow. + CallAttemptCameraRollSetup(/*has_access_been_granted=*/false); + EXPECT_TRUE(IsCameraRollAccessSetupOperationInProgress()); + + SimulateCameraRollOptInStatusChange(Status::kConnecting); + EXPECT_TRUE(IsCameraRollAccessSetupOperationInProgress()); + + SimulateCameraRollOptInStatusChange( + Status::kSentMessageToPhoneAndWaitingForResponse); + EXPECT_TRUE(IsCameraRollAccessSetupOperationInProgress()); + + SimulateCameraRollOptInStatusChange(Status::kCompletedSuccessfully); + EXPECT_FALSE(IsCameraRollAccessSetupOperationInProgress()); + + // Simulate cancel flow. + CallAttemptCameraRollSetup(/*has_access_been_granted=*/false); + EXPECT_TRUE(IsCameraRollAccessSetupOperationInProgress()); + + CallCancelCameraRollSetup(); + EXPECT_FALSE(IsCameraRollAccessSetupOperationInProgress()); + + // Simulate failure via time-out flow. + CallAttemptCameraRollSetup(/*has_access_been_granted=*/false); + EXPECT_TRUE(IsCameraRollAccessSetupOperationInProgress()); + + SimulateCameraRollOptInStatusChange(Status::kConnecting); + EXPECT_TRUE(IsCameraRollAccessSetupOperationInProgress()); + + SimulateCameraRollOptInStatusChange(Status::kTimedOutConnecting); + EXPECT_FALSE(IsCameraRollAccessSetupOperationInProgress()); + + // Simulate failure via connected then disconnected flow. + CallAttemptCameraRollSetup(/*has_access_been_granted=*/false); + EXPECT_TRUE(IsCameraRollAccessSetupOperationInProgress()); + + SimulateCameraRollOptInStatusChange(Status::kConnecting); + EXPECT_TRUE(IsCameraRollAccessSetupOperationInProgress()); + + SimulateCameraRollOptInStatusChange(Status::kConnectionDisconnected); + EXPECT_FALSE(IsCameraRollAccessSetupOperationInProgress()); + + // If access has already been granted, a setup operation should not occur. + CallAttemptCameraRollSetup(/*has_access_been_granted=*/true); + EXPECT_FALSE(IsCameraRollAccessSetupOperationInProgress()); +} + TEST_F(MultideviceHandlerTest, PageContentData) { CallGetPageContentData(); CallGetPageContentData();
diff --git a/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.cc b/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.cc index 512302e..52fd797 100644 --- a/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.cc +++ b/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.cc
@@ -94,7 +94,7 @@ EnterpriseProfileWelcomeUI::ScreenType type, const AccountInfo& account_info, absl::optional<SkColor> profile_color, - base::OnceCallback<void(bool)> proceed_callback) + signin::SigninChoiceCallback proceed_callback) : browser_(browser), type_(type), email_(base::UTF8ToUTF16(account_info.email)), @@ -112,12 +112,12 @@ EnterpriseProfileWelcomeHandler::~EnterpriseProfileWelcomeHandler() { BrowserList::RemoveObserver(this); - HandleCancel(nullptr); + HandleCancel(base::Value::List()); } void EnterpriseProfileWelcomeHandler::RegisterMessages() { profile_path_ = Profile::FromWebUI(web_ui())->GetPath(); - web_ui()->RegisterDeprecatedMessageCallback( + web_ui()->RegisterMessageCallback( "initialized", base::BindRepeating(&EnterpriseProfileWelcomeHandler::HandleInitialized, base::Unretained(this))); @@ -126,11 +126,11 @@ base::BindRepeating( &EnterpriseProfileWelcomeHandler::HandleInitializedWithSize, base::Unretained(this))); - web_ui()->RegisterDeprecatedMessageCallback( + web_ui()->RegisterMessageCallback( "proceed", base::BindRepeating(&EnterpriseProfileWelcomeHandler::HandleProceed, base::Unretained(this))); - web_ui()->RegisterDeprecatedMessageCallback( + web_ui()->RegisterMessageCallback( "cancel", base::BindRepeating(&EnterpriseProfileWelcomeHandler::HandleCancel, base::Unretained(this))); @@ -181,10 +181,10 @@ } void EnterpriseProfileWelcomeHandler::HandleInitialized( - const base::ListValue* args) { - CHECK_EQ(1u, args->GetListDeprecated().size()); + const base::Value::List& args) { + CHECK_EQ(1u, args.size()); AllowJavascript(); - const base::Value& callback_id = args->GetListDeprecated()[0]; + const base::Value& callback_id = args[0]; ResolveJavascriptCallback(callback_id, GetProfileInfoValue()); } @@ -197,15 +197,20 @@ } void EnterpriseProfileWelcomeHandler::HandleProceed( - const base::ListValue* args) { - if (proceed_callback_) - std::move(proceed_callback_).Run(true); + const base::Value::List& args) { + CHECK_EQ(1u, args.size()); + if (proceed_callback_) { + bool use_existing_profile = args[0].GetIfBool().value_or(false); + std::move(proceed_callback_) + .Run(use_existing_profile ? signin::SIGNIN_CHOICE_CONTINUE + : signin::SIGNIN_CHOICE_NEW_PROFILE); + } } void EnterpriseProfileWelcomeHandler::HandleCancel( - const base::ListValue* args) { + const base::Value::List& args) { if (proceed_callback_) - std::move(proceed_callback_).Run(false); + std::move(proceed_callback_).Run(signin::SIGNIN_CHOICE_CANCEL); } void EnterpriseProfileWelcomeHandler::UpdateProfileInfo( @@ -306,7 +311,7 @@ } void EnterpriseProfileWelcomeHandler::CallProceedCallbackForTesting( - bool proceed) { + signin::SigninChoice choice) { if (proceed_callback_) - std::move(proceed_callback_).Run(proceed); + std::move(proceed_callback_).Run(choice); }
diff --git a/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.h b/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.h index f4bab869..49c186de 100644 --- a/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.h +++ b/chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.h
@@ -15,6 +15,7 @@ #include "chrome/browser/profiles/profile_attributes_storage.h" #include "chrome/browser/ui/browser_list_observer.h" #include "chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.h" +#include "chrome/browser/ui/webui/signin/signin_utils.h" #include "components/signin/public/identity_manager/identity_manager.h" #include "content/public/browser/web_ui_message_handler.h" #include "google_apis/gaia/core_account_id.h" @@ -41,7 +42,7 @@ EnterpriseProfileWelcomeUI::ScreenType type, const AccountInfo& account_info, absl::optional<SkColor> profile_color, - base::OnceCallback<void(bool)> proceed_callback); + signin::SigninChoiceCallback proceed_callback); ~EnterpriseProfileWelcomeHandler() override; EnterpriseProfileWelcomeHandler(const EnterpriseProfileWelcomeHandler&) = @@ -69,16 +70,16 @@ // Access to construction parameters for tests. EnterpriseProfileWelcomeUI::ScreenType GetTypeForTesting(); - void CallProceedCallbackForTesting(bool proceed); + void CallProceedCallbackForTesting(signin::SigninChoice choice); private: - void HandleInitialized(const base::ListValue* args); + void HandleInitialized(const base::Value::List& args); // Handles the web ui message sent when the html content is done being laid // out and it's time to resize the native view hosting it to fit. |args| is // a single integer value for the height the native view should resize to. void HandleInitializedWithSize(const base::ListValue* args); - void HandleProceed(const base::ListValue* args); - void HandleCancel(const base::ListValue* args); + void HandleProceed(const base::Value::List& args); + void HandleCancel(const base::Value::List& args); // Sends an updated profile info (avatar and colors) to the WebUI. // `profile_path` is the path of the profile being updated, this function does @@ -108,7 +109,7 @@ const std::string domain_name_; const CoreAccountId account_id_; absl::optional<SkColor> profile_color_; - base::OnceCallback<void(bool)> proceed_callback_; + signin::SigninChoiceCallback proceed_callback_; }; #endif // CHROME_BROWSER_UI_WEBUI_SIGNIN_ENTERPRISE_PROFILE_WELCOME_HANDLER_H_
diff --git a/chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.cc b/chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.cc index a041fbf5..a4f7360 100644 --- a/chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.cc +++ b/chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.cc
@@ -7,6 +7,7 @@ #include "base/callback_helpers.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/webui/signin/enterprise_profile_welcome_handler.h" +#include "chrome/browser/ui/webui/signin/signin_utils.h" #include "chrome/browser/ui/webui/webui_util.h" #include "chrome/common/webui_url_constants.h" #include "chrome/grit/chromium_strings.h" @@ -43,6 +44,11 @@ source->AddLocalizedString("enterpriseProfileWelcomeTitle", IDS_ENTERPRISE_PROFILE_WELCOME_TITLE); source->AddLocalizedString("cancelLabel", IDS_CANCEL); + source->AddLocalizedString("proceedAlternateLabel", + IDS_WELCOME_SIGNIN_VIEW_SIGNIN); + source->AddLocalizedString("linkDataText", + IDS_ENTERPRISE_PROFILE_WELCOME_LINK_DATA_CHECKBOX); + source->AddBoolean("showLinkDataCheckbox", false); source->AddBoolean("isModalDialog", false); content::WebUIDataSource::Add(Profile::FromWebUI(web_ui), source); @@ -54,8 +60,9 @@ Browser* browser, EnterpriseProfileWelcomeUI::ScreenType type, const AccountInfo& account_info, + bool force_new_profile, absl::optional<SkColor> profile_color, - base::OnceCallback<void(bool)> proceed_callback) { + signin::SigninChoiceCallback proceed_callback) { auto handler = std::make_unique<EnterpriseProfileWelcomeHandler>( browser, type, account_info, profile_color, std::move(proceed_callback)); handler_ = handler.get(); @@ -64,10 +71,15 @@ EnterpriseProfileWelcomeUI::ScreenType::kEnterpriseAccountCreation) { base::DictionaryValue update_data; update_data.SetBoolKey("isModalDialog", true); - update_data.SetStringKey( - "enterpriseProfileWelcomeTitle", - l10n_util::GetStringUTF16( - IDS_ENTERPRISE_WELCOME_PROFILE_REQUIRED_TITLE)); + + int title_id = force_new_profile + ? IDS_ENTERPRISE_WELCOME_PROFILE_REQUIRED_TITLE + : IDS_ENTERPRISE_WELCOME_PROFILE_WILL_BE_MANAGED_TITLE; + update_data.SetStringKey("enterpriseProfileWelcomeTitle", + l10n_util::GetStringUTF16(title_id)); + if (force_new_profile) + update_data.SetBoolKey("showLinkDataCheckbox", true); + content::WebUIDataSource::Update( Profile::FromWebUI(web_ui()), chrome::kChromeUIEnterpriseProfileWelcomeHost,
diff --git a/chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.h b/chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.h index 3fa43da8..e7e62e6 100644 --- a/chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.h +++ b/chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.h
@@ -7,6 +7,7 @@ #include "base/callback.h" #include "base/memory/raw_ptr.h" +#include "chrome/browser/ui/webui/signin/signin_utils.h" #include "content/public/browser/web_ui_controller.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/skia/include/core/SkColor.h" @@ -40,8 +41,9 @@ void Initialize(Browser* browser, ScreenType type, const AccountInfo& account_info, + bool force_new_profile, absl::optional<SkColor> profile_color, - base::OnceCallback<void(bool)> proceed_callback); + signin::SigninChoiceCallback proceed_callback); // Allows tests to trigger page events. EnterpriseProfileWelcomeHandler* GetHandlerForTesting();
diff --git a/chrome/browser/ui/webui/signin/turn_sync_on_helper_delegate_impl.cc b/chrome/browser/ui/webui/signin/turn_sync_on_helper_delegate_impl.cc index 16cdfdc..30e00c1a 100644 --- a/chrome/browser/ui/webui/signin/turn_sync_on_helper_delegate_impl.cc +++ b/chrome/browser/ui/webui/signin/turn_sync_on_helper_delegate_impl.cc
@@ -82,16 +82,19 @@ ->GetProfileAttributesStorage() .GetProfileAttributesWithPath(browser->profile()->GetPath()); browser->signin_view_controller()->ShowModalEnterpriseConfirmationDialog( - account_info, GenerateNewProfileColor(entry).color, + account_info, /*force_new_profile=*/true, + GenerateNewProfileColor(entry).color, base::BindOnce( [](signin::SigninChoiceCallback callback, Browser* browser, - bool prompt_for_new_profile, bool create_profile) { + bool prompt_for_new_profile, signin::SigninChoice choice) { browser->signin_view_controller()->CloseModalSignin(); - std::move(callback).Run( - create_profile ? prompt_for_new_profile - ? signin::SIGNIN_CHOICE_NEW_PROFILE - : signin::SIGNIN_CHOICE_CONTINUE - : signin::SIGNIN_CHOICE_CANCEL); + signin::SigninChoice result = signin::SIGNIN_CHOICE_CANCEL; + if (choice != signin::SIGNIN_CHOICE_CANCEL) { + result = prompt_for_new_profile + ? signin::SIGNIN_CHOICE_NEW_PROFILE + : signin::SIGNIN_CHOICE_CONTINUE; + } + std::move(callback).Run(result); }, std::move(callback), browser.get(), prompt_for_new_profile)); }
diff --git a/chrome/browser/web_applications/BUILD.gn b/chrome/browser/web_applications/BUILD.gn index a4019745..34663f0 100644 --- a/chrome/browser/web_applications/BUILD.gn +++ b/chrome/browser/web_applications/BUILD.gn
@@ -379,6 +379,8 @@ "system_web_apps/test/test_system_web_app_url_data_source.h", "system_web_apps/test/test_system_web_app_web_ui_controller_factory.cc", "system_web_apps/test/test_system_web_app_web_ui_controller_factory.h", + "test/app_registration_waiter.cc", + "test/app_registration_waiter.h", "test/fake_data_retriever.cc", "test/fake_data_retriever.h", "test/fake_externally_managed_app_manager.cc", @@ -442,6 +444,7 @@ "//chrome/browser/ui", "//components/crx_file:crx_file", "//components/keyed_service/content", + "//components/services/app_service/public/cpp:app_update", "//components/services/app_service/public/cpp:app_url_handling", "//components/services/app_service/public/cpp:types", "//components/sync:test_support_model",
diff --git a/chrome/browser/web_applications/app_service/lacros_web_apps_controller_lacros_browsertest.cc b/chrome/browser/web_applications/app_service/lacros_web_apps_controller_lacros_browsertest.cc index f0d59e9d..0a7f8e9e 100644 --- a/chrome/browser/web_applications/app_service/lacros_web_apps_controller_lacros_browsertest.cc +++ b/chrome/browser/web_applications/app_service/lacros_web_apps_controller_lacros_browsertest.cc
@@ -10,6 +10,7 @@ #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/web_applications/web_app_controller_browsertest.h" #include "chrome/browser/web_applications/app_service/lacros_web_apps_controller.h" +#include "chrome/browser/web_applications/test/app_registration_waiter.h" #include "chrome/browser/web_applications/web_app_utils.h" #include "chromeos/crosapi/mojom/app_service_types.mojom.h" #include "chromeos/crosapi/mojom/test_controller.mojom-test-utils.h" @@ -21,40 +22,37 @@ namespace web_app { -using LacrosWebAppsControllerBrowserTest = web_app::WebAppControllerBrowserTest; +class LacrosWebAppsControllerBrowserTest : public WebAppControllerBrowserTest { + public: + LacrosWebAppsControllerBrowserTest() = default; + ~LacrosWebAppsControllerBrowserTest() override = default; -// TODO(crbug.com/1309148): Disabled for flakiness. -// Test that the default context menu for a web app has the correct items. -IN_PROC_BROWSER_TEST_F(LacrosWebAppsControllerBrowserTest, - DISABLED_DefaultContextMenu) { - // If ash is does not contain the relevant test controller functionality, then - // there's nothing to do for this test. - if (chromeos::LacrosService::Get()->GetInterfaceVersion( - crosapi::mojom::TestController::Uuid_) < - static_cast<int>(crosapi::mojom::TestController::MethodMinVersions:: - kDoesItemExistInShelfMinVersion)) { - LOG(WARNING) << "Unsupported ash version."; - return; + protected: + // If ash is does not contain the relevant test controller functionality, + // then there's nothing to do for this test. + bool IsServiceAvailable() { + DCHECK(IsWebAppsCrosapiEnabled()); + auto* const service = chromeos::LacrosService::Get(); + return service->GetInterfaceVersion( + crosapi::mojom::TestController::Uuid_) >= + static_cast<int>(crosapi::mojom::TestController::MethodMinVersions:: + kDoesItemExistInShelfMinVersion); } +}; - ASSERT_TRUE(embedded_test_server()->Start()); - - EXPECT_TRUE(IsWebAppsCrosapiEnabled()); - LacrosWebAppsController lacros_web_apps_controller(profile()); - lacros_web_apps_controller.Init(); +// Test that the default context menu for a web app has the correct items. +IN_PROC_BROWSER_TEST_F(LacrosWebAppsControllerBrowserTest, DefaultContextMenu) { + if (!IsServiceAvailable()) + GTEST_SKIP() << "Unsupported ash version."; const AppId app_id = - InstallPWA(embedded_test_server()->GetURL("/web_apps/basic.html")); + InstallPWA(https_server()->GetURL("/web_apps/basic.html")); + AppRegistrationWaiter(profile(), app_id).Await(); // No item should exist in the shelf before the web app is launched. browser_test_util::WaitForShelfItem(app_id, /*exists=*/false); - crosapi::mojom::LaunchParamsPtr launch_params = - crosapi::mojom::LaunchParams::New(); - launch_params->app_id = app_id; - launch_params->launch_source = apps::mojom::LaunchSource::kFromTest; - static_cast<crosapi::mojom::AppController&>(lacros_web_apps_controller) - .Launch(std::move(launch_params), base::DoNothing()); + LaunchWebAppBrowser(app_id); // Wait for item to exist in shelf. browser_test_util::WaitForShelfItem(app_id, /*exists=*/true);
diff --git a/chrome/browser/web_applications/policy/web_app_settings_policy_handler.cc b/chrome/browser/web_applications/policy/web_app_settings_policy_handler.cc index ac149b3..f84e282 100644 --- a/chrome/browser/web_applications/policy/web_app_settings_policy_handler.cc +++ b/chrome/browser/web_applications/policy/web_app_settings_policy_handler.cc
@@ -35,7 +35,8 @@ if (!policy_entry) return true; - const auto& web_apps_list = policy_entry->value()->GetList(); + const auto& web_apps_list = + policy_entry->value(base::Value::Type::LIST)->GetList(); const auto it = std::find_if( web_apps_list.begin(), web_apps_list.end(), [](const base::Value& entry) { return entry.FindKey(kManifestId)->GetString() == kWildcard;
diff --git a/chrome/browser/web_applications/test/app_registration_waiter.cc b/chrome/browser/web_applications/test/app_registration_waiter.cc new file mode 100644 index 0000000..7e46c1f --- /dev/null +++ b/chrome/browser/web_applications/test/app_registration_waiter.cc
@@ -0,0 +1,36 @@ +// 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 "chrome/browser/web_applications/test/app_registration_waiter.h" + +#include "chrome/browser/apps/app_service/app_service_proxy.h" +#include "chrome/browser/apps/app_service/app_service_proxy_factory.h" + +namespace web_app { + +AppRegistrationWaiter::AppRegistrationWaiter(Profile* profile, + const AppId& app_id) + : app_id_(app_id) { + apps::AppRegistryCache& cache = + apps::AppServiceProxyFactory::GetForProfile(profile)->AppRegistryCache(); + Observe(&cache); + if (cache.ForOneApp(app_id, [](const apps::AppUpdate&) {})) + run_loop_.Quit(); +} +AppRegistrationWaiter::~AppRegistrationWaiter() = default; + +void AppRegistrationWaiter::Await() { + run_loop_.Run(); +} + +void AppRegistrationWaiter::OnAppUpdate(const apps::AppUpdate& update) { + if (update.AppId() == app_id_) + run_loop_.Quit(); +} +void AppRegistrationWaiter::OnAppRegistryCacheWillBeDestroyed( + apps::AppRegistryCache* cache) { + Observe(nullptr); +} + +} // namespace web_app
diff --git a/chrome/browser/web_applications/test/app_registration_waiter.h b/chrome/browser/web_applications/test/app_registration_waiter.h new file mode 100644 index 0000000..ba4cf37e --- /dev/null +++ b/chrome/browser/web_applications/test/app_registration_waiter.h
@@ -0,0 +1,36 @@ +// 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 CHROME_BROWSER_WEB_APPLICATIONS_TEST_APP_REGISTRATION_WAITER_H_ +#define CHROME_BROWSER_WEB_APPLICATIONS_TEST_APP_REGISTRATION_WAITER_H_ + +#include "base/run_loop.h" +#include "chrome/browser/web_applications/web_app_id.h" +#include "components/services/app_service/public/cpp/app_registry_cache.h" +#include "components/services/app_service/public/cpp/app_update.h" + +class Profile; + +namespace web_app { + +class AppRegistrationWaiter : public apps::AppRegistryCache::Observer { + public: + AppRegistrationWaiter(Profile* profile, const AppId& app_id); + ~AppRegistrationWaiter() override; + + void Await(); + + private: + // apps::AppRegistryCache::Observer: + void OnAppUpdate(const apps::AppUpdate& update) override; + void OnAppRegistryCacheWillBeDestroyed( + apps::AppRegistryCache* cache) override; + + const AppId app_id_; + base::RunLoop run_loop_; +}; + +} // namespace web_app + +#endif // CHROME_BROWSER_WEB_APPLICATIONS_TEST_APP_REGISTRATION_WAITER_H_
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt index 1994a6b..8df3ae4 100644 --- a/chrome/build/linux.pgo.txt +++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@ -chrome-linux-main-1648231192-00a79f1e146c27c001ea594e843d589ee70a8fec.profdata +chrome-linux-main-1648252801-be15c64e80010c3c479f86d173140c681bb38a47.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt index 36b2253..bf99a74 100644 --- a/chrome/build/mac-arm.pgo.txt +++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@ -chrome-mac-arm-main-1648231192-3c2490485831b0f09c86bd23d8256a0603a99ece.profdata +chrome-mac-arm-main-1648252801-fed7e9ae334d0c3d1b2adeed1619fbba62a46d26.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt index 9f484b0e..44f020de 100644 --- a/chrome/build/mac.pgo.txt +++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@ -chrome-mac-main-1648231192-d0c8cc3c32150d711191de27df7aad35f66e8ced.profdata +chrome-mac-main-1648252801-e62c0cb609be3f897e97c5d5c9b3c0ed1ecb6da4.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt index 86373fb..58d8f0c 100644 --- a/chrome/build/win32.pgo.txt +++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@ -chrome-win32-main-1648231192-59d0208f446bf0d15979bd5241c3f83c74869aee.profdata +chrome-win32-main-1648252801-4b1b59ef8a58544a6771e1ede132941b688665a5.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt index 3bb852b..99bec3f 100644 --- a/chrome/build/win64.pgo.txt +++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@ -chrome-win64-main-1648231192-cf18dea6c5392b66b22b709e088251e1b107f6c7.profdata +chrome-win64-main-1648252801-68a4bbc7d1f1f5fb5aaaa0b064a3623fbf454ed2.profdata
diff --git a/chrome/test/data/webui/settings/chromeos/multidevice_permissions_setup_dialog_tests.js b/chrome/test/data/webui/settings/chromeos/multidevice_permissions_setup_dialog_tests.js index b5a4ff33..302a863 100644 --- a/chrome/test/data/webui/settings/chromeos/multidevice_permissions_setup_dialog_tests.js +++ b/chrome/test/data/webui/settings/chromeos/multidevice_permissions_setup_dialog_tests.js
@@ -6,7 +6,7 @@ // #import 'chrome://os-settings/chromeos/os_settings.js'; // #import {assert} from 'chrome://resources/js/assert.m.js'; -// #import {assertEquals, assertFalse, assertNotEquals, assertTrue} from '../../chai_assert.js'; +// #import {assertEquals, assertFalse, assertNotEquals, assertTrue, assertArrayEquals} from '../../chai_assert.js'; // #import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; // #import {TestMultideviceBrowserProxy} from './test_multidevice_browser_proxy.m.js'; // #import {MultiDeviceBrowserProxyImpl, PermissionsSetupStatus, SetupFlowStatus} from 'chrome://os-settings/chromeos/os_settings.js'; @@ -32,7 +32,7 @@ /** * @param {PermissionsSetupStatus} status */ - function simulateStatusChanged(status) { + function simulateNotificationStatusChanged(status) { cr.webUIListenerCallback( 'settings.onNotificationAccessSetupStatusChanged', status); Polymer.dom.flush(); @@ -46,6 +46,12 @@ Polymer.dom.flush(); } + function simulateCombinedStatusChanged(status) { + cr.webUIListenerCallback( + 'settings.onCombinedAccessSetupStatusChanged', status); + Polymer.dom.flush(); + } + /** * @param {SetupFlowStatus} status */ @@ -71,6 +77,7 @@ showCameraRoll: false, showNotifications: true, showAppStreaming: false, + combinedSetupSupported: false }); Polymer.dom.flush(); @@ -85,7 +92,8 @@ assertTrue( isExpectedFlowState(SetupFlowStatus.WAIT_FOR_PHONE_NOTIFICATION)); - simulateStatusChanged(PermissionsSetupStatus.CONNECTION_REQUESTED); + simulateNotificationStatusChanged( + PermissionsSetupStatus.CONNECTION_REQUESTED); assertFalse(!!dialogBody.querySelector('#start-setup-description')); assertFalse(!!buttonContainer.querySelector('#learnMore')); assertTrue(!!buttonContainer.querySelector('#cancelButton')); @@ -93,7 +101,7 @@ assertFalse(!!buttonContainer.querySelector('#doneButton')); assertFalse(!!buttonContainer.querySelector('#tryAgainButton')); - simulateStatusChanged(PermissionsSetupStatus.CONNECTING); + simulateNotificationStatusChanged(PermissionsSetupStatus.CONNECTING); assertFalse(!!dialogBody.querySelector('#start-setup-description')); assertFalse(!!buttonContainer.querySelector('#learnMore')); assertTrue(!!buttonContainer.querySelector('#cancelButton')); @@ -101,7 +109,7 @@ assertFalse(!!buttonContainer.querySelector('#doneButton')); assertFalse(!!buttonContainer.querySelector('#tryAgainButton')); - simulateStatusChanged( + simulateNotificationStatusChanged( PermissionsSetupStatus.SENT_MESSAGE_TO_PHONE_AND_WAITING_FOR_RESPONSE); assertFalse(!!dialogBody.querySelector('#start-setup-description')); assertTrue(!!buttonContainer.querySelector('#learnMore')); @@ -112,7 +120,8 @@ assertFalse(!!buttonContainer.querySelector('#tryAgainButton')); assertEquals(browserProxy.getCallCount('setFeatureEnabledState'), 0); - simulateStatusChanged(PermissionsSetupStatus.COMPLETED_SUCCESSFULLY); + simulateNotificationStatusChanged( + PermissionsSetupStatus.COMPLETED_SUCCESSFULLY); assertFalse(!!dialogBody.querySelector('#start-setup-description')); assertFalse(!!buttonContainer.querySelector('#learnMore')); assertFalse(!!buttonContainer.querySelector('#cancelButton')); @@ -135,6 +144,7 @@ showCameraRoll: false, showNotifications: true, showAppStreaming: false, + combinedSetupSupported: false }); Polymer.dom.flush(); @@ -147,7 +157,7 @@ assertTrue( isExpectedFlowState(SetupFlowStatus.WAIT_FOR_PHONE_NOTIFICATION)); - simulateStatusChanged(PermissionsSetupStatus.CONNECTING); + simulateNotificationStatusChanged(PermissionsSetupStatus.CONNECTING); assertTrue(!!buttonContainer.querySelector('#cancelButton')); assertFalse(!!buttonContainer.querySelector('#getStartedButton')); @@ -165,6 +175,7 @@ showCameraRoll: false, showNotifications: true, showAppStreaming: false, + combinedSetupSupported: false }); Polymer.dom.flush(); @@ -177,7 +188,8 @@ assertTrue( isExpectedFlowState(SetupFlowStatus.WAIT_FOR_PHONE_NOTIFICATION)); - simulateStatusChanged(PermissionsSetupStatus.TIMED_OUT_CONNECTING); + simulateNotificationStatusChanged( + PermissionsSetupStatus.TIMED_OUT_CONNECTING); assertTrue(!!buttonContainer.querySelector('#cancelButton')); assertFalse(!!buttonContainer.querySelector('#getStartedButton')); @@ -194,7 +206,8 @@ assertFalse(!!buttonContainer.querySelector('#doneButton')); assertFalse(!!buttonContainer.querySelector('#tryAgainButton')); - simulateStatusChanged(PermissionsSetupStatus.CONNECTION_DISCONNECTED); + simulateNotificationStatusChanged( + PermissionsSetupStatus.CONNECTION_DISCONNECTED); assertTrue(!!buttonContainer.querySelector('#cancelButton')); assertFalse(!!buttonContainer.querySelector('#getStartedButton')); @@ -212,6 +225,7 @@ showCameraRoll: false, showNotifications: true, showAppStreaming: false, + combinedSetupSupported: false }); Polymer.dom.flush(); @@ -225,7 +239,7 @@ assertTrue( isExpectedFlowState(SetupFlowStatus.WAIT_FOR_PHONE_NOTIFICATION)); - simulateStatusChanged( + simulateNotificationStatusChanged( PermissionsSetupStatus.NOTIFICATION_ACCESS_PROHIBITED); assertFalse(!!buttonContainer.querySelector('#cancelButton')); @@ -244,6 +258,7 @@ showCameraRoll: false, showNotifications: false, showAppStreaming: true, + combinedSetupSupported: false }); Polymer.dom.flush(); @@ -307,6 +322,7 @@ showCameraRoll: false, showNotifications: false, showAppStreaming: true, + combinedSetupSupported: false }); Polymer.dom.flush(); @@ -336,6 +352,7 @@ showCameraRoll: false, showNotifications: false, showAppStreaming: true, + combinedSetupSupported: false }); Polymer.dom.flush(); @@ -382,6 +399,7 @@ showCameraRoll: false, showNotifications: true, showAppStreaming: true, + combinedSetupSupported: false }); Polymer.dom.flush(); @@ -396,7 +414,7 @@ assertTrue( isExpectedFlowState(SetupFlowStatus.WAIT_FOR_PHONE_NOTIFICATION)); - simulateStatusChanged( + simulateNotificationStatusChanged( PermissionsSetupStatus.SENT_MESSAGE_TO_PHONE_AND_WAITING_FOR_RESPONSE); assertFalse(!!dialogBody.querySelector('#start-setup-description')); assertTrue(!!buttonContainer.querySelector('#learnMore')); @@ -407,7 +425,8 @@ assertFalse(!!buttonContainer.querySelector('#tryAgainButton')); assertEquals(browserProxy.getCallCount('setFeatureEnabledState'), 0); - simulateStatusChanged(PermissionsSetupStatus.COMPLETED_SUCCESSFULLY); + simulateNotificationStatusChanged( + PermissionsSetupStatus.COMPLETED_SUCCESSFULLY); assertFalse(!!dialogBody.querySelector('#start-setup-description')); assertFalse(!!buttonContainer.querySelector('#learnMore')); assertTrue(!!buttonContainer.querySelector('#cancelButton')); @@ -444,6 +463,7 @@ showCameraRoll: false, showNotifications: true, showAppStreaming: true, + combinedSetupSupported: false }); Polymer.dom.flush(); @@ -466,6 +486,7 @@ showCameraRoll: false, showNotifications: true, showAppStreaming: true, + combinedSetupSupported: false }); Polymer.dom.flush(); @@ -481,6 +502,7 @@ showCameraRoll: false, showNotifications: true, showAppStreaming: true, + combinedSetupSupported: false }); Polymer.dom.flush(); @@ -496,6 +518,7 @@ showCameraRoll: false, showNotifications: true, showAppStreaming: true, + combinedSetupSupported: false }); Polymer.dom.flush(); @@ -511,6 +534,7 @@ showCameraRoll: false, showNotifications: true, showAppStreaming: false, + combinedSetupSupported: false }); Polymer.dom.flush(); @@ -530,6 +554,7 @@ showCameraRoll: false, showNotifications: true, showAppStreaming: false, + combinedSetupSupported: false }); Polymer.dom.flush(); @@ -542,4 +567,145 @@ assertTrue( isExpectedFlowState(SetupFlowStatus.WAIT_FOR_PHONE_NOTIFICATION)); }); + + test('Test Camera Roll setup success flow', async () => { + permissionsSetupDialog.setProperties({ + showCameraRoll: true, + showNotifications: false, + showAppStreaming: false, + combinedSetupSupported: true + }); + Polymer.dom.flush(); + + assertTrue(!!dialogBody.querySelector('#start-setup-description')); + assertTrue(!!buttonContainer.querySelector('#learnMore')); + assertTrue(!!buttonContainer.querySelector('#cancelButton')); + assertTrue(!!buttonContainer.querySelector('#getStartedButton')); + assertFalse(!!buttonContainer.querySelector('#doneButton')); + assertFalse(!!buttonContainer.querySelector('#tryAgainButton')); + buttonContainer.querySelector('#getStartedButton').click(); + assertEquals(browserProxy.getCallCount('attemptCombinedFeatureSetup'), 1); + assertArrayEquals( + [true, false], browserProxy.getArgs('attemptCombinedFeatureSetup')[0]); + assertTrue(isExpectedFlowState(SetupFlowStatus.WAIT_FOR_PHONE_COMBINED)); + + simulateCombinedStatusChanged(PermissionsSetupStatus.CONNECTION_REQUESTED); + assertFalse(!!dialogBody.querySelector('#start-setup-description')); + assertFalse(!!buttonContainer.querySelector('#learnMore')); + assertTrue(!!buttonContainer.querySelector('#cancelButton')); + assertFalse(!!buttonContainer.querySelector('#getStartedButton')); + assertFalse(!!buttonContainer.querySelector('#doneButton')); + assertFalse(!!buttonContainer.querySelector('#tryAgainButton')); + + simulateCombinedStatusChanged(PermissionsSetupStatus.CONNECTING); + assertFalse(!!dialogBody.querySelector('#start-setup-description')); + assertFalse(!!buttonContainer.querySelector('#learnMore')); + assertTrue(!!buttonContainer.querySelector('#cancelButton')); + assertFalse(!!buttonContainer.querySelector('#getStartedButton')); + assertFalse(!!buttonContainer.querySelector('#doneButton')); + assertFalse(!!buttonContainer.querySelector('#tryAgainButton')); + + simulateCombinedStatusChanged( + PermissionsSetupStatus.SENT_MESSAGE_TO_PHONE_AND_WAITING_FOR_RESPONSE); + assertFalse(!!dialogBody.querySelector('#start-setup-description')); + assertTrue(!!buttonContainer.querySelector('#learnMore')); + assertTrue(!!buttonContainer.querySelector('#cancelButton')); + assertFalse(!!buttonContainer.querySelector('#getStartedButton')); + assertTrue(!!buttonContainer.querySelector('#doneButton')); + assertTrue(buttonContainer.querySelector('#doneButton').disabled); + assertFalse(!!buttonContainer.querySelector('#tryAgainButton')); + + assertEquals(browserProxy.getCallCount('setFeatureEnabledState'), 0); + simulateCombinedStatusChanged( + PermissionsSetupStatus.COMPLETED_SUCCESSFULLY); + assertFalse(!!dialogBody.querySelector('#start-setup-description')); + assertFalse(!!buttonContainer.querySelector('#learnMore')); + assertFalse(!!buttonContainer.querySelector('#cancelButton')); + assertFalse(!!buttonContainer.querySelector('#getStartedButton')); + assertTrue(!!buttonContainer.querySelector('#doneButton')); + assertFalse(buttonContainer.querySelector('#doneButton').disabled); + assertFalse(!!buttonContainer.querySelector('#tryAgainButton')); + + // The feature becomes enabled when the status becomes + // PermissionsSetupStatus.COMPLETED_SUCCESSFULLY. + assertEquals(browserProxy.getCallCount('setFeatureEnabledState'), 1); + + assertTrue(permissionsSetupDialog.$$('#dialog').open); + buttonContainer.querySelector('#doneButton').click(); + assertFalse(permissionsSetupDialog.$$('#dialog').open); + }); + + test( + 'Test Camera Roll and Notifications combined setup success flow', + async () => { + permissionsSetupDialog.setProperties({ + showCameraRoll: true, + showNotifications: true, + showAppStreaming: false, + combinedSetupSupported: true + }); + Polymer.dom.flush(); + + assertTrue(!!dialogBody.querySelector('#start-setup-description')); + assertTrue(!!buttonContainer.querySelector('#learnMore')); + assertTrue(!!buttonContainer.querySelector('#cancelButton')); + assertTrue(!!buttonContainer.querySelector('#getStartedButton')); + assertFalse(!!buttonContainer.querySelector('#doneButton')); + assertFalse(!!buttonContainer.querySelector('#tryAgainButton')); + buttonContainer.querySelector('#getStartedButton').click(); + assertEquals( + browserProxy.getCallCount('attemptCombinedFeatureSetup'), 1); + assertArrayEquals( + [true, true], + browserProxy.getArgs('attemptCombinedFeatureSetup')[0]); + assertTrue( + isExpectedFlowState(SetupFlowStatus.WAIT_FOR_PHONE_COMBINED)); + + simulateCombinedStatusChanged( + PermissionsSetupStatus.CONNECTION_REQUESTED); + assertFalse(!!dialogBody.querySelector('#start-setup-description')); + assertFalse(!!buttonContainer.querySelector('#learnMore')); + assertTrue(!!buttonContainer.querySelector('#cancelButton')); + assertFalse(!!buttonContainer.querySelector('#getStartedButton')); + assertFalse(!!buttonContainer.querySelector('#doneButton')); + assertFalse(!!buttonContainer.querySelector('#tryAgainButton')); + + simulateCombinedStatusChanged(PermissionsSetupStatus.CONNECTING); + assertFalse(!!dialogBody.querySelector('#start-setup-description')); + assertFalse(!!buttonContainer.querySelector('#learnMore')); + assertTrue(!!buttonContainer.querySelector('#cancelButton')); + assertFalse(!!buttonContainer.querySelector('#getStartedButton')); + assertFalse(!!buttonContainer.querySelector('#doneButton')); + assertFalse(!!buttonContainer.querySelector('#tryAgainButton')); + + simulateCombinedStatusChanged( + PermissionsSetupStatus + .SENT_MESSAGE_TO_PHONE_AND_WAITING_FOR_RESPONSE); + assertFalse(!!dialogBody.querySelector('#start-setup-description')); + assertTrue(!!buttonContainer.querySelector('#learnMore')); + assertTrue(!!buttonContainer.querySelector('#cancelButton')); + assertFalse(!!buttonContainer.querySelector('#getStartedButton')); + assertTrue(!!buttonContainer.querySelector('#doneButton')); + assertTrue(buttonContainer.querySelector('#doneButton').disabled); + assertFalse(!!buttonContainer.querySelector('#tryAgainButton')); + + assertEquals(browserProxy.getCallCount('setFeatureEnabledState'), 0); + simulateCombinedStatusChanged( + PermissionsSetupStatus.COMPLETED_SUCCESSFULLY); + assertFalse(!!dialogBody.querySelector('#start-setup-description')); + assertFalse(!!buttonContainer.querySelector('#learnMore')); + assertFalse(!!buttonContainer.querySelector('#cancelButton')); + assertFalse(!!buttonContainer.querySelector('#getStartedButton')); + assertTrue(!!buttonContainer.querySelector('#doneButton')); + assertFalse(buttonContainer.querySelector('#doneButton').disabled); + assertFalse(!!buttonContainer.querySelector('#tryAgainButton')); + + // The features become enabled when the status becomes + // PermissionsSetupStatus.COMPLETED_SUCCESSFULLY. + assertEquals(browserProxy.getCallCount('setFeatureEnabledState'), 2); + + assertTrue(permissionsSetupDialog.$$('#dialog').open); + buttonContainer.querySelector('#doneButton').click(); + assertFalse(permissionsSetupDialog.$$('#dialog').open); + }); });
diff --git a/chrome/test/data/webui/settings/chromeos/test_multidevice_browser_proxy.js b/chrome/test/data/webui/settings/chromeos/test_multidevice_browser_proxy.js index b871dbd..d60e86ddc 100644 --- a/chrome/test/data/webui/settings/chromeos/test_multidevice_browser_proxy.js +++ b/chrome/test/data/webui/settings/chromeos/test_multidevice_browser_proxy.js
@@ -60,6 +60,8 @@ 'cancelNotificationSetup', 'attemptAppsSetup', 'cancelAppsSetup', + 'attemptCombinedFeatureSetup', + 'cancelCombinedFeatureSetup', ]); this.data = createFakePageContentData( settings.MultiDeviceSettingsMode.NO_HOST_SET); @@ -138,6 +140,17 @@ this.methodCalled('cancelAppsSetup'); } + /** @override */ + attemptCombinedFeatureSetup(cameraRoll, notifications) { + this.methodCalled( + 'attemptCombinedFeatureSetup', [cameraRoll, notifications]); + } + + /** @override */ + cancelCombinedFeatureSetup() { + this.methodCalled('cancelCombinedFeatureSetup'); + } + /** * @param {settings.MultiDeviceFeature} state */
diff --git a/chrome/test/data/webui/signin/enterprise_profile_welcome_test.ts b/chrome/test/data/webui/signin/enterprise_profile_welcome_test.ts index 3d38d88..90befe3a2 100644 --- a/chrome/test/data/webui/signin/enterprise_profile_welcome_test.ts +++ b/chrome/test/data/webui/signin/enterprise_profile_welcome_test.ts
@@ -5,10 +5,10 @@ import 'chrome://enterprise-profile-welcome/enterprise_profile_welcome_app.js'; import {EnterpriseProfileWelcomeAppElement} from 'chrome://enterprise-profile-welcome/enterprise_profile_welcome_app.js'; - import {EnterpriseProfileWelcomeBrowserProxyImpl} from 'chrome://enterprise-profile-welcome/enterprise_profile_welcome_browser_proxy.js'; +import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js'; import {webUIListenerCallback} from 'chrome://resources/js/cr.m.js'; - +import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js'; import {isChildVisible, waitAfterNextRender} from 'chrome://webui-test/test_util.js'; @@ -69,6 +69,37 @@ await browserProxy.whenCalled('cancel'); }); + test('linkData', async function() { + assertTrue(isChildVisible(app, '#proceedButton')); + assertFalse(isChildVisible(app, '#linkData')); + + loadTimeData.overrideValues({'showLinkDataCheckbox': true}); + + document.body.innerHTML = ''; + app = document.createElement('enterprise-profile-welcome-app'); + document.body.appendChild(app); + await waitAfterNextRender(app); + await browserProxy.whenCalled('initialized'); + + assertTrue(isChildVisible(app, '#proceedButton')); + assertTrue(isChildVisible(app, '#linkData')); + + const linkDataCheckbox: CrCheckboxElement = + app.shadowRoot!.querySelector('#linkData')!; + assertEquals( + app.i18n('linkDataText'), linkDataCheckbox.textContent!.trim()); + assertFalse(linkDataCheckbox.checked); + + linkDataCheckbox.click(); + + await waitAfterNextRender(app.$.proceedButton); + + assertTrue(linkDataCheckbox.checked); + assertEquals( + app.i18n('proceedAlternateLabel'), + app.$.proceedButton.textContent!.trim()); + }); + test('onProfileInfoChanged', function() { // Helper to test all the text values in the UI. function checkTextValues(
diff --git a/chromeos/network/metrics/network_metrics_helper.cc b/chromeos/network/metrics/network_metrics_helper.cc index 72613a7..37d25e4d 100644 --- a/chromeos/network/metrics/network_metrics_helper.cc +++ b/chromeos/network/metrics/network_metrics_helper.cc
@@ -41,6 +41,7 @@ const char kVPN[] = "VPN"; const char kVPNBuiltIn[] = "VPN.TypeBuiltIn"; const char kVPNThirdParty[] = "VPN.TypeThirdParty"; +const char kVPNUnknown[] = "VPN.TypeUnknown"; const char kWifi[] = "WiFi"; const char kWifiOpen[] = "WiFi.SecurityOpen"; @@ -123,12 +124,14 @@ if (vpn_provider_type == shill::kProviderThirdPartyVpn || vpn_provider_type == shill::kProviderArcVpn) { vpn_histograms.emplace_back(kVPNThirdParty); - } else if (vpn_provider_type == shill::kProviderL2tpIpsec || + } else if (vpn_provider_type == shill::kProviderIKEv2 || + vpn_provider_type == shill::kProviderL2tpIpsec || vpn_provider_type == shill::kProviderOpenVpn || vpn_provider_type == shill::kProviderWireGuard) { vpn_histograms.emplace_back(kVPNBuiltIn); } else { NOTREACHED(); + vpn_histograms.emplace_back(kVPNUnknown); } return vpn_histograms; }
diff --git a/chromeos/network/metrics/network_metrics_helper_unittest.cc b/chromeos/network/metrics/network_metrics_helper_unittest.cc index 8255e04..d8b0521 100644 --- a/chromeos/network/metrics/network_metrics_helper_unittest.cc +++ b/chromeos/network/metrics/network_metrics_helper_unittest.cc
@@ -14,6 +14,7 @@ #include "chromeos/network/network_handler_test_helper.h" #include "chromeos/network/network_ui_data.h" #include "components/prefs/testing_pref_service.h" +#include "testing/gtest/include/gtest/gtest-spi.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/cros_system_api/dbus/service_constants.h" @@ -40,6 +41,8 @@ "Network.Ash.VPN.TypeBuiltIn.ConnectionResult.All"; const char kVpnThirdPartyConnectResultAllHistogram[] = "Network.Ash.VPN.TypeThirdParty.ConnectionResult.All"; +const char kVpnUnknownConnectResultAllHistogram[] = + "Network.Ash.VPN.TypeUnknown.ConnectionResult.All"; // LogAllConnectionResult() WiFi histograms. const char kWifiConnectResultAllHistogram[] = @@ -74,6 +77,8 @@ "Network.Ash.VPN.TypeBuiltIn.ConnectionResult.UserInitiated"; const char kVpnThirdPartyConnectResultUserInitiatedHistogram[] = "Network.Ash.VPN.TypeThirdParty.ConnectionResult.UserInitiated"; +const char kVpnUnknownConnectResultUserInitiatedHistogram[] = + "Network.Ash.VPN.TypeUnknown.ConnectionResult.UserInitiated"; // LogUserInitiatedConnectionResult() WiFi histograms. const char kWifiConnectResultUserInitiatedHistogram[] = @@ -108,6 +113,8 @@ "Network.Ash.VPN.TypeBuiltIn.DisconnectionsWithoutUserAction"; const char kVpnThirdPartyConnectionStateHistogram[] = "Network.Ash.VPN.TypeThirdParty.DisconnectionsWithoutUserAction"; +const char kVpnUnknownConnectionStateHistogram[] = + "Network.Ash.VPN.TypeUnknown.DisconnectionsWithoutUserAction"; // LogConnectionStateResult() WiFi histograms. const char kWifiConnectionStateHistogram[] = @@ -152,6 +159,25 @@ const char kTestDevicePath[] = "/device/network"; const char kTestName[] = "network_name"; const char kTestVpnHost[] = "test host"; +const char kTestUnknownVpn[] = "test_unknown_vpn"; + +void LogVpnResult(const std::string& provider, + base::RepeatingClosure func, + bool* failed_to_log_result) { + ASSERT_NE(failed_to_log_result, nullptr); + +// Emitting a metric for an unknown VPN provider will always cause a NOTREACHED +// to be hit. This can cause a CHECK to fail, depending on the build flags. We +// catch any failing CHECK below by asserting that we will crash when emitting. +#if !BUILDFLAG(ENABLE_LOG_ERROR_NOT_REACHED) + if (provider == kTestUnknownVpn) { + ASSERT_DEATH({ func.Run(); }, ""); + *failed_to_log_result = true; + return; + } +#endif // !BUILDFLAG(ENABLE_LOG_ERROR_NOT_REACHED) + func.Run(); +} } // namespace @@ -334,19 +360,35 @@ TEST_F(NetworkMetricsHelperTest, VPN) { const std::vector<const std::string> kProviders{{ + shill::kProviderIKEv2, shill::kProviderL2tpIpsec, shill::kProviderArcVpn, shill::kProviderOpenVpn, shill::kProviderThirdPartyVpn, shill::kProviderWireGuard, + kTestUnknownVpn, }}; size_t expected_all_count = 0; size_t expected_user_initiated_count = 0; size_t expected_built_in_count = 0; size_t expected_third_party_count = 0; + size_t expected_unknown_count = 0; + + base::RepeatingClosure log_all_connection_result = + base::BindRepeating(&NetworkMetricsHelper::LogAllConnectionResult, + kTestGuid, shill::kErrorNotRegistered); + base::RepeatingClosure log_user_initiated_connection_result = + base::BindRepeating( + &NetworkMetricsHelper::LogUserInitiatedConnectionResult, kTestGuid, + shill::kErrorNotRegistered); + base::RepeatingClosure log_connection_state_result = base::BindRepeating( + &NetworkMetricsHelper::LogConnectionStateResult, kTestGuid, + NetworkMetricsHelper::ConnectionState::kConnected); for (const auto& provider : kProviders) { + bool failed_to_log_result = false; + shill_service_client_->AddService(kTestServicePath, kTestGuid, kTestName, shill::kTypeVPN, shill::kStateIdle, /*visible=*/true); @@ -357,26 +399,36 @@ base::Value(kTestVpnHost)); base::RunLoop().RunUntilIdle(); - if (provider == shill::kProviderThirdPartyVpn || - provider == shill::kProviderArcVpn) { - ++expected_third_party_count; - } else { - ++expected_built_in_count; - } - ++expected_all_count; - ++expected_user_initiated_count; + LogVpnResult(provider, log_all_connection_result, &failed_to_log_result); + LogVpnResult(provider, log_user_initiated_connection_result, + &failed_to_log_result); + LogVpnResult(provider, log_connection_state_result, &failed_to_log_result); - NetworkMetricsHelper::LogAllConnectionResult(kTestGuid, - shill::kErrorNotRegistered); + if (!failed_to_log_result) { + if (provider == shill::kProviderThirdPartyVpn || + provider == shill::kProviderArcVpn) { + ++expected_third_party_count; + } else if (provider == shill::kProviderIKEv2 || + provider == shill::kProviderL2tpIpsec || + provider == shill::kProviderOpenVpn || + provider == shill::kProviderWireGuard) { + ++expected_built_in_count; + } else { + ++expected_unknown_count; + } + ++expected_all_count; + ++expected_user_initiated_count; + } + histogram_tester_->ExpectTotalCount(kVpnConnectResultAllHistogram, expected_all_count); histogram_tester_->ExpectTotalCount(kVpnBuiltInConnectResultAllHistogram, expected_built_in_count); histogram_tester_->ExpectTotalCount(kVpnThirdPartyConnectResultAllHistogram, expected_third_party_count); + histogram_tester_->ExpectTotalCount(kVpnUnknownConnectResultAllHistogram, + expected_unknown_count); - NetworkMetricsHelper::LogUserInitiatedConnectionResult( - kTestGuid, shill::kErrorNotRegistered); histogram_tester_->ExpectTotalCount(kVpnConnectResultUserInitiatedHistogram, expected_user_initiated_count); histogram_tester_->ExpectTotalCount( @@ -385,15 +437,17 @@ histogram_tester_->ExpectTotalCount( kVpnThirdPartyConnectResultUserInitiatedHistogram, expected_third_party_count); + histogram_tester_->ExpectTotalCount( + kVpnUnknownConnectResultUserInitiatedHistogram, expected_unknown_count); - NetworkMetricsHelper::LogConnectionStateResult( - kTestGuid, NetworkMetricsHelper::ConnectionState::kConnected); histogram_tester_->ExpectTotalCount(kVpnConnectionStateHistogram, expected_user_initiated_count); histogram_tester_->ExpectTotalCount(kVpnBuiltInConnectionStateHistogram, expected_built_in_count); histogram_tester_->ExpectTotalCount(kVpnThirdPartyConnectionStateHistogram, expected_third_party_count); + histogram_tester_->ExpectTotalCount(kVpnUnknownConnectionStateHistogram, + expected_unknown_count); shill_service_client_->RemoveService(kTestServicePath); base::RunLoop().RunUntilIdle();
diff --git a/chromeos/network/metrics/vpn_network_metrics_helper.cc b/chromeos/network/metrics/vpn_network_metrics_helper.cc index 0678bcc..94e429b 100644 --- a/chromeos/network/metrics/vpn_network_metrics_helper.cc +++ b/chromeos/network/metrics/vpn_network_metrics_helper.cc
@@ -20,6 +20,8 @@ // sources of created VPNs. const char kVpnConfigurationSourceBucketArc[] = "Network.Ash.VPN.ARC.ConfigurationSource"; +const char kVpnConfigurationSourceBucketIKEv2[] = + "Network.Ash.VPN.IKEv2.ConfigurationSource"; const char kVpnConfigurationSourceBucketL2tpIpsec[] = "Network.Ash.VPN.L2TPIPsec.ConfigurationSource"; const char kVpnConfigurationSourceBucketOpenVpn[] = @@ -28,10 +30,14 @@ "Network.Ash.VPN.ThirdParty.ConfigurationSource"; const char kVpnConfigurationSourceBucketWireGuard[] = "Network.Ash.VPN.WireGuard.ConfigurationSource"; +const char kVpnConfigurationSourceBucketUnknown[] = + "Network.Ash.VPN.Unknown.ConfigurationSource"; const char* GetBucketForVpnProviderType(const std::string& vpn_provider_type) { if (vpn_provider_type == shill::kProviderArcVpn) { return kVpnConfigurationSourceBucketArc; + } else if (vpn_provider_type == shill::kProviderIKEv2) { + return kVpnConfigurationSourceBucketIKEv2; } else if (vpn_provider_type == shill::kProviderL2tpIpsec) { return kVpnConfigurationSourceBucketL2tpIpsec; } else if (vpn_provider_type == shill::kProviderOpenVpn) { @@ -41,7 +47,8 @@ } else if (vpn_provider_type == shill::kProviderWireGuard) { return kVpnConfigurationSourceBucketWireGuard; } - return nullptr; + NOTREACHED(); + return kVpnConfigurationSourceBucketUnknown; } } // namespace @@ -71,10 +78,7 @@ const char* vpn_provider_type_bucket = GetBucketForVpnProviderType(network_state->GetVpnProviderType()); - if (!vpn_provider_type_bucket) { - NOTREACHED(); - return; - } + DCHECK(vpn_provider_type_bucket); base::UmaHistogramEnumeration( vpn_provider_type_bucket,
diff --git a/chromeos/network/metrics/vpn_network_metrics_helper_unittest.cc b/chromeos/network/metrics/vpn_network_metrics_helper_unittest.cc index e77c2a72..e5bae2d 100644 --- a/chromeos/network/metrics/vpn_network_metrics_helper_unittest.cc +++ b/chromeos/network/metrics/vpn_network_metrics_helper_unittest.cc
@@ -19,6 +19,7 @@ #include "chromeos/network/network_ui_data.h" #include "chromeos/network/shill_property_util.h" #include "components/prefs/testing_pref_service.h" +#include "testing/gtest/include/gtest/gtest-spi.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/cros_system_api/dbus/service_constants.h" @@ -28,6 +29,8 @@ const char kVpnHistogramConfigurationSourceArc[] = "Network.Ash.VPN.ARC.ConfigurationSource"; +const char kVpnHistogramConfigurationSourceIKEv2[] = + "Network.Ash.VPN.IKEv2.ConfigurationSource"; const char kVpnHistogramConfigurationSourceL2tpIpsec[] = "Network.Ash.VPN.L2TPIPsec.ConfigurationSource"; const char kVpnHistogramConfigurationSourceOpenVpn[] = @@ -36,11 +39,45 @@ "Network.Ash.VPN.ThirdParty.ConfigurationSource"; const char kVpnHistogramConfigurationSourceWireGuard[] = "Network.Ash.VPN.WireGuard.ConfigurationSource"; +const char kVpnHistogramConfigurationSourceUnknown[] = + "Network.Ash.VPN.Unknown.ConfigurationSource"; + +const char kTestUnknownVpn[] = "test_unknown_vpn"; void ErrorCallback(const std::string& error_name) { ADD_FAILURE() << "Unexpected error: " << error_name; } +// Helper function to create a VPN network using NetworkConfigurationHandler. +void CreateTestShillConfiguration(const std::string& vpn_provider_type, + bool is_managed) { + base::Value properties(base::Value::Type::DICTIONARY); + + properties.SetKey(shill::kGuidProperty, base::Value("vpn_guid")); + properties.SetKey(shill::kTypeProperty, base::Value(shill::kTypeVPN)); + properties.SetKey(shill::kStateProperty, base::Value(shill::kStateIdle)); + properties.SetKey(shill::kProviderHostProperty, base::Value("vpn_host")); + properties.SetKey(shill::kProviderTypeProperty, + base::Value(vpn_provider_type)); + properties.SetKey(shill::kProfileProperty, + base::Value(NetworkProfileHandler::GetSharedProfilePath())); + + if (is_managed) { + properties.SetKey(shill::kONCSourceProperty, + base::Value(shill::kONCSourceDevicePolicy)); + std::unique_ptr<NetworkUIData> ui_data = NetworkUIData::CreateFromONC( + ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY); + properties.SetKey(shill::kUIDataProperty, + base::Value(ui_data->GetAsJson())); + } + + NetworkHandler::Get() + ->network_configuration_handler() + ->CreateShillConfiguration(properties, base::DoNothing(), + base::BindOnce(&ErrorCallback)); + base::RunLoop().RunUntilIdle(); +} + } // namespace class VpnNetworkMetricsHelperTest : public testing::Test { @@ -68,37 +105,6 @@ base::RunLoop().RunUntilIdle(); } - // Helper function to create a VPN network using NetworkConfigurationHandler. - void CreateTestShillConfiguration(const char* vpn_provider_type, - bool is_managed) { - base::Value properties(base::Value::Type::DICTIONARY); - - properties.SetKey(shill::kGuidProperty, base::Value("vpn_guid")); - properties.SetKey(shill::kTypeProperty, base::Value(shill::kTypeVPN)); - properties.SetKey(shill::kStateProperty, base::Value(shill::kStateIdle)); - properties.SetKey(shill::kProviderHostProperty, base::Value("vpn_host")); - properties.SetKey(shill::kProviderTypeProperty, - base::Value(vpn_provider_type)); - properties.SetKey( - shill::kProfileProperty, - base::Value(NetworkProfileHandler::GetSharedProfilePath())); - - if (is_managed) { - properties.SetKey(shill::kONCSourceProperty, - base::Value(shill::kONCSourceDevicePolicy)); - std::unique_ptr<NetworkUIData> ui_data = NetworkUIData::CreateFromONC( - ::onc::ONCSource::ONC_SOURCE_DEVICE_POLICY); - properties.SetKey(shill::kUIDataProperty, - base::Value(ui_data->GetAsJson())); - } - - NetworkHandler::Get() - ->network_configuration_handler() - ->CreateShillConfiguration(properties, base::DoNothing(), - base::BindOnce(&ErrorCallback)); - base::RunLoop().RunUntilIdle(); - } - void ExpectConfigurationSourceCounts(const char* histogram, size_t manual_count, size_t policy_count) { @@ -122,8 +128,9 @@ }; TEST_F(VpnNetworkMetricsHelperTest, LogVpnVPNConfigurationSource) { - const std::vector<std::pair<const char*, const char*>> + const std::vector<std::pair<const std::string, const char*>> kProvidersAndHistograms{{ + {shill::kProviderIKEv2, kVpnHistogramConfigurationSourceIKEv2}, {shill::kProviderL2tpIpsec, kVpnHistogramConfigurationSourceL2tpIpsec}, {shill::kProviderArcVpn, kVpnHistogramConfigurationSourceArc}, @@ -132,12 +139,36 @@ kVpnHistogramConfigurationSourceThirdParty}, {shill::kProviderWireGuard, kVpnHistogramConfigurationSourceWireGuard}, + {kTestUnknownVpn, kVpnHistogramConfigurationSourceUnknown}, }}; for (const auto& it : kProvidersAndHistograms) { - ExpectConfigurationSourceCounts(it.first, /*manual_count=*/0, + ExpectConfigurationSourceCounts(it.first.c_str(), /*manual_count=*/0, /*policy_count=*/0); +// Emitting a metric for an unknown VPN provider will always cause a NOTREACHED +// to be hit. This can cause a CHECK to fail, depending on the build flags. We +// catch any failing CHECK below by asserting that we will crash when emitting. +#if !BUILDFLAG(ENABLE_LOG_ERROR_NOT_REACHED) + if (it.first == kTestUnknownVpn) { + ASSERT_DEATH( + { + CreateTestShillConfiguration(kTestUnknownVpn, /*is_managed=*/false); + }, + ""); + ClearServices(); + ASSERT_DEATH( + { + CreateTestShillConfiguration(kTestUnknownVpn, /*is_managed=*/true); + }, + ""); + ClearServices(); + ExpectConfigurationSourceCounts(it.second, /*manual_count=*/0, + /*policy_count=*/0); + continue; + } +#endif // !BUILDFLAG(ENABLE_LOG_ERROR_NOT_REACHED) + CreateTestShillConfiguration(it.first, /*is_managed=*/false); ExpectConfigurationSourceCounts(it.second, /*manual_count=*/1, /*policy_count=*/0);
diff --git a/chromeos/services/libassistant/grpc/assistant_client.h b/chromeos/services/libassistant/grpc/assistant_client.h index 3abc1d6..7b8cd88ea 100644 --- a/chromeos/services/libassistant/grpc/assistant_client.h +++ b/chromeos/services/libassistant/grpc/assistant_client.h
@@ -24,6 +24,7 @@ class OnConversationStateEventRequest; class OnDeviceStateEventRequest; class OnDisplayRequestRequest; +class OnMediaActionFallbackEventRequest; class OnSpeakerIdEnrollmentEventRequest; class StartSpeakerIdEnrollmentRequest; class UpdateAssistantSettingsResponse; @@ -78,6 +79,8 @@ // Media: using MediaStatus = ::assistant::api::events::DeviceState::MediaStatus; using OnDeviceStateEventRequest = ::assistant::api::OnDeviceStateEventRequest; + using OnMediaActionFallbackEventRequest = + ::assistant::api::OnMediaActionFallbackEventRequest; // Conversation: using OnConversationStateEventRequest = @@ -137,9 +140,10 @@ // Sets the current media status of media playing outside of libassistant. // Setting external state will stop any internally playing media. virtual void SetExternalPlaybackState(const MediaStatus& status_proto) = 0; - virtual void AddDeviceStateEventObserver( GrpcServicesObserver<OnDeviceStateEventRequest>* observer) = 0; + virtual void AddMediaActionFallbackEventObserver( + GrpcServicesObserver<OnMediaActionFallbackEventRequest>* observer) = 0; // Conversation methods. virtual void SendVoicelessInteraction(
diff --git a/chromeos/services/libassistant/grpc/assistant_client_impl.cc b/chromeos/services/libassistant/grpc/assistant_client_impl.cc index b3c3544..d8b176d 100644 --- a/chromeos/services/libassistant/grpc/assistant_client_impl.cc +++ b/chromeos/services/libassistant/grpc/assistant_client_impl.cc
@@ -175,6 +175,11 @@ grpc_services_.AddDeviceStateEventObserver(observer); } +void AssistantClientImpl::AddMediaActionFallbackEventObserver( + GrpcServicesObserver<OnMediaActionFallbackEventRequest>* observer) { + grpc_services_.AddMediaActionFallbackEventObserver(observer); +} + void AssistantClientImpl::SendVoicelessInteraction( const ::assistant::api::Interaction& interaction, const std::string& description,
diff --git a/chromeos/services/libassistant/grpc/assistant_client_impl.h b/chromeos/services/libassistant/grpc/assistant_client_impl.h index de2ebe1..6e2793f 100644 --- a/chromeos/services/libassistant/grpc/assistant_client_impl.h +++ b/chromeos/services/libassistant/grpc/assistant_client_impl.h
@@ -54,6 +54,9 @@ GrpcServicesObserver<OnAssistantDisplayEventRequest>* observer) override; void AddDeviceStateEventObserver( GrpcServicesObserver<OnDeviceStateEventRequest>* observer) override; + void AddMediaActionFallbackEventObserver( + GrpcServicesObserver<OnMediaActionFallbackEventRequest>* observer) + override; void SendVoicelessInteraction( const ::assistant::api::Interaction& interaction, const std::string& description,
diff --git a/chromeos/services/libassistant/grpc/assistant_client_v1.cc b/chromeos/services/libassistant/grpc/assistant_client_v1.cc index 533a72c..a757da8 100644 --- a/chromeos/services/libassistant/grpc/assistant_client_v1.cc +++ b/chromeos/services/libassistant/grpc/assistant_client_v1.cc
@@ -29,6 +29,7 @@ #include "chromeos/assistant/internal/proto/shared/proto/v2/speaker_id_enrollment_interface.pb.h" #include "chromeos/services/assistant/public/cpp/features.h" #include "chromeos/services/libassistant/callback_utils.h" +#include "chromeos/services/libassistant/grpc/assistant_client.h" #include "chromeos/services/libassistant/grpc/utils/media_status_utils.h" #include "chromeos/services/libassistant/grpc/utils/settings_utils.h" #include "chromeos/services/libassistant/grpc/utils/timer_utils.h" @@ -443,6 +444,17 @@ device_state_event_observer_list_.AddObserver(observer); } +void AssistantClientV1::AddMediaActionFallbackEventObserver( + GrpcServicesObserver<OnMediaActionFallbackEventRequest>* observer) { + media_action_fallback_event_observer_list_.AddObserver(observer); + + // Register handler for media actions. + auto callback = base::BindRepeating(&AssistantClientV1::HandleMediaAction, + weak_factory_.GetWeakPtr()); + assistant_manager_internal()->RegisterFallbackMediaHandler( + ToStdFunctionRepeating(BindToCurrentSequenceRepeating(callback))); +} + void AssistantClientV1::SendVoicelessInteraction( const ::assistant::api::Interaction& interaction, const std::string& description, @@ -536,6 +548,19 @@ media_manager_listener_.get()); } +void AssistantClientV1::HandleMediaAction( + const std::string& action_name, + const std::string& media_action_args_proto) { + OnMediaActionFallbackEventRequest request; + auto* media_action = request.mutable_event()->mutable_on_media_action_event(); + media_action->set_action_name(action_name); + media_action->set_action_args(media_action_args_proto); + + for (auto& observer : media_action_fallback_event_observer_list_) { + observer.OnGrpcMessage(request); + } +} + void AssistantClientV1::NotifyConversationStateEvent( const OnConversationStateEventRequest& request) { for (auto& observer : conversation_state_event_observer_list_) {
diff --git a/chromeos/services/libassistant/grpc/assistant_client_v1.h b/chromeos/services/libassistant/grpc/assistant_client_v1.h index 693ea5b9..e736ff1c 100644 --- a/chromeos/services/libassistant/grpc/assistant_client_v1.h +++ b/chromeos/services/libassistant/grpc/assistant_client_v1.h
@@ -57,6 +57,9 @@ void SetExternalPlaybackState(const MediaStatus& status_proto) override; void AddDeviceStateEventObserver( GrpcServicesObserver<OnDeviceStateEventRequest>* observer) override; + void AddMediaActionFallbackEventObserver( + GrpcServicesObserver<OnMediaActionFallbackEventRequest>* observer) + override; void SendVoicelessInteraction( const ::assistant::api::Interaction& interaction, const std::string& description, @@ -108,6 +111,8 @@ class AssistantManagerDelegateImpl; void AddMediaManagerListener(); + void HandleMediaAction(const std::string& action_name, + const std::string& media_action_args_proto); void NotifyConversationStateEvent( const OnConversationStateEventRequest& request); @@ -141,6 +146,9 @@ base::ObserverList<GrpcServicesObserver<OnDeviceStateEventRequest>> device_state_event_observer_list_; + base::ObserverList<GrpcServicesObserver<OnMediaActionFallbackEventRequest>> + media_action_fallback_event_observer_list_; + base::ObserverList< GrpcServicesObserver<::assistant::api::OnAlarmTimerEventRequest>> timer_event_observer_list_;
diff --git a/chromeos/services/libassistant/grpc/external_services/event_handler_driver.cc b/chromeos/services/libassistant/grpc/external_services/event_handler_driver.cc index efabe8d6..1b604c0 100644 --- a/chromeos/services/libassistant/grpc/external_services/event_handler_driver.cc +++ b/chromeos/services/libassistant/grpc/external_services/event_handler_driver.cc
@@ -16,6 +16,7 @@ constexpr char kAssistantDisplayEventName[] = "AssistantDisplayEvent"; constexpr char kConversationStateEventName[] = "ConversationStateEvent"; constexpr char kDeviceStateEventName[] = "DeviceStateEvent"; +constexpr char kMediaActionFallbackEventName[] = "MediaActionFallbackEvent"; constexpr char kHandlerMethodName[] = "OnEventFromLibas"; template <typename EventSelection> @@ -75,5 +76,16 @@ return request; } +template <> +::assistant::api::RegisterEventHandlerRequest CreateRegistrationRequest< + ::assistant::api::MediaActionFallbackEventHandlerInterface>( + const std::string& assistant_service_address) { + ::assistant::api::RegisterEventHandlerRequest request; + PopulateRequest(assistant_service_address, kMediaActionFallbackEventName, + &request, + request.mutable_media_action_fallback_events_to_handle()); + return request; +} + } // namespace libassistant } // namespace chromeos
diff --git a/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.cc b/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.cc index 2ae8909..028c4bb 100644 --- a/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.cc +++ b/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.cc
@@ -98,6 +98,12 @@ device_state_event_handler_driver_->AddObserver(observer); } +void GrpcServicesInitializer::AddMediaActionFallbackEventObserver( + GrpcServicesObserver<::assistant::api::OnMediaActionFallbackEventRequest>* + observer) { + media_action_fallback_event_handler_driver_->AddObserver(observer); +} + ActionService* GrpcServicesInitializer::GetActionService() { return action_handler_driver_.get(); } @@ -140,6 +146,14 @@ EventHandlerDriver<::assistant::api::DeviceStateEventHandlerInterface>>( &server_builder_, libassistant_client_.get(), assistant_service_address_); service_drivers_.emplace_back(device_state_event_handler_driver_.get()); + + media_action_fallback_event_handler_driver_ = + std::make_unique<EventHandlerDriver< + ::assistant::api::MediaActionFallbackEventHandlerInterface>>( + &server_builder_, libassistant_client_.get(), + assistant_service_address_); + service_drivers_.emplace_back( + media_action_fallback_event_handler_driver_.get()); } void GrpcServicesInitializer::InitLibassistGrpcClient() { @@ -172,6 +186,7 @@ assistant_display_event_handler_driver_->StartRegistration(); conversation_state_event_handler_driver_->StartRegistration(); device_state_event_handler_driver_->StartRegistration(); + media_action_fallback_event_handler_driver_->StartRegistration(); } } // namespace libassistant
diff --git a/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.h b/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.h index 1f8d7f1..2a5d2fd 100644 --- a/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.h +++ b/chromeos/services/libassistant/grpc/external_services/grpc_services_initializer.h
@@ -24,6 +24,7 @@ class AssistantDisplayEventHandlerInterface; class ConversationStateEventHandlerInterface; class DeviceStateEventHandlerInterface; +class MediaActionFallbackEventHandlerInterface; } // namespace api } // namespace assistant @@ -63,6 +64,9 @@ void AddDeviceStateEventObserver( GrpcServicesObserver<::assistant::api::OnDeviceStateEventRequest>* observer); + void AddMediaActionFallbackEventObserver( + GrpcServicesObserver<::assistant::api::OnMediaActionFallbackEventRequest>* + observer); ActionService* GetActionService(); @@ -133,6 +137,10 @@ std::unique_ptr< EventHandlerDriver<::assistant::api::DeviceStateEventHandlerInterface>> device_state_event_handler_driver_; + + std::unique_ptr<EventHandlerDriver< + ::assistant::api::MediaActionFallbackEventHandlerInterface>> + media_action_fallback_event_handler_driver_; }; } // namespace libassistant
diff --git a/chromeos/services/libassistant/media_controller.cc b/chromeos/services/libassistant/media_controller.cc index 5d00f6c..6de0ede 100644 --- a/chromeos/services/libassistant/media_controller.cc +++ b/chromeos/services/libassistant/media_controller.cc
@@ -97,14 +97,15 @@ } // namespace -class MediaController::DeviceStateEventObserver - : public GrpcServicesObserver<::assistant::api::OnDeviceStateEventRequest> { +class MediaController::GrpcEventsObserver + : public GrpcServicesObserver<::assistant::api::OnDeviceStateEventRequest>, + public GrpcServicesObserver< + ::assistant::api::OnMediaActionFallbackEventRequest> { public: - explicit DeviceStateEventObserver(MediaController* parent) - : parent_(parent) {} - DeviceStateEventObserver(const DeviceStateEventObserver&) = delete; - DeviceStateEventObserver& operator=(const DeviceStateEventObserver&) = delete; - ~DeviceStateEventObserver() override = default; + explicit GrpcEventsObserver(MediaController* parent) : parent_(parent) {} + GrpcEventsObserver(const GrpcEventsObserver&) = delete; + GrpcEventsObserver& operator=(const GrpcEventsObserver&) = delete; + ~GrpcEventsObserver() override = default; // GrpcServicesObserver: // Invoked when a device state event has been received. @@ -123,37 +124,20 @@ ConvertMediaStatusToMojomFromV2(new_state.media_status())); } - private: - mojom::MediaDelegate& delegate() { return *parent_->delegate_; } + // Invoked when a media action fallack event has been received. + void OnGrpcMessage(const ::assistant::api::OnMediaActionFallbackEventRequest& + request) override { + if (!request.event().has_on_media_action_event()) + return; - MediaController* const parent_; -}; - -class MediaController::LibassistantMediaHandler { - public: - LibassistantMediaHandler( - MediaController* parent, - assistant_client::AssistantManagerInternal* assistant_manager_internal) - : parent_(parent), - mojom_task_runner_(base::SequencedTaskRunnerHandle::Get()) { -#if !BUILDFLAG(IS_PREBUILT_LIBASSISTANT) - // Register handler for media actions. - assistant_manager_internal->RegisterFallbackMediaHandler( - [this](std::string action_name, std::string media_action_args_proto) { - HandleMediaAction(action_name, media_action_args_proto); - }); -#endif // !BUILDFLAG(IS_PREBUILT_LIBASSISTANT) + auto media_action_event = request.event().on_media_action_event(); + HandleMediaAction(media_action_event.action_name(), + media_action_event.action_args()); } - LibassistantMediaHandler(const LibassistantMediaHandler&) = delete; - LibassistantMediaHandler& operator=(const LibassistantMediaHandler&) = delete; - ~LibassistantMediaHandler() = default; private: - // Called from the Libassistant thread. void HandleMediaAction(const std::string& action_name, const std::string& media_action_args_proto) { - ENSURE_MOJOM_THREAD(&LibassistantMediaHandler::HandleMediaAction, - action_name, media_action_args_proto); if (action_name == kPlayMediaClientOp) OnPlayMedia(media_action_args_proto); else @@ -229,13 +213,10 @@ mojom::MediaDelegate& delegate() { return *parent_->delegate_; } MediaController* const parent_; - scoped_refptr<base::SequencedTaskRunner> mojom_task_runner_; - base::WeakPtrFactory<LibassistantMediaHandler> weak_factory_{this}; }; MediaController::MediaController() - : device_state_event_observer_( - std::make_unique<DeviceStateEventObserver>(this)) {} + : events_observer_(std::make_unique<GrpcEventsObserver>(this)) {} MediaController::~MediaController() = default; @@ -271,24 +252,9 @@ void MediaController::OnAssistantClientRunning( AssistantClient* assistant_client) { assistant_client_ = assistant_client; - - handler_ = std::make_unique<LibassistantMediaHandler>( - this, assistant_client->assistant_manager_internal()); - - // |device_state_event_observer_| outlives |assistant_client_|. - assistant_client->AddDeviceStateEventObserver( - device_state_event_observer_.get()); -} - -void MediaController::OnDestroyingAssistantClient( - AssistantClient* assistant_client) { - assistant_client_ = nullptr; -} - -void MediaController::OnAssistantClientDestroyed() { - // Handler can only be unset after the |AssistantManagerInternal| has been - // destroyed, as |AssistantManagerInternal| will call the handler. - handler_ = nullptr; + // `events_observer_` outlives `assistant_client_`. + assistant_client->AddDeviceStateEventObserver(events_observer_.get()); + assistant_client->AddMediaActionFallbackEventObserver(events_observer_.get()); } } // namespace libassistant
diff --git a/chromeos/services/libassistant/media_controller.h b/chromeos/services/libassistant/media_controller.h index 68c2345..93e4140 100644 --- a/chromeos/services/libassistant/media_controller.h +++ b/chromeos/services/libassistant/media_controller.h
@@ -32,19 +32,15 @@ // AssistantClientObserver implementation: void OnAssistantClientRunning(AssistantClient* assistant_client) override; - void OnDestroyingAssistantClient(AssistantClient* assistant_client) override; - void OnAssistantClientDestroyed() override; private: - class DeviceStateEventObserver; - class LibassistantMediaHandler; + class GrpcEventsObserver; AssistantClient* assistant_client_ = nullptr; mojo::Receiver<mojom::MediaController> receiver_{this}; mojo::Remote<mojom::MediaDelegate> delegate_; - std::unique_ptr<DeviceStateEventObserver> device_state_event_observer_; - std::unique_ptr<LibassistantMediaHandler> handler_; + std::unique_ptr<GrpcEventsObserver> events_observer_; }; } // namespace libassistant
diff --git a/chromeos/services/libassistant/test_support/fake_assistant_client.cc b/chromeos/services/libassistant/test_support/fake_assistant_client.cc index 1ee6c8c..ccf3377f7 100644 --- a/chromeos/services/libassistant/test_support/fake_assistant_client.cc +++ b/chromeos/services/libassistant/test_support/fake_assistant_client.cc
@@ -88,6 +88,9 @@ void FakeAssistantClient::AddConversationStateEventObserver( GrpcServicesObserver<OnConversationStateEventRequest>* observer) {} +void FakeAssistantClient::AddMediaActionFallbackEventObserver( + GrpcServicesObserver<OnMediaActionFallbackEventRequest>* observer) {} + void FakeAssistantClient::SetInternalOptions(const std::string& locale, bool spoken_feedback_enabled) {}
diff --git a/chromeos/services/libassistant/test_support/fake_assistant_client.h b/chromeos/services/libassistant/test_support/fake_assistant_client.h index f7f4126..9bfc1af 100644 --- a/chromeos/services/libassistant/test_support/fake_assistant_client.h +++ b/chromeos/services/libassistant/test_support/fake_assistant_client.h
@@ -56,6 +56,9 @@ void SetExternalPlaybackState(const MediaStatus& status_proto) override; void AddDeviceStateEventObserver( GrpcServicesObserver<OnDeviceStateEventRequest>* observer) override; + void AddMediaActionFallbackEventObserver( + GrpcServicesObserver<OnMediaActionFallbackEventRequest>* observer) + override; void SendVoicelessInteraction( const ::assistant::api::Interaction& interaction, const std::string& description,
diff --git a/components/autofill/android/BUILD.gn b/components/autofill/android/BUILD.gn index d2902f2..7e080415 100644 --- a/components/autofill/android/BUILD.gn +++ b/components/autofill/android/BUILD.gn
@@ -48,7 +48,6 @@ "payments/java/res/drawable-xxxhdpi/unionpay_card.png", "payments/java/res/drawable/discover_card.xml", "payments/java/res/drawable/elo_card.xml", - "payments/java/res/drawable/google_pay_plex.xml", "payments/java/res/drawable/ic_credit_card_black.xml", "payments/java/res/drawable/mir_card.xml", "payments/java/res/drawable/visa_card.xml",
diff --git a/components/autofill/android/payments/java/res/drawable/google_pay_plex.xml b/components/autofill/android/payments/java/res/drawable/google_pay_plex.xml deleted file mode 100644 index ce75376..0000000 --- a/components/autofill/android/payments/java/res/drawable/google_pay_plex.xml +++ /dev/null
@@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. --> - -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="32dp" - android:height="20dp" - android:viewportWidth="32" - android:viewportHeight="20"> - <path - android:pathData="M2,0L30,0A2,2 0,0 1,32 2L32,18A2,2 0,0 1,30 20L2,20A2,2 0,0 1,0 18L0,2A2,2 0,0 1,2 0z" - android:fillColor="#ffffff"/> - <path - android:pathData="M18.8265,7.1284L19.7068,7.6356C20.409,8.0397 20.6499,8.9378 20.244,9.6391L17.9584,13.5869L18.9118,14.1363C19.6149,14.5413 20.5131,14.3003 20.9199,13.5981L23.1371,9.7684C23.8806,8.484 23.4418,6.8434 22.1584,6.0981C21.0212,5.4503 19.5728,5.8403 18.9174,6.9719L18.8265,7.1284Z" - android:strokeWidth="1.5" - android:fillColor="#00000000" - android:strokeColor="#EA4335"/> - <path - android:pathData="M19.7097,7.6364L19.1519,7.3148C18.9728,7.2117 18.7441,7.2726 18.64,7.4517L15.746,12.4504C15.28,13.2558 14.2506,13.5314 13.4444,13.0673L12.6813,12.6276C12.011,13.8042 12.3663,15.2883 13.4781,16.0355C14.7625,16.748 16.3872,16.3036 17.125,15.0295L20.2469,9.637C20.651,8.9367 20.411,8.0404 19.7097,7.6364Z" - android:strokeWidth="1.5" - android:fillColor="#00000000" - android:strokeColor="#FBBC04"/> - <path - android:pathData="M12.864,12.7346L13.4415,13.0674C14.2487,13.5324 15.28,13.2558 15.7468,12.4496L18.9184,6.9718C19.5756,5.8365 21.0306,5.4474 22.1687,6.1027L20.1081,4.9159L18.6493,4.0759C17.0359,3.1468 14.9734,3.6981 14.0415,5.3068L13.5437,6.1665L14.4353,6.6783C15.1384,7.0824 15.3793,7.9758 14.9734,8.6752L12.7862,12.4439C12.7281,12.5461 12.7628,12.6755 12.864,12.7346Z" - android:strokeWidth="1.5" - android:fillColor="#00000000" - android:strokeColor="#34A853"/> - <path - android:pathData="M14.4362,6.6793L12.8594,5.7736C12.1562,5.3696 11.2581,5.6096 10.8522,6.3089L8.9603,9.5686C8.0294,11.1736 8.5816,13.2258 10.195,14.1521L11.3959,14.8421L12.8519,15.6783L13.4837,16.0402C12.3616,15.2911 12.0062,13.7911 12.6916,12.6099L13.1809,11.7661L14.9744,8.6752C15.3794,7.9768 15.1394,7.0824 14.4362,6.6793Z" - android:strokeWidth="1.5" - android:fillColor="#00000000" - android:strokeColor="#4285F4"/> -</vector>
diff --git a/components/autofill/content/browser/form_forest.cc b/components/autofill/content/browser/form_forest.cc index 2b7d117..45f6d97 100644 --- a/components/autofill/content/browser/form_forest.cc +++ b/components/autofill/content/browser/form_forest.cc
@@ -15,6 +15,7 @@ #include "base/stl_util.h" #include "components/autofill/content/browser/form_forest_util_inl.h" #include "components/autofill/core/common/autofill_constants.h" +#include "components/autofill/core/common/autofill_features.h" #include "content/public/browser/render_process_host.h" #include "third_party/abseil-cpp/absl/types/variant.h" #include "third_party/blink/public/common/permissions_policy/permissions_policy_features.h" @@ -618,8 +619,8 @@ return true; } }; - // Fields in frames whose permissions policy allows shared-autofill may - // be filled if the |triggered_origin| is the main origin. + // Fields whose document enables the policy-controlled feature + // shared-autofill may be safe to fill. auto HasSharedAutofillPermission = [&mutable_this]( LocalFrameToken frame_token) { FrameData* frame = mutable_this.GetFrameData(frame_token); @@ -632,12 +633,17 @@ auto it = field_type_map.find(field.global_id()); ServerFieldType field_type = it != field_type_map.end() ? it->second : UNKNOWN_TYPE; - return field.origin == triggered_origin || - (field.origin == main_origin && - HasSharedAutofillPermission(renderer_form->host_frame) && - !IsSensitiveFieldType(field_type)) || - (triggered_origin == main_origin && - HasSharedAutofillPermission(renderer_form->host_frame)); + if (features::kAutofillSharedAutofillRelaxedParam.Get()) { + return field.origin == triggered_origin || + HasSharedAutofillPermission(renderer_form->host_frame); + } else { + return field.origin == triggered_origin || + (field.origin == main_origin && + HasSharedAutofillPermission(renderer_form->host_frame) && + !IsSensitiveFieldType(field_type)) || + (triggered_origin == main_origin && + HasSharedAutofillPermission(renderer_form->host_frame)); + } }; renderer_form->fields.push_back(browser_field);
diff --git a/components/autofill/content/browser/form_forest.h b/components/autofill/content/browser/form_forest.h index 6d74370c..2fbabde 100644 --- a/components/autofill/content/browser/form_forest.h +++ b/components/autofill/content/browser/form_forest.h
@@ -232,25 +232,36 @@ // The |field_type_map| should contain the field types of the fields in // |browser_form|. // - // A field is *safe to fill* iff at least one of the conditions (1), (2), (3) - // and additionally condition (4) hold: + // There are two modes that determine whether a field is *safe to fill*. + // By default, a field is safe to fill iff at least one of the conditions + // (1–3) and additionally condition (4) hold: + // // (1) The field's origin is the |triggered_origin|. - // (2) The field's origin is the main origin and the field's type in - // |field_type_map| is not sensitive (see is_sensitive_field_type()). - // (3) The |triggered_origin| is main origin and the field's frame's - // permissions policy allows shared-autofill. + // (2) The field's origin is the main origin, the field's type in + // |field_type_map| is not sensitive (see IsSensitiveFieldType()), and the + // policy-controlled feature shared-autofill is enabled in the field's + // frame. + // (3) The |triggered_origin| is the main origin and the policy-controlled + // feature shared-autofill is enabled in the field's frame. // (4) No frame on the shortest path from the field on which Autofill was // triggered to the field in question, except perhaps the shallowest // frame, is a fenced frame. // + // If the Finch parameter relax_shared_autofill is true, the restriction to + // the main origin in condition 3 is lifted. Thus, conditions (2) and (3) + // reduce to the following: + // + // (2+3) The policy-controlled feature shared-autofill is enabled in the + // field's document. + // // The *origin of a field* is the origin of the frame that contains the // corresponding form-control element. // // The *main origin* is `browser_form.main_frame_origin`. // - // A frame's *permissions policy allows shared-autofill* if that frame is a - // main frame or its embedding <iframe> element lists "shared-autofill" in - // its "allow" attribute (see https://www.w3.org/TR/permissions-policy-1/). + // The "allow" attribute of the <iframe> element controls whether the + // *policy-controlled feature shared-autofill* is enabled in a document + // (see https://www.w3.org/TR/permissions-policy-1/). RendererForms GetRendererFormsOfBrowserForm( const FormData& browser_form, const url::Origin& triggered_origin,
diff --git a/components/autofill/content/browser/form_forest_unittest.cc b/components/autofill/content/browser/form_forest_unittest.cc index b83395c9..6031aaf5 100644 --- a/components/autofill/content/browser/form_forest_unittest.cc +++ b/components/autofill/content/browser/form_forest_unittest.cc
@@ -12,10 +12,12 @@ #include "base/strings/strcat.h" #include "base/strings/string_piece.h" +#include "base/test/scoped_feature_list.h" #include "components/autofill/content/browser/form_forest.h" #include "components/autofill/content/browser/form_forest_test_api.h" #include "components/autofill/content/browser/form_forest_util_inl.h" #include "components/autofill/core/browser/autofill_test_utils.h" +#include "components/autofill/core/common/autofill_features.h" #include "content/public/test/navigation_simulator.h" #include "content/public/test/test_renderer_host.h" #include "testing/gmock/include/gmock/gmock.h" @@ -312,6 +314,13 @@ // FormForest::GetBrowserFormOfRendererForm() for details). enum class Policy { kDefault, kSharedAutofill, kNoSharedAutofill }; + explicit FormForestTest(bool relax_shared_autofill = false) { + feature_list_.InitAndEnableFeatureWithParameters( + features::kAutofillSharedAutofill, + {{features::kAutofillSharedAutofillRelaxedParam.name, + relax_shared_autofill ? "true" : "false"}}); + } + void SetUp() override { RenderViewHostTestHarness::SetUp(); CHECK(kOpaqueOrigin.opaque()); @@ -400,6 +409,7 @@ return it->second.get(); } + base::test::ScopedFeatureList feature_list_; std::map<content::RenderFrameHost*, std::unique_ptr<MockContentAutofillDriver>> autofill_drivers_; @@ -437,6 +447,10 @@ size_t count = base::dynamic_extent; }; + explicit FormForestTestWithMockedTree(bool relax_shared_autofill = false) + : FormForestTest( + /*relax_shared_autofill=*/relax_shared_autofill) {} + void TearDown() override { mocked_forms_.Reset(); flattened_forms_.Reset(); @@ -1399,6 +1413,11 @@ // Tests of FormForest::GetRendererFormsOfBrowserForm(). class FormForestTestUnflatten : public FormForestTestWithMockedTree { + public: + explicit FormForestTestUnflatten(bool relax_shared_autofill = false) + : FormForestTestWithMockedTree( + /*relax_shared_autofill=*/relax_shared_autofill) {} + protected: // The subject of this test fixture. std::vector<FormData> GetRendererFormsOfBrowserForm( @@ -1570,9 +1589,17 @@ } // Fixture for the shared-autofill policy tests. +// The parameter controls the value of relax_shared_autofill. class FormForestTestUnflattenSharedAutofillPolicy - : public FormForestTestUnflatten { + : public FormForestTestUnflatten, + public ::testing::WithParamInterface<bool> { public: + FormForestTestUnflattenSharedAutofillPolicy() + : FormForestTestUnflatten( + /*relax_shared_autofill=*/relax_shared_autofill()) {} + + bool relax_shared_autofill() const { return GetParam(); } + void SetUp() override { FormForestTestUnflatten::SetUp(); MockFormForest( @@ -1589,7 +1616,7 @@ }; // Tests filling into frames with shared-autofill policy from the main origin. -TEST_F(FormForestTestUnflattenSharedAutofillPolicy, FromMainOrigin) { +TEST_P(FormForestTestUnflattenSharedAutofillPolicy, FromMainOrigin) { MockFlattening({{"main"}, {"disallowed"}, {"allowed"}}); std::vector<FormData> expectation = { WithValues(GetMockedForm("main"), Profile(0)), @@ -1600,12 +1627,18 @@ } // Tests filling into frames with shared-autofill policy from the main origin. -TEST_F(FormForestTestUnflattenSharedAutofillPolicy, FromOtherOrigin) { +TEST_P(FormForestTestUnflattenSharedAutofillPolicy, FromOtherOrigin) { MockFlattening({{"main"}, {"disallowed"}, {"allowed"}}); - std::vector<FormData> expectation = { - WithoutValues(GetMockedForm("main")), - WithValues(GetMockedForm("disallowed"), Profile(1)), - WithoutValues(GetMockedForm("allowed"))}; + std::vector<FormData> expectation; + if (!relax_shared_autofill()) { + expectation = {WithoutValues(GetMockedForm("main")), + WithValues(GetMockedForm("disallowed"), Profile(1)), + WithoutValues(GetMockedForm("allowed"))}; + } else { + expectation = {WithValues(GetMockedForm("main"), Profile(0)), + WithValues(GetMockedForm("disallowed"), Profile(1)), + WithValues(GetMockedForm("allowed"), Profile(2))}; + } EXPECT_THAT(GetRendererFormsOfBrowserForm("main", Origin(kOtherUrl), {}), UnorderedArrayEquals(expectation)); } @@ -1680,6 +1713,10 @@ EXPECT_EQ(num_equals_calls_, GetParam().expected_comparisons); } +INSTANTIATE_TEST_SUITE_P(FormForestTest, + FormForestTestUnflattenSharedAutofillPolicy, + testing::Bool()); + INSTANTIATE_TEST_SUITE_P( FormForestTest, ForEachInSetDifferenceTest,
diff --git a/components/autofill/core/browser/browser_autofill_manager_unittest.cc b/components/autofill/core/browser/browser_autofill_manager_unittest.cc index ccff80df..bdec93d 100644 --- a/components/autofill/core/browser/browser_autofill_manager_unittest.cc +++ b/components/autofill/core/browser/browser_autofill_manager_unittest.cc
@@ -1775,121 +1775,6 @@ browser_autofill_manager_->GetPackedCreditCardID(5))); } -TEST_P(BrowserAutofillManagerStructuredProfileTest, - GetCreditCardSuggestions_GoogleIssuedCard_CCNumber) { - base::test::ScopedFeatureList features; - features.InitAndEnableFeature( - autofill::features::kAutofillEnableGoogleIssuedCard); - personal_data_.ClearCreditCards(); - // Add a Google Issued Card. - CreditCard google_issued_card; - test::SetCreditCardInfo(&google_issued_card, "Lorem Ispium", - "5555555555554444", // Mastercard - "10", "2998", "1"); - google_issued_card.set_guid("00000000-0000-0000-0000-000000000007"); - google_issued_card.set_record_type( - CreditCard::RecordType::MASKED_SERVER_CARD); - google_issued_card.set_card_issuer(CreditCard::Issuer::GOOGLE); - personal_data_.AddServerCreditCard(google_issued_card); - // Set up our form data. - FormData form; - CreateTestCreditCardFormData(&form, true, false); - std::vector<FormData> forms(1, form); - FormsSeen(forms); - // Set the field being edited to CC field. - const FormFieldData& credit_card_number_field = form.fields[1]; - const std::string google_issued_card_value = base::JoinString( - {"Plex Mastercard ", test::ObfuscatedCardDigitsAsUTF8("4444")}, ""); -#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS) - const std::string google_issued_card_label = std::string("10/98"); -#else - const std::string google_issued_card_label = std::string("Expires on 10/98"); -#endif - - GetAutofillSuggestions(form, credit_card_number_field); - - CheckSuggestions( - kDefaultPageID, - Suggestion(google_issued_card_value, google_issued_card_label, - kGoogleIssuedCard, - browser_autofill_manager_->GetPackedCreditCardID(7))); -} - -TEST_P(BrowserAutofillManagerStructuredProfileTest, - GetCreditCardSuggestions_GoogleIssuedCard_NonCCNumber) { - base::test::ScopedFeatureList features; - features.InitAndEnableFeature( - autofill::features::kAutofillEnableGoogleIssuedCard); - personal_data_.ClearCreditCards(); - // Add a Google Issued Card. - CreditCard google_issued_card; - test::SetCreditCardInfo(&google_issued_card, "Lorem Ispium", - "5555555555554444", // Mastercard - "10", "2998", "1"); - google_issued_card.set_guid("00000000-0000-0000-0000-000000000007"); - google_issued_card.set_record_type( - CreditCard::RecordType::MASKED_SERVER_CARD); - google_issued_card.set_card_issuer(CreditCard::Issuer::GOOGLE); - personal_data_.AddServerCreditCard(google_issued_card); - // Set up our form data. - FormData form; - CreateTestCreditCardFormData(&form, true, false); - std::vector<FormData> forms(1, form); - FormsSeen(forms); - // Set the field being edited to the cardholder name field. - const FormFieldData& cardholder_name_field = form.fields[0]; -#if BUILDFLAG(IS_ANDROID) - const std::string google_issued_card_label = base::JoinString( - {"Plex Mastercard ", test::ObfuscatedCardDigitsAsUTF8("4444")}, ""); -#elif BUILDFLAG(IS_IOS) - const std::string google_issued_card_label = - test::ObfuscatedCardDigitsAsUTF8("4444"); -#else - const std::string google_issued_card_label = base::JoinString( - {"Plex Mastercard ", test::ObfuscatedCardDigitsAsUTF8("4444"), - ", expires on 10/98"}, - ""); -#endif - - GetAutofillSuggestions(form, cardholder_name_field); - - CheckSuggestions( - kDefaultPageID, - Suggestion("Lorem Ispium", google_issued_card_label, kGoogleIssuedCard, - browser_autofill_manager_->GetPackedCreditCardID(7))); -} - -TEST_P(BrowserAutofillManagerStructuredProfileTest, - GetCreditCardSuggestions_GoogleIssuedCardNotPresent_ExpOff) { - base::test::ScopedFeatureList features; - features.InitAndDisableFeature( - autofill::features::kAutofillEnableGoogleIssuedCard); - // Add 2 Server cards. - CreateTestServerCreditCards(); - // Add a Google Issued Card. - CreditCard google_issued_card; - test::SetCreditCardInfo(&google_issued_card, "Lorem Ispium", - "5555555555554444", // Mastercard - "10", "2998", "1"); - google_issued_card.set_guid("00000000-0000-0000-0000-000000000007"); - google_issued_card.set_record_type( - CreditCard::RecordType::MASKED_SERVER_CARD); - google_issued_card.set_card_issuer(CreditCard::Issuer::GOOGLE); - personal_data_.AddServerCreditCard(google_issued_card); - // Set up our form data. - FormData form; - CreateTestCreditCardFormData(&form, true, false); - std::vector<FormData> forms(1, form); - FormsSeen(forms); - // Set the field being edited to CC field. - const FormFieldData& credit_card_number_field = form.fields[1]; - - GetAutofillSuggestions(form, credit_card_number_field); - - // Assert that there are only two credit card suggestions returned. - external_delegate_->CheckSuggestionCount(kDefaultPageID, 2); -} - // Test that we will eventually return the credit card signin promo when there // are no credit card suggestions and the promo is active. See the tests in // AutofillExternalDelegateTest that test whether the promo is added.
diff --git a/components/autofill/core/browser/data_model/credit_card.cc b/components/autofill/core/browser/data_model/credit_card.cc index 50029fb..3a3b25c 100644 --- a/components/autofill/core/browser/data_model/credit_card.cc +++ b/components/autofill/core/browser/data_model/credit_card.cc
@@ -580,10 +580,6 @@ base::TrimString(nickname_, u" ", &nickname_); } -bool CreditCard::IsGoogleIssuedCard() const { - return card_issuer_ == CreditCard::Issuer::GOOGLE; -} - void CreditCard::operator=(const CreditCard& credit_card) { set_use_count(credit_card.use_count()); set_use_date(credit_card.use_date()); @@ -908,10 +904,6 @@ } std::string CreditCard::CardIconStringForAutofillSuggestion() const { - if (base::FeatureList::IsEnabled(features::kAutofillEnableGoogleIssuedCard) && - IsGoogleIssuedCard()) { - return kGoogleIssuedCard; - } return network_; } @@ -937,16 +929,7 @@ if (HasNonEmptyValidNickname() || !customized_nickname.empty()) { return NicknameAndLastFourDigits(customized_nickname, obfuscation_length); } - std::u16string networkAndLastFourDigits = - NetworkAndLastFourDigits(obfuscation_length); - // Add Plex before the network and last four digits to identify it as a Google - // Plex card. - if (base::FeatureList::IsEnabled(features::kAutofillEnableGoogleIssuedCard) && - IsGoogleIssuedCard()) { - return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_GOOGLE_ISSUED) + u" " + - networkAndLastFourDigits; - } - return networkAndLastFourDigits; + return NetworkAndLastFourDigits(obfuscation_length); } #if BUILDFLAG(IS_ANDROID) @@ -1172,7 +1155,6 @@ const char kDiscoverCard[] = "discoverCC"; const char kEloCard[] = "eloCC"; const char kGenericCard[] = "genericCC"; -const char kGoogleIssuedCard[] = "googleIssuedCC"; const char kJCBCard[] = "jcbCC"; const char kMasterCard[] = "masterCardCC"; const char kMirCard[] = "mirCC";
diff --git a/components/autofill/core/browser/data_model/credit_card.h b/components/autofill/core/browser/data_model/credit_card.h index a8a3592..a93b1ed4 100644 --- a/components/autofill/core/browser/data_model/credit_card.h +++ b/components/autofill/core/browser/data_model/credit_card.h
@@ -460,7 +460,6 @@ extern const char kDiscoverCard[]; extern const char kEloCard[]; extern const char kGenericCard[]; -extern const char kGoogleIssuedCard[]; extern const char kJCBCard[]; extern const char kMasterCard[]; extern const char kMirCard[];
diff --git a/components/autofill/core/browser/data_model/credit_card_unittest.cc b/components/autofill/core/browser/data_model/credit_card_unittest.cc index 580a8bb..40ad9c2 100644 --- a/components/autofill/core/browser/data_model/credit_card_unittest.cc +++ b/components/autofill/core/browser/data_model/credit_card_unittest.cc
@@ -256,119 +256,6 @@ credit_card2.CardIdentifierStringForAutofillDisplay()); } -TEST(CreditCardTest, CardIdentifierStringForIssuedCard) { - base::test::ScopedFeatureList scoped_feature_list; - // Enable the flag. - scoped_feature_list.InitAndEnableFeature( - features::kAutofillEnableGoogleIssuedCard); - // Case 1: Card Issuer set to GOOGLE with no nickname. - CreditCard credit_card1(base::GenerateGUID(), "https://www.example.com/"); - credit_card1.set_card_issuer(CreditCard::Issuer::GOOGLE); - test::SetCreditCardInfo(&credit_card1, "John Dillinger", - "5105 1051 0510 5100" /* Mastercard */, "01", "2020", - "1"); - EXPECT_EQ(l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_GOOGLE_ISSUED) + - UTF8ToUTF16(std::string(" Mastercard ") + - test::ObfuscatedCardDigitsAsUTF8("5100")), - credit_card1.CardIdentifierStringForAutofillDisplay()); - - // Case 2: Card Issuer set to GOOGLE with nickname. - std::u16string valid_nickname = u"My Visa Card"; - credit_card1.SetNickname(valid_nickname); - EXPECT_EQ( - valid_nickname + UTF8ToUTF16(std::string(" ") + - test::ObfuscatedCardDigitsAsUTF8("5100")), - credit_card1.CardIdentifierStringForAutofillDisplay()); - - // Case 3: Card Issuer set to ISSUER_UNKNOWN and no nickname. - CreditCard credit_card2(base::GenerateGUID(), "https://www.example.com/"); - test::SetCreditCardInfo(&credit_card2, "John Dillinger", - "5105 1051 0510 5100" /* Mastercard */, "01", "2020", - "1"); - credit_card2.set_card_issuer(CreditCard::Issuer::ISSUER_UNKNOWN); - EXPECT_EQ(UTF8ToUTF16(std::string("Mastercard ") + - test::ObfuscatedCardDigitsAsUTF8("5100")), - credit_card2.CardIdentifierStringForAutofillDisplay()); -} - -TEST(CreditCardTest, CardIdentifierStringForIssuedCardExpOff) { - base::test::ScopedFeatureList scoped_feature_list; - // Disable the flag. - scoped_feature_list.InitAndDisableFeature( - features::kAutofillEnableGoogleIssuedCard); - // Case 1: Card Issuer set to GOOGLE with no nickname. - CreditCard credit_card1(base::GenerateGUID(), "https://www.example.com/"); - credit_card1.set_card_issuer(CreditCard::Issuer::GOOGLE); - test::SetCreditCardInfo(&credit_card1, "John Dillinger", - "5105 1051 0510 5100" /* Mastercard */, "01", "2020", - "1"); - EXPECT_EQ(UTF8ToUTF16(std::string("Mastercard ") + - test::ObfuscatedCardDigitsAsUTF8("5100")), - credit_card1.CardIdentifierStringForAutofillDisplay()); - - // Case 2: Card Issuer set to GOOGLE with nickname. - std::u16string valid_nickname = u"My Visa Card"; - credit_card1.SetNickname(valid_nickname); - EXPECT_EQ( - valid_nickname + UTF8ToUTF16(std::string(" ") + - test::ObfuscatedCardDigitsAsUTF8("5100")), - credit_card1.CardIdentifierStringForAutofillDisplay()); - - // Case 3: Card Issuer set to ISSUER_UNKNOWN and no nickname. - CreditCard credit_card2(base::GenerateGUID(), "https://www.example.com/"); - test::SetCreditCardInfo(&credit_card2, "John Dillinger", - "5105 1051 0510 5100" /* Mastercard */, "01", "2020", - "1"); - credit_card2.set_card_issuer(CreditCard::Issuer::ISSUER_UNKNOWN); - EXPECT_EQ(UTF8ToUTF16(std::string("Mastercard ") + - test::ObfuscatedCardDigitsAsUTF8("5100")), - credit_card2.CardIdentifierStringForAutofillDisplay()); -} - -TEST(CreditCardTest, CardIconStringForGoogleIssuedCard) { - base::test::ScopedFeatureList scoped_feature_list; - // Enable the flag. - scoped_feature_list.InitAndEnableFeature( - features::kAutofillEnableGoogleIssuedCard); - // Case 1: Card Issuer set to GOOGLE. - CreditCard credit_card1(base::GenerateGUID(), "https://www.example.com/"); - credit_card1.set_card_issuer(CreditCard::Issuer::GOOGLE); - test::SetCreditCardInfo(&credit_card1, "John Dillinger", "", "01", "2020", - "1"); - EXPECT_EQ(kGoogleIssuedCard, - credit_card1.CardIconStringForAutofillSuggestion()); - - // Case 2: Card Issuer set to ISSUER_UNKNOWN. - CreditCard credit_card2(base::GenerateGUID(), "https://www.example.com/"); - test::SetCreditCardInfo(&credit_card2, "John Dillinger", - "5105 1051 0510 5100" /* Mastercard */, "01", "2020", - "1"); - credit_card2.set_card_issuer(CreditCard::Issuer::ISSUER_UNKNOWN); - EXPECT_EQ(kMasterCard, credit_card2.CardIconStringForAutofillSuggestion()); -} - -TEST(CreditCardTest, CardIconStringForGoogleIssuedCardExpOff) { - base::test::ScopedFeatureList scoped_feature_list; - // Enable the flag. - scoped_feature_list.InitAndDisableFeature( - features::kAutofillEnableGoogleIssuedCard); - // Case 1: Card Issuer set to GOOGLE. - CreditCard credit_card1(base::GenerateGUID(), "https://www.example.com/"); - credit_card1.set_card_issuer(CreditCard::Issuer::GOOGLE); - test::SetCreditCardInfo(&credit_card1, "John Dillinger", - "5105 1051 0510 5100" /* Mastercard */, "01", "2020", - "1"); - EXPECT_EQ(kMasterCard, credit_card1.CardIconStringForAutofillSuggestion()); - - // Case 2: Card Issuer set to ISSUER_UNKNOWN. - CreditCard credit_card2(base::GenerateGUID(), "https://www.example.com/"); - test::SetCreditCardInfo(&credit_card2, "John Dillinger", - "5105 1051 0510 5100" /* Mastercard */, "01", "2020", - "1"); - credit_card2.set_card_issuer(CreditCard::Issuer::ISSUER_UNKNOWN); - EXPECT_EQ(kMasterCard, credit_card2.CardIconStringForAutofillSuggestion()); -} - TEST(CreditCardTest, AssignmentOperator) { CreditCard a(base::GenerateGUID(), test::kEmptyOrigin); test::SetCreditCardInfo(&a, "John Dillinger", "123456789012", "01", "2010",
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager.cc b/components/autofill/core/browser/payments/credit_card_access_manager.cc index 699090c..b1661c2 100644 --- a/components/autofill/core/browser/payments/credit_card_access_manager.cc +++ b/components/autofill/core/browser/payments/credit_card_access_manager.cc
@@ -616,21 +616,6 @@ unmask_auth_flow_type_); } - // Local boolean denotes whether to show the dialog that offers opting-in to - // FIDO authentication after the CVC check. Note that this and - // |should_respond_immediately| are NOT mutually exclusive. If both are true, - // it represents the Desktop opt-in flow (fill the form first, and prompt the - // opt-in dialog). - bool should_offer_fido_auth = false; - // For iOS, FIDO auth is not supported yet. For Android, users have already - // been offered opt-in at this point. -#if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID) - should_offer_fido_auth = unmask_details_.offer_fido_opt_in && - !response.card_authorization_token.empty() && - !GetOrCreateFIDOAuthenticator() - ->GetOrCreateFidoAuthenticationStrikeDatabase() - ->IsMaxStrikesLimitReached(); -#endif bool should_register_card_with_fido = ShouldRegisterCardWithFido(response); if (ShouldRespondImmediately(response)) { // If ShouldRespondImmediately() returns true, @@ -665,7 +650,7 @@ request_options->Clone()); #endif } - if (should_offer_fido_auth) { + if (ShouldOfferFidoOptInDialog(response)) { // CreditCardFIDOAuthenticator will handle enrollment completely. ShowWebauthnOfferDialog(response.card_authorization_token); } @@ -673,9 +658,9 @@ HandleFidoOptInStatusChange(); // TODO(crbug.com/1249665): Add Reset() to this function after cleaning up the // FIDO opt-in status change. This should not have any negative impact now - // except for readability and cleanness. |should_offer_fido_auth| and - // |opt_in_intention_| are to some extent duplicate. We should be able to - // remove the two variables and use a function. + // except for readability and cleanness. The result of + // ShouldOfferFidoOptInDialog() and |opt_in_intention_| are to some extent + // duplicate. We should be able to combine them into one function. } #if BUILDFLAG(IS_ANDROID) @@ -895,6 +880,36 @@ return false; } +bool CreditCardAccessManager::ShouldOfferFidoOptInDialog( + const CreditCardCVCAuthenticator::CVCAuthenticationResponse& response) { +#if BUILDFLAG(IS_IOS) || BUILDFLAG(IS_ANDROID) + // We should not offer FIDO opt-in dialog on mobile. + return false; +#else + // If this card is not eligible for offering FIDO opt-in, we should not offer + // the FIDO opt-in dialog. + if (!unmask_details_.offer_fido_opt_in) + return false; + + // A card authorization token is required for FIDO opt-in, so if we did not + // receive one from the server we should not offer the FIDO opt-in dialog. + if (response.card_authorization_token.empty()) + return false; + + // If the strike limit was reached for the FIDO opt-in dialog, we should not + // offer it. + if (GetOrCreateFIDOAuthenticator() + ->GetOrCreateFidoAuthenticationStrikeDatabase() + ->IsMaxStrikesLimitReached()) { + return false; + } + + // None of the cases where we should not offer the FIDO opt-in dialog were + // true, so we should offer it. + return true; +#endif +} + void CreditCardAccessManager::ShowWebauthnOfferDialog( std::string card_authorization_token) { #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager.h b/components/autofill/core/browser/payments/credit_card_access_manager.h index 55f0251..6b29217 100644 --- a/components/autofill/core/browser/payments/credit_card_access_manager.h +++ b/components/autofill/core/browser/payments/credit_card_access_manager.h
@@ -323,6 +323,13 @@ bool ShouldRegisterCardWithFido( const CreditCardCVCAuthenticator::CVCAuthenticationResponse& response); + // Returns true if the we can offer FIDO opt-in for the user. In the + // downstream flow, after we offer FIDO opt-in, if the user accepts we might + // also offer FIDO authentication for the downstreamed card so that the FIDO + // registration flow is complete. + bool ShouldOfferFidoOptInDialog( + const CreditCardCVCAuthenticator::CVCAuthenticationResponse& response); + // TODO(crbug.com/991037): Move this function under the build flags after the // refactoring is done. Offer the option to use WebAuthn for authenticating // future card unmasking.
diff --git a/components/autofill/core/browser/payments/full_card_request.cc b/components/autofill/core/browser/payments/full_card_request.cc index f0bd94a..9ab7577 100644 --- a/components/autofill/core/browser/payments/full_card_request.cc +++ b/components/autofill/core/browser/payments/full_card_request.cc
@@ -299,8 +299,6 @@ const std::u16string cvc = (base::FeatureList::IsEnabled( - features::kAutofillEnableGoogleIssuedCard) || - base::FeatureList::IsEnabled( features::kAutofillAlwaysReturnCloudTokenizedCard) || base::FeatureList::IsEnabled( features::kAutofillEnableMerchantBoundVirtualCards)) &&
diff --git a/components/autofill/core/browser/payments/full_card_request_unittest.cc b/components/autofill/core/browser/payments/full_card_request_unittest.cc index 6ee2c0d..e81295d 100644 --- a/components/autofill/core/browser/payments/full_card_request_unittest.cc +++ b/components/autofill/core/browser/payments/full_card_request_unittest.cc
@@ -257,57 +257,6 @@ card_unmask_delegate()->OnUnmaskPromptClosed(); } -// Verify getting the full PAN and the CVV for a Google issued card when FIDO is -// used for authentication. -TEST_F(FullCardRequestTest, GetFullCardPanAndUseCvcInUnmaskResponse) { - scoped_feature_list_.InitAndEnableFeature( - features::kAutofillEnableGoogleIssuedCard); - EXPECT_CALL(*result_delegate(), - OnFullCardRequestSucceeded( - testing::Ref(*request()), - CardMatches(CreditCard::FULL_SERVER_CARD, "4111"), - testing::Eq(u"321"))); - EXPECT_CALL(*ui_delegate(), ShowUnmaskPrompt(_, _, _)); - EXPECT_CALL(*ui_delegate(), OnUnmaskVerificationResult( - AutofillClient::PaymentsRpcResult::kSuccess)); - - request()->GetFullCard( - CreditCard(CreditCard::MASKED_SERVER_CARD, "server_id"), - AutofillClient::UnmaskCardReason::kAutofill, - result_delegate()->AsWeakPtr(), ui_delegate()->AsWeakPtr()); - CardUnmaskDelegate::UserProvidedUnmaskDetails details; - details.cvc = u"123"; - card_unmask_delegate()->OnUnmaskPromptAccepted(details); - OnDidGetRealPanWithDcvv(AutofillClient::PaymentsRpcResult::kSuccess, "4111", - "321"); - card_unmask_delegate()->OnUnmaskPromptClosed(); -} - -// Verify getting the full PAN for a Google issued card when CVV is used for -// authentication. -TEST_F(FullCardRequestTest, GetFullCardPanWithoutCvcInUnmaskResponse) { - scoped_feature_list_.InitAndEnableFeature( - features::kAutofillEnableGoogleIssuedCard); - EXPECT_CALL(*result_delegate(), - OnFullCardRequestSucceeded( - testing::Ref(*request()), - CardMatches(CreditCard::FULL_SERVER_CARD, "4111"), - testing::Eq(u"123"))); - EXPECT_CALL(*ui_delegate(), ShowUnmaskPrompt(_, _, _)); - EXPECT_CALL(*ui_delegate(), OnUnmaskVerificationResult( - AutofillClient::PaymentsRpcResult::kSuccess)); - - request()->GetFullCard( - CreditCard(CreditCard::MASKED_SERVER_CARD, "server_id"), - AutofillClient::UnmaskCardReason::kAutofill, - result_delegate()->AsWeakPtr(), ui_delegate()->AsWeakPtr()); - CardUnmaskDelegate::UserProvidedUnmaskDetails details; - details.cvc = u"123"; - card_unmask_delegate()->OnUnmaskPromptAccepted(details); - OnDidGetRealPan(AutofillClient::PaymentsRpcResult::kSuccess, "4111"); - card_unmask_delegate()->OnUnmaskPromptClosed(); -} - // Verify getting the full PAN for a masked server card. TEST_F(FullCardRequestTest, GetFullCardPanAndCvcForMaskedServerCardViaFido) { EXPECT_CALL(
diff --git a/components/autofill/core/browser/personal_data_manager.cc b/components/autofill/core/browser/personal_data_manager.cc index 866f282..79e88c77 100644 --- a/components/autofill/core/browser/personal_data_manager.cc +++ b/components/autofill/core/browser/personal_data_manager.cc
@@ -1103,12 +1103,6 @@ result.reserve(server_credit_cards_.size()); for (const auto& card : server_credit_cards_) { - // Do not add Google issued credit card if experiment is disabled. - if (card.get()->IsGoogleIssuedCard() && - !base::FeatureList::IsEnabled( - autofill::features::kAutofillEnableGoogleIssuedCard)) { - continue; - } result.push_back(card.get()); } return result; @@ -1122,12 +1116,6 @@ result.push_back(card.get()); if (IsAutofillWalletImportEnabled()) { for (const auto& card : server_credit_cards_) { - // Do not add Google issued credit card if experiment is disabled. - if (card.get()->IsGoogleIssuedCard() && - !base::FeatureList::IsEnabled( - autofill::features::kAutofillEnableGoogleIssuedCard)) { - continue; - } result.push_back(card.get()); } }
diff --git a/components/autofill/core/browser/personal_data_manager_unittest.cc b/components/autofill/core/browser/personal_data_manager_unittest.cc index 3034858a..30c57d0 100644 --- a/components/autofill/core/browser/personal_data_manager_unittest.cc +++ b/components/autofill/core/browser/personal_data_manager_unittest.cc
@@ -1071,55 +1071,6 @@ ExpectSameElements(cards, personal_data_->GetCreditCards()); } -TEST_F(PersonalDataManagerTest, DoNotAddGoogleIssuedCreditCardExpOff) { - base::test::ScopedFeatureList scoped_features; - scoped_features.InitAndDisableFeature( - features::kAutofillEnableGoogleIssuedCard); - // Set up the credit cards. - CreditCard credit_card0 = test::GetMaskedServerCard(); - credit_card0.set_card_issuer(CreditCard::Issuer::ISSUER_UNKNOWN); - CreditCard credit_card1 = test::GetMaskedServerCardAmex(); - credit_card1.set_card_issuer(CreditCard::Issuer::GOOGLE); - // Add the above cards to server_cards. - std::vector<CreditCard> server_cards; - server_cards.push_back(credit_card0); - server_cards.push_back(credit_card1); - SetServerCards(server_cards); - - personal_data_->Refresh(); - WaitForOnPersonalDataChanged(); - - std::vector<CreditCard*> cards; - // Since the flag is off, only the card with ISSUER_UNKNOWN should be - // returned. - cards.push_back(&credit_card0); - ExpectSameElements(cards, personal_data_->GetCreditCards()); -} - -TEST_F(PersonalDataManagerTest, AddGoogleIssuedCreditCard) { - base::test::ScopedFeatureList scoped_features; - scoped_features.InitAndEnableFeature( - features::kAutofillEnableGoogleIssuedCard); - // Set up the credit cards. - CreditCard credit_card0 = test::GetMaskedServerCard(); - credit_card0.set_card_issuer(CreditCard::Issuer::ISSUER_UNKNOWN); - CreditCard credit_card1 = test::GetMaskedServerCardAmex(); - credit_card1.set_card_issuer(CreditCard::Issuer::GOOGLE); - // Add the above cards to server_cards. - std::vector<CreditCard> server_cards; - server_cards.push_back(credit_card0); - server_cards.push_back(credit_card1); - SetServerCards(server_cards); - - personal_data_->Refresh(); - WaitForOnPersonalDataChanged(); - - std::vector<CreditCard*> cards; - cards.push_back(&credit_card0); - cards.push_back(&credit_card1); - ExpectSameElements(cards, personal_data_->GetCreditCards()); -} - // Test that a new credit card has its basic information set. TEST_F(PersonalDataManagerTest, AddCreditCard_BasicInformation) { // Create the test clock and set the time to a specific value.
diff --git a/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.cc b/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.cc index 9025837a..6463633 100644 --- a/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.cc +++ b/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.cc
@@ -207,12 +207,6 @@ return l10n_util::GetStringFUTF16( ids, card_.CardIdentifierStringForAutofillDisplay()); #else - // For Google Pay Plex cards, show a specific message that include - // instructions to find the CVC for their Plex card. - if (card_.IsGoogleIssuedCard()) { - return l10n_util::GetStringUTF16( - IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS_GOOGLE_ISSUED_CARD); - } return l10n_util::GetStringUTF16( card_.record_type() == autofill::CreditCard::LOCAL_CARD ? IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS_LOCAL_CARD
diff --git a/components/autofill/core/common/autofill_features.cc b/components/autofill/core/common/autofill_features.cc index f0018ab4..bb1f9a85 100644 --- a/components/autofill/core/common/autofill_features.cc +++ b/components/autofill/core/common/autofill_features.cc
@@ -415,9 +415,13 @@ // Controls whether Autofill may fill across origins as part of the // AutofillAcrossIframes experiment. -// TODO(crbug.com/1220038): Clean up when launched. +// TODO(crbug.com/1304721): Clean up when launched. const base::Feature kAutofillSharedAutofill{"AutofillSharedAutofill", base::FEATURE_DISABLED_BY_DEFAULT}; +// Relaxes the conditions under which a field is safe to fill. +// See FormForest::GetRendererFormsOfBrowserForm() for details. +const base::FeatureParam<bool> kAutofillSharedAutofillRelaxedParam{ + &kAutofillSharedAutofill, "relax_shared_autofill", false}; // Controls attaching the autofill type predictions to their respective // element in the DOM.
diff --git a/components/autofill/core/common/autofill_features.h b/components/autofill/core/common/autofill_features.h index 74ef936..3291694c4 100644 --- a/components/autofill/core/common/autofill_features.h +++ b/components/autofill/core/common/autofill_features.h
@@ -149,6 +149,8 @@ COMPONENT_EXPORT(AUTOFILL) extern const base::Feature kAutofillSharedAutofill; COMPONENT_EXPORT(AUTOFILL) +extern const base::FeatureParam<bool> kAutofillSharedAutofillRelaxedParam; +COMPONENT_EXPORT(AUTOFILL) extern const base::Feature kAutofillShowTypePredictions; COMPONENT_EXPORT(AUTOFILL) extern const base::Feature kAutofillSilentProfileUpdateForInsufficientImport;
diff --git a/components/autofill/core/common/autofill_payments_features.cc b/components/autofill/core/common/autofill_payments_features.cc index 62c47656..696361f0 100644 --- a/components/autofill/core/common/autofill_payments_features.cc +++ b/components/autofill/core/common/autofill_payments_features.cc
@@ -54,10 +54,6 @@ const base::Feature kAutofillCreditCardUploadFeedback{ "AutofillCreditCardUploadFeedback", base::FEATURE_DISABLED_BY_DEFAULT}; -// Controls whether we show a Google-issued card in the suggestions list. -const base::Feature kAutofillEnableGoogleIssuedCard{ - "AutofillEnableGoogleIssuedCard", base::FEATURE_DISABLED_BY_DEFAULT}; - // When enabled, merchant bound virtual cards will be offered when users // interact with a payment form. const base::Feature kAutofillEnableMerchantBoundVirtualCards{
diff --git a/components/autofill/core/common/autofill_payments_features.h b/components/autofill/core/common/autofill_payments_features.h index 6de63ea..43eaafc 100644 --- a/components/autofill/core/common/autofill_payments_features.h +++ b/components/autofill/core/common/autofill_payments_features.h
@@ -21,7 +21,6 @@ extern const base::Feature kAutofillAutoTriggerManualFallbackForCards; extern const base::Feature kAutofillCreditCardAuthentication; extern const base::Feature kAutofillCreditCardUploadFeedback; -extern const base::Feature kAutofillEnableGoogleIssuedCard; extern const base::Feature kAutofillEnableMerchantBoundVirtualCards; extern const base::Feature kAutofillEnableOfferNotificationForPromoCodes; extern const base::Feature kAutofillEnableOffersInClankKeyboardAccessory;
diff --git a/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantAutofillCreditCard.java b/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantAutofillCreditCard.java index 7e3fc65..7e9dff7 100644 --- a/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantAutofillCreditCard.java +++ b/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantAutofillCreditCard.java
@@ -34,7 +34,6 @@ put("troyCC", R.drawable.troy_card); put("unionPayCC", R.drawable.unionpay_card); put("visaCC", R.drawable.visa_card); - put("googleIssuedCC", R.drawable.google_pay_plex); put("googlePay", R.drawable.google_pay); } };
diff --git a/components/autofill_payments_strings.grdp b/components/autofill_payments_strings.grdp index 29aa40b..d6ba1ea 100644 --- a/components/autofill_payments_strings.grdp +++ b/components/autofill_payments_strings.grdp
@@ -325,9 +325,6 @@ <message name="IDS_AUTOFILL_CARD_UNMASK_CVC_IMAGE_DESCRIPTION" desc="Accessible description for the CVC image. It should describe where to find the CVC on a credit card."> The CVC is located behind your card. </message> - <message name="IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS_GOOGLE_ISSUED_CARD" desc="Text explaining what the user should do in the card unmasking dialog for a GooglePay Plex card." formatter_data="android"> - After you confirm, card details from your Google Account will be shared with this site. Find the CVC in your Plex Account details. - </message> </if> <if expr="is_ios"> <message name="IDS_AUTOFILL_CARD_UNMASK_PROMPT_TITLE" desc="Title for the credit card unmasking dialog.">
diff --git a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS_GOOGLE_ISSUED_CARD.png.sha1 b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS_GOOGLE_ISSUED_CARD.png.sha1 deleted file mode 100644 index c5cbdb4..0000000 --- a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_CARD_UNMASK_PROMPT_INSTRUCTIONS_GOOGLE_ISSUED_CARD.png.sha1 +++ /dev/null
@@ -1 +0,0 @@ -fab9ff0c5292287ed84c9564b279cff97ae8cb25 \ No newline at end of file
diff --git a/components/autofill_strings.grdp b/components/autofill_strings.grdp index 1e26ea6..a5551ea2 100644 --- a/components/autofill_strings.grdp +++ b/components/autofill_strings.grdp
@@ -64,9 +64,6 @@ <message name="IDS_AUTOFILL_CC_ELO" desc="Elo credit card name."> Elo </message> - <message name="IDS_AUTOFILL_CC_GOOGLE_ISSUED" desc="Prefix for the Google Plex card" formatter_data="android_java"> - Plex - </message> <message name="IDS_AUTOFILL_CC_GOOGLE_PAY" desc="Google pay brand name"> Google Pay </message>
diff --git a/components/autofill_strings_grdp/IDS_AUTOFILL_CC_GOOGLE_ISSUED.png.sha1 b/components/autofill_strings_grdp/IDS_AUTOFILL_CC_GOOGLE_ISSUED.png.sha1 deleted file mode 100644 index 2c0d265..0000000 --- a/components/autofill_strings_grdp/IDS_AUTOFILL_CC_GOOGLE_ISSUED.png.sha1 +++ /dev/null
@@ -1 +0,0 @@ -25af0f307bc74982a197c2088146485505c0b018 \ No newline at end of file
diff --git a/components/desks_storage/core/desk_sync_bridge.cc b/components/desks_storage/core/desk_sync_bridge.cc index 56d2dea..8f38d7f 100644 --- a/components/desks_storage/core/desk_sync_bridge.cc +++ b/components/desks_storage/core/desk_sync_bridge.cc
@@ -215,7 +215,7 @@ if (app_id.empty()) return nullptr; - std::unique_ptr<app_restore::AppLaunchInfo> app_launch_info = + auto app_launch_info = std::make_unique<app_restore::AppLaunchInfo>(app_id, window_id); if (app.has_display_id()) @@ -558,7 +558,7 @@ // Fills an app with container and open disposition. This is only done in the // specific cases of Chrome Apps and PWAs. -void FillAppWithLaunchContianerAndOpenDisposition( +void FillAppWithLaunchContainerAndOpenDisposition( const app_restore::AppRestoreData* app_restore_data, WorkspaceDeskSpecifics_App* out_app) { // If present, fills the proto's `container` field with the information stored @@ -602,7 +602,7 @@ pwa_window->set_title( base::UTF16ToUTF8(app_restore_data->title.value())); } - FillAppWithLaunchContianerAndOpenDisposition(app_restore_data, out_app); + FillAppWithLaunchContainerAndOpenDisposition(app_restore_data, out_app); } break; } @@ -621,7 +621,7 @@ chrome_app_window->set_title( base::UTF16ToUTF8(app_restore_data->title.value())); } - FillAppWithLaunchContianerAndOpenDisposition(app_restore_data, out_app); + FillAppWithLaunchContainerAndOpenDisposition(app_restore_data, out_app); break; } case apps::AppType::kArc: { @@ -695,8 +695,7 @@ // Convert a desk template to |app_restore::RestoreData|. std::unique_ptr<app_restore::RestoreData> ConvertToRestoreData( const sync_pb::WorkspaceDeskSpecifics& entry_proto) { - std::unique_ptr<app_restore::RestoreData> restore_data = - std::make_unique<app_restore::RestoreData>(); + auto restore_data = std::make_unique<app_restore::RestoreData>(); for (auto app_proto : entry_proto.desk().apps()) { std::unique_ptr<app_restore::AppLaunchInfo> app_launch_info = @@ -776,7 +775,7 @@ pb_entry.created_time_windows_epoch_micros()); // Protobuf parsing enforces UTF-8 encoding for all strings. - std::unique_ptr<DeskTemplate> desk_template = std::make_unique<DeskTemplate>( + auto desk_template = std::make_unique<DeskTemplate>( uuid, ash::DeskTemplateSource::kUser, pb_entry.name(), created_time); if (pb_entry.has_updated_time_windows_epoch_micros()) {
diff --git a/components/desks_storage/core/local_desk_data_manager.cc b/components/desks_storage/core/local_desk_data_manager.cc index 3a8c83f..3eb40c0e 100644 --- a/components/desks_storage/core/local_desk_data_manager.cc +++ b/components/desks_storage/core/local_desk_data_manager.cc
@@ -211,10 +211,6 @@ void LocalDeskDataManager::AddOrUpdateEntry( std::unique_ptr<ash::DeskTemplate> new_entry, DeskModel::AddOrUpdateEntryCallback callback) { - // When a user creates a desk template locally, the desk template has |kUser| - // as its source. Only user desk templates should be saved. - DCHECK_EQ(ash::DeskTemplateSource::kUser, new_entry->source()); - auto status = std::make_unique<DeskModel::AddOrUpdateEntryStatus>(); task_runner_->PostTaskAndReply(
diff --git a/components/feed/core/proto/v2/store.proto b/components/feed/core/proto/v2/store.proto index 4c4efd9..67ad56d 100644 --- a/components/feed/core/proto/v2/store.proto +++ b/components/feed/core/proto/v2/store.proto
@@ -126,6 +126,9 @@ // Whether personalization is enabled for Discover, as reported by the last // FeedQuery response. bool discover_personalization_enabled = 8; + // Count of how many times the user has followed from the main menu, so we + // can show appropriate user education help for the following feed. + int32 followed_from_web_page_menu_count = 9; } // A set of StreamStructures that should be applied to a stream.
diff --git a/components/feed/core/v2/api_test/feed_api_stream_unittest.cc b/components/feed/core/v2/api_test/feed_api_stream_unittest.cc index 2eedfee5..ce09170 100644 --- a/components/feed/core/v2/api_test/feed_api_stream_unittest.cc +++ b/components/feed/core/v2/api_test/feed_api_stream_unittest.cc
@@ -2993,6 +2993,17 @@ "ContentSuggestions.Feed.NoticeAcknowledged.Youtube", true, 1); } +TEST_F(FeedApiTest, FollowedFromWebPageMenuCount) { + // Arrange. + TestWebFeedSurface surface(stream_.get()); + // Act. + stream_->IncrementFollowedFromWebPageMenuCount(); + // Assert. + EXPECT_EQ(1, stream_->GetMetadata().followed_from_web_page_menu_count()); + EXPECT_EQ(1, stream_->GetRequestMetadata(kWebFeedStream, false) + .followed_from_web_page_menu_count); +} + // Keep instantiations at the bottom. INSTANTIATE_TEST_SUITE_P(FeedApiTest, FeedStreamTestForAllStreamTypes,
diff --git a/components/feed/core/v2/feed_stream.cc b/components/feed/core/v2/feed_stream.cc index 67bb71c..4dac444 100644 --- a/components/feed/core/v2/feed_stream.cc +++ b/components/feed/core/v2/feed_stream.cc
@@ -912,6 +912,8 @@ result.session_id = session_id; } } + result.followed_from_web_page_menu_count = + metadata_.followed_from_web_page_menu_count(); DCHECK(result.session_id.empty() || result.client_instance_id.empty()); return result; @@ -1085,6 +1087,13 @@ return true; } +void FeedStream::IncrementFollowedFromWebPageMenuCount() { + feedstore::Metadata metadata = GetMetadata(); + metadata.set_followed_from_web_page_menu_count( + metadata.followed_from_web_page_menu_count() + 1); + SetMetadata(std::move(metadata)); +} + void FeedStream::ClearAll() { metrics_reporter_->OnClearAll(base::Time::Now() - GetLastFetchTime(kForYouStream));
diff --git a/components/feed/core/v2/feed_stream.h b/components/feed/core/v2/feed_stream.h index bfa21ae..6327fc3 100644 --- a/components/feed/core/v2/feed_stream.h +++ b/components/feed/core/v2/feed_stream.h
@@ -180,6 +180,7 @@ ContentOrder content_order) override; ContentOrder GetContentOrder(const StreamType& stream_type) override; ContentOrder GetContentOrderFromPrefs(const StreamType& stream_type) override; + void IncrementFollowedFromWebPageMenuCount() override; // offline_pages::TaskQueue::Delegate. void OnTaskQueueIsIdle() override;
diff --git a/components/feed/core/v2/metrics_reporter.cc b/components/feed/core/v2/metrics_reporter.cc index 593fd05c..bc55b949 100644 --- a/components/feed/core/v2/metrics_reporter.cc +++ b/components/feed/core/v2/metrics_reporter.cc
@@ -583,6 +583,7 @@ case FeedUserActionType::kTappedDismissPostFollowActiveHelp: case FeedUserActionType::kTappedDiscoverFeedPreview: case FeedUserActionType::kOpenedAutoplaySettings: + case FeedUserActionType::kTappedFollowButton: // Nothing additional for these actions. Note that some of these are iOS // only.
diff --git a/components/feed/core/v2/public/common_enums.cc b/components/feed/core/v2/public/common_enums.cc index a876d373..1041c32f 100644 --- a/components/feed/core/v2/public/common_enums.cc +++ b/components/feed/core/v2/public/common_enums.cc
@@ -98,6 +98,8 @@ return out << "kTappedManage"; case FeedUserActionType::kTappedManageHidden: return out << "kTappedManageHidden"; + case FeedUserActionType::kTappedFollowButton: + return out << "kTappedFollow"; } }
diff --git a/components/feed/core/v2/public/common_enums.h b/components/feed/core/v2/public/common_enums.h index e4e6aed..5e6a36d 100644 --- a/components/feed/core/v2/public/common_enums.h +++ b/components/feed/core/v2/public/common_enums.h
@@ -123,8 +123,10 @@ kTappedManage = 42, // User tapped "Hidden" in the manage intestitial. kTappedManageHidden = 43, + // User tapped the "Follow" button on the main menu. + kTappedFollowButton = 44, - kMaxValue = kTappedManageHidden, + kMaxValue = kTappedFollowButton, }; // For testing and debugging only.
diff --git a/components/feed/core/v2/public/feed_api.h b/components/feed/core/v2/public/feed_api.h index 3e7e45d..13b8f39 100644 --- a/components/feed/core/v2/public/feed_api.h +++ b/components/feed/core/v2/public/feed_api.h
@@ -213,6 +213,9 @@ const feedui::StreamUpdate& stream_update) = 0; // Returns the time of the last successful content fetch. virtual base::Time GetLastFetchTime(const StreamType& stream_type) = 0; + // Increase the count of the number of times the user has followed from the + // web page menu. + virtual void IncrementFollowedFromWebPageMenuCount() = 0; }; } // namespace feed
diff --git a/components/feed/core/v2/public/test/stub_feed_api.h b/components/feed/core/v2/public/test/stub_feed_api.h index e117da85..f171e0a 100644 --- a/components/feed/core/v2/public/test/stub_feed_api.h +++ b/components/feed/core/v2/public/test/stub_feed_api.h
@@ -107,6 +107,7 @@ ContentOrder content_order) override {} ContentOrder GetContentOrder(const StreamType& stream_type) override; ContentOrder GetContentOrderFromPrefs(const StreamType& stream_type) override; + void IncrementFollowedFromWebPageMenuCount() override {} private: StubWebFeedSubscriptions web_feed_subscriptions_;
diff --git a/components/feed/core/v2/types.h b/components/feed/core/v2/types.h index 3973ffd..884ff3d 100644 --- a/components/feed/core/v2/types.h +++ b/components/feed/core/v2/types.h
@@ -57,6 +57,7 @@ ContentOrder content_order = ContentOrder::kUnspecified; bool notice_card_acknowledged = false; bool autoplay_enabled = false; + int followed_from_web_page_menu_count = 0; std::vector<std::string> acknowledged_notice_keys; };
diff --git a/components/global_media_controls/public/media_item_producer.h b/components/global_media_controls/public/media_item_producer.h index b97b2f7f..6c21532 100644 --- a/components/global_media_controls/public/media_item_producer.h +++ b/components/global_media_controls/public/media_item_producer.h
@@ -25,7 +25,7 @@ virtual base::WeakPtr<media_message_center::MediaNotificationItem> GetMediaItem(const std::string& id) = 0; - virtual std::set<std::string> GetActiveControllableItemIds() = 0; + virtual std::set<std::string> GetActiveControllableItemIds() const = 0; // Returns true if the item producer has any "frozen" items, which are items // that were recently active with a chance to become active again.
diff --git a/components/global_media_controls/public/media_session_item_producer.cc b/components/global_media_controls/public/media_session_item_producer.cc index 017bd45b..bb142c7 100644 --- a/components/global_media_controls/public/media_session_item_producer.cc +++ b/components/global_media_controls/public/media_session_item_producer.cc
@@ -241,7 +241,8 @@ return it == sessions_.end() ? nullptr : it->second.item()->GetWeakPtr(); } -std::set<std::string> MediaSessionItemProducer::GetActiveControllableItemIds() { +std::set<std::string> MediaSessionItemProducer::GetActiveControllableItemIds() + const { return active_controllable_session_ids_; }
diff --git a/components/global_media_controls/public/media_session_item_producer.h b/components/global_media_controls/public/media_session_item_producer.h index 840583c..c47b071 100644 --- a/components/global_media_controls/public/media_session_item_producer.h +++ b/components/global_media_controls/public/media_session_item_producer.h
@@ -56,7 +56,7 @@ // MediaItemProducer: base::WeakPtr<media_message_center::MediaNotificationItem> GetMediaItem( const std::string& id) override; - std::set<std::string> GetActiveControllableItemIds() override; + std::set<std::string> GetActiveControllableItemIds() const override; bool HasFrozenItems() override; void OnItemShown(const std::string& id, MediaItemUI* item_ui) override; bool IsItemActivelyPlaying(const std::string& id) override;
diff --git a/components/global_media_controls/public/test/mock_media_item_producer.cc b/components/global_media_controls/public/test/mock_media_item_producer.cc index 276ccd5..920853b 100644 --- a/components/global_media_controls/public/test/mock_media_item_producer.cc +++ b/components/global_media_controls/public/test/mock_media_item_producer.cc
@@ -51,7 +51,8 @@ return iter->second.item.GetWeakPtr(); } -std::set<std::string> MockMediaItemProducer::GetActiveControllableItemIds() { +std::set<std::string> MockMediaItemProducer::GetActiveControllableItemIds() + const { std::set<std::string> active_items; for (auto const& item_pair : items_) { if (item_pair.second.active)
diff --git a/components/global_media_controls/public/test/mock_media_item_producer.h b/components/global_media_controls/public/test/mock_media_item_producer.h index cd61f35..da5de50 100644 --- a/components/global_media_controls/public/test/mock_media_item_producer.h +++ b/components/global_media_controls/public/test/mock_media_item_producer.h
@@ -25,7 +25,7 @@ base::WeakPtr<media_message_center::MediaNotificationItem> GetMediaItem( const std::string& id) override; - std::set<std::string> GetActiveControllableItemIds() override; + std::set<std::string> GetActiveControllableItemIds() const override; bool HasFrozenItems() override; MOCK_METHOD(void, OnItemShown, (const std::string&, MediaItemUI*)); MOCK_METHOD(void, OnDialogDisplayed, ());
diff --git a/components/history/core/browser/history_types.h b/components/history/core/browser/history_types.h index 6f0a354db..6eeb4e06 100644 --- a/components/history/core/browser/history_types.h +++ b/components/history/core/browser/history_types.h
@@ -234,8 +234,10 @@ QueryOptions& operator=(QueryOptions&&) noexcept; ~QueryOptions(); - // The time range to search for matches in. The beginning is inclusive and - // the ending is exclusive. Either one (or both) may be null. + // The time range to search for matches in. When `visit_order` is + // `RECENT_FIRST`, the beginning is inclusive and the ending is exclusive. + // When `VisitOrder` is `OLDEST_FIRST`, vice versa. Either one (or both) may + // be null. // // This will match only the one recent visit of a URL. For text search // queries, if the URL was visited in the given time period, but has also @@ -281,6 +283,15 @@ // When this is true, the matching_algorithm field is ignored. bool host_only = false; + enum VisitOrder { + RECENT_FIRST, + OLDEST_FIRST, + }; + + // Whether to prioritize most recent or oldest visits when `max_count` is + // reached. Will affect visit order as well. + VisitOrder visit_order = RECENT_FIRST; + // Helpers to get the effective parameters values, since a value of 0 means // "unspecified". int EffectiveMaxCount() const;
diff --git a/components/history/core/browser/visit_database.cc b/components/history/core/browser/visit_database.cc index 7ae471a..fb84c67 100644 --- a/components/history/core/browser/visit_database.cc +++ b/components/history/core/browser/visit_database.cc
@@ -356,12 +356,19 @@ VisitVector* visits) { visits->clear(); - sql::Statement statement(GetDB().GetCachedStatement( - SQL_FROM_HERE, - "SELECT" HISTORY_VISIT_ROW_FIELDS - "FROM visits " - "WHERE url=? AND visit_time >= ? AND visit_time < ? " - "ORDER BY visit_time DESC")); + sql::Statement statement; + if (options.visit_order == QueryOptions::RECENT_FIRST) { + statement.Assign(GetDB().GetCachedStatement( + SQL_FROM_HERE, "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits " + "WHERE url=? AND visit_time>=? AND visit_time<? " + "ORDER BY visit_time DESC")); + } else { + statement.Assign(GetDB().GetCachedStatement( + SQL_FROM_HERE, "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits " + "WHERE url=? AND visit_time>? AND visit_time<=? " + "ORDER BY visit_time ASC")); + } + statement.BindInt64(0, url_id); statement.BindInt64(1, options.EffectiveBeginTime()); statement.BindInt64(2, options.EffectiveEndTime()); @@ -454,12 +461,19 @@ visits->clear(); // The visit_time values can be duplicated in a redirect chain, so we sort // by id too, to ensure a consistent ordering just in case. - sql::Statement statement(GetDB().GetCachedStatement( - SQL_FROM_HERE, - "SELECT" HISTORY_VISIT_ROW_FIELDS - "FROM visits " - "WHERE visit_time >= ? AND visit_time < ? " - "ORDER BY visit_time DESC, id DESC")); + + sql::Statement statement; + if (options.visit_order == QueryOptions::RECENT_FIRST) { + statement.Assign(GetDB().GetCachedStatement( + SQL_FROM_HERE, "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits " + "WHERE visit_time>=? AND visit_time<? " + "ORDER BY visit_time DESC, id DESC")); + } else { + statement.Assign(GetDB().GetCachedStatement( + SQL_FROM_HERE, "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits " + "WHERE visit_time>? AND visit_time<=? " + "ORDER BY visit_time ASC, id DESC")); + } statement.BindInt64(0, options.EffectiveBeginTime()); statement.BindInt64(1, options.EffectiveEndTime());
diff --git a/components/history/core/browser/visit_database_unittest.cc b/components/history/core/browser/visit_database_unittest.cc index 5f58f7e..fcf7ead 100644 --- a/components/history/core/browser/visit_database_unittest.cc +++ b/components/history/core/browser/visit_database_unittest.cc
@@ -327,7 +327,7 @@ ASSERT_EQ(static_cast<size_t>(1), results.size()); EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[0])); - options = QueryOptions(); // Reset to options to default. + options = QueryOptions(); // Reset options to default. // Query for a max count and make sure we get only that number. options.max_count = 1; @@ -343,6 +343,13 @@ GetVisibleVisitsInRange(options, &results); ASSERT_EQ(static_cast<size_t>(1), results.size()); EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[1])); + + // Query oldest visits in a time range and make sure beginning is exclusive + // and ending is inclusive. + options.visit_order = QueryOptions::OLDEST_FIRST; + GetVisibleVisitsInRange(options, &results); + ASSERT_EQ(static_cast<size_t>(1), results.size()); + EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[3])); } TEST_F(VisitDatabaseTest, GetAllURLIDsForTransition) { @@ -424,6 +431,40 @@ EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[5])); EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[1])); EXPECT_TRUE(IsVisitInfoEqual(results[2], test_visit_rows[0])); + + // Now try with a `max_count` limit to get the newest 2 visits only. + options.max_count = 2; + GetVisibleVisitsForURL(url_id, options, &results); + ASSERT_EQ(static_cast<size_t>(2), results.size()); + EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[5])); + EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[1])); + + // Now try getting the oldest 2 visits and make sure they're ordered oldest + // first. + options.visit_order = QueryOptions::OLDEST_FIRST; + GetVisibleVisitsForURL(url_id, options, &results); + ASSERT_EQ(static_cast<size_t>(2), results.size()); + EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[0])); + EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[1])); + + // Query a time range and make sure beginning is inclusive and ending is + // exclusive. + options.begin_time = test_visit_rows[0].visit_time; + options.end_time = test_visit_rows[5].visit_time; + options.visit_order = QueryOptions::RECENT_FIRST; + options.max_count = 0; + GetVisibleVisitsForURL(url_id, options, &results); + ASSERT_EQ(static_cast<size_t>(2), results.size()); + EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[1])); + EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[0])); + + // Query oldest visits in a time range and make sure beginning is exclusive + // and ending is inclusive. + options.visit_order = QueryOptions::OLDEST_FIRST; + GetVisibleVisitsForURL(url_id, options, &results); + ASSERT_EQ(static_cast<size_t>(2), results.size()); + EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[1])); + EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[5])); } TEST_F(VisitDatabaseTest, GetHistoryCount) {
diff --git a/components/history_clusters/core/config.cc b/components/history_clusters/core/config.cc index b1cfe3e2..1a6ff10 100644 --- a/components/history_clusters/core/config.cc +++ b/components/history_clusters/core/config.cc
@@ -213,6 +213,9 @@ base::WhitespaceHandling::TRIM_WHITESPACE, base::SplitResult::SPLIT_WANT_NONEMPTY); hosts_to_skip_clustering_for = {hosts.begin(), hosts.end()}; + + use_continue_on_shutdown = base::FeatureList::IsEnabled( + internal::kHistoryClustersUseContinueOnShutdown); } Config::Config(const Config& other) = default;
diff --git a/components/history_clusters/core/config.h b/components/history_clusters/core/config.h index 07bffa23..6ae38c8 100644 --- a/components/history_clusters/core/config.h +++ b/components/history_clusters/core/config.h
@@ -194,6 +194,9 @@ // any cluster. base::flat_set<std::string> hosts_to_skip_clustering_for; + // True if the task runner should use trait CONTINUE_ON_SHUTDOWN. + bool use_continue_on_shutdown = true; + Config(); Config(const Config& other); ~Config();
diff --git a/components/history_clusters/core/features.cc b/components/history_clusters/core/features.cc index 44e9130..cd19bda 100644 --- a/components/history_clusters/core/features.cc +++ b/components/history_clusters/core/features.cc
@@ -45,6 +45,9 @@ const base::Feature kHistoryClustersInternalsPage{ "HistoryClustersInternalsPage", base::FEATURE_DISABLED_BY_DEFAULT}; +const base::Feature kHistoryClustersUseContinueOnShutdown{ + "HistoryClustersUseContinueOnShutdown", base::FEATURE_ENABLED_BY_DEFAULT}; + } // namespace internal } // namespace history_clusters
diff --git a/components/history_clusters/core/features.h b/components/history_clusters/core/features.h index 8ef3b998..c84cf422c 100644 --- a/components/history_clusters/core/features.h +++ b/components/history_clusters/core/features.h
@@ -43,6 +43,9 @@ // Enables the history clusters internals page. extern const base::Feature kHistoryClustersInternalsPage; +// Enables use of task runner with trait CONTINUE_ON_SHUTDOWN. +extern const base::Feature kHistoryClustersUseContinueOnShutdown; + } // namespace internal } // namespace history_clusters
diff --git a/components/history_clusters/core/on_device_clustering_backend.cc b/components/history_clusters/core/on_device_clustering_backend.cc index 9c25f81..bc8ac3ab 100644 --- a/components/history_clusters/core/on_device_clustering_backend.cc +++ b/components/history_clusters/core/on_device_clustering_backend.cc
@@ -90,12 +90,26 @@ : template_url_service_(template_url_service), entity_metadata_provider_(entity_metadata_provider), engagement_score_provider_(engagement_score_provider), + user_visible_task_traits_( + {base::MayBlock(), base::TaskPriority::USER_VISIBLE}), + continue_on_shutdown_user_visible_task_traits_( + {base::MayBlock(), base::TaskPriority::USER_VISIBLE, + base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}), user_visible_priority_background_task_runner_( base::ThreadPool::CreateSequencedTaskRunner( - {base::MayBlock(), base::TaskPriority::USER_VISIBLE})), + GetConfig().use_continue_on_shutdown + ? continue_on_shutdown_user_visible_task_traits_ + : user_visible_task_traits_)), + best_effort_task_traits_( + {base::MayBlock(), base::TaskPriority::BEST_EFFORT}), + continue_on_shutdown_best_effort_task_traits_( + {base::MayBlock(), base::TaskPriority::BEST_EFFORT, + base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}), best_effort_priority_background_task_runner_( base::ThreadPool::CreateSequencedTaskRunner( - {base::MayBlock(), base::TaskPriority::BEST_EFFORT})), + GetConfig().use_continue_on_shutdown + ? continue_on_shutdown_best_effort_task_traits_ + : best_effort_task_traits_)), engagement_score_cache_last_refresh_timestamp_(base::TimeTicks::Now()), engagement_score_cache_( GetFieldTrialParamByFeatureAsInt(features::kUseEngagementScoreCache,
diff --git a/components/history_clusters/core/on_device_clustering_backend.h b/components/history_clusters/core/on_device_clustering_backend.h index acbe1b5..4882967 100644 --- a/components/history_clusters/core/on_device_clustering_backend.h +++ b/components/history_clusters/core/on_device_clustering_backend.h
@@ -13,6 +13,7 @@ #include "base/memory/weak_ptr.h" #include "base/sequence_checker.h" #include "base/task/sequenced_task_runner.h" +#include "base/task/task_traits.h" #include "components/history_clusters/core/cluster_finalizer.h" #include "components/history_clusters/core/cluster_processor.h" #include "components/history_clusters/core/clusterer.h" @@ -102,8 +103,12 @@ // The task runners to run clustering passes on. // |user_visible_priority_background_task_runner_| should be used iff // clustering is blocking content on a page that user is actively looking at. + const base::TaskTraits user_visible_task_traits_; + const base::TaskTraits continue_on_shutdown_user_visible_task_traits_; scoped_refptr<base::SequencedTaskRunner> user_visible_priority_background_task_runner_; + const base::TaskTraits best_effort_task_traits_; + const base::TaskTraits continue_on_shutdown_best_effort_task_traits_; scoped_refptr<base::SequencedTaskRunner> best_effort_priority_background_task_runner_;
diff --git a/components/media_router/common/media_route.h b/components/media_router/common/media_route.h index 3f3f0ac..4a9a166 100644 --- a/components/media_router/common/media_route.h +++ b/components/media_router/common/media_route.h
@@ -143,6 +143,7 @@ // |true| if the presentation associated with this route is a local // presentation. + // TODO(crbug.com/1309770): Remove |is_local_presentation_|. bool is_local_presentation_ = false; // |true| if the route is created by the MRP but is waiting for receivers'
diff --git a/components/media_router/common/pref_names.cc b/components/media_router/common/pref_names.cc index 87a4e34..936855f 100644 --- a/components/media_router/common/pref_names.cc +++ b/components/media_router/common/pref_names.cc
@@ -13,6 +13,11 @@ // A list of website origins on which the user has chosen to use tab mirroring. const char kMediaRouterTabMirroringSources[] = "media_router.tab_mirroring_sources"; +// Whether or not the user has enabled to show Cast sessions started by +// other devices on the same network. This change only affects the Zenith +// dialog. Defaults to true. +const char kMediaRouterShowCastSessionsStartedByOtherDevices[] = + "media_router.show_cast_sessions_started_by_other_devices.enabled"; } // namespace prefs } // namespace media_router
diff --git a/components/media_router/common/pref_names.h b/components/media_router/common/pref_names.h index dc60725..0b776dd 100644 --- a/components/media_router/common/pref_names.h +++ b/components/media_router/common/pref_names.h
@@ -10,6 +10,7 @@ extern const char kMediaRouterMediaRemotingEnabled[]; extern const char kMediaRouterTabMirroringSources[]; +extern const char kMediaRouterShowCastSessionsStartedByOtherDevices[]; } // namespace prefs } // namespace media_router
diff --git a/components/optimization_guide/content/browser/page_content_annotations_model_manager.cc b/components/optimization_guide/content/browser/page_content_annotations_model_manager.cc index 6637e99..7568a9d 100644 --- a/components/optimization_guide/content/browser/page_content_annotations_model_manager.cc +++ b/components/optimization_guide/content/browser/page_content_annotations_model_manager.cc
@@ -4,6 +4,7 @@ #include "components/optimization_guide/content/browser/page_content_annotations_model_manager.h" +#include "base/feature_list.h" #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros_local.h" #include "base/strings/string_number_conversions.h" @@ -123,9 +124,22 @@ "PageEntitiesModelRequested", true); #if BUILDFLAG(BUILD_WITH_INTERNAL_OPTIMIZATION_GUIDE) + + base::TaskTraits task_traits = {base::MayBlock(), + base::TaskPriority::BEST_EFFORT}; + if (base::FeatureList::IsEnabled( + features:: + kOptimizationGuideUseContinueOnShutdownForPageContentAnnotations)) { + task_traits = {base::MayBlock(), base::TaskPriority::BEST_EFFORT, + base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}; + } + + scoped_refptr<base::SequencedTaskRunner> background_task_runner = + base::ThreadPool::CreateSequencedTaskRunner(task_traits); + page_entities_model_executor_ = std::make_unique<PageEntitiesModelExecutorImpl>( - optimization_guide_model_provider); + optimization_guide_model_provider, background_task_runner); #endif } @@ -212,10 +226,18 @@ proto::PAGE_TOPICS_SUPPORTED_OUTPUT_CATEGORIES); page_topics_model_metadata.SerializeToString(model_metadata.mutable_value()); + base::TaskTraits task_traits = {base::MayBlock(), + base::TaskPriority::BEST_EFFORT}; + if (base::FeatureList::IsEnabled( + features:: + kOptimizationGuideUseContinueOnShutdownForPageContentAnnotations)) { + task_traits = {base::MayBlock(), base::TaskPriority::BEST_EFFORT, + base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}; + } + page_topics_model_handler_ = std::make_unique<BertModelHandler>( optimization_guide_model_provider, - base::ThreadPool::CreateSequencedTaskRunner( - {base::MayBlock(), base::TaskPriority::BEST_EFFORT}), + base::ThreadPool::CreateSequencedTaskRunner(task_traits), proto::OPTIMIZATION_TARGET_PAGE_TOPICS, model_metadata); }
diff --git a/components/optimization_guide/core/optimization_guide_features.cc b/components/optimization_guide/core/optimization_guide_features.cc index b42818e..37ba4fc 100644 --- a/components/optimization_guide/core/optimization_guide_features.cc +++ b/components/optimization_guide/core/optimization_guide_features.cc
@@ -151,6 +151,11 @@ const base::Feature kPreventLongRunningPredictionModels{ "PreventLongRunningPredictionModels", base::FEATURE_DISABLED_BY_DEFAULT}; +const base::Feature + kOptimizationGuideUseContinueOnShutdownForPageContentAnnotations{ + "OptimizationGuideUseContinueOnShutdownForPageContentAnnotations", + base::FEATURE_ENABLED_BY_DEFAULT}; + // The default value here is a bit of a guess. // TODO(crbug/1163244): This should be tuned once metrics are available. base::TimeDelta PageTextExtractionOutstandingRequestsGracePeriod() {
diff --git a/components/optimization_guide/core/optimization_guide_features.h b/components/optimization_guide/core/optimization_guide_features.h index f90021a1..d17d191 100644 --- a/components/optimization_guide/core/optimization_guide_features.h +++ b/components/optimization_guide/core/optimization_guide_features.h
@@ -43,6 +43,11 @@ extern const base::Feature kBatchAnnotationsValidation; extern const base::Feature kPreventLongRunningPredictionModels; +// Enables use of task runner with trait CONTINUE_ON_SHUTDOWN for page content +// annotations on-device models. +extern const base::Feature + kOptimizationGuideUseContinueOnShutdownForPageContentAnnotations; + // The grace period duration for how long to give outstanding page text dump // requests to respond after DidFinishLoad. base::TimeDelta PageTextExtractionOutstandingRequestsGracePeriod();
diff --git a/components/optimization_guide/core/page_entities_model_executor_impl.h b/components/optimization_guide/core/page_entities_model_executor_impl.h index d39944fc2..1f12d294 100644 --- a/components/optimization_guide/core/page_entities_model_executor_impl.h +++ b/components/optimization_guide/core/page_entities_model_executor_impl.h
@@ -75,9 +75,7 @@ public: PageEntitiesModelExecutorImpl( OptimizationGuideModelProvider* model_provider, - scoped_refptr<base::SequencedTaskRunner> background_task_runner = - base::ThreadPool::CreateSequencedTaskRunner( - {base::MayBlock(), base::TaskPriority::BEST_EFFORT})); + scoped_refptr<base::SequencedTaskRunner> background_task_runner); ~PageEntitiesModelExecutorImpl() override; PageEntitiesModelExecutorImpl(const PageEntitiesModelExecutorImpl&) = delete; PageEntitiesModelExecutorImpl& operator=(
diff --git a/components/optimization_guide/core/page_entities_model_executor_impl_unittest.cc b/components/optimization_guide/core/page_entities_model_executor_impl_unittest.cc index e195445..96f8ca80 100644 --- a/components/optimization_guide/core/page_entities_model_executor_impl_unittest.cc +++ b/components/optimization_guide/core/page_entities_model_executor_impl_unittest.cc
@@ -64,8 +64,11 @@ public: void SetUp() override { model_observer_tracker_ = std::make_unique<ModelObserverTracker>(); + model_executor_ = std::make_unique<PageEntitiesModelExecutorImpl>( - model_observer_tracker_.get()); + model_observer_tracker_.get(), + base::ThreadPool::CreateSequencedTaskRunner( + {base::MayBlock(), base::TaskPriority::BEST_EFFORT})); // Wait for PageEntitiesModelExecutor to set everything up. task_environment_.RunUntilIdle();
diff --git a/components/resources/autofill_scaled_resources.grdp b/components/resources/autofill_scaled_resources.grdp index 7706b25..5661022 100644 --- a/components/resources/autofill_scaled_resources.grdp +++ b/components/resources/autofill_scaled_resources.grdp
@@ -15,11 +15,9 @@ <structure type="chrome_scaled_image" name="IDR_AUTOFILL_VIRTUAL_CARD_ENROLL_DIALOG_DARK" file="autofill/virtual_card_enroll_dark.png" /> <if expr="_google_chrome"> <then> - <structure type="chrome_scaled_image" name="IDR_AUTOFILL_GOOGLE_ISSUED_CARD" file="google_chrome/autofill/googlepay_plex.png" /> <structure type="chrome_scaled_image" name="IDR_AUTOFILL_GOOGLE_PAY" file="google_chrome/autofill/googlepay.png" /> </then> <else> - <structure type="chrome_scaled_image" name="IDR_AUTOFILL_GOOGLE_ISSUED_CARD" file="autofill/cc-generic.png" /> <structure type="chrome_scaled_image" name="IDR_AUTOFILL_GOOGLE_PAY" file="autofill/cc-generic.png" /> </else> </if>
diff --git a/components/segmentation_platform/components_unittests.filter b/components/segmentation_platform/components_unittests.filter index 31e8d1d..3addc0e 100644 --- a/components/segmentation_platform/components_unittests.filter +++ b/components/segmentation_platform/components_unittests.filter
@@ -32,6 +32,7 @@ TrainingDataCollectorImplTest.* UkmConfigTest.* UkmDatabaseBackendTest.* +UkmDataManagerImplTest.* UkmMetricsTableTest.* UkmObserverTest.* UkmUrlTableTest.*
diff --git a/components/segmentation_platform/internal/BUILD.gn b/components/segmentation_platform/internal/BUILD.gn index 60ee5cb..4ded4ec 100644 --- a/components/segmentation_platform/internal/BUILD.gn +++ b/components/segmentation_platform/internal/BUILD.gn
@@ -221,6 +221,8 @@ "mock_ukm_data_manager.h", "scheduler/model_execution_scheduler_unittest.cc", "segmentation_platform_service_impl_unittest.cc", + "segmentation_platform_service_test_base.cc", + "segmentation_platform_service_test_base.h", "segmentation_ukm_helper_unittest.cc", "selection/segment_result_provider_unittest.cc", "selection/segment_score_provider_unittest.cc", @@ -244,6 +246,7 @@ "//base", "//base/test:test_support", "//components/history/core/browser:browser", + "//components/history/core/test", "//components/leveldb_proto:test_support", "//components/optimization_guide/core", "//components/optimization_guide/core:test_support", @@ -264,10 +267,13 @@ if (build_with_tflite_lib) { # IMPORTANT NOTE: When adding new tests, also remember to update the list of # tests in //components/segmentation_platform/components_unittests.filter + # TODO(ssid): Move all these tests out of tflite check once + # model_execution_manager is not tflite dependent. sources += [ "execution/model_execution_manager_impl_unittest.cc", "execution/optimization_guide/optimization_guide_segmentation_model_provider_unittest.cc", "execution/optimization_guide/segmentation_model_executor_unittest.cc", + "ukm_data_manager_impl_unittest.cc", ] deps += [ ":optimization_guide_segmentation_handler",
diff --git a/components/segmentation_platform/internal/database/database_maintenance_impl.cc b/components/segmentation_platform/internal/database/database_maintenance_impl.cc index c404786..c81318c4 100644 --- a/components/segmentation_platform/internal/database/database_maintenance_impl.cc +++ b/components/segmentation_platform/internal/database/database_maintenance_impl.cc
@@ -40,10 +40,10 @@ namespace { std::set<SignalIdentifier> CollectAllSignalIdentifiers( - const SegmentInfoDatabase::SegmentInfoList& segment_infos) { + const DefaultModelManager::SegmentInfoList& segment_infos) { std::set<SignalIdentifier> signal_ids; - for (const auto& pair : segment_infos) { - const proto::SegmentInfo& segment_info = pair.second; + for (const auto& info : segment_infos) { + const proto::SegmentInfo& segment_info = info->segment_info; const auto& metadata = segment_info.model_metadata(); auto features = metadata_utils::GetAllUmaFeatures(metadata, /*include_outputs=*/true); @@ -115,9 +115,9 @@ } void DatabaseMaintenanceImpl::OnSegmentInfoCallback( - std::unique_ptr<SegmentInfoDatabase::SegmentInfoList> segment_infos) { + DefaultModelManager::SegmentInfoList segment_infos) { std::set<SignalIdentifier> signal_ids = - CollectAllSignalIdentifiers(*segment_infos); + CollectAllSignalIdentifiers(segment_infos); stats::RecordMaintenanceSignalIdentifierCount(signal_ids.size()); auto all_tasks = GetAllTasks(signal_ids);
diff --git a/components/segmentation_platform/internal/database/database_maintenance_impl.h b/components/segmentation_platform/internal/database/database_maintenance_impl.h index dbba9c72..ea70a01 100644 --- a/components/segmentation_platform/internal/database/database_maintenance_impl.h +++ b/components/segmentation_platform/internal/database/database_maintenance_impl.h
@@ -16,7 +16,7 @@ #include "base/memory/weak_ptr.h" #include "components/optimization_guide/proto/models.pb.h" #include "components/segmentation_platform/internal/database/database_maintenance.h" -#include "components/segmentation_platform/internal/database/segment_info_database.h" +#include "components/segmentation_platform/internal/execution/default_model_manager.h" #include "components/segmentation_platform/internal/proto/types.pb.h" namespace base { @@ -28,6 +28,7 @@ namespace segmentation_platform { class DefaultModelManager; +class SegmentInfoDatabase; class SignalDatabase; class SignalStorageConfig; @@ -58,7 +59,7 @@ // All tasks currently need information about various segments, so this is // the callback after the initial database lookup for this data. void OnSegmentInfoCallback( - std::unique_ptr<SegmentInfoDatabase::SegmentInfoList> segment_infos); + DefaultModelManager::SegmentInfoList segment_infos); // Returns an ordered vector of all the tasks we are supposed to perform. // These are unfinished and also need to be linked to the next task to be
diff --git a/components/segmentation_platform/internal/database/database_maintenance_impl_unittest.cc b/components/segmentation_platform/internal/database/database_maintenance_impl_unittest.cc index 2b37501..8c7bc74 100644 --- a/components/segmentation_platform/internal/database/database_maintenance_impl_unittest.cc +++ b/components/segmentation_platform/internal/database/database_maintenance_impl_unittest.cc
@@ -71,18 +71,30 @@ const std::vector<OptimizationTarget>& segment_ids, MultipleSegmentInfoCallback callback) override { base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, - base::BindOnce( - std::move(callback), - std::make_unique<DefaultModelManager::SegmentInfoList>())); + FROM_HERE, base::BindOnce(std::move(callback), + DefaultModelManager::SegmentInfoList())); } void GetAllSegmentInfoFromBothModels( const std::vector<OptimizationTarget>& segment_ids, SegmentInfoDatabase* segment_database, MultipleSegmentInfoCallback callback) override { - segment_database->GetSegmentInfoForSegments(segment_ids, - std::move(callback)); + segment_database->GetSegmentInfoForSegments( + segment_ids, + base::BindOnce( + [](DefaultModelManager::MultipleSegmentInfoCallback callback, + std::unique_ptr<SegmentInfoDatabase::SegmentInfoList> db_list) { + DefaultModelManager::SegmentInfoList list; + for (auto& pair : *db_list) { + list.push_back(std::make_unique< + DefaultModelManager::SegmentInfoWrapper>()); + list.back()->segment_source = + DefaultModelManager::SegmentSource::DATABASE; + list.back()->segment_info.Swap(&pair.second); + } + std::move(callback).Run(std::move(list)); + }, + std::move(callback))); } };
diff --git a/components/segmentation_platform/internal/database/metadata_utils.cc b/components/segmentation_platform/internal/database/metadata_utils.cc index 0f68c5a..7cd8209 100644 --- a/components/segmentation_platform/internal/database/metadata_utils.cc +++ b/components/segmentation_platform/internal/database/metadata_utils.cc
@@ -177,6 +177,10 @@ auto feature_result = ValidateMetadataCustomInput(feature.custom_input()); if (feature_result != ValidationResult::kValidationSuccess) return feature_result; + } else if (feature.has_sql_feature()) { + // TODO(haileywang): Fix sql validation with other requirements. + if (feature.sql_feature().sql().empty()) + return ValidationResult::kFeatureListInvalid; } else { return ValidationResult::kFeatureListInvalid; }
diff --git a/components/segmentation_platform/internal/execution/default_model_manager.cc b/components/segmentation_platform/internal/execution/default_model_manager.cc index 20ab094d..8c10c5d 100644 --- a/components/segmentation_platform/internal/execution/default_model_manager.cc +++ b/components/segmentation_platform/internal/execution/default_model_manager.cc
@@ -9,6 +9,9 @@ namespace segmentation_platform { +DefaultModelManager::SegmentInfoWrapper::SegmentInfoWrapper() = default; +DefaultModelManager::SegmentInfoWrapper::~SegmentInfoWrapper() = default; + DefaultModelManager::DefaultModelManager( ModelProviderFactory* model_provider_factory, const std::vector<OptimizationTarget>& segment_ids) @@ -66,7 +69,7 @@ if (!default_provider) { // If there are no more default providers, return the result so far. base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(std::move(callback), std::move(result))); + FROM_HERE, base::BindOnce(std::move(callback), std::move(*result))); return; } @@ -82,11 +85,12 @@ OptimizationTarget segment_id, proto::SegmentationModelMetadata metadata, int64_t model_version) { - proto::SegmentInfo segment_info; - segment_info.set_segment_id(segment_id); - segment_info.mutable_model_metadata()->CopyFrom(metadata); - segment_info.set_model_version(model_version); - result->push_back(std::make_pair(segment_id, segment_info)); + auto info = std::make_unique<SegmentInfoWrapper>(); + info->segment_source = DefaultModelManager::SegmentSource::DEFAULT_MODEL; + info->segment_info.set_segment_id(segment_id); + info->segment_info.mutable_model_metadata()->CopyFrom(metadata); + info->segment_info.set_model_version(model_version); + result->push_back(std::move(info)); GetNextSegmentInfoFromDefaultModel( std::move(result), std::move(remaining_segment_ids), std::move(callback)); @@ -117,12 +121,19 @@ void DefaultModelManager::OnGetAllSegmentInfoFromDefaultModel( MultipleSegmentInfoCallback callback, std::unique_ptr<SegmentInfoDatabase::SegmentInfoList> segment_infos_from_db, - std::unique_ptr<SegmentInfoDatabase::SegmentInfoList> - segment_infos_from_default_model) { - std::unique_ptr<SegmentInfoDatabase::SegmentInfoList> merged_results = - std::move(segment_infos_from_db); - for (const auto& segment_info : *segment_infos_from_default_model) - merged_results->push_back(std::move(segment_info)); + SegmentInfoList segment_infos_from_default_model) { + SegmentInfoList merged_results; + if (segment_infos_from_db) { + for (auto it : *segment_infos_from_db) { + merged_results.push_back(std::make_unique<SegmentInfoWrapper>()); + merged_results.back()->segment_source = SegmentSource::DATABASE; + merged_results.back()->segment_info.Swap(&it.second); + } + } + merged_results.insert( + merged_results.end(), + std::make_move_iterator(segment_infos_from_default_model.begin()), + std::make_move_iterator(segment_infos_from_default_model.end())); std::move(callback).Run(std::move(merged_results)); }
diff --git a/components/segmentation_platform/internal/execution/default_model_manager.h b/components/segmentation_platform/internal/execution/default_model_manager.h index c66a0ad..911a31cc 100644 --- a/components/segmentation_platform/internal/execution/default_model_manager.h +++ b/components/segmentation_platform/internal/execution/default_model_manager.h
@@ -14,6 +14,7 @@ #include "base/callback.h" #include "base/containers/flat_map.h" #include "base/logging.h" +#include "components/segmentation_platform/internal/database/segment_info_database.h" #include "components/segmentation_platform/internal/proto/model_metadata.pb.h" #include "components/segmentation_platform/internal/proto/model_prediction.pb.h" #include "components/segmentation_platform/public/model_provider.h" @@ -39,10 +40,21 @@ // Callback for returning a list of segment infos associated with IDs. // The same segment ID can be repeated multiple times. - using SegmentInfoList = - std::vector<std::pair<OptimizationTarget, proto::SegmentInfo>>; - using MultipleSegmentInfoCallback = - base::OnceCallback<void(std::unique_ptr<SegmentInfoList>)>; + enum class SegmentSource { + DATABASE, + DEFAULT_MODEL, + }; + struct SegmentInfoWrapper { + SegmentInfoWrapper(); + ~SegmentInfoWrapper(); + SegmentInfoWrapper(const SegmentInfoWrapper&) = delete; + SegmentInfoWrapper& operator=(const SegmentInfoWrapper&) = delete; + + SegmentSource segment_source; + proto::SegmentInfo segment_info; + }; + using SegmentInfoList = std::vector<std::unique_ptr<SegmentInfoWrapper>>; + using MultipleSegmentInfoCallback = base::OnceCallback<void(SegmentInfoList)>; // Utility function to get the segment info from both the database and the // default model for a given set of segment IDs. The result can contain @@ -80,12 +92,13 @@ void OnGetAllSegmentInfoFromDatabase( const std::vector<OptimizationTarget>& segment_ids, MultipleSegmentInfoCallback callback, - std::unique_ptr<SegmentInfoList> segment_infos); + std::unique_ptr<SegmentInfoDatabase::SegmentInfoList> segment_infos); void OnGetAllSegmentInfoFromDefaultModel( MultipleSegmentInfoCallback callback, - std::unique_ptr<SegmentInfoList> segment_infos_from_db, - std::unique_ptr<SegmentInfoList> segment_infos_from_default_model); + std::unique_ptr<SegmentInfoDatabase::SegmentInfoList> + segment_infos_from_db, + SegmentInfoList segment_infos_from_default_model); // Default model providers. std::map<OptimizationTarget, std::unique_ptr<ModelProvider>>
diff --git a/components/segmentation_platform/internal/execution/default_model_manager_unittest.cc b/components/segmentation_platform/internal/execution/default_model_manager_unittest.cc index f54d09e5..c7979cc4 100644 --- a/components/segmentation_platform/internal/execution/default_model_manager_unittest.cc +++ b/components/segmentation_platform/internal/execution/default_model_manager_unittest.cc
@@ -34,13 +34,12 @@ .second; } - void OnGetAllSegments( - std::unique_ptr<SegmentInfoDatabase::SegmentInfoList> entries) { + void OnGetAllSegments(DefaultModelManager::SegmentInfoList entries) { get_all_segment_result_.swap(entries); } - const SegmentInfoDatabase::SegmentInfoList& get_all_segment_result() const { - return *get_all_segment_result_; + const DefaultModelManager::SegmentInfoList& get_all_segment_result() const { + return get_all_segment_result_; } base::test::TaskEnvironment task_environment_; @@ -48,7 +47,7 @@ TestModelProviderFactory::Data model_provider_data_; TestModelProviderFactory model_provider_factory_; std::unique_ptr<DefaultModelManager> default_model_manager_; - std::unique_ptr<SegmentInfoDatabase::SegmentInfoList> get_all_segment_result_; + DefaultModelManager::SegmentInfoList get_all_segment_result_; base::WeakPtrFactory<DefaultModelManagerTest> weak_ptr_factory_{this}; }; @@ -101,15 +100,15 @@ // Verify that model exists from both sources in order: segment_1 from db, // segment_1 from model, segment_2 from model. EXPECT_EQ(3u, get_all_segment_result().size()); - EXPECT_EQ(segment_1, get_all_segment_result()[0].first); + EXPECT_EQ(segment_1, get_all_segment_result()[0]->segment_info.segment_id()); EXPECT_EQ(model_version_db, - get_all_segment_result()[0].second.model_version()); - EXPECT_EQ(segment_1, get_all_segment_result()[1].first); + get_all_segment_result()[0]->segment_info.model_version()); + EXPECT_EQ(segment_1, get_all_segment_result()[1]->segment_info.segment_id()); EXPECT_EQ(model_version_default, - get_all_segment_result()[1].second.model_version()); - EXPECT_EQ(segment_2, get_all_segment_result()[2].first); + get_all_segment_result()[1]->segment_info.model_version()); + EXPECT_EQ(segment_2, get_all_segment_result()[2]->segment_info.segment_id()); EXPECT_EQ(model_version_default, - get_all_segment_result()[2].second.model_version()); + get_all_segment_result()[2]->segment_info.model_version()); // Query again, this time with a segment ID that doesn't exist in either // sources. @@ -130,7 +129,7 @@ weak_ptr_factory_.GetWeakPtr())); task_environment_.RunUntilIdle(); EXPECT_EQ(1u, get_all_segment_result().size()); - EXPECT_EQ(segment_2, get_all_segment_result()[0].first); + EXPECT_EQ(segment_2, get_all_segment_result()[0]->segment_info.segment_id()); // Query for a model only available in the database. default_model_manager_->GetAllSegmentInfoFromBothModels( @@ -139,7 +138,7 @@ weak_ptr_factory_.GetWeakPtr())); task_environment_.RunUntilIdle(); EXPECT_EQ(1u, get_all_segment_result().size()); - EXPECT_EQ(segment_3, get_all_segment_result()[0].first); + EXPECT_EQ(segment_3, get_all_segment_result()[0]->segment_info.segment_id()); } } // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/execution/model_execution_manager_impl.h b/components/segmentation_platform/internal/execution/model_execution_manager_impl.h index 818a2fe..65b1ac1d 100644 --- a/components/segmentation_platform/internal/execution/model_execution_manager_impl.h +++ b/components/segmentation_platform/internal/execution/model_execution_manager_impl.h
@@ -73,6 +73,7 @@ private: friend class SegmentationPlatformServiceImplTest; + friend class TestServicesForPlatform; struct ExecutionState; struct ModelExecutionTraceEvent;
diff --git a/components/segmentation_platform/internal/segmentation_platform_service_impl.h b/components/segmentation_platform/internal/segmentation_platform_service_impl.h index c258b26..2ad0c7e6 100644 --- a/components/segmentation_platform/internal/segmentation_platform_service_impl.h +++ b/components/segmentation_platform/internal/segmentation_platform_service_impl.h
@@ -129,6 +129,7 @@ private: friend class SegmentationPlatformServiceImplTest; + friend class TestServicesForPlatform; void OnSegmentInfoDatabaseInitialized(bool success); void OnSignalDatabaseInitialized(bool success);
diff --git a/components/segmentation_platform/internal/segmentation_platform_service_impl_unittest.cc b/components/segmentation_platform/internal/segmentation_platform_service_impl_unittest.cc index 5ee42f6ab..ae8e1a7 100644 --- a/components/segmentation_platform/internal/segmentation_platform_service_impl_unittest.cc +++ b/components/segmentation_platform/internal/segmentation_platform_service_impl_unittest.cc
@@ -9,40 +9,21 @@ #include "base/bind.h" #include "base/files/file_path.h" -#include "base/memory/raw_ptr.h" #include "base/metrics/metrics_hashes.h" #include "base/metrics/user_metrics.h" #include "base/run_loop.h" #include "base/test/metrics/histogram_tester.h" -#include "base/test/simple_test_clock.h" #include "base/test/task_environment.h" -#include "base/test/test_simple_task_runner.h" -#include "components/leveldb_proto/public/proto_database_provider.h" -#include "components/leveldb_proto/public/shared_proto_database_client_list.h" -#include "components/leveldb_proto/testing/fake_db.h" #include "components/optimization_guide/machine_learning_tflite_buildflags.h" -#include "components/prefs/pref_registry_simple.h" #include "components/prefs/scoped_user_pref_update.h" -#include "components/prefs/testing_pref_service.h" #include "components/segmentation_platform/internal/constants.h" -#include "components/segmentation_platform/internal/database/segment_info_database.h" -#include "components/segmentation_platform/internal/database/signal_database_impl.h" -#include "components/segmentation_platform/internal/database/signal_storage_config.h" #include "components/segmentation_platform/internal/dummy_ukm_data_manager.h" -#include "components/segmentation_platform/internal/execution/feature_aggregator_impl.h" -#include "components/segmentation_platform/internal/execution/mock_model_provider.h" #include "components/segmentation_platform/internal/proto/model_metadata.pb.h" -#include "components/segmentation_platform/internal/proto/model_prediction.pb.h" -#include "components/segmentation_platform/internal/proto/signal.pb.h" -#include "components/segmentation_platform/internal/proto/signal_storage_config.pb.h" -#include "components/segmentation_platform/internal/scheduler/model_execution_scheduler_impl.h" -#include "components/segmentation_platform/internal/selection/segment_selector_impl.h" +#include "components/segmentation_platform/internal/segmentation_platform_service_test_base.h" #include "components/segmentation_platform/internal/selection/segmentation_result_prefs.h" -#include "components/segmentation_platform/internal/signals/histogram_signal_handler.h" -#include "components/segmentation_platform/internal/signals/signal_filter_processor.h" -#include "components/segmentation_platform/internal/signals/user_action_signal_handler.h" #include "components/segmentation_platform/internal/ukm_data_manager_impl.h" #include "components/segmentation_platform/public/config.h" +#include "components/segmentation_platform/public/segment_selection_result.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -52,52 +33,10 @@ namespace segmentation_platform { namespace { -constexpr char kTestSegmentationKey1[] = "test_key1"; -constexpr char kTestSegmentationKey2[] = "test_key2"; -constexpr char kTestSegmentationKey3[] = "test_key3"; - #if BUILDFLAG(BUILD_WITH_TFLITE_LIB) const int64_t kModelVersion = 123; #endif // BUILDFLAG(BUILD_WITH_TFLITE_LIB -std::vector<std::unique_ptr<Config>> CreateTestConfigs() { - std::vector<std::unique_ptr<Config>> configs; - { - std::unique_ptr<Config> config = std::make_unique<Config>(); - config->segmentation_key = kTestSegmentationKey1; - config->segment_selection_ttl = base::Days(28); - config->segment_ids = { - OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_NEW_TAB, - OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_SHARE}; - configs.push_back(std::move(config)); - } - { - std::unique_ptr<Config> config = std::make_unique<Config>(); - config->segmentation_key = kTestSegmentationKey2; - config->segment_selection_ttl = base::Days(10); - config->segment_ids = { - OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_SHARE, - OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_VOICE}; - configs.push_back(std::move(config)); - } - { - std::unique_ptr<Config> config = std::make_unique<Config>(); - config->segmentation_key = kTestSegmentationKey3; - config->segment_selection_ttl = base::Days(14); - config->segment_ids = { - OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_NEW_TAB}; - configs.push_back(std::move(config)); - } - { - // Empty config. - std::unique_ptr<Config> config = std::make_unique<Config>(); - config->segmentation_key = "test_key"; - configs.push_back(std::move(config)); - } - - return configs; -} - // A mock of the ServiceProxy::Observer. class MockServiceProxyObserver : public ServiceProxy::Observer { public: @@ -113,73 +52,32 @@ } // namespace -class SegmentationPlatformServiceImplTest : public testing::Test { +class SegmentationPlatformServiceImplTest + : public testing::Test, + public SegmentationPlatformServiceTestBase { public: explicit SegmentationPlatformServiceImplTest( - std::unique_ptr<UkmDataManager> ukm_data_manager = nullptr) { - if (ukm_data_manager) { - ukm_data_manager_ = std::move(ukm_data_manager); - } else { - ukm_data_manager_ = std::make_unique<UkmDataManagerImpl>(); - } - } + std::unique_ptr<UkmDataManager> ukm_data_manager = nullptr) + : ukm_data_manager_(ukm_data_manager + ? std::move(ukm_data_manager) + : std::make_unique<UkmDataManagerImpl>()) {} ~SegmentationPlatformServiceImplTest() override = default; void SetUp() override { - task_runner_ = base::MakeRefCounted<base::TestSimpleTaskRunner>(); base::SetRecordActionTaskRunner( task_environment_.GetMainThreadTaskRunner()); - auto segment_db = - std::make_unique<leveldb_proto::test::FakeDB<proto::SegmentInfo>>( - &segment_db_entries_); - auto signal_db = - std::make_unique<leveldb_proto::test::FakeDB<proto::SignalData>>( - &signal_db_entries_); - auto segment_storage_config_db = std::make_unique< - leveldb_proto::test::FakeDB<proto::SignalStorageConfigs>>( - &segment_storage_config_db_entries_); - segment_db_ = segment_db.get(); - signal_db_ = signal_db.get(); - segment_storage_config_db_ = segment_storage_config_db.get(); - - SegmentationPlatformService::RegisterProfilePrefs(pref_service_.registry()); - SetUpPrefs(); - - std::vector<std::unique_ptr<Config>> configs = CreateTestConfigs(); // TODO(ssid): use mock a history service here. - segmentation_platform_service_impl_ = - std::make_unique<SegmentationPlatformServiceImpl>( - std::move(segment_db), std::move(signal_db), - std::move(segment_storage_config_db), ukm_data_manager_.get(), - std::make_unique<TestModelProviderFactory>(&model_provider_data_), - &pref_service_, /*history_service=*/nullptr, task_runner_, - &test_clock_, std::move(configs)); + SegmentationPlatformServiceTestBase::InitPlatform( + ukm_data_manager_.get(), /*history_service=*/nullptr); + segmentation_platform_service_impl_->GetServiceProxy()->AddObserver( &observer_); } void TearDown() override { - segmentation_platform_service_impl_.reset(); - // Allow for the SegmentationModelExecutor owned by SegmentationModelHandler - // to be destroyed. - task_runner_->RunUntilIdle(); - } - - virtual void SetUpPrefs() { - DictionaryPrefUpdate update(&pref_service_, kSegmentationResultPref); - base::Value* dictionary = update.Get(); - - base::Value segmentation_result(base::Value::Type::DICTIONARY); - segmentation_result.SetIntKey( - "segment_id", - OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_SHARE); - dictionary->SetKey(kTestSegmentationKey1, std::move(segmentation_result)); - } - - virtual std::vector<std::unique_ptr<Config>> CreateConfigs() { - return CreateTestConfigs(); + SegmentationPlatformServiceTestBase::DestroyPlatform(); } void OnGetSelectedSegment(base::RepeatingClosure closure, @@ -343,22 +241,8 @@ base::test::TaskEnvironment task_environment_{ base::test::TaskEnvironment::TimeSource::MOCK_TIME}; - scoped_refptr<base::TestSimpleTaskRunner> task_runner_; - std::map<std::string, proto::SegmentInfo> segment_db_entries_; - std::map<std::string, proto::SignalData> signal_db_entries_; - std::map<std::string, proto::SignalStorageConfigs> - segment_storage_config_db_entries_; - raw_ptr<leveldb_proto::test::FakeDB<proto::SegmentInfo>> segment_db_; - raw_ptr<leveldb_proto::test::FakeDB<proto::SignalData>> signal_db_; - raw_ptr<leveldb_proto::test::FakeDB<proto::SignalStorageConfigs>> - segment_storage_config_db_; - TestModelProviderFactory::Data model_provider_data_; - TestingPrefServiceSimple pref_service_; - base::SimpleTestClock test_clock_; - std::unique_ptr<UkmDataManager> ukm_data_manager_; - std::unique_ptr<SegmentationPlatformServiceImpl> - segmentation_platform_service_impl_; MockServiceProxyObserver observer_; + std::unique_ptr<UkmDataManager> ukm_data_manager_; }; TEST_F(SegmentationPlatformServiceImplTest, InitializationFlow) {
diff --git a/components/segmentation_platform/internal/segmentation_platform_service_test_base.cc b/components/segmentation_platform/internal/segmentation_platform_service_test_base.cc new file mode 100644 index 0000000..d488984 --- /dev/null +++ b/components/segmentation_platform/internal/segmentation_platform_service_test_base.cc
@@ -0,0 +1,124 @@ +// 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/segmentation_platform_service_test_base.h" + +#include "base/test/test_simple_task_runner.h" +#include "base/time/time.h" +#include "base/values.h" +#include "components/prefs/scoped_user_pref_update.h" +#include "components/segmentation_platform/internal/constants.h" +#include "components/segmentation_platform/internal/database/segment_info_database.h" +#include "components/segmentation_platform/internal/execution/mock_model_provider.h" +#include "components/segmentation_platform/internal/segmentation_platform_service_impl.h" +#include "components/segmentation_platform/internal/ukm_data_manager.h" +#include "components/segmentation_platform/public/config.h" + +namespace segmentation_platform { + +namespace { + +std::vector<std::unique_ptr<Config>> CreateTestConfigs() { + std::vector<std::unique_ptr<Config>> configs; + { + std::unique_ptr<Config> config = std::make_unique<Config>(); + config->segmentation_key = kTestSegmentationKey1; + config->segment_selection_ttl = base::Days(28); + config->segment_ids = { + OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_NEW_TAB, + OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_SHARE}; + configs.push_back(std::move(config)); + } + { + std::unique_ptr<Config> config = std::make_unique<Config>(); + config->segmentation_key = kTestSegmentationKey2; + config->segment_selection_ttl = base::Days(10); + config->segment_ids = { + OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_SHARE, + OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_VOICE}; + configs.push_back(std::move(config)); + } + { + std::unique_ptr<Config> config = std::make_unique<Config>(); + config->segmentation_key = kTestSegmentationKey3; + config->segment_selection_ttl = base::Days(14); + config->segment_ids = { + OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_NEW_TAB}; + configs.push_back(std::move(config)); + } + { + // Empty config. + std::unique_ptr<Config> config = std::make_unique<Config>(); + config->segmentation_key = "test_key"; + configs.push_back(std::move(config)); + } + + return configs; +} + +} // namespace + +constexpr char kTestSegmentationKey1[] = "test_key1"; +constexpr char kTestSegmentationKey2[] = "test_key2"; +constexpr char kTestSegmentationKey3[] = "test_key3"; + +SegmentationPlatformServiceTestBase::SegmentationPlatformServiceTestBase() = + default; +SegmentationPlatformServiceTestBase::~SegmentationPlatformServiceTestBase() = + default; + +void SegmentationPlatformServiceTestBase::InitPlatform( + UkmDataManager* ukm_data_manager, + history::HistoryService* history_service) { + task_runner_ = base::MakeRefCounted<base::TestSimpleTaskRunner>(); + + auto segment_db = + std::make_unique<leveldb_proto::test::FakeDB<proto::SegmentInfo>>( + &segment_db_entries_); + auto signal_db = + std::make_unique<leveldb_proto::test::FakeDB<proto::SignalData>>( + &signal_db_entries_); + auto segment_storage_config_db = std::make_unique< + leveldb_proto::test::FakeDB<proto::SignalStorageConfigs>>( + &segment_storage_config_db_entries_); + segment_db_ = segment_db.get(); + signal_db_ = signal_db.get(); + segment_storage_config_db_ = segment_storage_config_db.get(); + + SegmentationPlatformService::RegisterProfilePrefs(pref_service_.registry()); + SetUpPrefs(); + + std::vector<std::unique_ptr<Config>> configs = CreateTestConfigs(); + segmentation_platform_service_impl_ = + std::make_unique<SegmentationPlatformServiceImpl>( + std::move(segment_db), std::move(signal_db), + std::move(segment_storage_config_db), ukm_data_manager, + std::make_unique<TestModelProviderFactory>(&model_provider_data_), + &pref_service_, history_service, task_runner_, &test_clock_, + std::move(configs)); +} + +void SegmentationPlatformServiceTestBase::DestroyPlatform() { + segmentation_platform_service_impl_.reset(); + // Allow for the SegmentationModelExecutor owned by SegmentationModelHandler + // to be destroyed. + task_runner_->RunUntilIdle(); +} + +void SegmentationPlatformServiceTestBase::SetUpPrefs() { + DictionaryPrefUpdate update(&pref_service_, kSegmentationResultPref); + base::Value* dictionary = update.Get(); + + base::Value segmentation_result(base::Value::Type::DICTIONARY); + segmentation_result.SetIntKey( + "segment_id", OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_SHARE); + dictionary->SetKey(kTestSegmentationKey1, std::move(segmentation_result)); +} + +std::vector<std::unique_ptr<Config>> +SegmentationPlatformServiceTestBase::CreateConfigs() { + return CreateTestConfigs(); +} + +} // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/segmentation_platform_service_test_base.h b/components/segmentation_platform/internal/segmentation_platform_service_test_base.h new file mode 100644 index 0000000..4e896a1c --- /dev/null +++ b/components/segmentation_platform/internal/segmentation_platform_service_test_base.h
@@ -0,0 +1,77 @@ +// 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_SEGMENTATION_PLATFORM_SERVICE_TEST_BASE_H_ +#define COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_SEGMENTATION_PLATFORM_SERVICE_TEST_BASE_H_ + +#include <memory> +#include <vector> + +#include "base/test/simple_test_clock.h" +#include "components/leveldb_proto/testing/fake_db.h" +#include "components/prefs/testing_pref_service.h" +#include "components/segmentation_platform/internal/execution/mock_model_provider.h" +#include "components/segmentation_platform/internal/proto/model_prediction.pb.h" +#include "components/segmentation_platform/internal/proto/signal.pb.h" +#include "components/segmentation_platform/internal/proto/signal_storage_config.pb.h" + +namespace history { +class HistoryService; +} + +namespace segmentation_platform { + +struct Config; +class SegmentationPlatformServiceImpl; +class UkmDataManager; + +extern const char kTestSegmentationKey1[]; +extern const char kTestSegmentationKey2[]; +extern const char kTestSegmentationKey3[]; + +// Wrapper around SegmentationPlatformServiceImpl for testing. Holds and manages +// a single platform instance. +class SegmentationPlatformServiceTestBase { + public: + SegmentationPlatformServiceTestBase(); + virtual ~SegmentationPlatformServiceTestBase(); + + // Creates the platform service, does not wait for initialization to complete. + void InitPlatform(UkmDataManager* ukm_data_manager, + history::HistoryService* history_service); + + // Destroys the platform, and setup. + void DestroyPlatform(); + + // Called to register additional segmentation prefs before creating the + // platform. + virtual void SetUpPrefs(); + // Called to create a config before creating the platform. Uses a default + // config with 3 keys: kTestSegmentationKey* with different selection TTLs. + virtual std::vector<std::unique_ptr<Config>> CreateConfigs(); + + leveldb_proto::test::FakeDB<proto::SegmentInfo>& segment_db() { + return *segment_db_; + } + + protected: + scoped_refptr<base::TestSimpleTaskRunner> task_runner_; + std::map<std::string, proto::SegmentInfo> segment_db_entries_; + std::map<std::string, proto::SignalData> signal_db_entries_; + std::map<std::string, proto::SignalStorageConfigs> + segment_storage_config_db_entries_; + raw_ptr<leveldb_proto::test::FakeDB<proto::SegmentInfo>> segment_db_; + raw_ptr<leveldb_proto::test::FakeDB<proto::SignalData>> signal_db_; + raw_ptr<leveldb_proto::test::FakeDB<proto::SignalStorageConfigs>> + segment_storage_config_db_; + TestModelProviderFactory::Data model_provider_data_; + TestingPrefServiceSimple pref_service_; + base::SimpleTestClock test_clock_; + std::unique_ptr<SegmentationPlatformServiceImpl> + segmentation_platform_service_impl_; +}; + +} // namespace segmentation_platform + +#endif // COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_SEGMENTATION_PLATFORM_SERVICE_TEST_BASE_H_
diff --git a/components/segmentation_platform/internal/selection/segment_result_provider.cc b/components/segmentation_platform/internal/selection/segment_result_provider.cc index 93bcc4cc..3fd85094 100644 --- a/components/segmentation_platform/internal/selection/segment_result_provider.cc +++ b/components/segmentation_platform/internal/selection/segment_result_provider.cc
@@ -65,18 +65,17 @@ std::string segmentation_key; }; - void OnGetSegmentInfo(std::unique_ptr<RequestState> request_state, - absl::optional<proto::SegmentInfo> available_segment); + void OnGetSegmentInfo( + std::unique_ptr<RequestState> request_state, + DefaultModelManager::SegmentInfoList available_segments); void TryGetScoreFromDefaultModel( std::unique_ptr<RequestState> request_state, - SegmentResultProvider::ResultState existing_state); - void OnDefaultModelFetched( - std::unique_ptr<RequestState> request_state, - std::unique_ptr<DefaultModelManager::SegmentInfoList> metadata_list); + SegmentResultProvider::ResultState existing_state, + DefaultModelManager::SegmentInfoList available_segments); void OnDefaultModelExecuted( std::unique_ptr<RequestState> request_state, - proto::SegmentInfo segment_info, + std::unique_ptr<proto::SegmentInfo> segment_info, const std::pair<float, ModelExecutionStatus>& result); void PostResultCallback(std::unique_ptr<RequestState> request_state, @@ -107,54 +106,64 @@ default_model_manager_ ? default_model_manager_->GetDefaultProvider(segment_id) : nullptr; - // TODO(ssid): Change default model manager to return both info instead of - // requesting here. - segment_database_->GetSegmentInfo( - segment_id, + + default_model_manager_->GetAllSegmentInfoFromBothModels( + {segment_id}, segment_database_, base::BindOnce(&SegmentResultProviderImpl::OnGetSegmentInfo, weak_ptr_factory_.GetWeakPtr(), std::move(request_state))); } void SegmentResultProviderImpl::OnGetSegmentInfo( std::unique_ptr<RequestState> request_state, - absl::optional<proto::SegmentInfo> available_segment) { + DefaultModelManager::SegmentInfoList available_segments) { + const proto::SegmentInfo* db_segment_info = nullptr; + for (const auto& info : available_segments) { + DCHECK_EQ(request_state->segment_id, info->segment_info.segment_id()); + if (info->segment_source == DefaultModelManager::SegmentSource::DATABASE) { + db_segment_info = &info->segment_info; + break; + } + } + // Don't compute results if we don't have enough signals, or don't have // valid unexpired results for any of the segments. - if (!available_segment) { + if (!db_segment_info) { VLOG(1) << __func__ << ": segment=" << OptimizationTarget_Name(request_state->segment_id) << " does not have segment info."; TryGetScoreFromDefaultModel(std::move(request_state), - ResultState::kSegmentNotAvailable); + ResultState::kSegmentNotAvailable, + std::move(available_segments)); return; } - proto::SegmentInfo& segment_info = *available_segment; // TODO(ssid): Remove this check since scheduler does this before executing // the model. if (!force_refresh_results_ && !signal_storage_config_->MeetsSignalCollectionRequirement( - segment_info.model_metadata())) { + db_segment_info->model_metadata())) { VLOG(1) << __func__ << ": segment=" - << OptimizationTarget_Name(segment_info.segment_id()) + << OptimizationTarget_Name(db_segment_info->segment_id()) << " does not meet signal collection requirements."; TryGetScoreFromDefaultModel(std::move(request_state), - ResultState::kSignalsNotCollected); + ResultState::kSignalsNotCollected, + std::move(available_segments)); return; } - if (metadata_utils::HasExpiredOrUnavailableResult(segment_info, + if (metadata_utils::HasExpiredOrUnavailableResult(*db_segment_info, clock_->Now())) { VLOG(1) << __func__ << ": segment=" - << OptimizationTarget_Name(segment_info.segment_id()) + << OptimizationTarget_Name(db_segment_info->segment_id()) << " has expired or unavailable result."; TryGetScoreFromDefaultModel(std::move(request_state), - ResultState::kDatabaseScoreNotReady); + ResultState::kDatabaseScoreNotReady, + std::move(available_segments)); return; } int rank = - ComputeDiscreteMapping(request_state->segmentation_key, segment_info); + ComputeDiscreteMapping(request_state->segmentation_key, *db_segment_info); PostResultCallback( std::move(request_state), std::make_unique<SegmentResult>(ResultState::kSuccessFromDatabase, rank)); @@ -162,7 +171,8 @@ void SegmentResultProviderImpl::TryGetScoreFromDefaultModel( std::unique_ptr<RequestState> request_state, - SegmentResultProvider::ResultState existing_state) { + SegmentResultProvider::ResultState existing_state, + DefaultModelManager::SegmentInfoList available_segments) { if (!request_state->default_provider || !request_state->default_provider->ModelAvailable()) { PostResultCallback(std::move(request_state), @@ -170,29 +180,29 @@ return; } - OptimizationTarget segment_id = request_state->segment_id; - default_model_manager_->GetAllSegmentInfoFromDefaultModel( - {segment_id}, - base::BindOnce(&SegmentResultProviderImpl::OnDefaultModelFetched, - weak_ptr_factory_.GetWeakPtr(), std::move(request_state))); -} + std::unique_ptr<proto::SegmentInfo> default_segment_info; + for (auto& info : available_segments) { + DCHECK_EQ(request_state->segment_id, info->segment_info.segment_id()); + if (info->segment_source == + DefaultModelManager::SegmentSource::DEFAULT_MODEL) { + default_segment_info = std::make_unique<proto::SegmentInfo>(); + default_segment_info->Swap(&info->segment_info); + break; + } + } -void SegmentResultProviderImpl::OnDefaultModelFetched( - std::unique_ptr<RequestState> request_state, - std::unique_ptr<DefaultModelManager::SegmentInfoList> metadata_list) { - if (!metadata_list || metadata_list->size() != 1 || - metadata_list->back().first != request_state->segment_id) { + if (!default_segment_info) { PostResultCallback(std::move(request_state), std::make_unique<SegmentResult>( ResultState::kDefaultModelMetadataMissing)); return; } - proto::SegmentInfo& segment_info = (*metadata_list)[0].second; - DCHECK_EQ(metadata_utils::ValidationResult::kValidationSuccess, - metadata_utils::ValidateMetadata(segment_info.model_metadata())); + DCHECK_EQ( + metadata_utils::ValidationResult::kValidationSuccess, + metadata_utils::ValidateMetadata(default_segment_info->model_metadata())); if (!signal_storage_config_->MeetsSignalCollectionRequirement( - segment_info.model_metadata())) { + default_segment_info->model_metadata())) { PostResultCallback(std::move(request_state), std::make_unique<SegmentResult>( ResultState::kDefaultModelSignalNotCollected)); @@ -201,21 +211,23 @@ ModelProvider* default_provider = request_state->default_provider; DCHECK(default_provider); + // The reference is kept alive by the unique_ptr in the callback. + const proto::SegmentInfo& info_ref = *default_segment_info; execution_manager_->ExecuteModel( - segment_info, default_provider, + info_ref, default_provider, base::BindOnce(&SegmentResultProviderImpl::OnDefaultModelExecuted, weak_ptr_factory_.GetWeakPtr(), std::move(request_state), - segment_info)); + std::move(default_segment_info))); } void SegmentResultProviderImpl::OnDefaultModelExecuted( std::unique_ptr<RequestState> request_state, - proto::SegmentInfo segment_info, + std::unique_ptr<proto::SegmentInfo> segment_info, const std::pair<float, ModelExecutionStatus>& result) { if (result.second == ModelExecutionStatus::kSuccess) { - segment_info.mutable_prediction_result()->set_result(result.first); + segment_info->mutable_prediction_result()->set_result(result.first); int rank = - ComputeDiscreteMapping(request_state->segmentation_key, segment_info); + ComputeDiscreteMapping(request_state->segmentation_key, *segment_info); PostResultCallback(std::move(request_state), std::make_unique<SegmentResult>( ResultState::kDefaultModelScoreUsed, rank));
diff --git a/components/segmentation_platform/internal/selection/segment_selector_unittest.cc b/components/segmentation_platform/internal/selection/segment_selector_unittest.cc index f2b8b35..5c015e51 100644 --- a/components/segmentation_platform/internal/selection/segment_selector_unittest.cc +++ b/components/segmentation_platform/internal/selection/segment_selector_unittest.cc
@@ -307,7 +307,8 @@ // Construct a segment selector. It should read result from last session. segment_selector_ = std::make_unique<SegmentSelectorImpl>( segment_database_.get(), &signal_storage_config_, prefs_.get(), &config_, - &clock_, PlatformOptions::CreateDefault(), nullptr, nullptr); + &clock_, PlatformOptions::CreateDefault(), default_manager_.get(), + nullptr); SegmentSelectionResult result; result.segment = segment_id0;
diff --git a/components/segmentation_platform/internal/signals/signal_filter_processor.cc b/components/segmentation_platform/internal/signals/signal_filter_processor.cc index 4b91710..808f09e 100644 --- a/components/segmentation_platform/internal/signals/signal_filter_processor.cc +++ b/components/segmentation_platform/internal/signals/signal_filter_processor.cc
@@ -24,10 +24,9 @@ class FilterExtractor { public: explicit FilterExtractor( - const std::vector<std::pair<OptimizationTarget, proto::SegmentInfo>>& - segment_infos) { - for (const auto& pair : segment_infos) { - const proto::SegmentInfo& segment_info = pair.second; + const DefaultModelManager::SegmentInfoList& segment_infos) { + for (const auto& info : segment_infos) { + const proto::SegmentInfo& segment_info = info->segment_info; const auto& metadata = segment_info.model_metadata(); AddUmaFeatures(metadata); AddUkmFeatures(metadata); @@ -105,8 +104,8 @@ } void SignalFilterProcessor::FilterSignals( - std::unique_ptr<SegmentInfoDatabase::SegmentInfoList> segment_infos) { - FilterExtractor extractor(*segment_infos); + DefaultModelManager::SegmentInfoList segment_infos) { + FilterExtractor extractor(segment_infos); stats::RecordSignalsListeningCount(extractor.user_actions, extractor.histograms);
diff --git a/components/segmentation_platform/internal/signals/signal_filter_processor.h b/components/segmentation_platform/internal/signals/signal_filter_processor.h index e75a84e7..1465966 100644 --- a/components/segmentation_platform/internal/signals/signal_filter_processor.h +++ b/components/segmentation_platform/internal/signals/signal_filter_processor.h
@@ -8,14 +8,14 @@ #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "components/optimization_guide/proto/models.pb.h" -#include "components/segmentation_platform/internal/database/segment_info_database.h" +#include "components/segmentation_platform/internal/execution/default_model_manager.h" using optimization_guide::proto::OptimizationTarget; namespace segmentation_platform { -class DefaultModelManager; class HistogramSignalHandler; +class SegmentInfoDatabase; class UserActionSignalHandler; class UkmDataManager; @@ -48,8 +48,7 @@ void EnableMetrics(bool enable_metrics); private: - void FilterSignals( - std::unique_ptr<SegmentInfoDatabase::SegmentInfoList> segment_infos); + void FilterSignals(DefaultModelManager::SegmentInfoList segment_infos); raw_ptr<SegmentInfoDatabase> segment_database_; raw_ptr<UserActionSignalHandler> user_action_signal_handler_;
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 77e935ec..dfc35a90 100644 --- a/components/segmentation_platform/internal/signals/signal_filter_processor_unittest.cc +++ b/components/segmentation_platform/internal/signals/signal_filter_processor_unittest.cc
@@ -56,18 +56,30 @@ const std::vector<OptimizationTarget>& segment_ids, MultipleSegmentInfoCallback callback) override { base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, - base::BindOnce( - std::move(callback), - std::make_unique<DefaultModelManager::SegmentInfoList>())); + FROM_HERE, base::BindOnce(std::move(callback), + DefaultModelManager::SegmentInfoList())); } void GetAllSegmentInfoFromBothModels( const std::vector<OptimizationTarget>& segment_ids, SegmentInfoDatabase* segment_database, MultipleSegmentInfoCallback callback) override { - segment_database->GetSegmentInfoForSegments(segment_ids, - std::move(callback)); + segment_database->GetSegmentInfoForSegments( + segment_ids, + base::BindOnce( + [](DefaultModelManager::MultipleSegmentInfoCallback callback, + std::unique_ptr<SegmentInfoDatabase::SegmentInfoList> db_list) { + DefaultModelManager::SegmentInfoList list; + for (auto& pair : *db_list) { + list.push_back(std::make_unique< + DefaultModelManager::SegmentInfoWrapper>()); + list.back()->segment_source = + DefaultModelManager::SegmentSource::DATABASE; + list.back()->segment_info.Swap(&pair.second); + } + std::move(callback).Run(std::move(list)); + }, + std::move(callback))); } };
diff --git a/components/segmentation_platform/internal/signals/user_action_signal_handler.cc b/components/segmentation_platform/internal/signals/user_action_signal_handler.cc index af8195f..b97d58d 100644 --- a/components/segmentation_platform/internal/signals/user_action_signal_handler.cc +++ b/components/segmentation_platform/internal/signals/user_action_signal_handler.cc
@@ -19,7 +19,8 @@ } UserActionSignalHandler::~UserActionSignalHandler() { - base::RemoveActionCallback(action_callback_); + if (metrics_enabled_) + base::RemoveActionCallback(action_callback_); } void UserActionSignalHandler::EnableMetrics(bool enable_metrics) {
diff --git a/components/segmentation_platform/internal/ukm_data_manager_impl.cc b/components/segmentation_platform/internal/ukm_data_manager_impl.cc index fe2333e5a..b26fb22 100644 --- a/components/segmentation_platform/internal/ukm_data_manager_impl.cc +++ b/components/segmentation_platform/internal/ukm_data_manager_impl.cc
@@ -24,8 +24,16 @@ ukm_database_.reset(); } +void UkmDataManagerImpl::InitializeForTesting( + std::unique_ptr<UkmDatabase> ukm_database) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_check_); + DCHECK(!ukm_database_); + ukm_database_ = std::move(ukm_database); +} + void UkmDataManagerImpl::Initialize(const base::FilePath& database_path) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_check_); + DCHECK(!ukm_database_); ukm_database_ = std::make_unique<UkmDatabase>(database_path); } @@ -74,6 +82,9 @@ void UkmDataManagerImpl::StopObservingUkm() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_check_); + if (!ukm_observer_) + return; + DCHECK(ukm_database_); DCHECK(url_signal_handler_); ukm_observer_.reset();
diff --git a/components/segmentation_platform/internal/ukm_data_manager_impl.h b/components/segmentation_platform/internal/ukm_data_manager_impl.h index fec82d8e..52e93cf6 100644 --- a/components/segmentation_platform/internal/ukm_data_manager_impl.h +++ b/components/segmentation_platform/internal/ukm_data_manager_impl.h
@@ -24,6 +24,8 @@ UkmDataManagerImpl(UkmDataManagerImpl&) = delete; UkmDataManagerImpl& operator=(UkmDataManagerImpl&) = delete; + void InitializeForTesting(std::unique_ptr<UkmDatabase> ukm_database); + // UkmDataManager implementation: void Initialize(const base::FilePath& database_path) override; bool IsUkmEngineEnabled() override;
diff --git a/components/segmentation_platform/internal/ukm_data_manager_impl_unittest.cc b/components/segmentation_platform/internal/ukm_data_manager_impl_unittest.cc new file mode 100644 index 0000000..18497dd4 --- /dev/null +++ b/components/segmentation_platform/internal/ukm_data_manager_impl_unittest.cc
@@ -0,0 +1,401 @@ +// 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/ukm_data_manager_impl.h" + +#include "base/files/scoped_temp_dir.h" +#include "base/metrics/metrics_hashes.h" +#include "base/test/task_environment.h" +#include "components/history/core/browser/history_service.h" +#include "components/history/core/test/history_service_test_util.h" +#include "components/segmentation_platform/internal/database/mock_ukm_database.h" +#include "components/segmentation_platform/internal/database/ukm_types.h" +#include "components/segmentation_platform/internal/execution/model_execution_manager_impl.h" +#include "components/segmentation_platform/internal/segmentation_platform_service_impl.h" +#include "components/segmentation_platform/internal/segmentation_platform_service_test_base.h" +#include "components/ukm/test_ukm_recorder.h" +#include "services/metrics/public/cpp/ukm_builders.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace segmentation_platform { + +namespace { + +using testing::_; +using ukm::builders::PageLoad; +using ukm::builders::PaintPreviewCapture; + +constexpr ukm::SourceId kSourceId = 10; +constexpr ukm::SourceId kSourceId2 = 12; + +ukm::mojom::UkmEntryPtr GetSamplePageLoadEntry( + ukm::SourceId source_id = kSourceId) { + ukm::mojom::UkmEntryPtr entry = ukm::mojom::UkmEntry::New(); + entry->source_id = source_id; + entry->event_hash = PageLoad::kEntryNameHash; + entry->metrics[PageLoad::kCpuTimeNameHash] = 10; + entry->metrics[PageLoad::kIsNewBookmarkNameHash] = 20; + entry->metrics[PageLoad::kIsNTPCustomLinkNameHash] = 30; + return entry; +} + +ukm::mojom::UkmEntryPtr GetSamplePaintPreviewEntry( + ukm::SourceId source_id = kSourceId) { + ukm::mojom::UkmEntryPtr entry = ukm::mojom::UkmEntry::New(); + entry->source_id = source_id; + entry->event_hash = PaintPreviewCapture::kEntryNameHash; + entry->metrics[PaintPreviewCapture::kBlinkCaptureTimeNameHash] = 5; + entry->metrics[PaintPreviewCapture::kCompressedOnDiskSizeNameHash] = 15; + return entry; +} + +proto::SegmentationModelMetadata PageLoadModelMetadata() { + proto::SegmentationModelMetadata metadata; + metadata.set_time_unit(proto::TimeUnit::DAY); + metadata.set_bucket_duration(42u); + auto* feature = metadata.add_input_features(); + auto* sql_feature = feature->mutable_sql_feature(); + sql_feature->set_sql("SELECT COUNT(*) from metrics;"); + + auto* ukm_event = sql_feature->mutable_signal_filter()->add_ukm_events(); + ukm_event->set_event_hash(PageLoad::kEntryNameHash); + ukm_event->add_metric_hash_filter(PageLoad::kCpuTimeNameHash); + ukm_event->add_metric_hash_filter(PageLoad::kIsNewBookmarkNameHash); + return metadata; +} + +proto::SegmentationModelMetadata PaintPreviewModelMetadata() { + proto::SegmentationModelMetadata metadata; + metadata.set_time_unit(proto::TimeUnit::DAY); + metadata.set_bucket_duration(42u); + + auto* feature = metadata.add_input_features(); + auto* sql_feature = feature->mutable_sql_feature(); + sql_feature->set_sql("SELECT COUNT(*) from metrics;"); + auto* ukm_event2 = sql_feature->mutable_signal_filter()->add_ukm_events(); + ukm_event2->set_event_hash(PaintPreviewCapture::kEntryNameHash); + ukm_event2->add_metric_hash_filter( + PaintPreviewCapture::kBlinkCaptureTimeNameHash); + return metadata; +} + +} // namespace + +class TestServicesForPlatform : public SegmentationPlatformServiceTestBase { + public: + explicit TestServicesForPlatform(UkmDataManagerImpl* ukm_data_manager) { + EXPECT_TRUE(profile_dir.CreateUniqueTempDir()); + history_service = history::CreateHistoryService(profile_dir.GetPath(), + /*create_db=*/true); + + InitPlatform(ukm_data_manager, history_service.get()); + + segment_db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK); + signal_db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK); + segment_storage_config_db_->InitStatusCallback( + leveldb_proto::Enums::InitStatus::kOK); + segment_storage_config_db_->LoadCallback(true); + + // If initialization is succeeded, model execution scheduler should start + // querying segment db. + segment_db_->LoadCallback(true); + } + + ~TestServicesForPlatform() override { + DestroyPlatform(); + history_service.reset(); + } + + void AddModel(const proto::SegmentationModelMetadata& metadata) { + ModelExecutionManagerImpl* mem_impl = + static_cast<ModelExecutionManagerImpl*>( + segmentation_platform_service_impl_->model_execution_manager_ + .get()); + mem_impl->OnSegmentationModelUpdated( + OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_SHARE, metadata, + 0); + segment_db_->GetCallback(true); + segment_db_->UpdateCallback(true); + segment_db_->LoadCallback(true); + base::RunLoop().RunUntilIdle(); + } + + SegmentationPlatformServiceImpl& platform() { + return *segmentation_platform_service_impl_; + } + + base::ScopedTempDir profile_dir; + std::unique_ptr<history::HistoryService> history_service; +}; + +class UkmDataManagerImplTest : public testing::Test { + public: + UkmDataManagerImplTest() = default; + ~UkmDataManagerImplTest() override = default; + + void SetUp() override { + data_manager_ = std::make_unique<UkmDataManagerImpl>(); + ukm_recorder_ = std::make_unique<ukm::TestUkmRecorder>(); + auto ukm_db = std::make_unique<MockUkmDatabase>(); + ukm_database_ = ukm_db.get(); + data_manager_->InitializeForTesting(std::move(ukm_db)); + } + + void TearDown() override { + data_manager_->StopObservingUkm(); + ukm_recorder_.reset(); + ukm_database_ = nullptr; + data_manager_.reset(); + } + + void RecordUkmAndWaitForDatabase(ukm::mojom::UkmEntryPtr entry) {} + + TestServicesForPlatform& CreatePlatform() { + platform_services_.push_back( + std::make_unique<TestServicesForPlatform>(data_manager_.get())); + return *platform_services_.back(); + } + + void RemovePlatform(const TestServicesForPlatform* platform) { + auto it = platform_services_.begin(); + while (it != platform_services_.end()) { + if (it->get() == platform) { + platform_services_.erase(it); + return; + } + it++; + } + } + + protected: + // Use system time to avoid history service expiration tasks to go into an + // infinite loop. + base::test::TaskEnvironment task_environment_{ + base::test::TaskEnvironment::TimeSource::SYSTEM_TIME}; + + std::unique_ptr<ukm::TestUkmRecorder> ukm_recorder_; + raw_ptr<MockUkmDatabase> ukm_database_; + std::unique_ptr<UkmDataManagerImpl> data_manager_; + std::vector<std::unique_ptr<TestServicesForPlatform>> platform_services_; + + std::vector<ukm::mojom::UkmEntryPtr> db_entries_; +}; + +MATCHER_P(HasEventHash, event_hash, "") { + return arg->event_hash == event_hash; +} + +TEST_F(UkmDataManagerImplTest, HistoryNotification) { + const GURL kUrl1 = GURL("https://www.url1.com/"); + + TestServicesForPlatform& platform1 = CreatePlatform(); + + // Add a page to history and check that the notification is sent to + // UkmDatabase. All notifications should be sent. + base::RunLoop wait_for_add1; + EXPECT_CALL(*ukm_database_, OnUrlValidated(kUrl1)) + .WillOnce([&wait_for_add1]() { wait_for_add1.QuitClosure().Run(); }); + platform1.history_service->AddPage(kUrl1, base::Time::Now(), + history::VisitSource::SOURCE_BROWSED); + wait_for_add1.Run(); + + platform1.history_service->DeleteURLs({kUrl1}); + + // Check that RemoveUrls() notification is sent to UkmDatabase. + base::RunLoop wait_for_remove1; + EXPECT_CALL(*ukm_database_, RemoveUrls(std::vector({kUrl1}))) + .WillOnce( + [&wait_for_remove1]() { wait_for_remove1.QuitClosure().Run(); }); + wait_for_remove1.Run(); + + RemovePlatform(&platform1); +} + +TEST_F(UkmDataManagerImplTest, UkmSourceObservation) { + const GURL kUrl1 = GURL("https://www.url1.com/"); + + data_manager_->NotifyCanObserveUkm(ukm_recorder_.get()); + + // Create a platform that observes PageLoad events. + TestServicesForPlatform& platform1 = CreatePlatform(); + platform1.AddModel(PageLoadModelMetadata()); + + // Source updates are notified to the database. + base::RunLoop wait_for_source; + EXPECT_CALL(*ukm_database_, + UpdateUrlForUkmSource(kSourceId, kUrl1, /*is_validated=*/false)) + .WillOnce([&wait_for_source](ukm::SourceId source_id, const GURL& url, + bool is_validated) { + wait_for_source.QuitClosure().Run(); + }); + ukm_recorder_->UpdateSourceURL(kSourceId, kUrl1); + wait_for_source.Run(); + + RemovePlatform(&platform1); +} + +TEST_F(UkmDataManagerImplTest, UkmEntryObservation) { + const GURL kUrl1 = GURL("https://www.url1.com/"); + + // UKM added before creating platform do not get recorded. + ukm_recorder_->AddEntry(GetSamplePageLoadEntry()); + ukm_recorder_->AddEntry(GetSamplePaintPreviewEntry()); + + // Create a platform that observes PageLoad events. + TestServicesForPlatform& platform1 = CreatePlatform(); + platform1.AddModel(PageLoadModelMetadata()); + + // Not added since UkmDataManager is not notified for UKM observation. + ukm_recorder_->AddEntry(GetSamplePageLoadEntry()); + + data_manager_->NotifyCanObserveUkm(ukm_recorder_.get()); + + // Not added since it is not PageLoad event. + ukm_recorder_->AddEntry(GetSamplePaintPreviewEntry()); + + // PageLoad event gets recorded in UkmDatabase. + base::RunLoop wait_for_record; + EXPECT_CALL(*ukm_database_, + StoreUkmEntry(HasEventHash(PageLoad::kEntryNameHash))) + .WillOnce([&wait_for_record](ukm::mojom::UkmEntryPtr entry) { + wait_for_record.QuitClosure().Run(); + }); + ukm_recorder_->AddEntry(GetSamplePageLoadEntry()); + wait_for_record.Run(); + + RemovePlatform(&platform1); +} + +TEST_F(UkmDataManagerImplTest, UkmServiceCreatedBeforePlatform) { + const GURL kUrl1 = GURL("https://www.url1.com/"); + + // Observation is available before platforms are created. + data_manager_->NotifyCanObserveUkm(ukm_recorder_.get()); + + TestServicesForPlatform& platform1 = CreatePlatform(); + platform1.AddModel(PageLoadModelMetadata()); + + // Entry should be recorded, This step does not wait for the database record + // here since it is waits for the next observation below. + EXPECT_CALL(*ukm_database_, + StoreUkmEntry(HasEventHash(PageLoad::kEntryNameHash))); + ukm_recorder_->AddEntry(GetSamplePageLoadEntry()); + + // Source updates should be notified. + base::RunLoop wait_for_source; + EXPECT_CALL(*ukm_database_, + UpdateUrlForUkmSource(kSourceId, kUrl1, /*is_validated=*/false)) + .WillOnce([&wait_for_source](ukm::SourceId source_id, const GURL& url, + bool is_validated) { + wait_for_source.QuitClosure().Run(); + }); + ukm_recorder_->UpdateSourceURL(kSourceId, kUrl1); + wait_for_source.Run(); + + RemovePlatform(&platform1); +} + +TEST_F(UkmDataManagerImplTest, UrlValidationWithHistory) { + const GURL kUrl1 = GURL("https://www.url1.com/"); + + data_manager_->NotifyCanObserveUkm(ukm_recorder_.get()); + TestServicesForPlatform& platform1 = CreatePlatform(); + platform1.AddModel(PageLoadModelMetadata()); + + // History page is added before source update. + base::RunLoop wait_for_add1; + EXPECT_CALL(*ukm_database_, OnUrlValidated(kUrl1)) + .WillOnce([&wait_for_add1]() { wait_for_add1.QuitClosure().Run(); }); + platform1.history_service->AddPage(kUrl1, base::Time::Now(), + history::VisitSource::SOURCE_BROWSED); + wait_for_add1.Run(); + + // Source update should have a validated URL. + base::RunLoop wait_for_source; + EXPECT_CALL(*ukm_database_, + UpdateUrlForUkmSource(kSourceId, kUrl1, /*is_validated=*/true)) + .WillOnce([&wait_for_source](ukm::SourceId source_id, const GURL& url, + bool is_validated) { + wait_for_source.QuitClosure().Run(); + }); + ukm_recorder_->UpdateSourceURL(kSourceId, kUrl1); + wait_for_source.Run(); + + RemovePlatform(&platform1); +} + +TEST_F(UkmDataManagerImplTest, MultiplePlatforms) { + const GURL kUrl1 = GURL("https://www.url1.com/"); + const GURL kUrl2 = GURL("https://www.url2.com/"); + + data_manager_->NotifyCanObserveUkm(ukm_recorder_.get()); + + // Create 2 platforms, and 1 of them observing UKM events. + TestServicesForPlatform& platform1 = CreatePlatform(); + TestServicesForPlatform& platform3 = CreatePlatform(); + platform1.AddModel(PageLoadModelMetadata()); + + // Only page load should be added to database. + EXPECT_CALL(*ukm_database_, + StoreUkmEntry(HasEventHash(PageLoad::kEntryNameHash))); + ukm_recorder_->AddEntry(GetSamplePageLoadEntry()); + ukm_recorder_->AddEntry(GetSamplePaintPreviewEntry()); + + // Create another platform observing paint preview. + TestServicesForPlatform& platform2 = CreatePlatform(); + platform2.AddModel(PaintPreviewModelMetadata()); + + // Both should be added to database. + EXPECT_CALL(*ukm_database_, + StoreUkmEntry(HasEventHash(PageLoad::kEntryNameHash))); + EXPECT_CALL(*ukm_database_, + StoreUkmEntry(HasEventHash(PaintPreviewCapture::kEntryNameHash))); + ukm_recorder_->AddEntry(GetSamplePageLoadEntry()); + ukm_recorder_->AddEntry(GetSamplePaintPreviewEntry()); + + // Sources should still be updated. + base::RunLoop wait_for_source; + EXPECT_CALL(*ukm_database_, + UpdateUrlForUkmSource(kSourceId, kUrl1, /*is_validated=*/false)) + .WillOnce([&wait_for_source](ukm::SourceId source_id, const GURL& url, + bool is_validated) { + wait_for_source.QuitClosure().Run(); + }); + ukm_recorder_->UpdateSourceURL(kSourceId, kUrl1); + wait_for_source.Run(); + + // Removing platform1 does not stop observing metrics. + RemovePlatform(&platform1); + EXPECT_CALL(*ukm_database_, + StoreUkmEntry(HasEventHash(PageLoad::kEntryNameHash))); + EXPECT_CALL(*ukm_database_, + StoreUkmEntry(HasEventHash(PaintPreviewCapture::kEntryNameHash))); + ukm_recorder_->AddEntry(GetSamplePageLoadEntry()); + ukm_recorder_->AddEntry(GetSamplePaintPreviewEntry()); + + // Update history service on one of the platforms, and the database should get + // a validated URL. + base::RunLoop wait_for_add1; + EXPECT_CALL(*ukm_database_, OnUrlValidated(kUrl2)) + .WillOnce([&wait_for_add1]() { wait_for_add1.QuitClosure().Run(); }); + platform2.history_service->AddPage(kUrl2, base::Time::Now(), + history::VisitSource::SOURCE_BROWSED); + wait_for_add1.Run(); + + base::RunLoop wait_for_source2; + EXPECT_CALL(*ukm_database_, + UpdateUrlForUkmSource(kSourceId2, kUrl2, /*is_validated=*/true)) + .WillOnce([&wait_for_source2](ukm::SourceId source_id, const GURL& url, + bool is_validated) { + wait_for_source2.QuitClosure().Run(); + }); + ukm_recorder_->UpdateSourceURL(kSourceId2, kUrl2); + wait_for_source2.Run(); + + RemovePlatform(&platform2); + RemovePlatform(&platform3); +} + +} // namespace segmentation_platform
diff --git a/components/url_formatter/spoof_checks/idn_spoof_checker_unittest.cc b/components/url_formatter/spoof_checks/idn_spoof_checker_unittest.cc index 4ffccc0..92cd3c6 100644 --- a/components/url_formatter/spoof_checks/idn_spoof_checker_unittest.cc +++ b/components/url_formatter/spoof_checks/idn_spoof_checker_unittest.cc
@@ -1328,73 +1328,63 @@ TEST(IDNSpoofCheckerNoFixtureTest, MultipleSkeletons) { IDNSpoofChecker checker; // apple with U+04CF (ӏ) - const GURL url1("http://appӏe.com"); - const url_formatter::IDNConversionResult result1 = - UnsafeIDNToUnicodeWithDetails(url1.host()); - Skeletons skeletons1 = checker.GetSkeletons(result1.result); - EXPECT_EQ(Skeletons({"apple.corn", "appie.corn"}), skeletons1); - - const GURL url2("http://œxamþle.com"); - const url_formatter::IDNConversionResult result2 = - UnsafeIDNToUnicodeWithDetails(url2.host()); - Skeletons skeletons2 = checker.GetSkeletons(result2.result); - // This skeleton set doesn't include strings with "œ" because it gets - // converted to "oe" by ICU during skeleton extraction. - EXPECT_EQ(Skeletons({"oexarnþle.corn", "oexarnple.corn", "oexarnble.corn", - "cexarnþle.corn", "cexarnple.corn", "cexarnble.corn"}), - skeletons2); + const GURL url("http://appӏe.com"); + const url_formatter::IDNConversionResult result = + UnsafeIDNToUnicodeWithDetails(url.host()); + Skeletons skeletons = checker.GetSkeletons(result.result); + EXPECT_EQ(Skeletons({"apple.corn", "appie.corn"}), skeletons); } TEST(IDNSpoofCheckerNoFixtureTest, AlternativeSkeletons) { struct TestCase { - // String whose alternative strings will be generated - std::u16string input; - // Maximum number of alternative strings to generate. - size_t max_alternatives; - // Expected string set. - base::flat_set<std::u16string> expected_strings; - } kTestCases[] = { - {u"", 0, {}}, - {u"", 1, {}}, - {u"", 2, {}}, - {u"", 100, {}}, + // Skeleton whose alternative skeletons will be generated + std::string skeleton; + // Maximum number of skeletons to generate. + size_t max_skeletons; + // Expected skeleton set. + Skeletons expected_skeletons; + } kTestCases[] = {{"", 0, {}}, + {"", 1, {}}, + {"", 2, {}}, + {"", 100, {}}, - {u"a", 0, {}}, - {u"a", 1, {u"a"}}, - {u"a", 2, {u"a"}}, - {u"a", 100, {u"a"}}, + {"a", 0, {}}, + {"a", 1, {"a"}}, + {"a", 2, {"a"}}, + {"a", 100, {"a"}}, - {u"ab", 0, {}}, - {u"ab", 1, {u"ab"}}, - {u"ab", 2, {u"ab"}}, - {u"ab", 100, {u"ab"}}, + {"ab", 0, {}}, + {"ab", 1, {"ab"}}, + {"ab", 2, {"ab"}}, + {"ab", 100, {"ab"}}, - {u"œ", 0, {}}, - {u"œ", 1, {u"œ"}}, - {u"œ", 2, {u"œ", u"ce"}}, - {u"œ", 100, {u"œ", u"ce", u"oe"}}, + {"œ", 0, {}}, + {"œ", 1, {"œ"}}, + {"œ", 2, {"œ", "ce"}}, + {"œ", 100, {"œ", "ce", "oe"}}, - {u"œxample", 0, {}}, - {u"œxample", 1, {u"œxample"}}, - {u"œxample", 2, {u"œxample", u"cexample"}}, - {u"œxample", 100, {u"œxample", u"cexample", u"oexample"}}, + {"œxample", 0, {}}, + {"œxample", 1, {"œxample"}}, + {"œxample", 2, {"œxample", "cexample"}}, + {"œxample", 100, {"œxample", "cexample", "oexample"}}, - {u"œxamþle", 0, {}}, - {u"œxamþle", 1, {u"œxamþle"}}, - {u"œxamþle", 2, {u"œxamþle", u"œxamble"}}, - {u"œxamþle", - 100, - {u"œxamþle", u"œxample", u"œxamble", u"oexamþle", u"oexample", - u"oexamble", u"cexamþle", u"cexample", u"cexamble"}}}; + {"œxamþle", 0, {}}, + {"œxamþle", 1, {"œxamþle"}}, + {"œxamþle", 2, {"œxamþle", "œxamble"}}, + {"œxamþle", + 100, + {"œxamþle", "œxample", "œxamble", "oexamþle", "oexample", + "oexamble", "cexamþle", "cexample", "cexamble"}}}; + // IDNSpoofChecker checker; SkeletonMap skeleton_map; skeleton_map[u'œ'] = {"ce", "oe"}; skeleton_map[u'þ'] = {"b", "p"}; for (const TestCase& test_case : kTestCases) { - const auto strings = SkeletonGenerator::GenerateSupplementalHostnames( - test_case.input, test_case.max_alternatives, skeleton_map); - EXPECT_LE(strings.size(), test_case.max_alternatives); - EXPECT_EQ(strings, test_case.expected_strings); + Skeletons skeletons = SkeletonGenerator::GenerateSupplementalSkeletons( + test_case.skeleton, test_case.max_skeletons, skeleton_map); + EXPECT_LE(skeletons.size(), test_case.max_skeletons); + EXPECT_EQ(skeletons, test_case.expected_skeletons); } }
diff --git a/components/url_formatter/spoof_checks/skeleton_generator.cc b/components/url_formatter/spoof_checks/skeleton_generator.cc index da557a5..df026d6 100644 --- a/components/url_formatter/spoof_checks/skeleton_generator.cc +++ b/components/url_formatter/spoof_checks/skeleton_generator.cc
@@ -7,7 +7,6 @@ #include <ostream> #include <queue> - #include "base/i18n/unicodestring.h" #include "base/memory/ptr_util.h" #include "base/strings/string_piece.h" @@ -21,12 +20,7 @@ using QueueItem = std::vector<std::u16string>; -// Maximum number of supplemental hostname to generate for a given input. -// If this number is too high, we may end up DOSing the browser process. -// If it's too low, we may not be able to cover some lookalike URLs. -const size_t kMaxSupplementalHostnames = 128; - -} // namespace +} SkeletonGenerator::SkeletonGenerator(const USpoofChecker* checker) : checker_(checker) { @@ -67,6 +61,7 @@ // - {U+0138 (ĸ), U+03BA (κ), U+043A (к), U+049B (қ), U+049D (ҝ), // U+049F (ҟ), U+04A1(ҡ), U+04C4 (ӄ), U+051F (ԟ)} => k // - {U+014B (ŋ), U+043F (п), U+0525 (ԥ), U+0E01 (ก), U+05D7 (ח)} => n + // - U+0153 (œ) => "ce" // TODO(crbug/843352): Handle multiple skeletons for U+0525 and U+0153. // - {U+0167 (ŧ), U+0442 (т), U+04AD (ҭ), U+050F (ԏ), U+4E03 (七), // U+4E05 (丅), U+4E06 (丆), U+4E01 (丁)} => t @@ -106,7 +101,7 @@ UNICODE_STRING_SIMPLE("ExtraConf"), icu::UnicodeString::fromUTF8( "[æӕ] > ae; [ϼҏ] > p; [ħнћңҥӈӊԋԧԩ] > h;" - "[ĸκкқҝҟҡӄԟ] > k; [ŋпԥกח] > n;" + "[ĸκкқҝҟҡӄԟ] > k; [ŋпԥกח] > n; œ > ce;" "[ŧтҭԏ七丅丆丁] > t; [ƅьҍв] > b; [ωшщพฟພຟ] > w;" "[мӎ] > m; [єҽҿၔ] > e; ґ > r; [ғӻ] > f;" "[ҫင] > c; [ұ丫] > y; [χҳӽӿ乂] > x;" @@ -126,10 +121,6 @@ DCHECK(U_SUCCESS(status)) << "Skeleton generator initialization failed due to an error: " << u_errorName(status); - - // Characters that look like multiple characters. - character_map_[u'þ'] = {"b", "p"}; - character_map_[u'œ'] = {"ce", "oe"}; } SkeletonGenerator::~SkeletonGenerator() = default; @@ -150,34 +141,25 @@ return base::i18n::UnicodeStringToString16(host); } -Skeletons SkeletonGenerator::GetSkeletons(base::StringPiece16 input_hostname) { - std::u16string hostname_no_diacritics = MaybeRemoveDiacritics(input_hostname); - - // Generate alternative versions of the input hostname and extract skeletons. +Skeletons SkeletonGenerator::GetSkeletons(base::StringPiece16 hostname) { Skeletons skeletons; - for (const std::u16string& hostname : GenerateSupplementalHostnames( - hostname_no_diacritics, kMaxSupplementalHostnames, character_map_)) { - size_t hostname_length = - hostname.length() - (hostname.back() == '.' ? 1 : 0); - icu::UnicodeString hostname_unicode(false, hostname.data(), - hostname_length); - extra_confusable_mapper_->transliterate(hostname_unicode); + size_t hostname_length = hostname.length() - (hostname.back() == '.' ? 1 : 0); + icu::UnicodeString host(false, hostname.data(), hostname_length); + MaybeRemoveDiacritics(host); + extra_confusable_mapper_->transliterate(host); - UErrorCode status = U_ZERO_ERROR; - icu::UnicodeString ustr_skeleton; + UErrorCode status = U_ZERO_ERROR; + icu::UnicodeString ustr_skeleton; - // Map U+04CF (ӏ) to lowercase L in addition to what uspoof_getSkeleton does - // (mapping it to lowercase I). - AddSkeletonMapping(hostname_unicode, 0x4CF /* ӏ */, 0x6C /* lowercase L */, - &skeletons); + // Map U+04CF (ӏ) to lowercase L in addition to what uspoof_getSkeleton does + // (mapping it to lowercase I). + AddSkeletonMapping(host, 0x4CF /* ӏ */, 0x6C /* lowercase L */, &skeletons); - uspoof_getSkeletonUnicodeString(checker_, 0, hostname_unicode, - ustr_skeleton, &status); - if (U_SUCCESS(status)) { - std::string skeleton; - ustr_skeleton.toUTF8String(skeleton); - skeletons.insert(skeleton); - } + uspoof_getSkeletonUnicodeString(checker_, 0, host, ustr_skeleton, &status); + if (U_SUCCESS(status)) { + std::string skeleton; + ustr_skeleton.toUTF8String(skeleton); + skeletons.insert(skeleton); } return skeletons; } @@ -215,19 +197,18 @@ } // static -base::flat_set<std::u16string> SkeletonGenerator::GenerateSupplementalHostnames( - base::StringPiece16 input, +Skeletons SkeletonGenerator::GenerateSupplementalSkeletons( + base::StringPiece input, size_t max_alternatives, const SkeletonMap& mapping) { - base::flat_set<std::u16string> output; if (!input.size() || max_alternatives == 0) { - return output; + return Skeletons(); } - icu::UnicodeString input_unicode = - icu::UnicodeString::fromUTF8(base::UTF16ToUTF8(input)); + icu::UnicodeString input_unicode = icu::UnicodeString::fromUTF8(input); // Read only buffer, doesn't need to be released. const char16_t* input_buffer = input_unicode.getBuffer(); + Skeletons output; // This queue contains vectors of skeleton strings. For each character in // the input string, its skeleton string will be appended to the queue item. // Thus, the number of skeleton strings in the queue item will always @@ -242,7 +223,7 @@ if (current.size() == static_cast<size_t>(input_unicode.length())) { // Reached the end of the original string. We now generated a complete // alternative string. Add the result to output. - output.insert(base::JoinString(current, u"")); + output.insert(base::UTF16ToUTF8(base::JoinString(current, u""))); if (output.size() == max_alternatives) { break; }
diff --git a/components/url_formatter/spoof_checks/skeleton_generator.h b/components/url_formatter/spoof_checks/skeleton_generator.h index 114371a..401c6ff3 100644 --- a/components/url_formatter/spoof_checks/skeleton_generator.h +++ b/components/url_formatter/spoof_checks/skeleton_generator.h
@@ -37,26 +37,18 @@ // 1. The hostname is "normalized" by removing its diacritics. This is done so // that more confusable hostnames can be detected than would be using the // plain ICU API. -// 2. Supplemental hostname strings are generated from the normalized hostname -// using a manually curated "multiple skeleton" table. This table has a -// one-to-many relationship between characters and their skeletons. The -// number of skeletons generated by this step is capped to a maximum number. -// This step is done before ICU's skeleton generation (which is many-to-one) -// so that we can generate more supplemental hostnames. For example, ICU -// maps "œ" to "oe". Since the character "œ" won't appear in the ICU -// skeleton, we can't produce supplemental skeletons for it. Therefore, we -// must map it to "oe" and "ce" before skeleton generation. -// 3. For each supplemental hostname, the following steps are performed: -// 4. Certain characters in the hostname are mapped to their confusable -// equivalents using a manually curated table (extra confusible mapper). This -// table has a many-to-one relationship between characters and their -// skeletons. For example, the characters є, ҽ, ҿ, and ၔ are all +// 2. Certain characters in the normalized hostname are mapped to their +// confusable equivalents using a manually curated table (extra confusable +// mapper). This table has a many-to-one relationship between characters and +// their skeletons. For example, the characters є, ҽ, ҿ, and ၔ are all // mapped to Latin lowercase e. -// 5. The hostname is passed to ICU to generate actual skeleton strings. -// 6. If the character U+04CF (ӏ) is present in the skeleton, another skeleton +// 3. The hostname is passed to ICU to generate actual skeleton strings. +// 3. If the character U+04CF (ӏ) is present in the skeleton, another skeleton // is generated by mapping it to lowercase L (U+6C). -// 7. The final output is a Skeletons instance which contains one or more -// skeleton strings that represent the input hostname. +// 4. Finally, alternative skeletons are generated from the skeleton set using +// a manually curated "multiple skeleton" table. This table has a one-to-many +// relationship between characters and their skeletons. The number of +// skeletons generated by this step is capped to a maximum number. class SkeletonGenerator { public: explicit SkeletonGenerator(const USpoofChecker* checker); @@ -76,12 +68,11 @@ std::u16string MaybeRemoveDiacritics(base::StringPiece16 hostname); // Returns the set of alternative strings using the one-to-many string - // mapping provided in `mapping`. Generates at most `max_alternatives` strings - // from the input string. - static base::flat_set<std::u16string> GenerateSupplementalHostnames( - base::StringPiece16 input, - size_t max_alternatives, - const SkeletonMap& mapping); + // mapping. Generates at most `max_alternatives` strings from the input + // string. + static Skeletons GenerateSupplementalSkeletons(base::StringPiece skeleton, + size_t max_alternatives, + const SkeletonMap& mapping); private: // Adds an additional mapping from |src_char| to |mapped_char| when generating @@ -99,9 +90,6 @@ std::unique_ptr<icu::Transliterator> diacritic_remover_; std::unique_ptr<icu::Transliterator> extra_confusable_mapper_; - // Map of characters to their skeletons. This map is manually curated. - std::map<char16_t, Skeletons> character_map_; - raw_ptr<const USpoofChecker> checker_; };
diff --git a/components/viz/common/features.cc b/components/viz/common/features.cc index 7f83f77d..f66e1d5f 100644 --- a/components/viz/common/features.cc +++ b/components/viz/common/features.cc
@@ -42,7 +42,7 @@ const base::Feature kEnableOverlayPrioritization { "EnableOverlayPrioritization", -#if BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA) +#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) base::FEATURE_ENABLED_BY_DEFAULT #else base::FEATURE_DISABLED_BY_DEFAULT
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc index 79a44687..51c829c9 100644 --- a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc +++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
@@ -894,35 +894,56 @@ // We require a begin frame if there's a callback pending, or if the client // requested it, or if the client needs to get some frame timing details. - bool needs_begin_frame = + needs_begin_frame_ = (client_needs_begin_frame_ || !frame_timing_details_.empty() || !pending_surfaces_.empty() || (compositor_frame_callback_ && !callback_received_begin_frame_) || - surface_animation_manager_.NeedsBeginFrame()) && - !bundle_id_.has_value(); + surface_animation_manager_.NeedsBeginFrame()); - if (needs_begin_frame == added_frame_observer_) + if (bundle_id_.has_value()) { + // When bundled with other sinks, observation of BeginFrame notifications is + // always delegated to the bundle. + if (added_frame_observer_) { + StopObservingBeginFrameSource(); + } + if (auto* bundle = frame_sink_manager_->GetFrameSinkBundle(*bundle_id_)) { + bundle->SetSinkNeedsBeginFrame(frame_sink_id_.sink_id(), + needs_begin_frame_); + } + return; + } + + if (needs_begin_frame_ == added_frame_observer_) return; - added_frame_observer_ = needs_begin_frame; - if (needs_begin_frame) { - begin_frame_source_->AddObserver(this); - if (power_mode_voter_) { - power_mode_voter_->VoteFor( - frame_sink_type_ == mojom::CompositorFrameSinkType::kMediaStream || - frame_sink_type_ == mojom::CompositorFrameSinkType::kVideo - ? power_scheduler::PowerMode::kVideoPlayback - : power_scheduler::PowerMode::kAnimation); - } + if (needs_begin_frame_) { + StartObservingBeginFrameSource(); } else { - begin_frame_source_->RemoveObserver(this); - if (power_mode_voter_) { - power_mode_voter_->ResetVoteAfterTimeout( - frame_sink_type_ == mojom::CompositorFrameSinkType::kMediaStream || - frame_sink_type_ == mojom::CompositorFrameSinkType::kVideo - ? power_scheduler::PowerModeVoter::kVideoTimeout - : power_scheduler::PowerModeVoter::kAnimationTimeout); - } + StopObservingBeginFrameSource(); + } +} + +void CompositorFrameSinkSupport::StartObservingBeginFrameSource() { + added_frame_observer_ = true; + begin_frame_source_->AddObserver(this); + if (power_mode_voter_) { + power_mode_voter_->VoteFor( + frame_sink_type_ == mojom::CompositorFrameSinkType::kMediaStream || + frame_sink_type_ == mojom::CompositorFrameSinkType::kVideo + ? power_scheduler::PowerMode::kVideoPlayback + : power_scheduler::PowerMode::kAnimation); + } +} + +void CompositorFrameSinkSupport::StopObservingBeginFrameSource() { + added_frame_observer_ = false; + begin_frame_source_->RemoveObserver(this); + if (power_mode_voter_) { + power_mode_voter_->ResetVoteAfterTimeout( + frame_sink_type_ == mojom::CompositorFrameSinkType::kMediaStream || + frame_sink_type_ == mojom::CompositorFrameSinkType::kVideo + ? power_scheduler::PowerModeVoter::kVideoTimeout + : power_scheduler::PowerModeVoter::kAnimationTimeout); } }
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.h b/components/viz/service/frame_sinks/compositor_frame_sink_support.h index 9dc79a4b3..f3a5548 100644 --- a/components/viz/service/frame_sinks/compositor_frame_sink_support.h +++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.h
@@ -107,6 +107,8 @@ return frame_timing_details_; } + bool needs_begin_frame() const { return needs_begin_frame_; } + [[nodiscard]] FrameTimingDetailsMap TakeFrameTimingDetailsMap(); // Viz hit-test setup is only called when |is_root_| is true (except on @@ -271,6 +273,8 @@ bool IsRoot() const override; void UpdateNeedsBeginFramesInternal(); + void StartObservingBeginFrameSource(); + void StopObservingBeginFrameSource(); // For the sync API calls, if we are blocking a client callback, runs it once // BeginFrame and FrameAck are done. @@ -332,6 +336,9 @@ // Whether a request for begin frames has been issued. bool client_needs_begin_frame_ = false; + // Whether the sink currently needs begin frames for any reason. + bool needs_begin_frame_ = false; + // Whether or not a frame observer has been added. bool added_frame_observer_ = false;
diff --git a/components/viz/service/frame_sinks/frame_sink_bundle_impl.cc b/components/viz/service/frame_sinks/frame_sink_bundle_impl.cc index 4bf3b45..22346e8 100644 --- a/components/viz/service/frame_sinks/frame_sink_bundle_impl.cc +++ b/components/viz/service/frame_sinks/frame_sink_bundle_impl.cc
@@ -23,26 +23,56 @@ // bundled CompositorFrameSink clients who all share a common BeginFrameSource. // FrameSinkBundleImpls may own any number of SinkGroups, and groups are created // or destroyed as needed when a sink is added to or removed from the bundle. +// +// Note that the BeginFrameSource is only observed by this SinkGroup while there +// are active FrameSinks present who have explicitly indicated a need for +// BeginFrame notifications. This avoids generation and processing of unused +// frame events which might otherwise incur substantial overhead. class FrameSinkBundleImpl::SinkGroup : public BeginFrameObserver { public: SinkGroup(FrameSinkManagerImpl& manager, FrameSinkBundleImpl& bundle, BeginFrameSource& source, mojom::FrameSinkBundleClient& client) - : manager_(manager), bundle_(bundle), source_(source), client_(client) { - source_.AddObserver(this); - } + : manager_(manager), bundle_(bundle), source_(source), client_(client) {} - ~SinkGroup() override { source_.RemoveObserver(this); } + ~SinkGroup() override { + if (is_observing_begin_frame_) { + source_.RemoveObserver(this); + } + } bool IsEmpty() const { return frame_sinks_.empty(); } - void AddFrameSink(uint32_t sink_id) { frame_sinks_.insert(sink_id); } + void AddFrameSink(uint32_t sink_id) { + frame_sinks_.insert(sink_id); + + FrameSinkId id(bundle_.id().client_id(), sink_id); + if (auto* support = manager_.GetFrameSinkForId(id)) { + if (support->needs_begin_frame()) { + frame_sinks_needing_begin_frame_.insert(sink_id); + UpdateBeginFrameObservation(); + } + } + } void RemoveFrameSink(uint32_t sink_id) { frame_sinks_.erase(sink_id); unacked_submissions_.erase(sink_id); FlushMessages(); + + frame_sinks_needing_begin_frame_.erase(sink_id); + UpdateBeginFrameObservation(); + } + + void SetNeedsBeginFrame(uint32_t sink_id, bool needs_begin_frame) { + if (needs_begin_frame) { + frame_sinks_needing_begin_frame_.insert(sink_id); + } else { + frame_sinks_needing_begin_frame_.erase(sink_id); + } + + UpdateBeginFrameObservation(); } void WillSubmitFrame(uint32_t sink_id) { @@ -136,6 +166,23 @@ } private: + void UpdateBeginFrameObservation() { + bool should_observe_begin_frame = !frame_sinks_needing_begin_frame_.empty(); + if (should_observe_begin_frame && !is_observing_begin_frame_) { + // NOTE: It's important to set this flag before adding the observer, + // because AddObserver() can synchronously enter CFSS::OnBeginFrame(), + // which can in turn re-enter this method. + is_observing_begin_frame_ = true; + source_.AddObserver(this); + return; + } + + if (is_observing_begin_frame_ && !should_observe_begin_frame) { + source_.RemoveObserver(this); + is_observing_begin_frame_ = false; + } + } + FrameSinkManagerImpl& manager_; FrameSinkBundleImpl& bundle_; BeginFrameSource& source_; @@ -146,6 +193,8 @@ std::vector<mojom::BundledReturnedResourcesPtr> pending_reclaimed_resources_; std::vector<mojom::BeginFrameInfoPtr> pending_on_begin_frames_; std::set<uint32_t> frame_sinks_; + std::set<uint32_t> frame_sinks_needing_begin_frame_; + bool is_observing_begin_frame_ = false; // Tracks which sinks in the group are still expecting an ack for a previously // submitted frame. @@ -169,6 +218,13 @@ FrameSinkBundleImpl::~FrameSinkBundleImpl() = default; +void FrameSinkBundleImpl::SetSinkNeedsBeginFrame(uint32_t sink_id, + bool needs_begin_frame) { + if (auto* group = GetSinkGroup(sink_id)) { + group->SetNeedsBeginFrame(sink_id, needs_begin_frame); + } +} + void FrameSinkBundleImpl::AddFrameSink(CompositorFrameSinkSupport* support) { uint32_t sink_id = support->frame_sink_id().sink_id(); auto* source = support->begin_frame_source();
diff --git a/components/viz/service/frame_sinks/frame_sink_bundle_impl.h b/components/viz/service/frame_sinks/frame_sink_bundle_impl.h index 85d1de3a..f7b958ad 100644 --- a/components/viz/service/frame_sinks/frame_sink_bundle_impl.h +++ b/components/viz/service/frame_sinks/frame_sink_bundle_impl.h
@@ -51,6 +51,11 @@ const FrameSinkBundleId& id() const { return id_; } + // Called by the identified sink itself to notify the bundle that the sink + // needs (or no longer needs) BeginFrame notifications. This is distinct from + // SetNeedsBeginFrame(), as the latter is only called by clients. + void SetSinkNeedsBeginFrame(uint32_t sink_id, bool needs_begin_frame); + void AddFrameSink(CompositorFrameSinkSupport* support); void UpdateFrameSink(CompositorFrameSinkSupport* support, BeginFrameSource* old_source);
diff --git a/components/viz/service/frame_sinks/frame_sink_bundle_impl_unittest.cc b/components/viz/service/frame_sinks/frame_sink_bundle_impl_unittest.cc index 87e67a7..0b8095e 100644 --- a/components/viz/service/frame_sinks/frame_sink_bundle_impl_unittest.cc +++ b/components/viz/service/frame_sinks/frame_sink_bundle_impl_unittest.cc
@@ -266,6 +266,9 @@ FrameSinkManagerImpl& manager() { return manager_; } TestBundleClient& test_client() { return test_client_; } mojo::Remote<mojom::FrameSinkBundle>& bundle() { return bundle_; } + FakeExternalBeginFrameSource& begin_frame_source() { + return begin_frame_source_; + } private: const gpu::SyncToken frame_sync_token_{MakeVerifiedSyncToken(42)}; @@ -300,10 +303,18 @@ } TEST_F(FrameSinkBundleImplTest, OnBeginFrame) { + // By default the bundle does not observe the BeginFrameSource. The only + // observer is the (non-bundled) main-frame sink. + EXPECT_EQ(1u, begin_frame_source().num_observers()); + TestFrameSink frame_a(manager(), kSubFrameA, kMainFrame, kBundleId); TestFrameSink frame_b(manager(), kSubFrameB, kMainFrame, kBundleId); TestFrameSink frame_c(manager(), kSubFrameC, kMainFrame, kBundleId); + // The bundle should observe the BeginFrameSource on behalf of all its sinks, + // so the only observers should now be the main-frame sink and the bundle. + EXPECT_EQ(2u, begin_frame_source().num_observers()); + // OnBeginFrame() should elicit a single batch of notifications to the bundle // client, with a notification for each frame in the bundle. std::vector<mojom::BeginFrameInfoPtr> begin_frames; @@ -320,6 +331,14 @@ test_client().WaitForNextFlush(nullptr, &begin_frames, nullptr); EXPECT_THAT(begin_frames, UnorderedElementsAre(ForSink(kSubFrameA), ForSink(kSubFrameC))); + + // Finally, if all sinks unsubscribe from BeginFrame notifications, the bundle + // should stop observing the BeginFrameSource. + EXPECT_EQ(2u, begin_frame_source().num_observers()); + manager().GetFrameSinkForId(kSubFrameA)->SetNeedsBeginFrame(false); + EXPECT_EQ(2u, begin_frame_source().num_observers()); + manager().GetFrameSinkForId(kSubFrameC)->SetNeedsBeginFrame(false); + EXPECT_EQ(1u, begin_frame_source().num_observers()); } TEST_F(FrameSinkBundleImplTest, SubmitAndAck) {
diff --git a/components/zucchini/BUILD.gn b/components/zucchini/BUILD.gn index 9fa2ea9..6f55fd0 100644 --- a/components/zucchini/BUILD.gn +++ b/components/zucchini/BUILD.gn
@@ -76,8 +76,6 @@ "patch_utils.h", "patch_writer.cc", "patch_writer.h", - "reference_bytes_mixer.cc", - "reference_bytes_mixer.h", "reference_set.cc", "reference_set.h", "rel32_finder.cc",
diff --git a/components/zucchini/disassembler.cc b/components/zucchini/disassembler.cc index 4a210acd..beb5f9ab 100644 --- a/components/zucchini/disassembler.cc +++ b/components/zucchini/disassembler.cc
@@ -42,6 +42,15 @@ return (disasm->*writer_factory_)(image); } +std::unique_ptr<ReferenceMixer> ReferenceGroup::GetMixer( + ConstBufferView old_image, + ConstBufferView new_image, + Disassembler* disasm) const { + if (mixer_factory_) + return (disasm->*mixer_factory_)(old_image, new_image); + return nullptr; +} + /******** Disassembler ********/ Disassembler::Disassembler(int num_equivalence_iterations)
diff --git a/components/zucchini/disassembler.h b/components/zucchini/disassembler.h index 48ee0fb..c676e60 100644 --- a/components/zucchini/disassembler.h +++ b/components/zucchini/disassembler.h
@@ -102,6 +102,10 @@ using WriterFactory = std::unique_ptr<ReferenceWriter> (Disassembler::*)( MutableBufferView image); + // Member function pointer used to obtain a ReferenceMixer. + using MixerFactory = std::unique_ptr<ReferenceMixer> ( + Disassembler::*)(ConstBufferView old_image, ConstBufferView new_image); + // RefinedGeneratorFactory and RefinedReceptorFactory don't have to be // identical to GeneratorFactory and ReceptorFactory, but they must be // convertible. As a result, they can be pointer to member function of a @@ -114,6 +118,18 @@ reader_factory_(static_cast<ReaderFactory>(reader_factory)), writer_factory_(static_cast<WriterFactory>(writer_factory)) {} + template <class RefinedReaderFactory, + class RefinedWriterFactory, + class RefinedMixerFactory> + ReferenceGroup(ReferenceTypeTraits traits, + RefinedReaderFactory reader_factory, + RefinedWriterFactory writer_factory, + RefinedMixerFactory mixer_factory) + : traits_(traits), + reader_factory_(static_cast<ReaderFactory>(reader_factory)), + writer_factory_(static_cast<WriterFactory>(writer_factory)), + mixer_factory_(static_cast<MixerFactory>(mixer_factory)) {} + // Returns a reader for all references in the binary. // Invalidates any other writer or reader previously obtained for |disasm|. std::unique_ptr<ReferenceReader> GetReader(Disassembler* disasm) const; @@ -131,6 +147,12 @@ std::unique_ptr<ReferenceWriter> GetWriter(MutableBufferView image, Disassembler* disasm) const; + // Returns mixer for references between |old_image| and |new_image|, assuming + // they both contain the same type of executable as |disasm|. + std::unique_ptr<ReferenceMixer> GetMixer(ConstBufferView old_image, + ConstBufferView new_image, + Disassembler* disasm) const; + // Returns traits describing the reference type. const ReferenceTypeTraits& traits() const { return traits_; } @@ -147,6 +169,7 @@ ReferenceTypeTraits traits_; ReaderFactory reader_factory_ = nullptr; WriterFactory writer_factory_ = nullptr; + MixerFactory mixer_factory_ = nullptr; }; } // namespace zucchini
diff --git a/components/zucchini/disassembler_elf.cc b/components/zucchini/disassembler_elf.cc index 22a29bab..524d118f 100644 --- a/components/zucchini/disassembler_elf.cc +++ b/components/zucchini/disassembler_elf.cc
@@ -614,6 +614,14 @@ image); } +template <class TRAITS> +template <class ADDR_TRAITS> +std::unique_ptr<ReferenceMixer> DisassemblerElfArm<TRAITS>::MakeMixRel32( + ConstBufferView src_image, + ConstBufferView dst_image) { + return std::make_unique<Rel32MixerArm<ADDR_TRAITS>>(src_image, dst_image); +} + /******** DisassemblerElfAArch32 ********/ DisassemblerElfAArch32::DisassemblerElfAArch32() = default; @@ -637,30 +645,40 @@ &DisassemblerElfAArch32::MakeReadRel32< AArch32Rel32Translator::AddrTraits_A24>, &DisassemblerElfAArch32::MakeWriteRel32< + AArch32Rel32Translator::AddrTraits_A24>, + &DisassemblerElfAArch32::MakeMixRel32< AArch32Rel32Translator::AddrTraits_A24>}, {ReferenceTypeTraits{2, TypeTag(AArch32ReferenceType::kRel32_T8), PoolTag(ArmReferencePool::kPoolRel32)}, &DisassemblerElfAArch32::MakeReadRel32< AArch32Rel32Translator::AddrTraits_T8>, &DisassemblerElfAArch32::MakeWriteRel32< + AArch32Rel32Translator::AddrTraits_T8>, + &DisassemblerElfAArch32::MakeMixRel32< AArch32Rel32Translator::AddrTraits_T8>}, {ReferenceTypeTraits{2, TypeTag(AArch32ReferenceType::kRel32_T11), PoolTag(ArmReferencePool::kPoolRel32)}, &DisassemblerElfAArch32::MakeReadRel32< AArch32Rel32Translator::AddrTraits_T11>, &DisassemblerElfAArch32::MakeWriteRel32< + AArch32Rel32Translator::AddrTraits_T11>, + &DisassemblerElfAArch32::MakeMixRel32< AArch32Rel32Translator::AddrTraits_T11>}, {ReferenceTypeTraits{4, TypeTag(AArch32ReferenceType::kRel32_T20), PoolTag(ArmReferencePool::kPoolRel32)}, &DisassemblerElfAArch32::MakeReadRel32< AArch32Rel32Translator::AddrTraits_T20>, &DisassemblerElfAArch32::MakeWriteRel32< + AArch32Rel32Translator::AddrTraits_T20>, + &DisassemblerElfAArch32::MakeMixRel32< AArch32Rel32Translator::AddrTraits_T20>}, {ReferenceTypeTraits{4, TypeTag(AArch32ReferenceType::kRel32_T24), PoolTag(ArmReferencePool::kPoolRel32)}, &DisassemblerElfAArch32::MakeReadRel32< AArch32Rel32Translator::AddrTraits_T24>, &DisassemblerElfAArch32::MakeWriteRel32< + AArch32Rel32Translator::AddrTraits_T24>, + &DisassemblerElfAArch32::MakeMixRel32< AArch32Rel32Translator::AddrTraits_T24>}, }; } @@ -725,18 +743,24 @@ &DisassemblerElfAArch64::MakeReadRel32< AArch64Rel32Translator::AddrTraits_Immd14>, &DisassemblerElfAArch64::MakeWriteRel32< + AArch64Rel32Translator::AddrTraits_Immd14>, + &DisassemblerElfAArch32::MakeMixRel32< AArch64Rel32Translator::AddrTraits_Immd14>}, {ReferenceTypeTraits{4, TypeTag(AArch64ReferenceType::kRel32_Immd19), PoolTag(ArmReferencePool::kPoolRel32)}, &DisassemblerElfAArch64::MakeReadRel32< AArch64Rel32Translator::AddrTraits_Immd19>, &DisassemblerElfAArch64::MakeWriteRel32< + AArch64Rel32Translator::AddrTraits_Immd19>, + &DisassemblerElfAArch32::MakeMixRel32< AArch64Rel32Translator::AddrTraits_Immd19>}, {ReferenceTypeTraits{4, TypeTag(AArch64ReferenceType::kRel32_Immd26), PoolTag(ArmReferencePool::kPoolRel32)}, &DisassemblerElfAArch64::MakeReadRel32< AArch64Rel32Translator::AddrTraits_Immd26>, &DisassemblerElfAArch64::MakeWriteRel32< + AArch64Rel32Translator::AddrTraits_Immd26>, + &DisassemblerElfAArch32::MakeMixRel32< AArch64Rel32Translator::AddrTraits_Immd26>}, }; }
diff --git a/components/zucchini/disassembler_elf.h b/components/zucchini/disassembler_elf.h index 8b834fa2..7368e6e 100644 --- a/components/zucchini/disassembler_elf.h +++ b/components/zucchini/disassembler_elf.h
@@ -300,13 +300,17 @@ std::unique_ptr<ReferenceReader> MakeReadAbs32(offset_t lo, offset_t hi); std::unique_ptr<ReferenceWriter> MakeWriteAbs32(MutableBufferView image); - // Specialized Read/Write functions for different rel32 address types. + // Specialized Read/Write/Mix functions for different rel32 address types. template <class ADDR_TRAITS> std::unique_ptr<ReferenceReader> MakeReadRel32(offset_t lower, offset_t upper); template <class ADDR_TRAITS> std::unique_ptr<ReferenceWriter> MakeWriteRel32(MutableBufferView image); + template <class ADDR_TRAITS> + std::unique_ptr<ReferenceMixer> MakeMixRel32(ConstBufferView old_image, + ConstBufferView new_image); + protected: // Sorted file offsets of rel32 locations for each rel32 address type. std::deque<offset_t>
diff --git a/components/zucchini/image_utils.h b/components/zucchini/image_utils.h index 748e20b2..5272c5f 100644 --- a/components/zucchini/image_utils.h +++ b/components/zucchini/image_utils.h
@@ -108,6 +108,56 @@ virtual void PutNext(Reference reference) = 0; }; +// References encoding may be quite complex in some architectures (e.g., ARM), +// requiring bit-level manipulation. In general, bits in a reference body fall +// under 2 categories: +// * Operation bits: Instruction op code, conditionals, or structural data. +// * Payload bits: Actual target data of the reference. These may be absolute, +// or be displacements relative to instruction pointer / program counter. +// During patch application, +// Old reference bytes = {old operation, old payload}, +// is transformed to +// New reference bytes = {new operation, new payload}. +// New image bytes are written by three sources: +// (1) Direct copy from old image to new image for matched blocks. +// (2) Bytewise diff correction. +// (3) Dedicated reference target correction. +// +// For references whose operation and payload bits are stored in easily +// separable bytes (e.g., rel32 reference in X86), (2) can exclude payload bits. +// So during patch application, (1) naively copies everything, (2) fixes +// operation bytes only, and (3) fixes payload bytes only. +// +// For architectures with references whose operation and payload bits may mix +// within shared bytes (e.g., ARM rel32), a dilemma arises: +// * (2) cannot ignores shared bytes, since otherwise new operation bits would +// not properly transfer. +// * Having (2) always overwrite these bytes would reduce the benefits of +// reference correction, since references are likely to change. +// +// Our solution applies a hybrid approach: For each matching old / new reference +// pair, define: +// Mixed reference bytes = {new operation, old payload}, +// +// During patch generation, we compute bytewise correction from old reference +// bytes to the mixed reference bytes. So during patch application, (2) only +// corrects operation bit changes (and skips if they don't change), and (3) +// overwrites old payload bits to new payload bits. + +// Interface for mixed reference byte generation. This base class +// serves as a stub. Architectures whose references store operation bits and +// payload bits can share common bytes (e.g., ARM rel32) should override this. +class ReferenceMixer { + public: + virtual ~ReferenceMixer() = default; + + // Computes mixed reference bytes by combining (a) "payload bits" from an + // "old" reference at |old_offset| with (b) "operation bits" from a "new" + // reference at |new_offset|. Returns the result as ConstBufferView, which is + // valid only until the next call to Mix(). + virtual ConstBufferView Mix(offset_t old_offset, offset_t new_offset) = 0; +}; + // An Equivalence is a block of length |length| that approximately match in // |old_image| at an offset of |src_offset| and in |new_image| at an offset of // |dst_offset|.
diff --git a/components/zucchini/reference_bytes_mixer.cc b/components/zucchini/reference_bytes_mixer.cc deleted file mode 100644 index 6855853..0000000 --- a/components/zucchini/reference_bytes_mixer.cc +++ /dev/null
@@ -1,150 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "components/zucchini/reference_bytes_mixer.h" - -#include <algorithm> - -#include "base/check_op.h" -#include "base/logging.h" -#include "base/notreached.h" -#include "components/zucchini/disassembler.h" -#include "components/zucchini/disassembler_elf.h" - -namespace zucchini { - -/******** ReferenceBytesMixer ********/ - -// Default implementation is a stub, i.e., for architectures whose references -// have operation bits and payload bits stored in separate bytes. So during -// patch application, payload bits are copied for matched blocks, ignored by -// bytewise corrections, and fixed by reference target corrections. -ReferenceBytesMixer::ReferenceBytesMixer() {} - -ReferenceBytesMixer::~ReferenceBytesMixer() = default; - -// static. -std::unique_ptr<ReferenceBytesMixer> ReferenceBytesMixer::Create( - const Disassembler& src_dis, - const Disassembler& dst_dis) { - ExecutableType exe_type = src_dis.GetExeType(); - DCHECK_EQ(exe_type, dst_dis.GetExeType()); - if (exe_type == kExeTypeElfAArch32) - return std::make_unique<ReferenceBytesMixerElfArm>(exe_type); - if (exe_type == kExeTypeElfAArch64) - return std::make_unique<ReferenceBytesMixerElfArm>(exe_type); - return std::make_unique<ReferenceBytesMixer>(); -} - -// Stub implementation. -int ReferenceBytesMixer::NumBytes(uint8_t type) const { - return 0; -} - -// Base class implementation is a stub that should not be called. -ConstBufferView ReferenceBytesMixer::Mix(uint8_t type, - ConstBufferView old_view, - offset_t old_offset, - ConstBufferView new_view, - offset_t new_offset) { - NOTREACHED() << "Stub."; - return ConstBufferView(); -} - -/******** ReferenceBytesMixerElfArm ********/ - -ReferenceBytesMixerElfArm::ReferenceBytesMixerElfArm(ExecutableType exe_type) - : exe_type_(exe_type), out_buffer_(4) {} // 4 is a bound on NumBytes(). - -ReferenceBytesMixerElfArm::~ReferenceBytesMixerElfArm() = default; - -int ReferenceBytesMixerElfArm::NumBytes(uint8_t type) const { - if (exe_type_ == kExeTypeElfAArch32) { - switch (type) { - case AArch32ReferenceType::kRel32_A24: // Falls through. - case AArch32ReferenceType::kRel32_T20: - case AArch32ReferenceType::kRel32_T24: - return 4; - case AArch32ReferenceType::kRel32_T8: // Falls through. - case AArch32ReferenceType::kRel32_T11: - return 2; - } - } else if (exe_type_ == kExeTypeElfAArch64) { - switch (type) { - case AArch64ReferenceType::kRel32_Immd14: // Falls through. - case AArch64ReferenceType::kRel32_Immd19: - case AArch64ReferenceType::kRel32_Immd26: - return 4; - } - } - return 0; -} - -ConstBufferView ReferenceBytesMixerElfArm::Mix(uint8_t type, - ConstBufferView old_view, - offset_t old_offset, - ConstBufferView new_view, - offset_t new_offset) { - int num_bytes = NumBytes(type); - ConstBufferView::const_iterator new_it = new_view.begin() + new_offset; - DCHECK_LE(static_cast<size_t>(num_bytes), out_buffer_.size()); - MutableBufferView out_buffer_view(&out_buffer_[0], num_bytes); - std::copy(new_it, new_it + num_bytes, out_buffer_view.begin()); - - ArmCopyDispFun copier = GetCopier(type); - DCHECK_NE(copier, nullptr); - - if (!copier(old_view, old_offset, out_buffer_view, 0U)) { - // Failed to mix old payload bits with new operation bits. The main cause of - // of this rare failure is when BL (encoding T1) with payload bits - // representing disp % 4 == 2 transforms into BLX (encoding T2). Error - // arises because BLX requires payload bits to have disp == 0 (mod 4). - // Mixing failures are not fatal to patching; we simply fall back to direct - // copy and forgo benefits from mixing for these cases. - // TODO(huangs, etiennep): Ongoing discussion on whether we should just - // nullify all payload disp so we won't have to deal with this case, but at - // the cost of having Zucchini-apply do more work. - static int output_quota = 10; - if (output_quota > 0) { - LOG(WARNING) << "Reference byte mix failed with type = " - << static_cast<uint32_t>(type) << "." << std::endl; - --output_quota; - if (!output_quota) - LOG(WARNING) << "(Additional output suppressed)"; - } - // Fall back to direct copy. - std::copy(new_it, new_it + num_bytes, out_buffer_view.begin()); - } - return ConstBufferView(out_buffer_view); -} - -ArmCopyDispFun ReferenceBytesMixerElfArm::GetCopier(uint8_t type) const { - if (exe_type_ == kExeTypeElfAArch32) { - switch (type) { - case AArch32ReferenceType::kRel32_A24: - return ArmCopyDisp<AArch32Rel32Translator::AddrTraits_A24>; - case AArch32ReferenceType::kRel32_T8: - return ArmCopyDisp<AArch32Rel32Translator::AddrTraits_T8>; - case AArch32ReferenceType::kRel32_T11: - return ArmCopyDisp<AArch32Rel32Translator::AddrTraits_T11>; - case AArch32ReferenceType::kRel32_T20: - return ArmCopyDisp<AArch32Rel32Translator::AddrTraits_T20>; - case AArch32ReferenceType::kRel32_T24: - return ArmCopyDisp<AArch32Rel32Translator::AddrTraits_T24>; - } - } else if (exe_type_ == kExeTypeElfAArch64) { - switch (type) { - case AArch64ReferenceType::kRel32_Immd14: - return ArmCopyDisp<AArch64Rel32Translator::AddrTraits_Immd14>; - case AArch64ReferenceType::kRel32_Immd19: - return ArmCopyDisp<AArch64Rel32Translator::AddrTraits_Immd19>; - case AArch64ReferenceType::kRel32_Immd26: - return ArmCopyDisp<AArch64Rel32Translator::AddrTraits_Immd26>; - } - } - DLOG(FATAL) << "NOTREACHED"; - return nullptr; -} - -} // namespace zucchini
diff --git a/components/zucchini/reference_bytes_mixer.h b/components/zucchini/reference_bytes_mixer.h deleted file mode 100644 index f20b0ef9..0000000 --- a/components/zucchini/reference_bytes_mixer.h +++ /dev/null
@@ -1,118 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef COMPONENTS_ZUCCHINI_REFERENCE_BYTES_MIXER_H_ -#define COMPONENTS_ZUCCHINI_REFERENCE_BYTES_MIXER_H_ - -#include <stdint.h> - -#include <memory> - -#include "components/zucchini/buffer_view.h" -#include "components/zucchini/image_utils.h" -#include "components/zucchini/rel32_utils.h" - -namespace zucchini { - -class Disassembler; - -// References encoding may be quite complex in some architectures (e.g., ARM), -// requiring bit-level manipulation. In general, bits in a reference body fall -// under 2 categories: -// - Operation bits: Instruction op code, conditionals, or structural data. -// - Payload bits: Actual target data of the reference. These may be absolute, -// or be displacements relative to instruction pointer / program counter. -// During patch application, -// Old reference bytes = {old operation, old payload}, -// is transformed to -// New reference bytes = {new operation, new payload}. -// New image bytes are written by three sources: -// (1) Direct copy from old image to new image for matched blocks. -// (2) Bytewise diff correction. -// (3) Dedicated reference target correction. -// -// For references whose operation and payload bits are stored in easily -// separable bytes (e.g., rel32 reference in X86), (2) can exclude payload bits. -// So during patch application, (1) naively copies everything, (2) fixes -// operation bytes only, and (3) fixes payload bytes only. -// -// For architectures with references whose operation and payload bits may mix -// within shared bytes (e.g., ARM rel32), a dilemma arises: -// - (2) cannot ignores shared bytes, since otherwise new operation bits not -// properly transfer. -// - Having (2) always overwrite these bytes would reduce the benefits of -// reference correction, since references are likely to change. -// -// Our solution applies a hybrid approach: For each matching old / new reference -// pair, define: -// Mixed reference bytes = {new operation, old payload}, -// -// During patch generation, we compute bytewise correction from old reference -// bytes to the mixed reference bytes. So during patch application, (2) only -// corrects operation bit changes (and skips if they don't change), and (3) -// overwrites old payload bits to new payload bits. - -// A base class for (stateful) mixed reference byte generation. This base class -// serves as a stub. Architectures whose references store operation bits and -// payload bits can share common bytes (e.g., ARM rel32) should override this. -class ReferenceBytesMixer { - public: - ReferenceBytesMixer(); - ReferenceBytesMixer(const ReferenceBytesMixer&) = delete; - const ReferenceBytesMixer& operator=(const ReferenceBytesMixer&) = delete; - virtual ~ReferenceBytesMixer(); - - // Returns a new ReferenceBytesMixer instance that's owned by the caller. - static std::unique_ptr<ReferenceBytesMixer> Create( - const Disassembler& src_dis, - const Disassembler& dst_dis); - - // Returns the number of bytes that need to be mixed for references with given - // |type|. Returns 0 if no mixing is required. - virtual int NumBytes(uint8_t type) const; - - // Computes mixed reference bytes by combining (a) "payload bits" from an - // "old" reference of |type| at |old_view[old_offset]| with (b) "operation - // bits" from a "new" reference of |type| at |new_view[new_offset]|. Returns - // the result as ConstBufferView, which is valid only until the next call to - // Mix(). - virtual ConstBufferView Mix(uint8_t type, - ConstBufferView old_view, - offset_t old_offset, - ConstBufferView new_view, - offset_t new_offset); -}; - -// In AArch32 and AArch64, instructions mix operation bits and payload bits in -// complex ways. This is the main use case of ReferenceBytesMixer. -class ReferenceBytesMixerElfArm : public ReferenceBytesMixer { - public: - // |exe_type| must be EXE_TYPE_ELF_ARM or EXE_TYPE_ELF_AARCH64. - explicit ReferenceBytesMixerElfArm(ExecutableType exe_type); - ReferenceBytesMixerElfArm(const ReferenceBytesMixerElfArm&) = delete; - const ReferenceBytesMixerElfArm& operator=(const ReferenceBytesMixerElfArm&) = - delete; - ~ReferenceBytesMixerElfArm() override; - - // ReferenceBytesMixer: - int NumBytes(uint8_t type) const override; - ConstBufferView Mix(uint8_t type, - ConstBufferView old_view, - offset_t old_offset, - ConstBufferView new_view, - offset_t new_offset) override; - - private: - ArmCopyDispFun GetCopier(uint8_t type) const; - - // For simplicity, 32-bit vs. 64-bit distinction is represented by state - // |exe_type_|, instead of creating derived classes. - const ExecutableType exe_type_; - - std::vector<uint8_t> out_buffer_; -}; - -} // namespace zucchini - -#endif // COMPONENTS_ZUCCHINI_REFERENCE_BYTES_MIXER_H_
diff --git a/components/zucchini/rel32_utils.cc b/components/zucchini/rel32_utils.cc index c22cb23..0ede84f 100644 --- a/components/zucchini/rel32_utils.cc +++ b/components/zucchini/rel32_utils.cc
@@ -64,4 +64,23 @@ image_.write<uint32_t>(ref.location, code); } +void OutputArmCopyDispFailure(uint32_t addr_type) { + // Failed to mix old payload bits with new operation bits. The main cause of + // this rare failure is when BL (encoding T1) with payload bits representing + // disp % 4 == 2 transforms into BLX (encoding T2). Error arises because BLX + // requires payload bits to have disp == 0 (mod 4). Mixing failures are not + // fatal to patching; we simply fall back to direct copy and forgo benefits + // from mixing for these cases. TODO(huangs, etiennep): Ongoing discussion on + // whether we should just nullify all payload disp so we won't have to deal + // with this case, but at the cost of having Zucchini-apply do more work. + static int output_quota = 10; + if (output_quota > 0) { + LOG(WARNING) << "Reference byte mix failed with type = " << addr_type << "." + << std::endl; + --output_quota; + if (!output_quota) + LOG(WARNING) << "(Additional output suppressed)"; + } +} + } // namespace zucchini
diff --git a/components/zucchini/rel32_utils.h b/components/zucchini/rel32_utils.h index f54c5cd..10921d0 100644 --- a/components/zucchini/rel32_utils.h +++ b/components/zucchini/rel32_utils.h
@@ -6,6 +6,7 @@ #define COMPONENTS_ZUCCHINI_REL32_UTILS_H_ #include <algorithm> +#include <array> #include <deque> #include <memory> @@ -148,14 +149,6 @@ AddressTranslator::OffsetToRvaCache offset_to_rva_; }; -// Type for specialized versions of ArmCopyDisp(). -// TODO(etiennep/huangs): Fold ReferenceByteMixer into Disassembler and remove -// direct function pointer usage. -using ArmCopyDispFun = bool (*)(ConstBufferView src_view, - offset_t src_idx, - MutableBufferView dst_view, - offset_t dst_idx); - // Copier that makes |*dst_it| similar to |*src_it| (both assumed to point to // rel32 instructions of type ADDR_TRAITS) by copying the displacement (i.e., // payload bits) from |src_it| to |dst_it|. If successful, updates |*dst_it|, @@ -179,6 +172,41 @@ return false; } +// Outputs error message (throttled) on ArmCopyDisp failure. +void OutputArmCopyDispFailure(uint32_t addr_type); + +// Mixer for ARM rel32 References of a specific type. +template <class ADDR_TRAITS> +class Rel32MixerArm : public ReferenceMixer { + using code_t = typename ADDR_TRAITS::code_t; + static constexpr size_t kCodeWidth = sizeof(code_t); + + public: + Rel32MixerArm(ConstBufferView src_image, ConstBufferView dst_image) + : src_image_(src_image), dst_image_(dst_image) {} + ~Rel32MixerArm() override = default; + + ConstBufferView Mix(offset_t src_offset, offset_t dst_offset) override { + ConstBufferView::const_iterator new_it = dst_image_.begin() + dst_offset; + MutableBufferView out_buffer_view(out_buffer_.data(), kCodeWidth); + std::copy(new_it, new_it + kCodeWidth, out_buffer_view.begin()); + + if (!ArmCopyDisp<ADDR_TRAITS>(src_image_, src_offset, out_buffer_view, + 0U)) { + OutputArmCopyDispFailure(static_cast<uint32_t>(ADDR_TRAITS::addr_type)); + // Fall back to direct copy. + std::copy(new_it, new_it + kCodeWidth, out_buffer_.begin()); + } + return ConstBufferView(out_buffer_.data(), kCodeWidth); + } + + private: + ConstBufferView src_image_; + ConstBufferView dst_image_; + + std::array<uint8_t, sizeof(code_t)> out_buffer_; +}; + } // namespace zucchini #endif // COMPONENTS_ZUCCHINI_REL32_UTILS_H_
diff --git a/components/zucchini/rel32_utils_unittest.cc b/components/zucchini/rel32_utils_unittest.cc index f4a6bde..e122680 100644 --- a/components/zucchini/rel32_utils_unittest.cc +++ b/components/zucchini/rel32_utils_unittest.cc
@@ -43,6 +43,11 @@ EXPECT_EQ(absl::nullopt, reader->GetNext()); // Nothing should be left. } +using ArmCopyDispFun = bool (*)(ConstBufferView src_view, + offset_t src_idx, + MutableBufferView dst_view, + offset_t dst_idx); + // Copies displacements from |bytes1| to |bytes2| and checks results against // |bytes_exp_1_to_2|. Then repeats for |*bytes2| , |*byte1|, and // |bytes_exp_2_to_1|. Empty expected bytes mean failure is expected. The copy
diff --git a/components/zucchini/zucchini_gen.cc b/components/zucchini/zucchini_gen.cc index 3735d0f..45b1f72 100644 --- a/components/zucchini/zucchini_gen.cc +++ b/components/zucchini/zucchini_gen.cc
@@ -24,7 +24,6 @@ #include "components/zucchini/image_index.h" #include "components/zucchini/imposed_ensemble_matcher.h" #include "components/zucchini/patch_writer.h" -#include "components/zucchini/reference_bytes_mixer.h" #include "components/zucchini/suffix_array.h" #include "components/zucchini/targets_affinity.h" @@ -119,12 +118,13 @@ return true; } -bool GenerateRawDelta(ConstBufferView old_image, - ConstBufferView new_image, - const EquivalenceMap& equivalence_map, - const ImageIndex& new_image_index, - ReferenceBytesMixer* reference_bytes_mixer, - PatchElementWriter* patch_writer) { +bool GenerateRawDelta( + ConstBufferView old_image, + ConstBufferView new_image, + const EquivalenceMap& equivalence_map, + const ImageIndex& new_image_index, + const std::map<TypeTag, std::unique_ptr<ReferenceMixer>>& reference_mixers, + PatchElementWriter* patch_writer) { RawDeltaSink raw_delta_sink; // Visit |equivalence_map| blocks in |new_image| order. Find and emit all @@ -139,24 +139,24 @@ DCHECK(new_image_index.IsToken(equivalence.dst_offset + i)); TypeTag type_tag = new_image_index.LookupType(equivalence.dst_offset + i); + ReferenceMixer* mixer = reference_mixers.at(type_tag).get(); + offset_t width = new_image_index.refs(type_tag).width(); // Reference delta has its own flow. On some architectures (e.g., x86) // this does not involve raw delta, so we skip. On other architectures // (e.g., ARM) references are mixed with other bits that may change, so // we need to "mix" data and store some changed bits into raw delta. - int num_bytes = reference_bytes_mixer->NumBytes(type_tag.value()); - if (num_bytes) { - ConstBufferView mixed_ref_bytes = reference_bytes_mixer->Mix( - type_tag.value(), old_image, equivalence.src_offset + i, - new_image, equivalence.dst_offset + i); - for (int j = 0; j < num_bytes; ++j) { + if (mixer) { + ConstBufferView mixed_reference = mixer->Mix( + equivalence.src_offset + i, equivalence.dst_offset + i); + for (offset_t j = 0; j < width; ++j) { int8_t diff = - mixed_ref_bytes[j] - old_image[equivalence.src_offset + i + j]; - if (diff) + mixed_reference[j] - old_image[equivalence.src_offset + i + j]; + if (diff != 0) raw_delta_sink.PutNext({base_copy_offset + i + j, diff}); } } - i += new_image_index.refs(type_tag).width(); + i += width; DCHECK_LE(i, equivalence.length); } else { int8_t diff = new_image[equivalence.dst_offset + i] - @@ -252,11 +252,11 @@ patch_writer->SetReferenceDeltaSink({}); - ReferenceBytesMixer no_op_bytes_mixer; + std::map<TypeTag, std::unique_ptr<ReferenceMixer>> reference_mixers; return GenerateEquivalencesAndExtraData(new_image, equivalences, patch_writer) && GenerateRawDelta(old_image, new_image, equivalences, new_image_index, - &no_op_bytes_mixer, patch_writer); + reference_mixers, patch_writer); } bool GenerateExecutableElement(ExecutableType exe_type, @@ -311,13 +311,20 @@ } } } + std::map<TypeTag, std::unique_ptr<ReferenceMixer>> reference_mixers; + std::vector<ReferenceGroup> ref_groups = old_disasm->MakeReferenceGroups(); + for (const auto& group : ref_groups) { + auto result = reference_mixers.emplace( + group.type_tag(), + group.GetMixer(old_image, new_image, old_disasm.get())); + DCHECK(result.second); + } + patch_writer->SetReferenceDeltaSink(std::move(reference_delta_sink)); - std::unique_ptr<ReferenceBytesMixer> reference_bytes_mixer = - ReferenceBytesMixer::Create(*old_disasm, *new_disasm); return GenerateEquivalencesAndExtraData(new_image, equivalences, patch_writer) && GenerateRawDelta(old_image, new_image, equivalences, new_image_index, - reference_bytes_mixer.get(), patch_writer); + reference_mixers, patch_writer); } status::Code GenerateBufferCommon(ConstBufferView old_image,
diff --git a/components/zucchini/zucchini_gen.h b/components/zucchini/zucchini_gen.h index ac28263..84e2f4b6 100644 --- a/components/zucchini/zucchini_gen.h +++ b/components/zucchini/zucchini_gen.h
@@ -17,7 +17,6 @@ class OffsetMapper; class ImageIndex; class PatchElementWriter; -class ReferenceBytesMixer; class ReferenceDeltaSink; class ReferenceSet; class TargetPool; @@ -44,12 +43,13 @@ // Writes raw delta between |old_image| and |new_image| matched by // |equivalence_map| to |patch_writer|, using |new_image_index| to ignore // reference bytes. -bool GenerateRawDelta(ConstBufferView old_image, - ConstBufferView new_image, - const EquivalenceMap& equivalence_map, - const ImageIndex& new_image_index, - ReferenceBytesMixer* reference_bytes_mixer, - PatchElementWriter* patch_writer); +bool GenerateRawDelta( + ConstBufferView old_image, + ConstBufferView new_image, + const EquivalenceMap& equivalence_map, + const ImageIndex& new_image_index, + const std::map<TypeTag, std::unique_ptr<ReferenceMixer>>& reference_mixers, + PatchElementWriter* patch_writer); // Writes reference delta between references from |old_refs| and from // |new_refs| to |patch_writer|. |projected_target_pool| contains projected
diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h index 3209677..f8ea8ad6 100644 --- a/content/browser/bad_message.h +++ b/content/browser/bad_message.h
@@ -297,6 +297,7 @@ RFH_BEFOREUNLOAD_HANDLER_NOT_ALLOWED_IN_FENCED_FRAME = 270, MSDH_GET_OPEN_DEVICE_USE_WITHOUT_FEATURE = 271, RFHI_SUBFRAME_NAV_WOULD_CHANGE_MAINFRAME_ORIGIN = 272, + FF_CREATE_WHILE_PRERENDERING = 273, // Please add new elements here. The naming convention is abbreviated class // name (e.g. RenderFrameHost becomes RFH) plus a unique description of the
diff --git a/content/browser/fenced_frame/fenced_frame.cc b/content/browser/fenced_frame/fenced_frame.cc index cbf337b..b75bd48d 100644 --- a/content/browser/fenced_frame/fenced_frame.cc +++ b/content/browser/fenced_frame/fenced_frame.cc
@@ -34,7 +34,8 @@ } // namespace FencedFrame::FencedFrame( - base::SafeRef<RenderFrameHostImpl> owner_render_frame_host) + base::SafeRef<RenderFrameHostImpl> owner_render_frame_host, + blink::mojom::FencedFrameMode mode) : web_contents_(static_cast<WebContentsImpl*>( WebContents::FromRenderFrameHost(&*owner_render_frame_host))), owner_render_frame_host_(owner_render_frame_host), @@ -50,7 +51,8 @@ /*render_widget_delegate=*/web_contents_, /*manager_delegate=*/web_contents_, /*page_delegate=*/web_contents_, - FrameTree::Type::kFencedFrame)) { + FrameTree::Type::kFencedFrame)), + mode_(mode) { scoped_refptr<SiteInstance> site_instance = SiteInstance::Create(web_contents_->GetBrowserContext()); // Note that even though this is happening in response to an event in the @@ -84,6 +86,11 @@ void FencedFrame::Navigate(const GURL& url, base::TimeTicks navigation_start_time) { + // We don't need guard against a bad message in the case of prerendering since + // we wouldn't even establish the mojo connection in that case. + DCHECK_NE(RenderFrameHost::LifecycleState::kPrerendering, + owner_render_frame_host_->GetLifecycleState()); + FrameTreeNode* inner_root = frame_tree_->root(); // TODO(crbug.com/1237552): Resolve the discussion around navigations being
diff --git a/content/browser/fenced_frame/fenced_frame.h b/content/browser/fenced_frame/fenced_frame.h index a046c9c..83cba30f 100644 --- a/content/browser/fenced_frame/fenced_frame.h +++ b/content/browser/fenced_frame/fenced_frame.h
@@ -35,7 +35,8 @@ public NavigationControllerDelegate { public: explicit FencedFrame( - base::SafeRef<RenderFrameHostImpl> owner_render_frame_host); + base::SafeRef<RenderFrameHostImpl> owner_render_frame_host, + blink::mojom::FencedFrameMode mode); ~FencedFrame() override; void Bind(mojo::PendingAssociatedReceiver<blink::mojom::FencedFrameOwnerHost> @@ -66,6 +67,8 @@ RenderFrameHostImpl* GetInnerRoot() { return frame_tree_->GetMainFrame(); } + blink::mojom::FencedFrameMode mode() const { return mode_; } + private: // NavigationControllerDelegate void NotifyNavigationStateChanged(InvalidateTypes changed_flags) override; @@ -113,6 +116,13 @@ // The FrameTree that we create to host the "inner" fenced frame contents. std::unique_ptr<FrameTree> frame_tree_; + // The `mode` attribute set on the fenced frame. The mode will stay the same + // across navigations to avoid privacy leak. Since each mode might have + // different access constraints, privacy leak might occur if the mode is + // mutable as a fenced frame can pass the information it learned in one mode + // to the other mode if mode was changed across navigations. + const blink::mojom::FencedFrameMode mode_; + // Receives messages from the frame owner element in Blink. mojo::AssociatedReceiver<blink::mojom::FencedFrameOwnerHost> receiver_{this}; };
diff --git a/content/browser/fenced_frame/fenced_frame_browsertest.cc b/content/browser/fenced_frame/fenced_frame_browsertest.cc index 244c05b..fd5d5c05 100644 --- a/content/browser/fenced_frame/fenced_frame_browsertest.cc +++ b/content/browser/fenced_frame/fenced_frame_browsertest.cc
@@ -265,12 +265,13 @@ void CreateFencedFrame( mojo::PendingAssociatedReceiver<blink::mojom::FencedFrameOwnerHost> pending_receiver, + blink::mojom::FencedFrameMode mode, CreateFencedFrameCallback callback) override { mojo::PendingAssociatedRemote<blink::mojom::FencedFrameOwnerHost> original_remote; GetForwardingInterface()->CreateFencedFrame( - original_remote.InitWithNewEndpointAndPassReceiver(), + original_remote.InitWithNewEndpointAndPassReceiver(), mode, std::move(callback)); std::vector<FencedFrame*> fenced_frames = render_frame_host_->GetFencedFrames();
diff --git a/content/browser/prerender/prerender_browsertest.cc b/content/browser/prerender/prerender_browsertest.cc index 3c60f8d..00b9d21 100644 --- a/content/browser/prerender/prerender_browsertest.cc +++ b/content/browser/prerender/prerender_browsertest.cc
@@ -5895,4 +5895,65 @@ NavigatePrimaryPage(kPrerenderingUrl); } +class PrerenderFencedFrameBrowserTest + : public PrerenderBrowserTest, + public testing::WithParamInterface<bool /* shadow_dom_fenced_frames */> { + public: + PrerenderFencedFrameBrowserTest() { + feature_list_.InitAndEnableFeatureWithParameters( + blink::features::kFencedFrames, + {{"implementation_type", GetParam() ? "shadow_dom" : "mparch"}}); + } + ~PrerenderFencedFrameBrowserTest() override = default; + + bool IsShadowDomImpl() const { return GetParam(); } + + private: + base::test::ScopedFeatureList feature_list_; +}; + +IN_PROC_BROWSER_TEST_P(PrerenderFencedFrameBrowserTest, + PrerenderFencedFrameBrowserTest) { + const GURL kInitialUrl = GetUrl("/empty.html"); + const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); + const GURL kFencedFrameUrl = GetUrl("/title1.html"); + constexpr char kAddFencedFrameScript[] = R"({ + const fenced_frame = document.createElement('fencedframe'); + fenced_frame.src = $1; + document.body.appendChild(fenced_frame); + })"; + + // We see a navigation to about:blank for Shadow DOM, but not MPArch, so we + // need to account for another navigation with that implementation. + const int kNumNavigations = IsShadowDomImpl() ? 4 : 3; + TestNavigationObserver nav_observer(web_contents(), kNumNavigations); + + ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); + + // Start a prerender. + int host_id = AddPrerender(kPrerenderingUrl); + auto* prerendered_rfh = GetPrerenderedMainFrameHost(host_id); + EXPECT_EQ(kPrerenderingUrl, nav_observer.last_navigation_url()); + EXPECT_TRUE(ExecJs(prerendered_rfh, + JsReplace(kAddFencedFrameScript, kFencedFrameUrl))); + // Since we've deferred creating the fenced frame delegate, we should see no + // child frames. + size_t child_frame_count = 0; + prerendered_rfh->ForEachRenderFrameHost( + base::BindLambdaForTesting([&](RenderFrameHostImpl* rfh) { + if (rfh != prerendered_rfh) + child_frame_count++; + })); + EXPECT_EQ(0lu, child_frame_count); + + NavigatePrimaryPage(kPrerenderingUrl); + EXPECT_EQ(kPrerenderingUrl, nav_observer.last_navigation_url()); + nav_observer.Wait(); + EXPECT_EQ(kFencedFrameUrl, nav_observer.last_navigation_url()); +} + +INSTANTIATE_TEST_SUITE_P(PrerenderFencedFrameBrowserTest, + PrerenderFencedFrameBrowserTest, + testing::Bool()); + } // namespace content
diff --git a/content/browser/renderer_host/frame_tree_browsertest.cc b/content/browser/renderer_host/frame_tree_browsertest.cc index 233a0b1..8294b79e 100644 --- a/content/browser/renderer_host/frame_tree_browsertest.cc +++ b/content/browser/renderer_host/frame_tree_browsertest.cc
@@ -2216,6 +2216,7 @@ EXPECT_TRUE(ExecJs(root, "var f = document.createElement('fencedframe');" + "f.mode = 'opaque-ads';" "document.body.appendChild(f);")); EXPECT_EQ(1U, root->child_count()); @@ -2241,11 +2242,11 @@ if (!test_case.expect_allowed) EXPECT_EQ("fenced-frame-src;", EvalJs(root, "violation")); - absl::optional<FrameTreeNode::FencedFrameMode> fenced_frame_mode = - fenced_frame_root_node->fenced_frame_mode(); + absl::optional<blink::mojom::FencedFrameMode> fenced_frame_mode = + fenced_frame_root_node->GetFencedFrameMode(); EXPECT_TRUE(fenced_frame_mode.has_value()); EXPECT_EQ(fenced_frame_mode.value(), - FrameTreeNode::FencedFrameMode::kOpaque); + blink::mojom::FencedFrameMode::kOpaqueAds); } }
diff --git a/content/browser/renderer_host/frame_tree_node.cc b/content/browser/renderer_host/frame_tree_node.cc index 6d6c142..ac13436 100644 --- a/content/browser/renderer_host/frame_tree_node.cc +++ b/content/browser/renderer_host/frame_tree_node.cc
@@ -48,6 +48,23 @@ base::LazyInstance<FrameTreeNodeIdMap>::DestructorAtExit g_frame_tree_node_id_map = LAZY_INSTANCE_INITIALIZER; +FencedFrame* FindFencedFrame(const FrameTreeNode* frame_tree_node) { + // TODO(crbug.com/1123606): Consider having a pointer to `FencedFrame` in + // `FrameTreeNode` or having a map between them. + + // Try and find the `FencedFrame` that `frame_tree_node` represents. + DCHECK(frame_tree_node->parent()); + std::vector<FencedFrame*> fenced_frames = + frame_tree_node->parent()->GetFencedFrames(); + for (FencedFrame* fenced_frame : fenced_frames) { + if (frame_tree_node->frame_tree_node_id() == + fenced_frame->GetOuterDelegateFrameTreeNodeId()) { + return fenced_frame; + } + } + return nullptr; +} + } // namespace // This observer watches the opener of its owner FrameTreeNode and clears the @@ -151,18 +168,7 @@ } if (is_outer_dummy_node) { - DCHECK(parent()); - // Try and find the `FencedFrame` that `this` represents. - std::vector<FencedFrame*> fenced_frames = parent()->GetFencedFrames(); - FencedFrame* doomed_fenced_frame = nullptr; - for (FencedFrame* fenced_frame : fenced_frames) { - if (frame_tree_node_id() == - fenced_frame->GetOuterDelegateFrameTreeNodeId()) { - doomed_fenced_frame = fenced_frame; - break; - } - } - + FencedFrame* doomed_fenced_frame = FindFencedFrame(this); // `doomed_fenced_frame` might not actually exist, because some outer dummy // `FrameTreeNode`s might correspond to `Portal`s, which do not have their // lifetime managed in the same way as `FencedFrames`. @@ -843,16 +849,21 @@ fenced_frame_nonce_ = nonce; } -void FrameTreeNode::SetFencedFrameModeIfNeeded( - FencedFrameMode fenced_frame_mode) { +absl::optional<blink::mojom::FencedFrameMode> +FrameTreeNode::GetFencedFrameMode() { if (!IsFencedFrameRoot()) - return; + return absl::nullopt; - // TODO(crbug.com/1123606): The 'mode' attribute cannot be changed once - // applied to a fenced frame. This will be enforced before this point so add - // a DCHECK here. + if (blink::features::IsFencedFramesShadowDOMBased()) + return pending_frame_policy_.fenced_frame_mode; - fenced_frame_mode_ = fenced_frame_mode; + FrameTreeNode* outer_delegate = render_manager()->GetOuterDelegateNode(); + DCHECK(outer_delegate); + + FencedFrame* fenced_frame = FindFencedFrame(outer_delegate); + DCHECK(fenced_frame); + + return fenced_frame->mode(); } bool FrameTreeNode::IsErrorPageIsolationEnabled() const {
diff --git a/content/browser/renderer_host/frame_tree_node.h b/content/browser/renderer_host/frame_tree_node.h index 93c771229..d73a443de 100644 --- a/content/browser/renderer_host/frame_tree_node.h +++ b/content/browser/renderer_host/frame_tree_node.h
@@ -67,15 +67,6 @@ virtual ~Observer() = default; }; - // Indicates whether the fenced frame url is opaque or not. - // - // TODO(https://crbug.com/1123606): Revisit where to define the mode when the - // 'mode' attribute is introduced. - enum class FencedFrameMode { - kOpaque, - kDefault, - }; - static const int kFrameTreeNodeInvalidId; // Returns the FrameTreeNode with the given global |frame_tree_node_id|, @@ -496,16 +487,9 @@ // by FrameTree::Init() or FrameTree::AddFrame(). void SetFencedFrameNonceIfNeeded(); - // Returns the fenced frame mode if `IsFencedFrameRoot()` returns true for - // `this`. Returns nullopt otherwise. See comments on `fenced_frame_mode_` for - // more details. - absl::optional<FencedFrameMode> fenced_frame_mode() { - return fenced_frame_mode_; - } - - // If applicable, set the fenced frame mode if it's not been set yet. Invoked - // by `NavigationRequest::BeginNavigation()`. - void SetFencedFrameModeIfNeeded(FencedFrameMode fenced_frame_mode); + // Returns the mode attribute set on the fenced frame if this is a fenced + // frame root, otherwise returns `absl::nullopt`. + absl::optional<blink::mojom::FencedFrameMode> GetFencedFrameMode(); // Helper for GetParentOrOuterDocument/GetParentOrOuterDocumentOrEmbedder. // Do not use directly. @@ -728,17 +712,6 @@ // partition will be used. absl::optional<base::UnguessableToken> fenced_frame_nonce_; - // Fenced Frames: - // Indicates whether the fenced frame is navigated to a urn:uuid or not. Not - // set if this frame is not fenced frame or it is a fenced frame but before - // `NavigationRequest::BeginNavigation()` is called which implicitly sets the - // mode. The mode will stay the same across navigations to avoid privacy leak. - // Since each mode might have different access constraints, privacy leak might - // occur if the mode is mutable as a fenced frame can pass the information it - // learned in one mode to the other mode if mode was changed across - // navigations. - absl::optional<FencedFrameMode> fenced_frame_mode_; - // Manages creation and swapping of RenderFrameHosts for this frame. // // This field needs to be declared last, because destruction of
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc index 8ae18e3..b1c45d1 100644 --- a/content/browser/renderer_host/navigation_request.cc +++ b/content/browser/renderer_host/navigation_request.cc
@@ -1808,14 +1808,9 @@ } } - bool need_url_mapping = NeedFencedFrameURLMapping(); - frame_tree_node_->SetFencedFrameModeIfNeeded( - need_url_mapping ? FrameTreeNode::FencedFrameMode::kOpaque - : FrameTreeNode::FencedFrameMode::kDefault); - // If this is a fenced frame with a urn:uuid, then convert it to a url before // starting the navigation; otherwise, proceed directly with the navigation. - if (need_url_mapping) { + if (NeedFencedFrameURLMapping()) { FencedFrameURLMapping& fenced_frame_urls_map = GetFencedFrameURLMap(); // If the mapping finishes synchronously, OnFencedFrameURLMappingComplete @@ -5058,8 +5053,8 @@ // [frame-src] or [fenced-frame-src] if (parent_policies) { - bool is_opaque_fenced_frame = frame_tree_node_->fenced_frame_mode() == - FrameTreeNode::FencedFrameMode::kOpaque; + bool is_opaque_fenced_frame = frame_tree_node_->GetFencedFrameMode() == + blink::mojom::FencedFrameMode::kOpaqueAds; if (!IsAllowedByCSPDirective( parent_policies->content_security_policies, &parent_context, frame_tree_node_->IsFencedFrameRoot()
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc index 9e820792..be78955 100644 --- a/content/browser/renderer_host/render_frame_host_impl.cc +++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -7203,7 +7203,18 @@ void RenderFrameHostImpl::CreateFencedFrame( mojo::PendingAssociatedReceiver<blink::mojom::FencedFrameOwnerHost> pending_receiver, + blink::mojom::FencedFrameMode mode, CreateFencedFrameCallback callback) { + // We should defer fenced frame creation during prerendering, so creation at + // this point is an error. + if (GetLifecycleState() == RenderFrameHost::LifecycleState::kPrerendering) { + bad_message::ReceivedBadMessage(GetProcess(), + bad_message::FF_CREATE_WHILE_PRERENDERING); + std::move(callback).Run(0, blink::mojom::FrameReplicationState::New(), + blink::RemoteFrameToken(), + base::UnguessableToken::Create()); + return; + } if (!blink::features::IsFencedFramesEnabled() || !blink::features::IsFencedFramesMPArchBased()) { bad_message::ReceivedBadMessage( @@ -7225,7 +7236,7 @@ return; } fenced_frames_.push_back( - std::make_unique<FencedFrame>(weak_ptr_factory_.GetSafeRef())); + std::make_unique<FencedFrame>(weak_ptr_factory_.GetSafeRef(), mode)); FencedFrame* fenced_frame = fenced_frames_.back().get(); fenced_frame->Bind(std::move(pending_receiver));
diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h index d7eae7b..e357de40 100644 --- a/content/browser/renderer_host/render_frame_host_impl.h +++ b/content/browser/renderer_host/render_frame_host_impl.h
@@ -2677,6 +2677,7 @@ void CreateFencedFrame( mojo::PendingAssociatedReceiver<blink::mojom::FencedFrameOwnerHost> pending_receiver, + blink::mojom::FencedFrameMode mode, CreateFencedFrameCallback callback) override; void GetKeepAliveHandleFactory( mojo::PendingReceiver<blink::mojom::KeepAliveHandleFactory> receiver)
diff --git a/content/browser/security_exploit_browsertest.cc b/content/browser/security_exploit_browsertest.cc index b693bbb..03f59cf 100644 --- a/content/browser/security_exploit_browsertest.cc +++ b/content/browser/security_exploit_browsertest.cc
@@ -91,6 +91,7 @@ #include "third_party/blink/public/common/navigation/navigation_policy.h" #include "third_party/blink/public/mojom/blob/blob_url_store.mojom-test-utils.h" #include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h" +#include "third_party/blink/public/mojom/fenced_frame/fenced_frame.mojom.h" #include "third_party/blink/public/mojom/frame/frame.mojom-test-utils.h" #include "third_party/blink/public/mojom/frame/frame.mojom.h" #include "third_party/blink/public/mojom/loader/mixed_content.mojom.h" @@ -1896,7 +1897,7 @@ compromised_rfh->GetProcess()); static_cast<mojom::FrameHost*>(compromised_rfh) ->CreateFencedFrame( - std::move(receiver), + std::move(receiver), blink::mojom::FencedFrameMode::kDefault, base::BindOnce([](int, blink::mojom::FrameReplicationStatePtr, const blink::RemoteFrameToken&, const base::UnguessableToken&) {}));
diff --git a/content/browser/site_instance_impl.cc b/content/browser/site_instance_impl.cc index a2911a1b..a076b90 100644 --- a/content/browser/site_instance_impl.cc +++ b/content/browser/site_instance_impl.cc
@@ -745,6 +745,17 @@ return site_info_.RequiresDedicatedProcess(GetIsolationContext()); } +bool SiteInstanceImpl::RequiresOriginKeyedProcess() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (!has_site_) + return false; + + // TODO(wjmaclean): once SiteInstanceGroups are ready we may give logically + // (same-process) isolated origins their own SiteInstances ... in that case we + // should consider updating this function. + return site_info_.requires_origin_keyed_process(); +} + void SiteInstanceImpl::IncrementRelatedActiveContentsCount() { browsing_instance_->increment_active_contents_count(); }
diff --git a/content/browser/site_instance_impl.h b/content/browser/site_instance_impl.h index 5349a68..74136c8 100644 --- a/content/browser/site_instance_impl.h +++ b/content/browser/site_instance_impl.h
@@ -125,6 +125,7 @@ bool IsRelatedSiteInstance(const SiteInstance* instance) override; size_t GetRelatedActiveContentsCount() override; bool RequiresDedicatedProcess() override; + bool RequiresOriginKeyedProcess() override; bool IsSameSiteWithURL(const GURL& url) override; bool IsGuest() override; SiteInstanceProcessAssignment GetLastProcessAssignmentOutcome() override;
diff --git a/content/browser/webid/federated_auth_request_impl_unittest.cc b/content/browser/webid/federated_auth_request_impl_unittest.cc index b126e3f7..869b14e 100644 --- a/content/browser/webid/federated_auth_request_impl_unittest.cc +++ b/content/browser/webid/federated_auth_request_impl_unittest.cc
@@ -143,14 +143,6 @@ bool customized_dialog; } MockConfiguration; -// absl::optional fields should be nullopt to prevent the corresponding -// methods from having EXPECT_CALL set on the mocks. -typedef struct { - RequestParameters inputs; - RequestExpectations expected; - MockConfiguration config; -} AuthRequestTestCase; - static const MockClientIdConfiguration kDefaultClientMetadata{ FetchStatus::kSuccess, kPrivacyPolicyUrl, kTermsOfServiceUrl}; @@ -347,10 +339,8 @@ void RunAuthTest(const RequestParameters& request_parameters, const RequestExpectations& expectation, const MockConfiguration& configuration) { - AuthRequestTestCase test_case = {request_parameters, expectation, - configuration}; SetupIdpNetworkRequestManager(GURL(request_parameters.provider)); - SetMockExpectations(test_case); + SetMockExpectations(request_parameters, expectation, configuration); auto auth_response = PerformAuthRequest( GURL(request_parameters.provider), request_parameters.client_id, request_parameters.nonce, request_parameters.prefer_auto_sign_in); @@ -465,29 +455,66 @@ return revoke_helper.status(); } - bool IsExpectedFetched(MockConfiguration conf, + bool IsExpectedFetched(MockConfiguration config, FetchedEndpoint expected_endpoint) { - return (conf.expected_fetched_endpoints & expected_endpoint) != 0; + return (config.expected_fetched_endpoints & expected_endpoint) != 0; } - void SetMediatedMockExpectations(const MockConfiguration& conf, - std::string token, - bool prefer_auto_sign_in) { - if (IsExpectedFetched(conf, FetchedEndpoint::ACCOUNTS)) { + void SetMockExpectations(const RequestParameters& request_parameters, + const RequestExpectations& expectation, + const MockConfiguration& config) { + if (IsExpectedFetched(config, FetchedEndpoint::MANIFEST)) { + EXPECT_CALL(*mock_request_manager_, FetchManifest(_, _, _)) + .WillOnce(Invoke( + [&](absl::optional<int>, absl::optional<int>, + IdpNetworkRequestManager::FetchManifestCallback callback) { + IdpNetworkRequestManager::Endpoints endpoints; + endpoints.accounts = config.manifest.accounts_endpoint; + endpoints.token = config.manifest.token_endpoint; + endpoints.client_metadata = + config.manifest.client_metadata_endpoint; + std::move(callback).Run(config.manifest.fetch_status, endpoints, + IdentityProviderMetadata()); + })); + } else { + EXPECT_CALL(*mock_request_manager_, FetchManifest(_, _, _)).Times(0); + } + + if (IsExpectedFetched(config, FetchedEndpoint::CLIENT_METADATA)) { + EXPECT_CALL(*mock_request_manager_, FetchClientMetadata(_, _, _)) + .WillOnce( + Invoke([&](const GURL&, const std::string& client_id, + IdpNetworkRequestManager::FetchClientMetadataCallback + callback) { + EXPECT_EQ(request_parameters.client_id, client_id); + std::move(callback).Run( + config.client_metadata.fetch_status, + IdpNetworkRequestManager::ClientMetadata{ + config.client_metadata.privacy_policy_url, + config.client_metadata.terms_of_service_url}); + })); + } else { + EXPECT_CALL(*mock_request_manager_, FetchClientMetadata(_, _, _)) + .Times(0); + } + + if (IsExpectedFetched(config, FetchedEndpoint::ACCOUNTS)) { EXPECT_CALL(*mock_request_manager_, SendAccountsRequest(_, _, _)) .WillOnce(Invoke( [&](const GURL&, const std::string&, IdpNetworkRequestManager::AccountsRequestCallback callback) { - std::move(callback).Run(conf.accounts_response, conf.accounts); + std::move(callback).Run(config.accounts_response, + config.accounts); })); } else { EXPECT_CALL(*mock_request_manager_, SendAccountsRequest(_, _, _)) .Times(0); } - if (IsExpectedFetched(conf, FetchedEndpoint::ACCOUNTS) && - conf.accounts_response == FetchStatus::kSuccess) { - if (!prefer_auto_sign_in && !conf.customized_dialog) { + if (IsExpectedFetched(config, FetchedEndpoint::ACCOUNTS) && + config.accounts_response == FetchStatus::kSuccess) { + if (!request_parameters.prefer_auto_sign_in && + !config.customized_dialog) { // Expects a dialog if prefer_auto_sign_in is not set by RP. However, // even though the bit is set we may not exercise the AutoSignIn flow. // e.g. for sign up flow, multiple accounts, user opt-out etc. In this @@ -515,15 +542,16 @@ .Times(0); } - if (IsExpectedFetched(conf, FetchedEndpoint::TOKEN)) { - auto delivered_token = - conf.token_response == FetchStatus::kSuccess ? token : std::string(); + if (IsExpectedFetched(config, FetchedEndpoint::TOKEN)) { + auto delivered_token = config.token_response == FetchStatus::kSuccess + ? config.token + : std::string(); EXPECT_CALL(*mock_request_manager_, SendTokenRequest(_, _, _, _)) .WillOnce(Invoke( [=](const GURL& idp_signin_url, const std::string& account_id, const std::string& request, IdpNetworkRequestManager::TokenRequestCallback callback) { - std::move(callback).Run(conf.token_response, delivered_token); + std::move(callback).Run(config.token_response, delivered_token); })); task_environment()->FastForwardBy(base::Seconds(3)); } else { @@ -532,47 +560,6 @@ } } - void SetMockExpectations(const AuthRequestTestCase& test_case) { - if (IsExpectedFetched(test_case.config, FetchedEndpoint::MANIFEST)) { - EXPECT_CALL(*mock_request_manager_, FetchManifest(_, _, _)) - .WillOnce(Invoke( - [&](absl::optional<int>, absl::optional<int>, - IdpNetworkRequestManager::FetchManifestCallback callback) { - IdpNetworkRequestManager::Endpoints endpoints; - endpoints.accounts = - test_case.config.manifest.accounts_endpoint; - endpoints.token = test_case.config.manifest.token_endpoint; - endpoints.client_metadata = - test_case.config.manifest.client_metadata_endpoint; - std::move(callback).Run(test_case.config.manifest.fetch_status, - endpoints, IdentityProviderMetadata()); - })); - } else { - EXPECT_CALL(*mock_request_manager_, FetchManifest(_, _, _)).Times(0); - } - - if (IsExpectedFetched(test_case.config, FetchedEndpoint::CLIENT_METADATA)) { - EXPECT_CALL(*mock_request_manager_, FetchClientMetadata(_, _, _)) - .WillOnce( - Invoke([&](const GURL&, const std::string& client_id, - IdpNetworkRequestManager::FetchClientMetadataCallback - callback) { - EXPECT_EQ(test_case.inputs.client_id, client_id); - std::move(callback).Run( - test_case.config.client_metadata.fetch_status, - IdpNetworkRequestManager::ClientMetadata{ - test_case.config.client_metadata.privacy_policy_url, - test_case.config.client_metadata.terms_of_service_url}); - })); - } else { - EXPECT_CALL(*mock_request_manager_, FetchClientMetadata(_, _, _)) - .Times(0); - } - - SetMediatedMockExpectations(test_case.config, test_case.config.token, - test_case.inputs.prefer_auto_sign_in); - } - // Expectations have to be set explicitly in advance using // logout_return_count() and logout_requests(). void SetLogoutMockExpectations() {
diff --git a/content/common/frame.mojom b/content/common/frame.mojom index afc0dc3..ec594f2 100644 --- a/content/common/frame.mojom +++ b/content/common/frame.mojom
@@ -675,7 +675,8 @@ // the "inner" fenced frame FrameTree [Sync] CreateFencedFrame( pending_associated_receiver<blink.mojom.FencedFrameOwnerHost> - fenced_frame) + fenced_frame, + blink.mojom.FencedFrameMode mode) => (int32 proxy_routing_id, blink.mojom.FrameReplicationState initial_replication_state, blink.mojom.RemoteFrameToken frame_token,
diff --git a/content/public/browser/site_instance.h b/content/public/browser/site_instance.h index f57e6ac..408a0765 100644 --- a/content/public/browser/site_instance.h +++ b/content/public/browser/site_instance.h
@@ -169,6 +169,10 @@ // process. This only returns true under the "site per process" process model. virtual bool RequiresDedicatedProcess() = 0; + // Returns true if this SiteInstance is for a process-isolated origin with its + // own OriginAgentCluster. + virtual bool RequiresOriginKeyedProcess() = 0; + // Return whether this SiteInstance and the provided |url| are part of the // same web site, for the purpose of assigning them to processes accordingly. // The decision is currently based on the registered domain of the URLs
diff --git a/content/public/test/test_renderer_host.h b/content/public/test/test_renderer_host.h index 2e76323b..a02288f 100644 --- a/content/public/test/test_renderer_host.h +++ b/content/public/test/test_renderer_host.h
@@ -22,6 +22,7 @@ #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/public/common/input/synthetic_web_input_event_builders.h" #include "third_party/blink/public/common/input/web_input_event.h" +#include "third_party/blink/public/mojom/fenced_frame/fenced_frame.mojom.h" #include "ui/base/page_transition_types.h" #if defined(USE_AURA) @@ -137,7 +138,9 @@ virtual void SimulateManifestURLUpdate(const GURL& manifest_url) = 0; // Creates and appends a fenced frame. - virtual RenderFrameHost* AppendFencedFrame() = 0; + virtual RenderFrameHost* AppendFencedFrame( + blink::mojom::FencedFrameMode mode = + blink::mojom::FencedFrameMode::kDefault) = 0; }; // An interface and utility for driving tests of RenderViewHost.
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc index c929696..e0d4faf8 100644 --- a/content/renderer/render_frame_impl.cc +++ b/content/renderer/render_frame_impl.cc
@@ -3437,6 +3437,27 @@ return GetRemoteAssociatedInterfaces(); } +namespace { + +// Emit the trace event using a helper as we: +// a) want to ensure that the trace event covers the entire function. +// b) we want to emit the new child routing id as an argument. +// c) child routing id becomes available only after a sync call. +struct CreateChildFrameTraceEvent { + explicit CreateChildFrameTraceEvent(int routing_id) { + TRACE_EVENT_BEGIN("navigation,rail", "RenderFrameImpl::createChildFrame", + "routing_id", routing_id); + } + + ~CreateChildFrameTraceEvent() { + TRACE_EVENT_END("navigation,rail", "child_routing_id", child_routing_id); + } + + int child_routing_id = -1; +}; + +} // namespace + blink::WebLocalFrame* RenderFrameImpl::CreateChildFrame( blink::mojom::TreeScopeType scope, const blink::WebString& name, @@ -3445,6 +3466,10 @@ const blink::WebFrameOwnerProperties& frame_owner_properties, blink::FrameOwnerElementType frame_owner_element_type, blink::WebPolicyContainerBindParams policy_container_bind_params) { + // Tracing analysis uses this to find main frames when this value is + // MSG_ROUTING_NONE, and build the frame tree otherwise. + CreateChildFrameTraceEvent trace_event(routing_id_); + // Allocate child routing ID. This is a synchronous call. int child_routing_id; blink::LocalFrameToken frame_token; @@ -3453,6 +3478,7 @@ child_routing_id, frame_token, devtools_frame_token)) { return nullptr; } + trace_event.child_routing_id = child_routing_id; // The unique name generation logic was moved out of Blink, so for historical // reasons, unique name generation needs to take something called the @@ -3491,11 +3517,6 @@ blink::mojom::FrameOwnerProperties::From(frame_owner_properties), frame_owner_element_type); - // Tracing analysis uses this to find main frames when this value is - // MSG_ROUTING_NONE, and build the frame tree otherwise. - TRACE_EVENT2("navigation,rail", "RenderFrameImpl::createChildFrame", "id", - routing_id_, "child", child_routing_id); - // Create the RenderFrame and WebLocalFrame, linking the two. RenderFrameImpl* child_render_frame = RenderFrameImpl::Create( agent_scheduling_group_, render_view_, child_routing_id, @@ -3569,16 +3590,16 @@ const blink::WebElement& fenced_frame, blink::CrossVariantMojoAssociatedReceiver< blink::mojom::FencedFrameOwnerHostInterfaceBase> receiver, - blink::mojom::FencedFrameMode) { + blink::mojom::FencedFrameMode mode) { int proxy_routing_id = MSG_ROUTING_NONE; blink::mojom::FrameReplicationStatePtr initial_replicated_state = blink::mojom::FrameReplicationState::New(); blink::RemoteFrameToken frame_token; base::UnguessableToken devtools_frame_token; - GetFrameHost()->CreateFencedFrame(std::move(receiver), &proxy_routing_id, - &initial_replicated_state, &frame_token, - &devtools_frame_token); + GetFrameHost()->CreateFencedFrame( + std::move(receiver), mode, &proxy_routing_id, &initial_replicated_state, + &frame_token, &devtools_frame_token); RenderFrameProxy* proxy = RenderFrameProxy::CreateProxyForPortalOrFencedFrame( agent_scheduling_group_, this, proxy_routing_id, frame_token,
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 a156cdc..01a67b0 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
@@ -715,6 +715,7 @@ # All platforms, Vulkan backend crbug.com/1309304 [ angle-swiftshader passthrough ] conformance/rendering/out-of-bounds-array-buffers.html [ Failure ] +crbug.com/1309304 [ angle-swiftshader passthrough ] conformance/rendering/out-of-bounds-index-buffers.html [ Failure ] ####################################################################### # Automated Entries After This Point - Do Not Manually Add Below Here #
diff --git a/content/test/test_render_frame.cc b/content/test/test_render_frame.cc index 68d4358..3a5b41d4 100644 --- a/content/test/test_render_frame.cc +++ b/content/test/test_render_frame.cc
@@ -167,6 +167,7 @@ void CreateFencedFrame( mojo::PendingAssociatedReceiver<blink::mojom::FencedFrameOwnerHost>, + blink::mojom::FencedFrameMode, CreateFencedFrameCallback) override { NOTREACHED() << "At the moment, content::FencedFrame is not used in any " "unit tests, so this path should not be hit";
diff --git a/content/test/test_render_frame_host.cc b/content/test/test_render_frame_host.cc index 1ae1918..a7902b6 100644 --- a/content/test/test_render_frame_host.cc +++ b/content/test/test_render_frame_host.cc
@@ -253,9 +253,10 @@ GetPage().UpdateManifestUrl(manifest_url); } -TestRenderFrameHost* TestRenderFrameHost::AppendFencedFrame() { +TestRenderFrameHost* TestRenderFrameHost::AppendFencedFrame( + blink::mojom::FencedFrameMode mode) { fenced_frames_.push_back( - std::make_unique<FencedFrame>(weak_ptr_factory_.GetSafeRef())); + std::make_unique<FencedFrame>(weak_ptr_factory_.GetSafeRef(), mode)); return static_cast<TestRenderFrameHost*>( fenced_frames_.back().get()->GetInnerRoot()); }
diff --git a/content/test/test_render_frame_host.h b/content/test/test_render_frame_host.h index dccdced..710fa2f4 100644 --- a/content/test/test_render_frame_host.h +++ b/content/test/test_render_frame_host.h
@@ -103,7 +103,9 @@ const std::vector<std::string>& GetConsoleMessages() override; int GetHeavyAdIssueCount(HeavyAdIssueType type) override; void SimulateManifestURLUpdate(const GURL& manifest_url) override; - TestRenderFrameHost* AppendFencedFrame() override; + TestRenderFrameHost* AppendFencedFrame( + blink::mojom::FencedFrameMode mode = + blink::mojom::FencedFrameMode::kDefault) override; void SendNavigate(int nav_entry_id, bool did_create_new_entry,
diff --git a/docs/python3_migration.md b/docs/python3_migration.md index 3c197539..3fbdcad 100644 --- a/docs/python3_migration.md +++ b/docs/python3_migration.md
@@ -114,9 +114,7 @@ Test targets that run by invoking python scripts (like telemetry_unittests or blink_web_tests) should eventually migrate to using the [script_test] -GN templates. Once you do that, they will use Python3 by default. However, -some tests may specify `run_under_python2 = true` as a template variable -to use Python2, so when you're ready to test Python3, just delete that line. +GN templates. Once you do that, they will use Python3 by default. Some tests still need to be migrated to `script_test()` ([crbug.com/1208648](https://crbug.com/1208648)). The process for
diff --git a/extensions/browser/api/audio/audio_api.cc b/extensions/browser/api/audio/audio_api.cc index 7bddfdb..9271f11 100644 --- a/extensions/browser/api/audio/audio_api.cc +++ b/extensions/browser/api/audio/audio_api.cc
@@ -48,11 +48,13 @@ .is_available(); } -bool CanReceiveDeprecatedAudioEvent(content::BrowserContext* browser_context, - Feature::Context target_context, - const Extension* extension, - Event* event, - const base::DictionaryValue* filter) { +bool CanReceiveDeprecatedAudioEvent( + content::BrowserContext* browser_context, + Feature::Context target_context, + const Extension* extension, + const base::DictionaryValue* filter, + std::unique_ptr<base::Value::List>* event_args, + mojom::EventFilteringInfoPtr* event_filtering_info_out) { return CanUseDeprecatedAudioApi(extension); }
diff --git a/extensions/browser/api/hid/hid_device_manager.cc b/extensions/browser/api/hid/hid_device_manager.cc index e453371..246ae16 100644 --- a/extensions/browser/api/hid/hid_device_manager.cc +++ b/extensions/browser/api/hid/hid_device_manager.cc
@@ -73,13 +73,15 @@ } } -bool WillDispatchDeviceEvent(base::WeakPtr<HidDeviceManager> device_manager, - const device::mojom::HidDeviceInfo& device_info, - content::BrowserContext* browser_context, - Feature::Context target_context, - const Extension* extension, - Event* event, - const base::DictionaryValue* listener_filter) { +bool WillDispatchDeviceEvent( + base::WeakPtr<HidDeviceManager> device_manager, + const device::mojom::HidDeviceInfo& device_info, + content::BrowserContext* browser_context, + Feature::Context target_context, + const Extension* extension, + const base::DictionaryValue* listener_filter, + std::unique_ptr<base::Value::List>* event_args_out, + mojom::EventFilteringInfoPtr* event_filtering_info_out) { if (device_manager && extension) { return device_manager->HasPermission(extension, device_info, false); }
diff --git a/extensions/browser/api/printer_provider/printer_provider_api.cc b/extensions/browser/api/printer_provider/printer_provider_api.cc index f84ee8d..d2ba1793 100644 --- a/extensions/browser/api/printer_provider/printer_provider_api.cc +++ b/extensions/browser/api/printer_provider/printer_provider_api.cc
@@ -297,12 +297,14 @@ // in the event. If the extension listens to the event, it's added to the set // of |request| sources. |request| is |GetPrintersRequest| object associated // with the event. - bool WillRequestPrinters(int request_id, - content::BrowserContext* browser_context, - Feature::Context target_context, - const Extension* extension, - Event* event, - const base::DictionaryValue* listener_filter); + bool WillRequestPrinters( + int request_id, + content::BrowserContext* browser_context, + Feature::Context target_context, + const Extension* extension, + const base::DictionaryValue* listener_filter, + std::unique_ptr<base::Value::List>* event_args_out, + mojom::EventFilteringInfoPtr* event_filtering_info_out); raw_ptr<content::BrowserContext> browser_context_; @@ -759,8 +761,9 @@ content::BrowserContext* browser_context, Feature::Context target_context, const Extension* extension, - Event* event, - const base::DictionaryValue* listener_filter) { + const base::DictionaryValue* listener_filter, + std::unique_ptr<base::Value::List>* event_args_out, + mojom::EventFilteringInfoPtr* event_filtering_info_out) { if (!extension) return false; EventRouter* event_router = EventRouter::Get(browser_context_);
diff --git a/extensions/browser/api/usb/usb_device_manager.cc b/extensions/browser/api/usb/usb_device_manager.cc index 8a5c742..d698cc9 100644 --- a/extensions/browser/api/usb/usb_device_manager.cc +++ b/extensions/browser/api/usb/usb_device_manager.cc
@@ -64,12 +64,14 @@ // Returns true if the given extension has permission to receive events // regarding this device. -bool WillDispatchDeviceEvent(const device::mojom::UsbDeviceInfo& device_info, - content::BrowserContext* browser_context, - Feature::Context target_context, - const Extension* extension, - Event* event, - const base::DictionaryValue* listener_filter) { +bool WillDispatchDeviceEvent( + const device::mojom::UsbDeviceInfo& device_info, + content::BrowserContext* browser_context, + Feature::Context target_context, + const Extension* extension, + const base::DictionaryValue* listener_filter, + std::unique_ptr<base::Value::List>* event_args_out, + mojom::EventFilteringInfoPtr* event_filtering_info_out) { // Check install-time and optional permissions. std::unique_ptr<UsbDevicePermission::CheckParam> param = UsbDevicePermission::CheckParam::ForUsbDevice(extension, device_info);
diff --git a/extensions/browser/event_router.cc b/extensions/browser/event_router.cc index a843c1f..aaa47d0e 100644 --- a/extensions/browser/event_router.cc +++ b/extensions/browser/event_router.cc
@@ -968,17 +968,33 @@ return; } + std::unique_ptr<base::Value::List> modified_event_args; + mojom::EventFilteringInfoPtr modified_event_filter_info; if (!event->will_dispatch_callback.is_null() && - !event->will_dispatch_callback.Run(listener_context, target_context, - extension, event, listener_filter)) { + !event->will_dispatch_callback.Run( + listener_context, target_context, extension, listener_filter, + &modified_event_args, &modified_event_filter_info)) { return; } + base::ListValue* event_args_to_use = event->event_args.get(); + std::unique_ptr<base::ListValue> list_modified_event_args; + if (modified_event_args) { + // If `will_dispatch_callback` provided modified args, use it. + list_modified_event_args = base::ListValue::From( + std::make_unique<base::Value>(std::move(*modified_event_args))); + event_args_to_use = list_modified_event_args.get(); + } + + mojom::EventFilteringInfoPtr filter_info = + modified_event_filter_info ? std::move(modified_event_filter_info) + : event->filter_info.Clone(); + int event_id = g_extension_event_id.GetNext(); DispatchExtensionMessage(process, worker_thread_id, listener_context, extension_id, event_id, event->event_name, - event->event_args.get(), event->user_gesture, - event->filter_info.Clone()); + event_args_to_use, event->user_gesture, + std::move(filter_info)); for (TestObserver& observer : test_observers_) observer.OnDidDispatchEventToProcess(*event);
diff --git a/extensions/browser/event_router.h b/extensions/browser/event_router.h index 82adadd..7b90fb9 100644 --- a/extensions/browser/event_router.h +++ b/extensions/browser/event_router.h
@@ -516,12 +516,13 @@ struct Event { // This callback should return true if the event should be dispatched to the // given context and extension, and false otherwise. - using WillDispatchCallback = - base::RepeatingCallback<bool(content::BrowserContext*, - Feature::Context, - const Extension*, - Event*, - const base::DictionaryValue*)>; + using WillDispatchCallback = base::RepeatingCallback<bool( + content::BrowserContext*, + Feature::Context, + const Extension*, + const base::DictionaryValue*, + std::unique_ptr<base::Value::List>* event_args_out, + mojom::EventFilteringInfoPtr* event_filtering_info_out)>; // The identifier for the event, for histograms. In most cases this // correlates 1:1 with |event_name|, in some cases events will generate @@ -550,10 +551,11 @@ mojom::EventFilteringInfoPtr filter_info; // If specified, this is called before dispatching an event to each - // extension. The third argument is a mutable reference to event_args, - // allowing the caller to provide different arguments depending on the - // extension and profile. This is guaranteed to be called synchronously with + // extension. This is guaranteed to be called synchronously with // DispatchEvent, so callers don't need to worry about lifetime. + // The args |event_args_out|, |event_filtering_info_out| allows caller to + // provide modified `Event::event_args`, `Event::filter_info` depending on the + // extension and profile. // // NOTE: the Extension argument to this may be NULL because it's possible for // this event to be dispatched to non-extension processes, like WebUI.
diff --git a/extensions/browser/events/lazy_event_dispatcher.cc b/extensions/browser/events/lazy_event_dispatcher.cc index f30cef7..884be8a2 100644 --- a/extensions/browser/events/lazy_event_dispatcher.cc +++ b/extensions/browser/events/lazy_event_dispatcher.cc
@@ -85,16 +85,25 @@ // to avoid lifetime issues. Use a separate copy of the event args, so they // last until the event is dispatched. if (!dispatched_event->will_dispatch_callback.is_null()) { + std::unique_ptr<base::Value::List> modified_event_args; + mojom::EventFilteringInfoPtr modified_event_filter_info; if (!dispatched_event->will_dispatch_callback.Run( dispatch_context.browser_context(), // The only lazy listeners belong to an extension's background // context (either an event page or a service worker), which are // always BLESSED_EXTENSION_CONTEXTs extensions::Feature::BLESSED_EXTENSION_CONTEXT, extension, - dispatched_event.get(), listener_filter)) { + listener_filter, &modified_event_args, + &modified_event_filter_info)) { // The event has been canceled. return true; } + if (modified_event_args) { + dispatched_event->event_args = base::ListValue::From( + std::make_unique<base::Value>(std::move(*modified_event_args))); + } + if (modified_event_filter_info) + dispatched_event->filter_info = std::move(modified_event_filter_info); // Ensure we don't call it again at dispatch time. dispatched_event->will_dispatch_callback.Reset(); }
diff --git "a/infra/config/generated/builders/ci/GPU FYI Win x64 Builder \050dbg\051/properties.json" "b/infra/config/generated/builders/ci/GPU FYI Win x64 Builder \050dbg\051/properties.json" index def7636..a6a7b71f 100644 --- "a/infra/config/generated/builders/ci/GPU FYI Win x64 Builder \050dbg\051/properties.json" +++ "b/infra/config/generated/builders/ci/GPU FYI Win x64 Builder \050dbg\051/properties.json"
@@ -1,9 +1,8 @@ { - "$build/goma": { - "enable_ats": true, - "rpc_extra_params": "?prod", - "server_host": "goma.chromium.org", - "use_luci_auth": true + "$build/reclient": { + "instance": "rbe-chromium-trusted", + "jobs": 80, + "metrics_project": "chromium-reclient-metrics" }, "$recipe_engine/resultdb/test_presentation": { "column_keys": [],
diff --git a/infra/config/generated/builders/ci/GPU FYI Win x64 Builder/properties.json b/infra/config/generated/builders/ci/GPU FYI Win x64 Builder/properties.json index def7636..a6a7b71f 100644 --- a/infra/config/generated/builders/ci/GPU FYI Win x64 Builder/properties.json +++ b/infra/config/generated/builders/ci/GPU FYI Win x64 Builder/properties.json
@@ -1,9 +1,8 @@ { - "$build/goma": { - "enable_ats": true, - "rpc_extra_params": "?prod", - "server_host": "goma.chromium.org", - "use_luci_auth": true + "$build/reclient": { + "instance": "rbe-chromium-trusted", + "jobs": 80, + "metrics_project": "chromium-reclient-metrics" }, "$recipe_engine/resultdb/test_presentation": { "column_keys": [],
diff --git "a/infra/config/generated/builders/ci/GPU FYI Win x64 DX12 Vulkan Builder \050dbg\051/properties.json" "b/infra/config/generated/builders/ci/GPU FYI Win x64 DX12 Vulkan Builder \050dbg\051/properties.json" index def7636..a6a7b71f 100644 --- "a/infra/config/generated/builders/ci/GPU FYI Win x64 DX12 Vulkan Builder \050dbg\051/properties.json" +++ "b/infra/config/generated/builders/ci/GPU FYI Win x64 DX12 Vulkan Builder \050dbg\051/properties.json"
@@ -1,9 +1,8 @@ { - "$build/goma": { - "enable_ats": true, - "rpc_extra_params": "?prod", - "server_host": "goma.chromium.org", - "use_luci_auth": true + "$build/reclient": { + "instance": "rbe-chromium-trusted", + "jobs": 80, + "metrics_project": "chromium-reclient-metrics" }, "$recipe_engine/resultdb/test_presentation": { "column_keys": [],
diff --git a/infra/config/generated/builders/ci/GPU FYI Win x64 DX12 Vulkan Builder/properties.json b/infra/config/generated/builders/ci/GPU FYI Win x64 DX12 Vulkan Builder/properties.json index def7636..a6a7b71f 100644 --- a/infra/config/generated/builders/ci/GPU FYI Win x64 DX12 Vulkan Builder/properties.json +++ b/infra/config/generated/builders/ci/GPU FYI Win x64 DX12 Vulkan Builder/properties.json
@@ -1,9 +1,8 @@ { - "$build/goma": { - "enable_ats": true, - "rpc_extra_params": "?prod", - "server_host": "goma.chromium.org", - "use_luci_auth": true + "$build/reclient": { + "instance": "rbe-chromium-trusted", + "jobs": 80, + "metrics_project": "chromium-reclient-metrics" }, "$recipe_engine/resultdb/test_presentation": { "column_keys": [],
diff --git a/infra/config/generated/builders/ci/GPU FYI XR Win x64 Builder/properties.json b/infra/config/generated/builders/ci/GPU FYI XR Win x64 Builder/properties.json index def7636..a6a7b71f 100644 --- a/infra/config/generated/builders/ci/GPU FYI XR Win x64 Builder/properties.json +++ b/infra/config/generated/builders/ci/GPU FYI XR Win x64 Builder/properties.json
@@ -1,9 +1,8 @@ { - "$build/goma": { - "enable_ats": true, - "rpc_extra_params": "?prod", - "server_host": "goma.chromium.org", - "use_luci_auth": true + "$build/reclient": { + "instance": "rbe-chromium-trusted", + "jobs": 80, + "metrics_project": "chromium-reclient-metrics" }, "$recipe_engine/resultdb/test_presentation": { "column_keys": [],
diff --git "a/infra/config/generated/builders/ci/GPU Win x64 Builder \050dbg\051/properties.json" "b/infra/config/generated/builders/ci/GPU Win x64 Builder \050dbg\051/properties.json" index 6208bc0..47af5e9c 100644 --- "a/infra/config/generated/builders/ci/GPU Win x64 Builder \050dbg\051/properties.json" +++ "b/infra/config/generated/builders/ci/GPU Win x64 Builder \050dbg\051/properties.json"
@@ -1,9 +1,8 @@ { - "$build/goma": { - "enable_ats": true, - "rpc_extra_params": "?prod", - "server_host": "goma.chromium.org", - "use_luci_auth": true + "$build/reclient": { + "instance": "rbe-chromium-trusted", + "jobs": 80, + "metrics_project": "chromium-reclient-metrics" }, "$recipe_engine/resultdb/test_presentation": { "column_keys": [],
diff --git a/infra/config/generated/builders/ci/ios-asan/properties.json b/infra/config/generated/builders/ci/ios-asan/properties.json index 8b9835b9..c18caf9e 100644 --- a/infra/config/generated/builders/ci/ios-asan/properties.json +++ b/infra/config/generated/builders/ci/ios-asan/properties.json
@@ -1,23 +1,4 @@ { - "$build/archive": { - "archive_datas": [ - { - "archive_type": "ARCHIVE_TYPE_ZIP", - "dirs": [ - "ios_cwt_chromedriver_tests_module.xctest", - "ios_cwt_chromedriver_tests_module-Runner.app", - "ios_cwt_chromedriver_tests.app", - "ios", - "testing" - ], - "files": [ - "libclang_rt.asan_iossim_dynamic.dylib" - ], - "gcs_bucket": "chromium-browser-asan", - "gcs_path": "ios-release/asan-ios-release-{%timestamp%}.zip" - } - ] - }, "$build/goma": { "rpc_extra_params": "?prod", "server_host": "goma.chromium.org",
diff --git a/infra/config/subprojects/chromium/ci/chromium.fyi.star b/infra/config/subprojects/chromium/ci/chromium.fyi.star index 60e2c72a..e3109d8 100644 --- a/infra/config/subprojects/chromium/ci/chromium.fyi.star +++ b/infra/config/subprojects/chromium/ci/chromium.fyi.star
@@ -1329,27 +1329,6 @@ category = "iOS", short_name = "asan", ), - properties = { - "$build/archive": { - "archive_datas": [ - { - "archive_type": "ARCHIVE_TYPE_ZIP", - "files": [ - "libclang_rt.asan_iossim_dynamic.dylib", - ], - "dirs": [ - "ios_cwt_chromedriver_tests_module.xctest", - "ios_cwt_chromedriver_tests_module-Runner.app", - "ios_cwt_chromedriver_tests.app", - "ios", - "testing", - ], - "gcs_bucket": "chromium-browser-asan", - "gcs_path": "ios-release/asan-ios-release-{%timestamp%}.zip", - }, - ], - }, - }, ) fyi_ios_builder(
diff --git a/infra/config/subprojects/chromium/ci/chromium.gpu.fyi.star b/infra/config/subprojects/chromium/ci/chromium.gpu.fyi.star index 929332c2..dd34789 100644 --- a/infra/config/subprojects/chromium/ci/chromium.gpu.fyi.star +++ b/infra/config/subprojects/chromium/ci/chromium.gpu.fyi.star
@@ -628,6 +628,9 @@ category = "Windows|Builder|Release", short_name = "x64", ), + goma_backend = None, + reclient_jobs = rbe_jobs.LOW_JOBS_FOR_CI, + reclient_instance = rbe_instance.DEFAULT, ) gpu_fyi_windows_builder( @@ -636,6 +639,9 @@ category = "Windows|Builder|Debug", short_name = "x64", ), + goma_backend = None, + reclient_jobs = rbe_jobs.LOW_JOBS_FOR_CI, + reclient_instance = rbe_instance.DEFAULT, ) gpu_fyi_windows_builder( @@ -644,6 +650,9 @@ category = "Windows|Builder|dx12vk", short_name = "rel", ), + goma_backend = None, + reclient_jobs = rbe_jobs.LOW_JOBS_FOR_CI, + reclient_instance = rbe_instance.DEFAULT, ) gpu_fyi_windows_builder( @@ -652,6 +661,9 @@ category = "Windows|Builder|dx12vk", short_name = "dbg", ), + goma_backend = None, + reclient_jobs = rbe_jobs.LOW_JOBS_FOR_CI, + reclient_instance = rbe_instance.DEFAULT, ) gpu_fyi_windows_builder( @@ -660,6 +672,9 @@ category = "Windows|Builder|XR", short_name = "x64", ), + goma_backend = None, + reclient_jobs = rbe_jobs.LOW_JOBS_FOR_CI, + reclient_instance = rbe_instance.DEFAULT, ) _gpu_fyi_windows_builder_shadow(
diff --git a/infra/config/subprojects/chromium/ci/chromium.gpu.star b/infra/config/subprojects/chromium/ci/chromium.gpu.star index 9cff9001..c5c6c7de 100644 --- a/infra/config/subprojects/chromium/ci/chromium.gpu.star +++ b/infra/config/subprojects/chromium/ci/chromium.gpu.star
@@ -119,6 +119,9 @@ ), sheriff_rotations = args.ignore_default(None), tree_closing = False, + goma_backend = None, + reclient_jobs = rbe_jobs.LOW_JOBS_FOR_CI, + reclient_instance = rbe_instance.DEFAULT, ) ci.thin_tester(
diff --git a/ios/web/public/thread/web_task_traits.h b/ios/web/public/thread/web_task_traits.h index 375cf01..d757873 100644 --- a/ios/web/public/thread/web_task_traits.h +++ b/ios/web/public/thread/web_task_traits.h
@@ -26,10 +26,10 @@ // to a WebThread. // // To post a task to the UI thread (analogous for IO thread): -// base::PostTask(FROM_HERE, {WebThread::UI}, task); +// web::GetUIThreadTaskRunner({})->PostTask(FROM_HERE, task); // // To obtain a TaskRunner for the UI thread (analogous for the IO thread): -// base::CreateSingleThreadTaskRunner({WebThread::UI}); +// web::GetUIThreadTaskRunner({}); // // Tasks posted to the same WebThread with the same traits will be executed // in the order they were posted, regardless of the TaskRunners they were
diff --git a/media/mojo/mojom/stable/BUILD.gn b/media/mojo/mojom/stable/BUILD.gn index 6ea5af91..1d48e02 100644 --- a/media/mojo/mojom/stable/BUILD.gn +++ b/media/mojo/mojom/stable/BUILD.gn
@@ -150,19 +150,28 @@ mojom("native_pixmap_handle") { sources = [ "native_pixmap_handle.mojom" ] - public_deps = [ "//ui/gfx/mojom:native_handle_types" ] - - cpp_typemaps = [ - { - types = [ - { - mojom = "media.stable.mojom.NativePixmapHandle" - cpp = "::gfx::NativePixmapHandle" - move_only = true - }, - ] - traits_headers = [ "native_pixmap_handle_mojom_traits.h" ] - traits_sources = [ "native_pixmap_handle_mojom_traits.cc" ] - }, - ] + if (is_linux || is_chromeos) { + cpp_typemaps = [ + { + types = [ + { + mojom = "media.stable.mojom.NativePixmapHandle" + cpp = "::gfx::NativePixmapHandle" + move_only = true + }, + { + mojom = "media.stable.mojom.NativePixmapPlane" + cpp = "::gfx::NativePixmapPlane" + move_only = true + }, + ] + traits_headers = [ + "native_pixmap_handle_mojom_traits.h", + "//ui/gfx/native_pixmap_handle.h", + ] + traits_sources = [ "native_pixmap_handle_mojom_traits.cc" ] + traits_public_deps = [ "//ui/gfx:memory_buffer" ] + }, + ] + } }
diff --git a/media/mojo/mojom/stable/native_pixmap_handle.mojom b/media/mojo/mojom/stable/native_pixmap_handle.mojom index 8484e29e..765e6773 100644 --- a/media/mojo/mojom/stable/native_pixmap_handle.mojom +++ b/media/mojo/mojom/stable/native_pixmap_handle.mojom
@@ -4,13 +4,23 @@ module media.stable.mojom; -import "ui/gfx/mojom/native_handle_types.mojom"; +// Based on |gfx.mojom.NativePixmapPlane|. +// Next min field ID: 4 +[Stable] +struct NativePixmapPlane { + uint32 stride@0; + uint64 offset@1; + uint64 size@2; + + // A platform-specific handle to the underlying memory object. + handle<platform> buffer_handle@3; +}; // Based on |gfx.mojom.NativePixmapHandle|. // Next min field ID: 2 [Stable] struct NativePixmapHandle { - array<gfx.mojom.NativePixmapPlane> planes@0; + array<NativePixmapPlane> planes@0; uint64 modifier@1; };
diff --git a/media/mojo/mojom/stable/native_pixmap_handle_mojom_traits.cc b/media/mojo/mojom/stable/native_pixmap_handle_mojom_traits.cc index 4348c31..8e695ff 100644 --- a/media/mojo/mojom/stable/native_pixmap_handle_mojom_traits.cc +++ b/media/mojo/mojom/stable/native_pixmap_handle_mojom_traits.cc
@@ -9,7 +9,7 @@ // This file contains a variety of conservative compile-time assertions that // help us detect changes that may break the backward compatibility requirement // of the StableVideoDecoder API. Specifically, we have static_asserts() that -// ensure the type of the media struct member is *exactly* the same as the +// ensure the type of the gfx struct member is *exactly* the same as the // corresponding mojo struct member. If this changes, we must be careful to // validate ranges and avoid implicit conversions. // @@ -19,6 +19,77 @@ namespace mojo { // static +uint32_t StructTraits< + media::stable::mojom::NativePixmapPlaneDataView, + gfx::NativePixmapPlane>::stride(const gfx::NativePixmapPlane& plane) { + static_assert( + std::is_same<decltype(::gfx::NativePixmapPlane::stride), + decltype( + media::stable::mojom::NativePixmapPlane::stride)>::value, + "Unexpected type for gfx::NativePixmapPlane::stride. If you need to " + "change this assertion, please contact chromeos-gfx-video@google.com."); + + return plane.stride; +} + +// static +uint64_t StructTraits< + media::stable::mojom::NativePixmapPlaneDataView, + gfx::NativePixmapPlane>::offset(const gfx::NativePixmapPlane& plane) { + static_assert( + std::is_same<decltype(::gfx::NativePixmapPlane::offset), + decltype( + media::stable::mojom::NativePixmapPlane::offset)>::value, + "Unexpected type for gfx::NativePixmapPlane::offset. If you need to " + "change this assertion, please contact chromeos-gfx-video@google.com."); + + return plane.offset; +} + +// static +uint64_t StructTraits< + media::stable::mojom::NativePixmapPlaneDataView, + gfx::NativePixmapPlane>::size(const gfx::NativePixmapPlane& plane) { + static_assert( + std::is_same<decltype(::gfx::NativePixmapPlane::size), + decltype( + media::stable::mojom::NativePixmapPlane::size)>::value, + "Unexpected type for gfx::NativePixmapPlane::size. If you need to change " + "this assertion, please contact chromeos-gfx-video@google.com."); + + return plane.size; +} + +// static +mojo::PlatformHandle StructTraits< + media::stable::mojom::NativePixmapPlaneDataView, + gfx::NativePixmapPlane>::buffer_handle(gfx::NativePixmapPlane& plane) { + static_assert( + std::is_same<decltype(::gfx::NativePixmapPlane::fd), + base::ScopedFD>::value, + "Unexpected type for gfx::NativePixmapPlane::fd. If you need to change " + "this assertion, please contact chromeos-gfx-video@google.com."); + CHECK(plane.fd.is_valid()); + return mojo::PlatformHandle(std::move(plane.fd)); +} + +// static +bool StructTraits<media::stable::mojom::NativePixmapPlaneDataView, + gfx::NativePixmapPlane>:: + Read(media::stable::mojom::NativePixmapPlaneDataView data, + gfx::NativePixmapPlane* out) { + out->stride = data.stride(); + out->offset = data.offset(); + out->size = data.size(); + + mojo::PlatformHandle handle = data.TakeBufferHandle(); + if (!handle.is_fd() || !handle.is_valid_fd()) + return false; + out->fd = handle.TakeFD(); + return true; +} + +// static std::vector<gfx::NativePixmapPlane>& StructTraits< media::stable::mojom::NativePixmapHandleDataView, gfx::NativePixmapHandle>::planes(gfx::NativePixmapHandle& pixmap_handle) {
diff --git a/media/mojo/mojom/stable/native_pixmap_handle_mojom_traits.h b/media/mojo/mojom/stable/native_pixmap_handle_mojom_traits.h index 911356d..5b611da 100644 --- a/media/mojo/mojom/stable/native_pixmap_handle_mojom_traits.h +++ b/media/mojo/mojom/stable/native_pixmap_handle_mojom_traits.h
@@ -9,11 +9,27 @@ namespace gfx { struct NativePixmapHandle; +struct NativePixmapPlane; } // namespace gfx namespace mojo { template <> +struct StructTraits<media::stable::mojom::NativePixmapPlaneDataView, + gfx::NativePixmapPlane> { + static uint32_t stride(const gfx::NativePixmapPlane& plane); + + static uint64_t offset(const gfx::NativePixmapPlane& plane); + + static uint64_t size(const gfx::NativePixmapPlane& plane); + + static mojo::PlatformHandle buffer_handle(gfx::NativePixmapPlane& plane); + + static bool Read(media::stable::mojom::NativePixmapPlaneDataView data, + gfx::NativePixmapPlane* out); +}; + +template <> struct StructTraits<media::stable::mojom::NativePixmapHandleDataView, gfx::NativePixmapHandle> { static std::vector<gfx::NativePixmapPlane>& planes(
diff --git a/media/mojo/mojom/stable/stable_video_decoder_types_mojom_traits.cc b/media/mojo/mojom/stable/stable_video_decoder_types_mojom_traits.cc index 28ad47c..00ad8b6 100644 --- a/media/mojo/mojom/stable/stable_video_decoder_types_mojom_traits.cc +++ b/media/mojo/mojom/stable/stable_video_decoder_types_mojom_traits.cc
@@ -40,8 +40,15 @@ CHECK(input->HasGpuMemoryBuffer()); gfx::GpuMemoryBufferHandle gpu_memory_buffer_handle = input->GetGpuMemoryBuffer()->CloneHandle(); + +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) CHECK_EQ(gpu_memory_buffer_handle.type, gfx::NATIVE_PIXMAP); CHECK(!gpu_memory_buffer_handle.native_pixmap_handle.planes.empty()); +#else + // We should not be trying to serialize a media::VideoFrame for the purposes + // of this interface outside of Linux and Chrome OS. + CHECK(false); +#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) return media::stable::mojom::VideoFrameData::NewGpuMemoryBufferData( media::stable::mojom::GpuMemoryBufferVideoFrameData::New( @@ -525,6 +532,7 @@ return input.id; } +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) // static gfx::NativePixmapHandle StructTraits< media::stable::mojom::NativeGpuMemoryBufferHandleDataView, @@ -533,6 +541,7 @@ CHECK_EQ(input.type, gfx::NATIVE_PIXMAP); return std::move(input.native_pixmap_handle); } +#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) // static bool StructTraits<media::stable::mojom::NativeGpuMemoryBufferHandleDataView, @@ -542,12 +551,17 @@ if (!data.ReadId(&output->id)) return false; - if (!data.ReadPlatformHandle(&output->native_pixmap_handle)) - return false; - output->type = gfx::NATIVE_PIXMAP; +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) + if (!data.ReadPlatformHandle(&output->native_pixmap_handle)) + return false; return true; +#else + // We should not be trying to de-serialize a gfx::GpuMemoryBufferHandle for + // the purposes of this interface outside of Linux and Chrome OS. + return false; +#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) } // static
diff --git a/media/mojo/mojom/stable/stable_video_decoder_types_mojom_traits.h b/media/mojo/mojom/stable/stable_video_decoder_types_mojom_traits.h index f2caf80..a6475841 100644 --- a/media/mojo/mojom/stable/stable_video_decoder_types_mojom_traits.h +++ b/media/mojo/mojom/stable/stable_video_decoder_types_mojom_traits.h
@@ -580,8 +580,18 @@ static const gfx::GpuMemoryBufferId& id( const gfx::GpuMemoryBufferHandle& input); +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) static gfx::NativePixmapHandle platform_handle( gfx::GpuMemoryBufferHandle& input); +#else + static media::stable::mojom::NativePixmapHandlePtr platform_handle( + gfx::GpuMemoryBufferHandle& input) { + // We should not be trying to serialize a gfx::GpuMemoryBufferHandle for the + // purposes of this interface outside of Linux and Chrome OS. + CHECK(false); + return media::stable::mojom::NativePixmapHandle::New(); + } +#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) static bool Read( media::stable::mojom::NativeGpuMemoryBufferHandleDataView data,
diff --git a/media/renderers/paint_canvas_video_renderer.cc b/media/renderers/paint_canvas_video_renderer.cc index bdaaf118..06eff2d3 100644 --- a/media/renderers/paint_canvas_video_renderer.cc +++ b/media/renderers/paint_canvas_video_renderer.cc
@@ -568,9 +568,7 @@ convert_yuva(matrix, libyuv::I444AlphaToARGBMatrix); break; case PIXEL_FORMAT_YUV420AP10: - // TODO(wtc): Use libyuv::I010AlphaToARGBMatrixFilter after - // https://crbug.com/libyuv/922 is fixed. - convert_yuva16(matrix, libyuv::I010AlphaToARGBMatrix); + convert_yuva16_with_filter(matrix, libyuv::I010AlphaToARGBMatrixFilter); break; case PIXEL_FORMAT_YUV422AP10: convert_yuva16_with_filter(matrix, libyuv::I210AlphaToARGBMatrixFilter);
diff --git a/media/renderers/win/media_foundation_renderer.cc b/media/renderers/win/media_foundation_renderer.cc index 61860c60..442f4d4a 100644 --- a/media/renderers/win/media_foundation_renderer.cc +++ b/media/renderers/win/media_foundation_renderer.cc
@@ -679,7 +679,7 @@ return; } - const int kSignificantPlaybackFrames = 1800; // About 30 fps for 1 minute. + const int kSignificantPlaybackFrames = 5400; // About 30 fps for 3 minutes. if (!has_reported_significant_playback_ && cdm_proxy_ && new_stats.video_frames_decoded >= kSignificantPlaybackFrames) { has_reported_significant_playback_ = true;
diff --git a/printing/printing_context_system_dialog_win.cc b/printing/printing_context_system_dialog_win.cc index ba604d3..b7ba6ba 100644 --- a/printing/printing_context_system_dialog_win.cc +++ b/printing/printing_context_system_dialog_win.cc
@@ -10,12 +10,34 @@ #include "base/strings/utf_string_conversions.h" #include "base/task/current_thread.h" #include "printing/backend/win_helper.h" +#include "printing/buildflags/buildflags.h" #include "printing/mojom/print.mojom.h" #include "printing/print_settings_initializer_win.h" #include "skia/ext/skia_utils_win.h" +#if BUILDFLAG(ENABLE_OOP_PRINTING) +#include "printing/printing_features.h" +#endif + namespace printing { +HWND PrintingContextSystemDialogWin::GetWindow() { +#if BUILDFLAG(ENABLE_OOP_PRINTING) + if (features::kEnableOopPrintDriversJobPrint.Get()) { + // Delving through the view tree to get to root window happens separately + // in the browser process (i.e., not in `PrintingContextSystemDialogWin`) + // before sending the identified window owner to the Print Backend service. + // This means that this call is happening in the service, and thus should + // just use the parent view as-is instead of looking for the root window. + // TODO(crbug.com/809738) Pursue having a service-level instantiation of + // `PrintingContextSystemDialogWin` for this behavior. That would ensure + // this logic would be compile-time driven and only invoked by the service. + return reinterpret_cast<HWND>(delegate_->GetParentView()); + } +#endif + return GetRootWindow(delegate_->GetParentView()); +} + PrintingContextSystemDialogWin::PrintingContextSystemDialogWin( Delegate* delegate) : PrintingContextWin(delegate) {} @@ -29,7 +51,7 @@ PrintSettingsCallback callback) { DCHECK(!in_print_job_); - HWND window = GetRootWindow(delegate_->GetParentView()); + HWND window = GetWindow(); DCHECK(window); // Show the OS-dependent dialog box.
diff --git a/printing/printing_context_system_dialog_win.h b/printing/printing_context_system_dialog_win.h index d44698f..d159465 100644 --- a/printing/printing_context_system_dialog_win.h +++ b/printing/printing_context_system_dialog_win.h
@@ -36,6 +36,8 @@ private: friend class MockPrintingContextWin; + HWND GetWindow(); + virtual HRESULT ShowPrintDialog(PRINTDLGEX* options); // Reads the settings from the selected device context. Updates settings_ and
diff --git a/printing/test_printing_context.cc b/printing/test_printing_context.cc index 38ecb4f..3c996a5 100644 --- a/printing/test_printing_context.cc +++ b/printing/test_printing_context.cc
@@ -59,7 +59,15 @@ bool is_scripted, PrintSettingsCallback callback) { // Do not actually ask the user with a dialog, just pretend like user - // selected the default printer and used the default settings for it. + // made some kind of interaction. + if (ask_user_for_settings_cancel_) { + // Pretend the user hit the Cancel button. + std::move(callback).Run(mojom::ResultCode::kCanceled); + return; + } + + // Pretend the user selected the default printer and used the default + // settings for it. scoped_refptr<PrintBackend> print_backend = PrintBackend::CreateInstance(/*locale=*/std::string()); std::string printer_name; @@ -80,6 +88,9 @@ mojom::ResultCode TestPrintingContext::UseDefaultSettings() { scoped_refptr<PrintBackend> print_backend = PrintBackend::CreateInstance(/*locale=*/std::string()); + if (use_default_settings_fails_) + return mojom::ResultCode::kFailed; + std::string printer_name; mojom::ResultCode result = print_backend->GetDefaultPrinterName(printer_name); if (result != mojom::ResultCode::kSuccess)
diff --git a/printing/test_printing_context.h b/printing/test_printing_context.h index 71b93bd..4841b7c 100644 --- a/printing/test_printing_context.h +++ b/printing/test_printing_context.h
@@ -56,6 +56,12 @@ document_done_blocked_by_permissions_ = true; } + // Enables tests to fail with a failed error. + void SetUseDefaultSettingsFails() { use_default_settings_fails_ = true; } + + // Enables tests to fail with a canceled error. + void SetAskUserForSettingsCanceled() { ask_user_for_settings_cancel_ = true; } + // PrintingContext overrides: void AskUserForSettings(int max_pages, bool has_selection, @@ -84,6 +90,8 @@ private: base::flat_map<std::string, std::unique_ptr<PrintSettings>> device_settings_; + bool use_default_settings_fails_ = false; + bool ask_user_for_settings_cancel_ = false; bool new_document_blocked_by_permissions_ = false; #if BUILDFLAG(IS_WIN) bool render_page_blocked_by_permissions_ = false;
diff --git a/sandbox/win/src/handle_closer_agent.cc b/sandbox/win/src/handle_closer_agent.cc index a0bb656..1aea0ebb 100644 --- a/sandbox/win/src/handle_closer_agent.cc +++ b/sandbox/win/src/handle_closer_agent.cc
@@ -7,7 +7,9 @@ #include <stddef.h> #include "base/check.h" +#include "base/logging.h" #include "base/win/static_constants.h" +#include "base/win/win_util.h" #include "base/win/windows_version.h" #include "sandbox/win/src/win_utils.h" #include "third_party/abseil-cpp/absl/types/optional.h" @@ -144,7 +146,7 @@ bool HandleCloserAgent::CloseHandles() { // Skip closing these handles when Application Verifier is in use in order to // avoid invalid-handle exceptions. - if (GetModuleHandleA(base::win::kApplicationVerifierDllName)) + if (base::win::IsAppVerifierLoaded()) return true; // If the accurate handle enumeration fails then fallback to the old brute // force approach. This should only happen on Windows 7.
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json index 5bf6966..d5b3f10 100644 --- a/testing/buildbot/chromium.android.fyi.json +++ b/testing/buildbot/chromium.android.fyi.json
@@ -9581,7 +9581,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M100", - "revision": "version:100.0.4896.58" + "revision": "version:100.0.4896.60" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -9665,7 +9665,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M101", - "revision": "version:101.0.4951.9" + "revision": "version:101.0.4951.11" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -10085,7 +10085,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M100", - "revision": "version:100.0.4896.58" + "revision": "version:100.0.4896.60" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -10169,7 +10169,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M101", - "revision": "version:101.0.4951.9" + "revision": "version:101.0.4951.11" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json index ee310f3..55ff66d 100644 --- a/testing/buildbot/chromium.android.json +++ b/testing/buildbot/chromium.android.json
@@ -44881,7 +44881,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M100", - "revision": "version:100.0.4896.58" + "revision": "version:100.0.4896.60" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -44965,7 +44965,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M101", - "revision": "version:101.0.4951.9" + "revision": "version:101.0.4951.11" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -45385,7 +45385,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M100", - "revision": "version:100.0.4896.58" + "revision": "version:100.0.4896.60" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -45469,7 +45469,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M101", - "revision": "version:101.0.4951.9" + "revision": "version:101.0.4951.11" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -45893,7 +45893,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M100", - "revision": "version:100.0.4896.58" + "revision": "version:100.0.4896.60" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -45977,7 +45977,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M101", - "revision": "version:101.0.4951.9" + "revision": "version:101.0.4951.11" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -46397,7 +46397,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M100", - "revision": "version:100.0.4896.58" + "revision": "version:100.0.4896.60" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -46481,7 +46481,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M101", - "revision": "version:101.0.4951.9" + "revision": "version:101.0.4951.11" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -46972,7 +46972,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M100", - "revision": "version:100.0.4896.58" + "revision": "version:100.0.4896.60" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -47056,7 +47056,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M101", - "revision": "version:101.0.4951.9" + "revision": "version:101.0.4951.11" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -47476,7 +47476,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M100", - "revision": "version:100.0.4896.58" + "revision": "version:100.0.4896.60" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -47560,7 +47560,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M101", - "revision": "version:101.0.4951.9" + "revision": "version:101.0.4951.11" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -48051,7 +48051,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M100", - "revision": "version:100.0.4896.58" + "revision": "version:100.0.4896.60" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -48135,7 +48135,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M101", - "revision": "version:101.0.4951.9" + "revision": "version:101.0.4951.11" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -48555,7 +48555,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M100", - "revision": "version:100.0.4896.58" + "revision": "version:100.0.4896.60" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}", @@ -48639,7 +48639,7 @@ { "cipd_package": "chromium/testing/weblayer-x86", "location": "weblayer_instrumentation_test_M101", - "revision": "version:101.0.4951.9" + "revision": "version:101.0.4951.11" }, { "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/filters/fuchsia.storage_unittests.filter b/testing/buildbot/filters/fuchsia.storage_unittests.filter index f4a2906..e7798fa 100644 --- a/testing/buildbot/filters/fuchsia.storage_unittests.filter +++ b/testing/buildbot/filters/fuchsia.storage_unittests.filter
@@ -1,16 +1,3 @@ -# AmountOfFreeDiskSpace not implemented for Fuchsia. --All/BlobMemoryControllerTest.FullEviction/0 --All/BlobMemoryControllerTest.MultipleFilesPaged/0 --All/BlobMemoryControllerTest.OnMemoryPressure/0 --All/BlobMemoryControllerTest.PageToDisk/0 --All/BlobMemoryControllerTest.PagingStopsWhenFull/0 - --All/BlobMemoryControllerTest.FullEviction/1 --All/BlobMemoryControllerTest.MultipleFilesPaged/1 --All/BlobMemoryControllerTest.OnMemoryPressure/1 --All/BlobMemoryControllerTest.PageToDisk/1 --All/BlobMemoryControllerTest.PagingStopsWhenFull/1 - # crbug.com/1077456 -All/ObfuscatedFileUtilTest.TestTouch/0 -DraggedFileUtilTest.TouchTest
diff --git a/testing/buildbot/filters/ozone-linux.interactive_ui_tests_wayland.filter b/testing/buildbot/filters/ozone-linux.interactive_ui_tests_wayland.filter index 58a7b0c..2aff816 100644 --- a/testing/buildbot/filters/ozone-linux.interactive_ui_tests_wayland.filter +++ b/testing/buildbot/filters/ozone-linux.interactive_ui_tests_wayland.filter
@@ -42,10 +42,15 @@ -WidgetInputMethodInteractiveTest.OneWindow -WidgetInputMethodInteractiveTest.TwoWindows -# Extremely flaky. --MenuControllerUITest.TestMouseOverShownMenu +# TODO(crbug.com/523255,crbug.com/1192997): Fix flaky MenuItemView tests. +-MenuItemViewTestBasic2.SelectItem2 -MenuItemViewTestInsert20.InsertItem20 -MenuItemViewTestInsert00.InsertItem00 +-MenuItemViewTestInsert12.InsertItem12 +-MenuItemViewTestInsert22.InsertItem22 + +# Extremely flaky. +-MenuControllerUITest.TestMouseOverShownMenu -BookmarkBarViewTest18.BookmarkBarViewTest18_SiblingMenu # TODO(crbug.com/1195324): flaky fullscreen notification test
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl index 4a39a8d2..63a5807 100644 --- a/testing/buildbot/variants.pyl +++ b/testing/buildbot/variants.pyl
@@ -459,7 +459,7 @@ { 'cipd_package': 'chromium/testing/weblayer-x86', 'location': 'weblayer_instrumentation_test_M101', - 'revision': 'version:101.0.4951.9', + 'revision': 'version:101.0.4951.11', } ], }, @@ -483,7 +483,7 @@ { 'cipd_package': 'chromium/testing/weblayer-x86', 'location': 'weblayer_instrumentation_test_M100', - 'revision': 'version:100.0.4896.58', + 'revision': 'version:100.0.4896.60', } ], }, @@ -603,7 +603,7 @@ { 'cipd_package': 'chromium/testing/weblayer-x86', 'location': 'weblayer_instrumentation_test_M101', - 'revision': 'version:101.0.4951.9', + 'revision': 'version:101.0.4951.11', } ], }, @@ -627,7 +627,7 @@ { 'cipd_package': 'chromium/testing/weblayer-x86', 'location': 'weblayer_instrumentation_test_M100', - 'revision': 'version:100.0.4896.58', + 'revision': 'version:100.0.4896.60', } ], }, @@ -747,7 +747,7 @@ { 'cipd_package': 'chromium/testing/weblayer-x86', 'location': 'weblayer_instrumentation_test_M101', - 'revision': 'version:101.0.4951.9', + 'revision': 'version:101.0.4951.11', } ], }, @@ -771,7 +771,7 @@ { 'cipd_package': 'chromium/testing/weblayer-x86', 'location': 'weblayer_instrumentation_test_M100', - 'revision': 'version:100.0.4896.58', + 'revision': 'version:100.0.4896.60', } ], },
diff --git a/testing/test.gni b/testing/test.gni index a7682c6..8b24f252 100644 --- a/testing/test.gni +++ b/testing/test.gni
@@ -971,11 +971,6 @@ data = [ invoker.script ] - if (defined(invoker.run_under_python2) && invoker.run_under_python2) { - use_vpython3 = false - data += [ "//.vpython" ] - } - if (defined(invoker.data)) { data += invoker.data }
diff --git a/third_party/blink/common/BUILD.gn b/third_party/blink/common/BUILD.gn index fc53ff5..8992a17 100644 --- a/third_party/blink/common/BUILD.gn +++ b/third_party/blink/common/BUILD.gn
@@ -182,6 +182,7 @@ "notifications/notification_mojom_traits.cc", "notifications/notification_resources.cc", "notifications/platform_notification_data.cc", + "origin_trials/manual_completion_origin_trial_features.cc", "origin_trials/navigation_origin_trial_features.cc", "origin_trials/trial_token.cc", "origin_trials/trial_token_result.cc",
diff --git a/third_party/blink/common/origin_trials/OT_OWNERS b/third_party/blink/common/origin_trials/OT_OWNERS new file mode 100644 index 0000000..3af6cd4b --- /dev/null +++ b/third_party/blink/common/origin_trials/OT_OWNERS
@@ -0,0 +1,7 @@ +# This file covers ownership of the following file: +# //third_party/blink/common/origin_trials/manual_completion_origin_trial_features.cc + +chasej@chromium.org +danielrsmith@google.com +kyleju@chromium.org +pastithas@google.com
diff --git a/third_party/blink/common/origin_trials/OWNERS b/third_party/blink/common/origin_trials/OWNERS index 9e491c4..f34e244 100644 --- a/third_party/blink/common/origin_trials/OWNERS +++ b/third_party/blink/common/origin_trials/OWNERS
@@ -8,5 +8,7 @@ iclelland@chromium.org mek@chromium.org +per-file manual_completion_origin_trial_features.cc=set noparent +per-file manual_completion_origin_trial_features.cc=file://third_party/blink/common/origin_trials/OT_OWNERS per-file navigation_origin_trial_features.cc=set noparent per-file navigation_origin_trial_features.cc=file://third_party/blink/SECURITY_OWNERS
diff --git a/third_party/blink/common/origin_trials/manual_completion_origin_trial_features.cc b/third_party/blink/common/origin_trials/manual_completion_origin_trial_features.cc new file mode 100644 index 0000000..e1ad052 --- /dev/null +++ b/third_party/blink/common/origin_trials/manual_completion_origin_trial_features.cc
@@ -0,0 +1,25 @@ +// 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. + +// This file provides FeatureHasExpiryGracePeriod which is declared in +// origin_trials.h. FeatureHasExpiryGracePeriod is defined in this file since +// changes to it require review from the origin trials team, listed in the +// OWNERS file. + +#include "third_party/blink/public/common/origin_trials/origin_trials.h" + +#include "base/containers/contains.h" + +namespace blink::origin_trials { + +bool FeatureHasExpiryGracePeriod(OriginTrialFeature feature) { + static OriginTrialFeature const kHasExpiryGracePeriod[] = { + // Enable the kOriginTrialsSampleAPIExpiryGracePeriod feature as a manual + // completion feature, for tests. + OriginTrialFeature::kOriginTrialsSampleAPIExpiryGracePeriod, + }; + return base::Contains(kHasExpiryGracePeriod, feature); +} + +} // namespace blink::origin_trials
diff --git a/third_party/blink/common/origin_trials/trial_token_validator.cc b/third_party/blink/common/origin_trials/trial_token_validator.cc index 033711b..746d783 100644 --- a/third_party/blink/common/origin_trials/trial_token_validator.cc +++ b/third_party/blink/common/origin_trials/trial_token_validator.cc
@@ -12,6 +12,7 @@ #include "net/http/http_response_headers.h" #include "net/url_request/url_request.h" #include "third_party/blink/public/common/origin_trials/origin_trial_policy.h" +#include "third_party/blink/public/common/origin_trials/origin_trials.h" #include "third_party/blink/public/common/origin_trials/trial_token.h" #include "third_party/blink/public/common/origin_trials/trial_token_result.h" @@ -30,6 +31,30 @@ return policy && policy->IsOriginTrialsSupported(); } +// Validates the provided trial_token. If provided, the third_party_origins is +// only used for validating third-party tokens. +OriginTrialTokenStatus IsTokenValid( + const TrialToken& trial_token, + const url::Origin& origin, + base::span<const url::Origin> third_party_origins, + base::Time current_time) { + OriginTrialTokenStatus status; + if (trial_token.is_third_party()) { + if (!third_party_origins.empty()) { + for (const auto& third_party_origin : third_party_origins) { + status = trial_token.IsValid(third_party_origin, current_time); + if (status == OriginTrialTokenStatus::kSuccess) + break; + } + } else { + status = OriginTrialTokenStatus::kWrongOrigin; + } + } else { + status = trial_token.IsValid(origin, current_time); + } + return status; +} + } // namespace TrialTokenValidator::TrialTokenValidator() {} @@ -80,20 +105,27 @@ if (status != OriginTrialTokenStatus::kSuccess) return TrialTokenResult(status); - // If the third_party flag is set on the token, we match it against third - // party origin if it exists. Otherwise match against document origin. - if (trial_token->is_third_party()) { - if (!third_party_origins.empty()) { - for (const auto& third_party_origin : third_party_origins) { - status = trial_token->IsValid(third_party_origin, current_time); - if (status == OriginTrialTokenStatus::kSuccess) - break; + status = + IsTokenValid(*trial_token, origin, third_party_origins, current_time); + + if (status == OriginTrialTokenStatus::kExpired) { + if (origin_trials::IsTrialValid(trial_token->feature_name())) { + base::Time validated_time = current_time; + // Manual completion trials have an expiry grace period. For these trials + // the token expiry time is valid if: + // token.expiry_time + kExpiryGracePeriod > current_time + for (OriginTrialFeature feature : + origin_trials::FeaturesForTrial(trial_token->feature_name())) { + if (origin_trials::FeatureHasExpiryGracePeriod(feature)) { + validated_time = current_time - kExpiryGracePeriod; + status = IsTokenValid(*trial_token, origin, third_party_origins, + validated_time); + if (status == OriginTrialTokenStatus::kSuccess) { + break; + } + } } - } else { - status = OriginTrialTokenStatus::kWrongOrigin; } - } else { - status = trial_token->IsValid(origin, current_time); } if (status != OriginTrialTokenStatus::kSuccess)
diff --git a/third_party/blink/common/origin_trials/trial_token_validator_unittest.cc b/third_party/blink/common/origin_trials/trial_token_validator_unittest.cc index b23e23e3..e19c504 100644 --- a/third_party/blink/common/origin_trials/trial_token_validator_unittest.cc +++ b/third_party/blink/common/origin_trials/trial_token_validator_unittest.cc
@@ -170,6 +170,49 @@ "MiLCAidXNhZ2UiOiAic3Vic2V0IiwgImZlYXR1cmUiOiAiRnJvYnVsYXRlVGhpcmRQYXJ0eSIs" "ICJleHBpcnkiOiAyMDAwMDAwMDAwfQ=="; +// Well-formed token for a feature with an expiry grace period. +// Generate this token with the command (in tools/origin_trials): +// generate_token.py valid.example.com FrobulateExpiryGracePeriod +// --expire-timestamp=2000000000 +const char kExpiryGracePeriodToken[] = + "A2AVLsM2Set66KCwTfxH1ni9v8Jcs685qHKDLGam1LmpvnJE9GhYQwbLid3Xlqs/" + "2Em2HBp8CMZlj11Qk6R06QUAAABqeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5leGFtcGxlLm" + "NvbTo0NDMiLCAiZmVhdHVyZSI6ICJGcm9idWxhdGVFeHBpcnlHcmFjZVBlcmlvZCIsICJleHBp" + "cnkiOiAyMDAwMDAwMDAwfQ=="; + +// Well-formed token for match against third party origins and a feature with an +// expiry grace period. +// Generate this token with the command (in tools/origin_trials): +// generate_token.py valid.example.com FrobulateExpiryGracePeriod +// --is-third-party --expire-timestamp=2000000000 +const char kExpiryGracePeriodThirdPartyToken[] = + "A3wCXDPU5jfARV5KUetX5PI46W41gAbndIZKA7mKrTy6WyXoGFavV+" + "vBZejzC2D3Ffti4thz0AOMP+K/" + "oWxUvA8AAACAeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5leGFtcGxlLmNvbTo0NDMiLCAiZm" + "VhdHVyZSI6ICJGcm9idWxhdGVFeHBpcnlHcmFjZVBlcmlvZCIsICJleHBpcnkiOiAyMDAwMDAw" + "MDAwLCAiaXNUaGlyZFBhcnR5IjogdHJ1ZX0="; + +// Well-formed token, with an unknown feature name. +// Generate this token with the command (in tools/origin_trials): +// generate_token.py valid.example.com Grokalyze +// --expire-timestamp=2000000000 +const char kUnknownFeatureToken[] = + "AxjosEuqWyp9mrBFMOHJtO84YyY4QYuJ6TUNBMVzKMUWPE+B7Nwg2kgZKGO+" + "85m0bG0vWEs4m53TWtO1LNf0RgsAAABZeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5leGFtcG" + "xlLmNvbTo0NDMiLCAiZmVhdHVyZSI6ICJHcm9rYWx5emUiLCAiZXhwaXJ5IjogMjAwMDAwMDAw" + "MH0="; + +// Well-formed token for match against third party origins, with an unknown +// feature name. Generate this token with the command (in tools/origin_trials): +// generate_token.py valid.example.com Grokalyze +// --is-third-party --expire-timestamp=2000000000 +const char kUnknownFeatureThirdPartyToken[] = + "A7BJkSTbLJ8/EM61BwStBGK3+hAnss/" + "fmvpkRmuGuBssyEKczr0iqmj4J3hvRM+" + "WzjotyzFopeNLSNU6FGlFZwMAAABveyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5leGFtcGxlL" + "mNvbTo0NDMiLCAiZmVhdHVyZSI6ICJHcm9rYWx5emUiLCAiZXhwaXJ5IjogMjAwMDAwMDAwMCw" + "gImlzVGhpcmRQYXJ0eSI6IHRydWV9"; + // This timestamp is set to a time after the expiry timestamp of kExpiredToken, // but before the expiry timestamp of kValidToken. double kNowTimestamp = 1500000000; @@ -533,4 +576,69 @@ kAppropriateFeatureName, Now())); } +TEST_F(TrialTokenValidatorTest, ValidateValidExpiryGraceToken) { + // This token is valid one day before the end of the expiry grace period, + // even though it is past the token's expiry time. + auto current_time = + kSampleTokenExpiryTime + kExpiryGracePeriod - base::Days(1); + TrialTokenResult result = validator_.ValidateToken( + kExpiryGracePeriodToken, appropriate_origin_, current_time); + EXPECT_EQ(result.Status(), blink::OriginTrialTokenStatus::kSuccess); + EXPECT_EQ(kSampleTokenExpiryTime, result.ParsedToken()->expiry_time()); +} + +TEST_F(TrialTokenValidatorTest, ValidateExpiredExpiryGraceToken) { + // This token is expired at the end of the expiry grace period. + auto current_time = kSampleTokenExpiryTime + kExpiryGracePeriod; + TrialTokenResult result = validator_.ValidateToken( + kExpiryGracePeriodToken, appropriate_origin_, current_time); + EXPECT_EQ(result.Status(), blink::OriginTrialTokenStatus::kExpired); + EXPECT_EQ(kSampleTokenExpiryTime, result.ParsedToken()->expiry_time()); +} + +TEST_F(TrialTokenValidatorTest, ValidateValidExpiryGraceThirdPartyToken) { + url::Origin third_party_origins[] = {appropriate_origin_}; + // This token is valid one day before the end of the expiry grace period, + // even though it is past the token's expiry time. + auto current_time = + kSampleTokenExpiryTime + kExpiryGracePeriod - base::Days(1); + TrialTokenResult result = validator_.ValidateToken( + kExpiryGracePeriodThirdPartyToken, appropriate_origin_, + third_party_origins, current_time); + EXPECT_EQ(result.Status(), blink::OriginTrialTokenStatus::kSuccess); + EXPECT_EQ(kSampleTokenExpiryTime, result.ParsedToken()->expiry_time()); + EXPECT_EQ(true, result.ParsedToken()->is_third_party()); +} + +TEST_F(TrialTokenValidatorTest, ValidateExpiredExpiryGraceThirdPartyToken) { + url::Origin third_party_origins[] = {appropriate_origin_}; + // This token is expired at the end of the expiry grace period. + auto current_time = kSampleTokenExpiryTime + kExpiryGracePeriod; + TrialTokenResult result = validator_.ValidateToken( + kExpiryGracePeriodThirdPartyToken, appropriate_origin_, + third_party_origins, current_time); + EXPECT_EQ(result.Status(), blink::OriginTrialTokenStatus::kExpired); + EXPECT_EQ(kSampleTokenExpiryTime, result.ParsedToken()->expiry_time()); + EXPECT_EQ(true, result.ParsedToken()->is_third_party()); +} + +TEST_F(TrialTokenValidatorTest, ValidateUnknownFeatureToken) { + TrialTokenResult result = validator_.ValidateToken( + kUnknownFeatureToken, appropriate_origin_, Now()); + EXPECT_EQ(result.Status(), blink::OriginTrialTokenStatus::kSuccess); + EXPECT_EQ(kInappropriateFeatureName, result.ParsedToken()->feature_name()); + EXPECT_EQ(kSampleTokenExpiryTime, result.ParsedToken()->expiry_time()); +} + +TEST_F(TrialTokenValidatorTest, ValidateUnknownFeatureThirdPartyToken) { + url::Origin third_party_origins[] = {appropriate_origin_}; + TrialTokenResult result = + validator_.ValidateToken(kUnknownFeatureThirdPartyToken, + appropriate_origin_, third_party_origins, Now()); + EXPECT_EQ(result.Status(), blink::OriginTrialTokenStatus::kSuccess); + EXPECT_EQ(kInappropriateFeatureName, result.ParsedToken()->feature_name()); + EXPECT_EQ(kSampleTokenExpiryTime, result.ParsedToken()->expiry_time()); + EXPECT_EQ(true, result.ParsedToken()->is_third_party()); +} + } // namespace blink::trial_token_validator_unittest
diff --git a/third_party/blink/public/common/origin_trials/origin_trials.h b/third_party/blink/public/common/origin_trials/origin_trials.h index 0664c51..aa35f10 100644 --- a/third_party/blink/public/common/origin_trials/origin_trials.h +++ b/third_party/blink/public/common/origin_trials/origin_trials.h
@@ -52,6 +52,10 @@ BLINK_COMMON_EXPORT bool FeatureEnabledForNavigation( OriginTrialFeature feature); +// Returns true if |feature| has an expiry grace period. +BLINK_COMMON_EXPORT bool FeatureHasExpiryGracePeriod( + OriginTrialFeature feature); + } // namespace origin_trials } // namespace blink
diff --git a/third_party/blink/public/common/origin_trials/trial_token_validator.h b/third_party/blink/public/common/origin_trials/trial_token_validator.h index cabf757..79880d4d 100644 --- a/third_party/blink/public/common/origin_trials/trial_token_validator.h +++ b/third_party/blink/public/common/origin_trials/trial_token_validator.h
@@ -26,6 +26,9 @@ class OriginTrialPolicy; class TrialTokenResult; +// The expiry grace period for origin trials that must be manually completed. +constexpr base::TimeDelta kExpiryGracePeriod = base::Days(30); + // TrialTokenValidator checks that a page's OriginTrial token enables a certain // feature. //
diff --git a/third_party/blink/public/mojom/BUILD.gn b/third_party/blink/public/mojom/BUILD.gn index 83d68e1..23cc3eea 100644 --- a/third_party/blink/public/mojom/BUILD.gn +++ b/third_party/blink/public/mojom/BUILD.gn
@@ -102,6 +102,7 @@ "keyboard_lock/keyboard_lock.mojom", "leak_detector/leak_detector.mojom", "link_to_text/link_to_text.mojom", + "loader/anchor_element_interaction_host.mojom", "loader/code_cache.mojom", "loader/content_security_notifier.mojom", "loader/fetch_client_settings_object.mojom",
diff --git a/third_party/blink/public/mojom/loader/anchor_element_interaction_host.mojom b/third_party/blink/public/mojom/loader/anchor_element_interaction_host.mojom new file mode 100644 index 0000000..a28eab1 --- /dev/null +++ b/third_party/blink/public/mojom/loader/anchor_element_interaction_host.mojom
@@ -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. + +module blink.mojom; + +import "url/mojom/url.mojom"; + +// Interface for sending the URL from the renderer side to browser process. +interface AnchorElementInteractionHost { + // On PointerDown events for anchor elements that are part + // of the HTTP family, the renderer calls OnPointerDown to pass + // the URL to the browser process. In the browser process, the URL gets + // preresolved and preconnected. + OnPointerDown(url.mojom.Url target); +}; \ No newline at end of file
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn index 5c2f8dc5..6fd98e6 100644 --- a/third_party/blink/renderer/core/BUILD.gn +++ b/third_party/blink/renderer/core/BUILD.gn
@@ -1282,7 +1282,6 @@ "fragment_directive/text_fragment_selector_generator_test.cc", "fragment_directive/text_fragment_selector_test.cc", "frame/ad_tracker_test.cc", - "frame/anchor_element_listener_test.cc", "frame/attribution_response_parsing_test.cc", "frame/attribution_src_loader_test.cc", "frame/browser_controls_test.cc", @@ -1346,6 +1345,7 @@ "inspector/protocol_unittest.cc", "intersection_observer/intersection_observer_test.cc", "loader/alternate_signed_exchange_resource_info_test.cc", + "loader/anchor_element_interaction_test.cc", "loader/base_fetch_context_test.cc", "loader/cookie_jar_unittest.cc", "loader/document_load_timing_test.cc",
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc index e4853bd..79c6959 100644 --- a/third_party/blink/renderer/core/dom/document.cc +++ b/third_party/blink/renderer/core/dom/document.cc
@@ -249,6 +249,7 @@ #include "third_party/blink/renderer/core/layout/layout_object_factory.h" #include "third_party/blink/renderer/core/layout/layout_view.h" #include "third_party/blink/renderer/core/layout/text_autosizer.h" +#include "third_party/blink/renderer/core/loader/anchor_element_interaction_tracker.h" #include "third_party/blink/renderer/core/loader/cookie_jar.h" #include "third_party/blink/renderer/core/loader/document_loader.h" #include "third_party/blink/renderer/core/loader/frame_fetch_context.h" @@ -2723,6 +2724,11 @@ autosizer->UpdatePageInfo(); GetFrame()->DidAttachDocument(); + if (AnchorElementInteractionTracker::IsFeatureEnabled() && + !GetFrame()->IsProvisional()) { + anchor_element_interaction_tracker_ = + MakeGarbageCollected<AnchorElementInteractionTracker>(*this); + } lifecycle_.AdvanceTo(DocumentLifecycle::kStyleClean); if (View()) @@ -7943,6 +7949,7 @@ visitor->Trace(meta_theme_color_elements_); visitor->Trace(unassociated_listed_elements_); visitor->Trace(intrinsic_size_observer_); + visitor->Trace(anchor_element_interaction_tracker_); Supplementable<Document>::Trace(visitor); TreeScope::Trace(visitor); ContainerNode::Trace(visitor);
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h index 8bbfac2..ad9038ec 100644 --- a/third_party/blink/renderer/core/dom/document.h +++ b/third_party/blink/renderer/core/dom/document.h
@@ -110,6 +110,7 @@ namespace blink { +class AnchorElementInteractionTracker; class AnimationClock; class AXContext; class AXObjectCache; @@ -2119,6 +2120,7 @@ Member<Element> document_element_; UserActionElementSet user_action_elements_; Member<RootScrollerController> root_scroller_controller_; + Member<AnchorElementInteractionTracker> anchor_element_interaction_tracker_; double overscroll_accumulated_delta_x_ = 0; double overscroll_accumulated_delta_y_ = 0;
diff --git a/third_party/blink/renderer/core/frame/anchor_element_interaction_tracker.cc b/third_party/blink/renderer/core/frame/anchor_element_interaction_tracker.cc deleted file mode 100644 index 9fd54000..0000000 --- a/third_party/blink/renderer/core/frame/anchor_element_interaction_tracker.cc +++ /dev/null
@@ -1,29 +0,0 @@ -// 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/frame/anchor_element_interaction_tracker.h" -#include "third_party/blink/public/common/features.h" -#include "third_party/blink/renderer/core/dom/document.h" -#include "third_party/blink/renderer/core/event_type_names.h" -#include "third_party/blink/renderer/core/frame/anchor_element_listener.h" - -namespace blink { - -AnchorElementInteractionTracker::AnchorElementInteractionTracker( - Document& document) { - anchor_element_listener_ = MakeGarbageCollected<AnchorElementListener>(); - document.addEventListener(event_type_names::kPointerdown, - anchor_element_listener_, true); -} - -void AnchorElementInteractionTracker::Trace(Visitor* visitor) const { - visitor->Trace(anchor_element_listener_); -} - -// static -bool AnchorElementInteractionTracker::IsFeatureEnabled() { - return base::FeatureList::IsEnabled(features::kAnchorElementInteraction); -} - -} // namespace blink
diff --git a/third_party/blink/renderer/core/frame/anchor_element_listener.h b/third_party/blink/renderer/core/frame/anchor_element_listener.h deleted file mode 100644 index f2ed820..0000000 --- a/third_party/blink/renderer/core/frame/anchor_element_listener.h +++ /dev/null
@@ -1,45 +0,0 @@ -// 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_FRAME_ANCHOR_ELEMENT_LISTENER_H_ -#define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_ANCHOR_ELEMENT_LISTENER_H_ - -#include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/core/dom/events/native_event_listener.h" - -namespace blink { - -class Node; -class Event; -class HTMLAnchorElement; -class KURL; - -// Listens for kPointerdown events, and checks to see if an anchor -// element is clicked with a valid href to be eligible for preloading. -class CORE_EXPORT AnchorElementListener : public NativeEventListener { - public: - void Invoke(ExecutionContext* execution_context, Event* event) override; - - private: - HTMLAnchorElement* FirstAnchorElementIncludingSelf(Node* node); - - // Gets the `html_anchor_element's` href attribute if it is part - // of the HTTP family - KURL GetHrefEligibleForPreloading( - const HTMLAnchorElement& html_anchor_element); - - FRIEND_TEST_ALL_PREFIXES(AnchorElementListenerTest, ValidHref); - FRIEND_TEST_ALL_PREFIXES(AnchorElementListenerTest, InvalidHref); - FRIEND_TEST_ALL_PREFIXES(AnchorElementListenerTest, OneAnchorElementCheck); - FRIEND_TEST_ALL_PREFIXES(AnchorElementListenerTest, NestedAnchorElementCheck); - FRIEND_TEST_ALL_PREFIXES(AnchorElementListenerTest, - NestedDivAnchorElementCheck); - FRIEND_TEST_ALL_PREFIXES(AnchorElementListenerTest, - MultipleNestedAnchorElementCheck); - FRIEND_TEST_ALL_PREFIXES(AnchorElementListenerTest, NoAnchorElementCheck); -}; - -} // namespace blink - -#endif // THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_ANCHOR_ELEMENT_LISTENER_H_
diff --git a/third_party/blink/renderer/core/frame/anchor_element_listener_test.cc b/third_party/blink/renderer/core/frame/anchor_element_listener_test.cc deleted file mode 100644 index d617e9e..0000000 --- a/third_party/blink/renderer/core/frame/anchor_element_listener_test.cc +++ /dev/null
@@ -1,126 +0,0 @@ -// 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/frame/anchor_element_listener.h" -#include "third_party/blink/renderer/core/dom/element.h" -#include "third_party/blink/renderer/core/html/html_anchor_element.h" -#include "third_party/blink/renderer/core/testing/sim/sim_request.h" -#include "third_party/blink/renderer/core/testing/sim/sim_test.h" - -namespace blink { - -class AnchorElementListenerTest : public SimTest {}; - -TEST_F(AnchorElementListenerTest, ValidHref) { - String source("https://example.com/p1"); - SimRequest main_resource(source, "text/html"); - LoadURL(source); - main_resource.Complete( - "<a id='anchor1' href='https://anchor1.com/'>example</a>"); - auto* anchor_element_listener_ = - MakeGarbageCollected<AnchorElementListener>(); - auto* anchor_element = - DynamicTo<HTMLAnchorElement>(GetDocument().getElementById("anchor1")); - KURL URL = - anchor_element_listener_->GetHrefEligibleForPreloading(*anchor_element); - KURL expected_url = KURL("https://anchor1.com/"); - EXPECT_FALSE(URL.IsEmpty()); - EXPECT_EQ(expected_url, URL); -} - -TEST_F(AnchorElementListenerTest, InvalidHref) { - String source("https://example.com/p1"); - SimRequest main_resource(source, "text/html"); - LoadURL(source); - main_resource.Complete("<a id='anchor1' href='about:blank'>example</a>"); - auto* anchor_element_listener_ = - MakeGarbageCollected<AnchorElementListener>(); - auto* anchor_element = - DynamicTo<HTMLAnchorElement>(GetDocument().getElementById("anchor1")); - EXPECT_TRUE( - anchor_element_listener_->GetHrefEligibleForPreloading(*anchor_element) - .IsEmpty()); -} - -TEST_F(AnchorElementListenerTest, OneAnchorElementCheck) { - String source("https://example.com/p1"); - SimRequest main_resource(source, "text/html"); - LoadURL(source); - main_resource.Complete( - "<a id='anchor1' href='https://anchor1.com/'>example</a>"); - auto* anchor_element_listener_ = - MakeGarbageCollected<AnchorElementListener>(); - auto* element = GetDocument().getElementById("anchor1"); - auto* anchor_element = - anchor_element_listener_->FirstAnchorElementIncludingSelf(element); - auto* expected_anchor = - DynamicTo<HTMLAnchorElement>(GetDocument().getElementById("anchor1")); - EXPECT_EQ(expected_anchor, anchor_element); -} - -TEST_F(AnchorElementListenerTest, NestedAnchorElementCheck) { - String source("https://example.com/p1"); - SimRequest main_resource(source, "text/html"); - LoadURL(source); - main_resource.Complete( - "<a id='anchor1' href='https://anchor1.com/'><a id='anchor2' " - "href='https://anchor2.com/'></a></a>"); - auto* anchor_element_listener_ = - MakeGarbageCollected<AnchorElementListener>(); - auto* element = GetDocument().getElementById("anchor2"); - auto* anchor_element = - anchor_element_listener_->FirstAnchorElementIncludingSelf(element); - auto* expected_anchor = - DynamicTo<HTMLAnchorElement>(GetDocument().getElementById("anchor2")); - EXPECT_EQ(expected_anchor, anchor_element); -} - -TEST_F(AnchorElementListenerTest, NestedDivAnchorElementCheck) { - String source("https://example.com/p1"); - SimRequest main_resource(source, "text/html"); - LoadURL(source); - main_resource.Complete( - "<a id='anchor1' href='https://anchor1.com/'><div " - "id='div1id'></div></a>"); - auto* anchor_element_listener_ = - MakeGarbageCollected<AnchorElementListener>(); - auto* element = GetDocument().getElementById("div1id"); - auto* anchor_element = - anchor_element_listener_->FirstAnchorElementIncludingSelf(element); - auto* expected_anchor = - DynamicTo<HTMLAnchorElement>(GetDocument().getElementById("anchor1")); - EXPECT_EQ(expected_anchor, anchor_element); -} - -TEST_F(AnchorElementListenerTest, MultipleNestedAnchorElementCheck) { - String source("https://example.com/p1"); - SimRequest main_resource(source, "text/html"); - LoadURL(source); - main_resource.Complete( - "<a id='anchor1' href='https://anchor1.com/'><p id='paragraph1id'><div " - "id='div1id'><div id='div2id'></div></div></p></a>"); - auto* anchor_element_listener_ = - MakeGarbageCollected<AnchorElementListener>(); - auto* element = GetDocument().getElementById("div2id"); - auto* anchor_element = - anchor_element_listener_->FirstAnchorElementIncludingSelf(element); - auto* expected_anchor = - DynamicTo<HTMLAnchorElement>(GetDocument().getElementById("anchor1")); - EXPECT_EQ(expected_anchor, anchor_element); -} - -TEST_F(AnchorElementListenerTest, NoAnchorElementCheck) { - String source("https://example.com/p1"); - SimRequest main_resource(source, "text/html"); - LoadURL(source); - main_resource.Complete("<div id='div1id'></div>"); - auto* anchor_element_listener_ = - MakeGarbageCollected<AnchorElementListener>(); - auto* element = GetDocument().getElementById("div1id"); - auto* anchor_element = - anchor_element_listener_->FirstAnchorElementIncludingSelf(element); - EXPECT_EQ(nullptr, anchor_element); -} - -} // namespace blink
diff --git a/third_party/blink/renderer/core/frame/build.gni b/third_party/blink/renderer/core/frame/build.gni index e94c900..00d49ab8 100644 --- a/third_party/blink/renderer/core/frame/build.gni +++ b/third_party/blink/renderer/core/frame/build.gni
@@ -5,10 +5,6 @@ blink_core_sources_frame = [ "ad_tracker.cc", "ad_tracker.h", - "anchor_element_listener.cc", - "anchor_element_listener.h", - "anchor_element_interaction_tracker.cc", - "anchor_element_interaction_tracker.h", "attribution_reporting.cc", "attribution_reporting.h", "attribution_response_parsing.cc",
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc index 85849e4..ae445a52 100644 --- a/third_party/blink/renderer/core/frame/local_frame.cc +++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -109,7 +109,6 @@ #include "third_party/blink/renderer/core/fileapi/public_url_manager.h" #include "third_party/blink/renderer/core/fragment_directive/text_fragment_handler.h" #include "third_party/blink/renderer/core/frame/ad_tracker.h" -#include "third_party/blink/renderer/core/frame/anchor_element_interaction_tracker.h" #include "third_party/blink/renderer/core/frame/attribution_src_loader.h" #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" #include "third_party/blink/renderer/core/frame/event_handler_registry.h" @@ -398,7 +397,6 @@ visitor->Trace(console_); visitor->Trace(smooth_scroll_sequencer_); visitor->Trace(content_capture_manager_); - visitor->Trace(anchor_element_interaction_tracker_); visitor->Trace(system_clipboard_); visitor->Trace(virtual_keyboard_overlay_changed_observers_); visitor->Trace(pause_handle_receivers_); @@ -711,10 +709,6 @@ // appendChild()), the drag source will detach and stop firing drag events // even after the frame reattaches. GetEventHandler().Clear(); - if (AnchorElementInteractionTracker::IsFeatureEnabled() && !IsProvisional()) { - anchor_element_interaction_tracker_ = - MakeGarbageCollected<AnchorElementInteractionTracker>(*document); - } Selection().DidAttachDocument(document); notified_color_scheme_ = false; }
diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/blink/renderer/core/frame/local_frame.h index 3ba2ab1..e2a7640 100644 --- a/third_party/blink/renderer/core/frame/local_frame.h +++ b/third_party/blink/renderer/core/frame/local_frame.h
@@ -99,7 +99,6 @@ class AdTracker; class AttributionSrcLoader; -class AnchorElementInteractionTracker; class AssociatedInterfaceProvider; class BrowserInterfaceBrokerProxy; class Color; @@ -876,7 +875,6 @@ // use the instance owned by their local root. Member<SmoothScrollSequencer> smooth_scroll_sequencer_; Member<ContentCaptureManager> content_capture_manager_; - Member<AnchorElementInteractionTracker> anchor_element_interaction_tracker_; InterfaceRegistry* const interface_registry_;
diff --git a/third_party/blink/renderer/core/html/canvas/predefined_color_space.cc b/third_party/blink/renderer/core/html/canvas/predefined_color_space.cc index 90deffe..a30ca2a 100644 --- a/third_party/blink/renderer/core/html/canvas/predefined_color_space.cc +++ b/third_party/blink/renderer/core/html/canvas/predefined_color_space.cc
@@ -48,4 +48,22 @@ return true; } +V8PredefinedColorSpace PredefinedColorSpaceToV8( + PredefinedColorSpace color_space) { + switch (color_space) { + case PredefinedColorSpace::kSRGB: + return V8PredefinedColorSpace(V8PredefinedColorSpace::Enum::kSRGB); + case PredefinedColorSpace::kRec2020: + return V8PredefinedColorSpace(V8PredefinedColorSpace::Enum::kRec2020); + case PredefinedColorSpace::kP3: + return V8PredefinedColorSpace(V8PredefinedColorSpace::Enum::kDisplayP3); + case PredefinedColorSpace::kRec2100HLG: + return V8PredefinedColorSpace(V8PredefinedColorSpace::Enum::kRec2100Hlg); + case PredefinedColorSpace::kRec2100PQ: + return V8PredefinedColorSpace(V8PredefinedColorSpace::Enum::kRec2100Pq); + case PredefinedColorSpace::kSRGBLinear: + return V8PredefinedColorSpace(V8PredefinedColorSpace::Enum::kSRGBLinear); + } +} + } // namespace blink
diff --git a/third_party/blink/renderer/core/html/canvas/predefined_color_space.h b/third_party/blink/renderer/core/html/canvas/predefined_color_space.h index c292994..56a7511 100644 --- a/third_party/blink/renderer/core/html/canvas/predefined_color_space.h +++ b/third_party/blink/renderer/core/html/canvas/predefined_color_space.h
@@ -25,6 +25,10 @@ PredefinedColorSpace& color_space, ExceptionState& exception_state); +// Convert from a PredefinedColorSpace to a V8PredefinedColorSpace. +V8PredefinedColorSpace CORE_EXPORT +PredefinedColorSpaceToV8(PredefinedColorSpace color_space); + } // namespace blink #endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_CANVAS_PREDEFINED_COLOR_SPACE_H_
diff --git a/third_party/blink/renderer/core/html/fenced_frame/html_fenced_frame_element.cc b/third_party/blink/renderer/core/html/fenced_frame/html_fenced_frame_element.cc index 03cc743..ac92370 100644 --- a/third_party/blink/renderer/core/html/fenced_frame/html_fenced_frame_element.cc +++ b/third_party/blink/renderer/core/html/fenced_frame/html_fenced_frame_element.cc
@@ -76,6 +76,8 @@ } void HTMLFencedFrameElement::DisconnectContentFrame() { + DCHECK(!GetDocument().IsPrerendering()); + // The `frame_delegate_` will not exist if the element was not allowed to // create its underlying frame at insertion-time. if (frame_delegate_) @@ -108,6 +110,21 @@ DCHECK(RuntimeEnabledFeatures::FencedFramesEnabled( outer_element->GetExecutionContext())); + // If the element has been disconnected by the time we attempt to create the + // delegate (eg, due to deferral while prerendering), we should not create the + // delegate. + // + // NB: this check should remain at the beginning of this function so that the + // remainder of the function can safely assume the frame is connected. + if (!outer_element->isConnected()) { + outer_element->GetDocument().AddConsoleMessage( + MakeGarbageCollected<ConsoleMessage>( + mojom::blink::ConsoleMessageSource::kJavaScript, + mojom::blink::ConsoleMessageLevel::kWarning, + "Can't create a fenced frame when disconnected.")); + return nullptr; + } + if (outer_element->GetExecutionContext()->IsSandboxed( kFencedFrameMandatoryUnsandboxedFlags)) { outer_element->GetDocument().AddConsoleMessage( @@ -122,8 +139,29 @@ return nullptr; } - // We know we're not in a detached frame because of the other checks in - // `DidNotifySubtreeInsertionsToDocument()`. + if (!SubframeLoadingDisabler::CanLoadFrame(*outer_element)) { + outer_element->GetDocument().AddConsoleMessage( + MakeGarbageCollected<ConsoleMessage>( + mojom::blink::ConsoleMessageSource::kJavaScript, + mojom::blink::ConsoleMessageLevel::kWarning, + "Can't create a fenced frame. Subframe loading disabled.")); + return nullptr; + } + + // The frame limit only needs to be checked on initial creation before + // attempting to insert it into the DOM. This behavior matches how iframes + // handles frame limits. + if (!outer_element->IsCurrentlyWithinFrameLimit()) { + outer_element->GetDocument().AddConsoleMessage( + MakeGarbageCollected<ConsoleMessage>( + mojom::blink::ConsoleMessageSource::kJavaScript, + mojom::blink::ConsoleMessageLevel::kWarning, + "Can't create a fenced frame. Frame limit exceeded.")); + return nullptr; + } + + // We must be connected at this point due to the isConnected check at the top + // of this function. DCHECK(outer_element->GetDocument().GetFrame()); if (Frame* ancestor = outer_element->GetDocument().GetFrame()) { mojom::blink::FencedFrameMode current_mode = outer_element->GetMode(); @@ -195,21 +233,7 @@ } void HTMLFencedFrameElement::DidNotifySubtreeInsertionsToDocument() { - // This method is the only place that sets `frame_delegate_`, and it cannot be - // called twice before removal. - DCHECK(!frame_delegate_); - - if (!SubframeLoadingDisabler::CanLoadFrame(*this)) - return; - - // The frame limit only needs to be checked on initial creation before - // attempting to insert it into the DOM. This behavior matches how iframes - // handles frame limits. - if (!IsCurrentlyWithinFrameLimit()) - return; - - frame_delegate_ = FencedFrameDelegate::Create(this); - Navigate(); + CreateDelegateAndNavigate(); } void HTMLFencedFrameElement::RemovedFrom(ContainerNode& node) { @@ -270,6 +294,13 @@ void HTMLFencedFrameElement::Navigate() { if (!isConnected()) return; + + // Please see HTMLFencedFrameDelegate::Create for a list of conditions which + // could result in not having a frame delegate at this point, one of which is + // prerendering. If this function is called while prerendering we won't have a + // delegate and will bail early, but this should still be correct since, + // post-activation, CreateDelegateAndNavigate will be run which will navigate + // to the most current src. if (!frame_delegate_) return; @@ -300,6 +331,24 @@ FreezeFrameSize(); } +void HTMLFencedFrameElement::CreateDelegateAndNavigate() { + // We may queue up several calls to CreateDelegateAndNavigate while + // prerendering, but we should only actually create the delegate once. Note, + // this will also mean that we skip calling Navigate() again, but the result + // should still be correct since the first Navigate call will use the + // up-to-date src. + if (frame_delegate_) + return; + if (GetDocument().IsPrerendering()) { + GetDocument().AddPostPrerenderingActivationStep( + WTF::Bind(&HTMLFencedFrameElement::CreateDelegateAndNavigate, + WrapWeakPersistent(this))); + return; + } + frame_delegate_ = FencedFrameDelegate::Create(this); + Navigate(); +} + void HTMLFencedFrameElement::AttachLayoutTree(AttachContext& context) { HTMLFrameOwnerElement::AttachLayoutTree(context); if (features::IsFencedFramesMPArchBased()) {
diff --git a/third_party/blink/renderer/core/html/fenced_frame/html_fenced_frame_element.h b/third_party/blink/renderer/core/html/fenced_frame/html_fenced_frame_element.h index 8fc36d8..76e7a142 100644 --- a/third_party/blink/renderer/core/html/fenced_frame/html_fenced_frame_element.h +++ b/third_party/blink/renderer/core/html/fenced_frame/html_fenced_frame_element.h
@@ -106,9 +106,12 @@ private: // This method will only navigate the underlying frame if the element - // `isConnected()`. + // `isConnected()`. It will be deferred if the page is currently prerendering. void Navigate(); + // Delegate creation will be deferred if the page is currently prerendering. + void CreateDelegateAndNavigate(); + // Node overrides. Node::InsertionNotificationRequest InsertedInto(ContainerNode&) override; void DidNotifySubtreeInsertionsToDocument() override;
diff --git a/third_party/blink/renderer/core/loader/anchor_element_interaction_test.cc b/third_party/blink/renderer/core/loader/anchor_element_interaction_test.cc new file mode 100644 index 0000000..2082e437 --- /dev/null +++ b/third_party/blink/renderer/core/loader/anchor_element_interaction_test.cc
@@ -0,0 +1,195 @@ +// 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 <cstddef> +#include "base/run_loop.h" +#include "base/test/scoped_feature_list.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "third_party/blink/public/common/features.h" +#include "third_party/blink/public/mojom/loader/anchor_element_interaction_host.mojom-blink.h" +#include "third_party/blink/renderer/core/dom/element.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/html/html_anchor_element.h" +#include "third_party/blink/renderer/core/loader/anchor_element_interaction_tracker.h" +#include "third_party/blink/renderer/core/loader/anchor_element_listener.h" +#include "third_party/blink/renderer/core/testing/sim/sim_request.h" +#include "third_party/blink/renderer/core/testing/sim/sim_test.h" +#include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h" + +namespace blink { + +class MockAnchorElementInteractionHost + : public mojom::blink::AnchorElementInteractionHost { + public: + explicit MockAnchorElementInteractionHost( + mojo::PendingReceiver<mojom::blink::AnchorElementInteractionHost> + pending_receiver) { + receiver_.Bind(std::move(pending_receiver)); + } + + absl::optional<KURL> url_received_ = absl::nullopt; + + private: + void OnPointerDown(const KURL& target) override { url_received_ = target; } + + private: + mojo::Receiver<mojom::blink::AnchorElementInteractionHost> receiver_{this}; +}; + +class AnchorElementInteractionTest : public SimTest { + public: + protected: + void SetUp() override { + SimTest::SetUp(); + + feature_list_.InitAndEnableFeature(features::kAnchorElementInteraction); + + MainFrame().GetFrame()->GetBrowserInterfaceBroker().SetBinderForTesting( + mojom::blink::AnchorElementInteractionHost::Name_, + WTF::BindRepeating(&AnchorElementInteractionTest::Bind, + WTF::Unretained(this))); + } + + void TearDown() override { + MainFrame().GetFrame()->GetBrowserInterfaceBroker().SetBinderForTesting( + mojom::blink::AnchorElementInteractionHost::Name_, {}); + hosts_.clear(); + SimTest::TearDown(); + } + + void Bind(mojo::ScopedMessagePipeHandle message_pipe_handle) { + auto host = std::make_unique<MockAnchorElementInteractionHost>( + mojo::PendingReceiver<mojom::blink::AnchorElementInteractionHost>( + std::move(message_pipe_handle))); + hosts_.push_back(std::move(host)); + } + + base::test::ScopedFeatureList feature_list_; + std::vector<std::unique_ptr<MockAnchorElementInteractionHost>> hosts_; +}; + +TEST_F(AnchorElementInteractionTest, InvalidHref) { + String source("https://example.com/p1"); + SimRequest main_resource(source, "text/html"); + LoadURL(source); + main_resource.Complete(R"HTML( + <a id='anchor1' href='about:blank'>example</a> + <script> + const a = document.getElementById('anchor1'); + var event = new PointerEvent('pointerdown'); + a.dispatchEvent(event); + </script> + )HTML"); + base::RunLoop().RunUntilIdle(); + absl::optional<KURL> expected_null_url = absl::nullopt; + EXPECT_EQ(1u, hosts_.size()); + absl::optional<KURL> url_received = hosts_[0]->url_received_; + EXPECT_FALSE(url_received.has_value()); + EXPECT_EQ(expected_null_url, url_received); +} + +TEST_F(AnchorElementInteractionTest, NestedAnchorElementCheck) { + String source("https://example.com/p1"); + SimRequest main_resource(source, "text/html"); + LoadURL(source); + main_resource.Complete(R"HTML( + <a id='anchor1' href='https://anchor1.com/'><a id='anchor2' + href='https://anchor2.com/'></a></a> + <script> + const a = document.getElementById('anchor2'); + var event = new PointerEvent('pointerdown'); + a.dispatchEvent(event); + </script> + )HTML"); + base::RunLoop().RunUntilIdle(); + KURL expected_url = KURL("https://anchor2.com/"); + EXPECT_EQ(1u, hosts_.size()); + absl::optional<KURL> url_received = hosts_[0]->url_received_; + EXPECT_TRUE(url_received.has_value()); + EXPECT_EQ(expected_url, url_received); +} + +TEST_F(AnchorElementInteractionTest, NestedDivAnchorElementCheck) { + String source("https://example.com/p1"); + SimRequest main_resource(source, "text/html"); + LoadURL(source); + main_resource.Complete(R"HTML( + <a id='anchor1' href='https://anchor1.com/'><div + id='div1id'></div></a> + <script> + const a = document.getElementById('div1id'); + var event = new PointerEvent('pointerdown'); + a.dispatchEvent(event); + </script> + )HTML"); + base::RunLoop().RunUntilIdle(); + KURL expected_url = KURL("https://anchor1.com/"); + EXPECT_EQ(1u, hosts_.size()); + absl::optional<KURL> url_received = hosts_[0]->url_received_; + EXPECT_TRUE(url_received.has_value()); + EXPECT_EQ(expected_url, url_received); +} + +TEST_F(AnchorElementInteractionTest, MultipleNestedAnchorElementCheck) { + String source("https://example.com/p1"); + SimRequest main_resource(source, "text/html"); + LoadURL(source); + main_resource.Complete(R"HTML( + <a id='anchor1' href='https://anchor1.com/'><p id='paragraph1id'><div + id='div1id'><div id='div2id'></div></div></p></a> + <script> + const a = document.getElementById('div2id'); + var event = new PointerEvent('pointerdown'); + a.dispatchEvent(event); + </script> + )HTML"); + base::RunLoop().RunUntilIdle(); + KURL expected_url = KURL("https://anchor1.com/"); + EXPECT_EQ(1u, hosts_.size()); + absl::optional<KURL> url_received = hosts_[0]->url_received_; + EXPECT_TRUE(url_received.has_value()); + EXPECT_EQ(expected_url, url_received); +} + +TEST_F(AnchorElementInteractionTest, NoAnchorElementCheck) { + String source("https://example.com/p1"); + SimRequest main_resource(source, "text/html"); + LoadURL(source); + main_resource.Complete(R"HTML( + <div id='div1id'></div> + <script> + const a = document.getElementById('div2id'); + var event = new PointerEvent('pointerdown'); + a.dispatchEvent(event); + </script> + )HTML"); + base::RunLoop().RunUntilIdle(); + absl::optional<KURL> expected_null_url = absl::nullopt; + EXPECT_EQ(1u, hosts_.size()); + absl::optional<KURL> url_received = hosts_[0]->url_received_; + EXPECT_FALSE(url_received.has_value()); + EXPECT_EQ(expected_null_url, url_received); +} + +TEST_F(AnchorElementInteractionTest, OneAnchorElementCheck) { + String source("https://example.com/p1"); + SimRequest main_resource(source, "text/html"); + LoadURL(source); + main_resource.Complete(R"HTML( + <a id="anchor1" href="https://anchor1.com/">foo</a> + <script> + const a = document.getElementById('anchor1'); + var event = new PointerEvent('pointerdown'); + a.dispatchEvent(event); + </script> + )HTML"); + base::RunLoop().RunUntilIdle(); + KURL expected_url = KURL("https://anchor1.com/"); + EXPECT_EQ(1u, hosts_.size()); + absl::optional<KURL> url_received = hosts_[0]->url_received_; + EXPECT_TRUE(url_received.has_value()); + EXPECT_EQ(expected_url, url_received); +} + +} // namespace blink
diff --git a/third_party/blink/renderer/core/loader/anchor_element_interaction_tracker.cc b/third_party/blink/renderer/core/loader/anchor_element_interaction_tracker.cc new file mode 100644 index 0000000..fb9d0530 --- /dev/null +++ b/third_party/blink/renderer/core/loader/anchor_element_interaction_tracker.cc
@@ -0,0 +1,52 @@ +// 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/loader/anchor_element_interaction_tracker.h" +#include "base/memory/weak_ptr.h" +#include "third_party/blink/public/common/browser_interface_broker_proxy.h" +#include "third_party/blink/public/common/features.h" +#include "third_party/blink/public/mojom/loader/anchor_element_interaction_host.mojom-blink.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/event_type_names.h" +#include "third_party/blink/renderer/core/execution_context/execution_context.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/loader/anchor_element_listener.h" +#include "third_party/blink/renderer/platform/heap/persistent.h" + +namespace blink { + +AnchorElementInteractionTracker::AnchorElementInteractionTracker( + Document& document) + : interaction_host_(document.GetExecutionContext()) { + base::RepeatingCallback<void(const KURL&)> callback = + WTF::BindRepeating(&AnchorElementInteractionTracker::OnPointerDown, + WrapWeakPersistent(this)); + + anchor_element_listener_ = + MakeGarbageCollected<AnchorElementListener>(callback); + + document.addEventListener(event_type_names::kPointerdown, + anchor_element_listener_, true); + + document.GetFrame()->GetBrowserInterfaceBroker().GetInterface( + interaction_host_.BindNewPipeAndPassReceiver( + document.GetExecutionContext()->GetTaskRunner( + TaskType::kInternalDefault))); +} + +void AnchorElementInteractionTracker::Trace(Visitor* visitor) const { + visitor->Trace(anchor_element_listener_); + visitor->Trace(interaction_host_); +} + +// static +bool AnchorElementInteractionTracker::IsFeatureEnabled() { + return base::FeatureList::IsEnabled(features::kAnchorElementInteraction); +} + +void AnchorElementInteractionTracker::OnPointerDown(const KURL& url) { + interaction_host_->OnPointerDown(url); +} + +} // namespace blink
diff --git a/third_party/blink/renderer/core/frame/anchor_element_interaction_tracker.h b/third_party/blink/renderer/core/loader/anchor_element_interaction_tracker.h similarity index 66% rename from third_party/blink/renderer/core/frame/anchor_element_interaction_tracker.h rename to third_party/blink/renderer/core/loader/anchor_element_interaction_tracker.h index 4db7633..44d8d09 100644 --- a/third_party/blink/renderer/core/frame/anchor_element_interaction_tracker.h +++ b/third_party/blink/renderer/core/loader/anchor_element_interaction_tracker.h
@@ -2,16 +2,19 @@ // 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_FRAME_ANCHOR_ELEMENT_INTERACTION_TRACKER_H_ -#define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_ANCHOR_ELEMENT_INTERACTION_TRACKER_H_ +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_ANCHOR_ELEMENT_INTERACTION_TRACKER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_ANCHOR_ELEMENT_INTERACTION_TRACKER_H_ +#include "third_party/blink/public/mojom/loader/anchor_element_interaction_host.mojom-blink.h" #include "third_party/blink/renderer/platform/heap/garbage_collected.h" #include "third_party/blink/renderer/platform/heap/member.h" +#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h" namespace blink { class AnchorElementListener; class Document; +class KURL; // Creates an event listener for mousedown events anywhere on a document. // If there is one, the listener will retrieve the valid href from the anchor @@ -26,12 +29,15 @@ static bool IsFeatureEnabled(); + void OnPointerDown(const KURL& url); + void Trace(Visitor* visitor) const; private: Member<AnchorElementListener> anchor_element_listener_; + HeapMojoRemote<mojom::blink::AnchorElementInteractionHost> interaction_host_; }; } // namespace blink -#endif // THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_ANCHOR_ELEMENT_INTERACTION_TRACKER_H_ +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_ANCHOR_ELEMENT_INTERACTION_TRACKER_H_
diff --git a/third_party/blink/renderer/core/frame/anchor_element_listener.cc b/third_party/blink/renderer/core/loader/anchor_element_listener.cc similarity index 68% rename from third_party/blink/renderer/core/frame/anchor_element_listener.cc rename to third_party/blink/renderer/core/loader/anchor_element_listener.cc index 5dcc8ef1e..d866b4e7 100644 --- a/third_party/blink/renderer/core/frame/anchor_element_listener.cc +++ b/third_party/blink/renderer/core/loader/anchor_element_listener.cc
@@ -2,13 +2,23 @@ // 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/frame/anchor_element_listener.h" +#include "third_party/blink/renderer/core/loader/anchor_element_listener.h" #include "third_party/blink/renderer/core/dom/events/event.h" +#include "third_party/blink/renderer/core/events/pointer_event.h" #include "third_party/blink/renderer/core/html/html_anchor_element.h" #include "third_party/blink/renderer/platform/weborigin/kurl.h" +namespace { +constexpr const int16_t kMainEventButtonValue = 0; +constexpr const int16_t kAuxiliaryEventButtonValue = 1; +} // namespace + namespace blink { +AnchorElementListener::AnchorElementListener( + base::RepeatingCallback<void(const KURL&)> callback) + : tracker_callback_(std::move(callback)) {} + void AnchorElementListener::Invoke(ExecutionContext* execution_context, Event* event) { if (!event->target()) { @@ -20,6 +30,11 @@ if (!event->target()->ToNode()->IsHTMLElement()) { return; } + // TODO(crbug.com/1297312): Check if user changed the default mouse settings + if (DynamicTo<PointerEvent>(event)->button() != kMainEventButtonValue && + DynamicTo<PointerEvent>(event)->button() != kAuxiliaryEventButtonValue) { + return; + } Node* node = event->srcElement()->ToNode(); HTMLAnchorElement* html_anchor_element = FirstAnchorElementIncludingSelf(node); @@ -30,8 +45,7 @@ if (anchor_url.IsEmpty()) { return; } - // TODO(crbug.com/1297312): send URL back up to the tracker. Tracker will then - // communicate with with the browser process to preload. + tracker_callback_.Run(anchor_url); } HTMLAnchorElement* AnchorElementListener::FirstAnchorElementIncludingSelf(
diff --git a/third_party/blink/renderer/core/loader/anchor_element_listener.h b/third_party/blink/renderer/core/loader/anchor_element_listener.h new file mode 100644 index 0000000..dac0832b --- /dev/null +++ b/third_party/blink/renderer/core/loader/anchor_element_listener.h
@@ -0,0 +1,42 @@ +// 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_LOADER_ANCHOR_ELEMENT_LISTENER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_ANCHOR_ELEMENT_LISTENER_H_ + +#include "base/callback.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/dom/events/native_event_listener.h" + +namespace blink { + +class Node; +class Event; +class HTMLAnchorElement; +class KURL; +class ExecutionContext; + +// Listens for kPointerdown events, and checks to see if an anchor +// element is clicked with a valid href to be eligible for preloading. +class CORE_EXPORT AnchorElementListener : public NativeEventListener { + public: + explicit AnchorElementListener( + base::RepeatingCallback<void(const KURL&)> callback); + + void Invoke(ExecutionContext* execution_context, Event* event) override; + + private: + HTMLAnchorElement* FirstAnchorElementIncludingSelf(Node* node); + + // Gets the `html_anchor_element's` href attribute if it is part + // of the HTTP family + KURL GetHrefEligibleForPreloading( + const HTMLAnchorElement& html_anchor_element); + + base::RepeatingCallback<void(const KURL&)> tracker_callback_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_ANCHOR_ELEMENT_LISTENER_H_
diff --git a/third_party/blink/renderer/core/loader/build.gni b/third_party/blink/renderer/core/loader/build.gni index 5be28c5..a0428b0 100644 --- a/third_party/blink/renderer/core/loader/build.gni +++ b/third_party/blink/renderer/core/loader/build.gni
@@ -5,6 +5,10 @@ blink_core_sources_loader = [ "alternate_signed_exchange_resource_info.cc", "alternate_signed_exchange_resource_info.h", + "anchor_element_listener.cc", + "anchor_element_listener.h", + "anchor_element_interaction_tracker.cc", + "anchor_element_interaction_tracker.h", "back_forward_cache_loader_helper_impl.cc", "back_forward_cache_loader_helper_impl.h", "base_fetch_context.cc",
diff --git a/third_party/blink/renderer/core/paint/theme_painter.cc b/third_party/blink/renderer/core/paint/theme_painter.cc index 0e3a7ef..60a3ee0e 100644 --- a/third_party/blink/renderer/core/paint/theme_painter.cc +++ b/third_party/blink/renderer/core/paint/theme_painter.cc
@@ -293,6 +293,9 @@ double min = input->Minimum(); double max = input->Maximum(); + if (min >= max) + return; + ControlPart part = o.StyleRef().EffectiveAppearance(); // We don't support ticks on alternate sliders like MediaVolumeSliders. if (part != kSliderHorizontalPart && part != kSliderVerticalPart)
diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc index 12844458..9982a69 100644 --- a/third_party/blink/renderer/core/style/computed_style.cc +++ b/third_party/blink/renderer/core/style/computed_style.cc
@@ -2118,18 +2118,10 @@ if (lh.IsNegative() && GetFont().PrimaryFont()) return GetFont().PrimaryFont()->GetFontMetrics().LineSpacing(); - if (RuntimeEnabledFeatures::FractionalLineHeightEnabled()) { - if (lh.IsPercentOrCalc()) { - return MinimumValueForLength(lh, LayoutUnit(ComputedFontSize())); - } + if (lh.IsPercentOrCalc()) + return MinimumValueForLength(lh, LayoutUnit(ComputedFontSize())); - return lh.Value(); - } else { - if (lh.IsPercentOrCalc()) - return MinimumValueForLength(lh, LayoutUnit(ComputedFontSize())).ToInt(); - - return static_cast<int>(std::min(lh.Value(), LayoutUnit::Max().ToFloat())); - } + return lh.Value(); } LayoutUnit ComputedStyle::ComputedLineHeightAsFixed(const Font& font) const { @@ -2140,20 +2132,10 @@ if (lh.IsNegative() && font.PrimaryFont()) return font.PrimaryFont()->GetFontMetrics().FixedLineSpacing(); - if (RuntimeEnabledFeatures::FractionalLineHeightEnabled()) { - if (lh.IsPercentOrCalc()) { - return MinimumValueForLength(lh, ComputedFontSizeAsFixed(font)); - } + if (lh.IsPercentOrCalc()) + return MinimumValueForLength(lh, ComputedFontSizeAsFixed(font)); - return LayoutUnit::FromFloatFloor(lh.Value()); - } else { - if (lh.IsPercentOrCalc()) { - return LayoutUnit( - MinimumValueForLength(lh, ComputedFontSizeAsFixed(font)).ToInt()); - } - - return LayoutUnit(floorf(lh.Value())); - } + return LayoutUnit::FromFloatFloor(lh.Value()); } LayoutUnit ComputedStyle::ComputedLineHeightAsFixed() const {
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc index c65dd76..3274a710 100644 --- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc +++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
@@ -56,6 +56,7 @@ #include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.h" #include "third_party/blink/renderer/core/html/canvas/html_canvas_element.h" #include "third_party/blink/renderer/core/html/canvas/image_data.h" +#include "third_party/blink/renderer/core/html/canvas/predefined_color_space.h" #include "third_party/blink/renderer/core/html/html_image_element.h" #include "third_party/blink/renderer/core/html/media/html_video_element.h" #include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h" @@ -1039,7 +1040,7 @@ // TODO(https://crbug.com/1208480): Move color space to being a read-write // attribute instead of a context creation attribute. if (RuntimeEnabledFeatures::CanvasColorManagementV2Enabled()) { - color_space_ = requested_attributes.color_space; + drawing_buffer_color_space_ = requested_attributes.color_space; pixel_format_deprecated_ = requested_attributes.pixel_format; } @@ -1123,7 +1124,7 @@ ClampedCanvasSize(), premultiplied_alpha, want_alpha_channel, want_depth_buffer, want_stencil_buffer, want_antialiasing, preserve, web_gl_version, chromium_image_usage, Host()->FilterQuality(), - color_space_, pixel_format_deprecated_, + drawing_buffer_color_space_, pixel_format_deprecated_, PowerPreferenceToGpuPreference(attrs.power_preference)); } @@ -1844,13 +1845,40 @@ return isContextLost() ? 0 : GetDrawingBuffer()->StorageFormat(); } -V8PredefinedColorSpace WebGLRenderingContextBase::colorSpace() const { - return V8PredefinedColorSpace(V8PredefinedColorSpace::Enum::kSRGB); +V8PredefinedColorSpace WebGLRenderingContextBase::drawingBufferColorSpace() + const { + return PredefinedColorSpaceToV8(drawing_buffer_color_space_); } -void WebGLRenderingContextBase::setColorSpace( - const V8PredefinedColorSpace& color_space) const { +void WebGLRenderingContextBase::setDrawingBufferColorSpace( + const V8PredefinedColorSpace& v8_color_space, + ExceptionState& exception_state) { + // Some values for PredefinedColorSpace are supposed to be guarded behind + // runtime flags. Use `ValidateAndConvertColorSpace` to throw an exception if + // `v8_color_space` should not be exposed. + PredefinedColorSpace color_space = PredefinedColorSpace::kSRGB; + if (!ValidateAndConvertColorSpace(v8_color_space, color_space, + exception_state)) { + return; + } NOTIMPLEMENTED(); + drawing_buffer_color_space_ = color_space; +} + +V8PredefinedColorSpace WebGLRenderingContextBase::unpackColorSpace() const { + return PredefinedColorSpaceToV8(unpack_color_space_); +} + +void WebGLRenderingContextBase::setUnpackColorSpace( + const V8PredefinedColorSpace& v8_color_space, + ExceptionState& exception_state) { + PredefinedColorSpace color_space = PredefinedColorSpace::kSRGB; + if (!ValidateAndConvertColorSpace(v8_color_space, color_space, + exception_state)) { + return; + } + NOTIMPLEMENTED(); + unpack_color_space_ = color_space; } void WebGLRenderingContextBase::activeTexture(GLenum texture) { @@ -5221,9 +5249,9 @@ // have been intentional. const SkAlphaType alpha_type = CreationAttributes().alpha ? kPremul_SkAlphaType : kOpaque_SkAlphaType; - return SkColorInfo(CanvasPixelFormatToSkColorType(pixel_format_deprecated_), - alpha_type, - PredefinedColorSpaceToSkColorSpace(color_space_)); + return SkColorInfo( + CanvasPixelFormatToSkColorType(pixel_format_deprecated_), alpha_type, + PredefinedColorSpaceToSkColorSpace(drawing_buffer_color_space_)); } gfx::Rect WebGLRenderingContextBase::GetImageDataSize(ImageData* pixels) {
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h index 7328aeb..4ae837d 100644 --- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h +++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
@@ -176,8 +176,13 @@ int drawingBufferWidth() const; int drawingBufferHeight() const; GLenum drawingBufferFormat() const; - V8PredefinedColorSpace colorSpace() const; - void setColorSpace(const V8PredefinedColorSpace& color_space) const; + V8PredefinedColorSpace drawingBufferColorSpace() const; + void setDrawingBufferColorSpace(const V8PredefinedColorSpace& color_space, + ExceptionState&); + + V8PredefinedColorSpace unpackColorSpace() const; + void setUnpackColorSpace(const V8PredefinedColorSpace& color_space, + ExceptionState&); void activeTexture(GLenum texture); void attachShader(WebGLProgram*, WebGLShader*); @@ -1918,7 +1923,10 @@ bool has_been_drawn_to_ = false; - PredefinedColorSpace color_space_ = PredefinedColorSpace::kSRGB; + PredefinedColorSpace drawing_buffer_color_space_ = + PredefinedColorSpace::kSRGB; + PredefinedColorSpace unpack_color_space_ = PredefinedColorSpace::kSRGB; + // The pixel format of the WebGL canvas. This is based on a deprecated // specification that is being replaced by drawingBufferStorage. CanvasPixelFormat pixel_format_deprecated_ = CanvasPixelFormat::kUint8;
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.idl b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.idl index 4afb970..85e395d 100644 --- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.idl +++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.idl
@@ -465,7 +465,8 @@ readonly attribute GLsizei drawingBufferWidth; readonly attribute GLsizei drawingBufferHeight; [RuntimeEnabled=CanvasColorManagementV2] readonly attribute GLenum drawingBufferFormat; - [RuntimeEnabled=CanvasColorManagementV2] attribute PredefinedColorSpace colorSpace; + [RaisesException=Setter, RuntimeEnabled=CanvasColorManagementV2] attribute PredefinedColorSpace drawingBufferColorSpace; + [RaisesException=Setter, RuntimeEnabled=CanvasColorManagementV2] attribute PredefinedColorSpace unpackColorSpace; void activeTexture(GLenum texture); void attachShader(WebGLProgram program, WebGLShader shader);
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 index 28458b75..6b8041b 100644 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1202,10 +1202,6 @@ status: {"ChromeOS_Ash": "stable", "ChromeOS_Lacros": "stable"}, }, { - name: "FractionalLineHeight", - status: "stable", - }, - { name: "FractionalScrollOffsets", status: "experimental", }, @@ -1726,6 +1722,12 @@ // As above. Do not change this flag to stable, as it exists solely to // generate code used by the origin trials sample API implementation. { + name: "OriginTrialsSampleAPIExpiryGracePeriod", + origin_trial_feature_name: "FrobulateExpiryGracePeriod", + }, + // As above. Do not change this flag to stable, as it exists solely to + // generate code used by the origin trials sample API implementation. + { name: "OriginTrialsSampleAPIImplied", origin_trial_feature_name: "FrobulateImplied", implied_by: ["OriginTrialsSampleAPI", "OriginTrialsSampleAPIInvalidOS"],
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations index bccd58c..e36daf2 100644 --- a/third_party/blink/web_tests/TestExpectations +++ b/third_party/blink/web_tests/TestExpectations
@@ -7616,3 +7616,7 @@ # Sheriff 2022-03-24 crbug.com/1309756 http/tests/images/force-reload.html [ Skip ] crbug.com/1309756 [ Mac11-arm64 ] http/tests/images/force-reload-image-document.html [ Skip ] + +# Sheriff 2022-03-25 +crbug.com/1197296 [ Linux ] virtual/unified-autoplay/external/wpt/feature-policy/feature-policy-frame-policy-timing.https.sub.html [ Failure Pass ] +crbug.com/1197296 [ Linux ] external/wpt/feature-policy/feature-policy-frame-policy-timing.https.sub.html [ Failure Pass ]
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-input-element/invalid-datalist-options-crash.html b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-input-element/invalid-datalist-options-crash.html new file mode 100644 index 0000000..7cdd551 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-input-element/invalid-datalist-options-crash.html
@@ -0,0 +1,6 @@ +<!DOCTYPE html> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-input-element"> +<input max="0" list="ticks" type="range"> +<datalist id="ticks"> + <option value="0"></option> +</datalist>
diff --git a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt index b176815..4563af4 100644 --- a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt +++ b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -2184,10 +2184,11 @@ attribute WAIT_FAILED attribute ZERO getter canvas - getter colorSpace + getter drawingBufferColorSpace getter drawingBufferFormat getter drawingBufferHeight getter drawingBufferWidth + getter unpackColorSpace method activeTexture method attachShader method beginQuery @@ -2416,7 +2417,8 @@ method vertexAttribPointer method viewport method waitSync - setter colorSpace + setter drawingBufferColorSpace + setter unpackColorSpace interface WebGLActiveInfo attribute @@toStringTag getter name @@ -2737,10 +2739,11 @@ attribute VIEWPORT attribute ZERO getter canvas - getter colorSpace + getter drawingBufferColorSpace getter drawingBufferFormat getter drawingBufferHeight getter drawingBufferWidth + getter unpackColorSpace method activeTexture method attachShader method bindAttribLocation @@ -2881,7 +2884,8 @@ method vertexAttrib4fv method vertexAttribPointer method viewport - setter colorSpace + setter drawingBufferColorSpace + setter unpackColorSpace interface WebGLSampler attribute @@toStringTag method constructor
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt index 0d3585a..3e219074 100644 --- a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt +++ b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -2423,10 +2423,11 @@ [Worker] attribute WAIT_FAILED [Worker] attribute ZERO [Worker] getter canvas -[Worker] getter colorSpace +[Worker] getter drawingBufferColorSpace [Worker] getter drawingBufferFormat [Worker] getter drawingBufferHeight [Worker] getter drawingBufferWidth +[Worker] getter unpackColorSpace [Worker] method activeTexture [Worker] method attachShader [Worker] method beginQuery @@ -2655,7 +2656,8 @@ [Worker] method vertexAttribPointer [Worker] method viewport [Worker] method waitSync -[Worker] setter colorSpace +[Worker] setter drawingBufferColorSpace +[Worker] setter unpackColorSpace [Worker] interface WebGLActiveInfo [Worker] attribute @@toStringTag [Worker] getter name @@ -2976,10 +2978,11 @@ [Worker] attribute VIEWPORT [Worker] attribute ZERO [Worker] getter canvas -[Worker] getter colorSpace +[Worker] getter drawingBufferColorSpace [Worker] getter drawingBufferFormat [Worker] getter drawingBufferHeight [Worker] getter drawingBufferWidth +[Worker] getter unpackColorSpace [Worker] method activeTexture [Worker] method attachShader [Worker] method bindAttribLocation @@ -3120,7 +3123,8 @@ [Worker] method vertexAttrib4fv [Worker] method vertexAttribPointer [Worker] method viewport -[Worker] setter colorSpace +[Worker] setter drawingBufferColorSpace +[Worker] setter unpackColorSpace [Worker] interface WebGLSampler [Worker] attribute @@toStringTag [Worker] method constructor
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 153ddda5..690509d 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
@@ -9931,10 +9931,11 @@ attribute WAIT_FAILED attribute ZERO getter canvas - getter colorSpace + getter drawingBufferColorSpace getter drawingBufferFormat getter drawingBufferHeight getter drawingBufferWidth + getter unpackColorSpace method activeTexture method attachShader method beginQuery @@ -10163,7 +10164,8 @@ method vertexAttribPointer method viewport method waitSync - setter colorSpace + setter drawingBufferColorSpace + setter unpackColorSpace interface WebGLActiveInfo attribute @@toStringTag getter name @@ -10488,10 +10490,11 @@ attribute VIEWPORT attribute ZERO getter canvas - getter colorSpace + getter drawingBufferColorSpace getter drawingBufferFormat getter drawingBufferHeight getter drawingBufferWidth + getter unpackColorSpace method activeTexture method attachShader method bindAttribLocation @@ -10632,7 +10635,8 @@ method vertexAttrib4fv method vertexAttribPointer method viewport - setter colorSpace + setter drawingBufferColorSpace + setter unpackColorSpace interface WebGLSampler attribute @@toStringTag method constructor
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt index ff89e8ec..793699fe 100644 --- a/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt +++ b/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -2063,10 +2063,11 @@ [Worker] attribute WAIT_FAILED [Worker] attribute ZERO [Worker] getter canvas -[Worker] getter colorSpace +[Worker] getter drawingBufferColorSpace [Worker] getter drawingBufferFormat [Worker] getter drawingBufferHeight [Worker] getter drawingBufferWidth +[Worker] getter unpackColorSpace [Worker] method activeTexture [Worker] method attachShader [Worker] method beginQuery @@ -2295,7 +2296,8 @@ [Worker] method vertexAttribPointer [Worker] method viewport [Worker] method waitSync -[Worker] setter colorSpace +[Worker] setter drawingBufferColorSpace +[Worker] setter unpackColorSpace [Worker] interface WebGLActiveInfo [Worker] attribute @@toStringTag [Worker] getter name @@ -2616,10 +2618,11 @@ [Worker] attribute VIEWPORT [Worker] attribute ZERO [Worker] getter canvas -[Worker] getter colorSpace +[Worker] getter drawingBufferColorSpace [Worker] getter drawingBufferFormat [Worker] getter drawingBufferHeight [Worker] getter drawingBufferWidth +[Worker] getter unpackColorSpace [Worker] method activeTexture [Worker] method attachShader [Worker] method bindAttribLocation @@ -2760,7 +2763,8 @@ [Worker] method vertexAttrib4fv [Worker] method vertexAttribPointer [Worker] method viewport -[Worker] setter colorSpace +[Worker] setter drawingBufferColorSpace +[Worker] setter unpackColorSpace [Worker] interface WebGLSampler [Worker] attribute @@toStringTag [Worker] method constructor
diff --git a/third_party/blink/web_tests/wpt_internal/fenced_frame/csp-urn.https.html b/third_party/blink/web_tests/wpt_internal/fenced_frame/csp-urn.https.html index a066e5c..9222ca8 100644 --- a/third_party/blink/web_tests/wpt_internal/fenced_frame/csp-urn.https.html +++ b/third_party/blink/web_tests/wpt_internal/fenced_frame/csp-urn.https.html
@@ -20,7 +20,7 @@ setupCSP(csp); const key = token(); - attachFencedFrame(await generateURN("resources/embeddee.html", [key])); + attachFencedFrame(await generateURN("resources/embeddee.html", [key]), "opaque-ads"); const result = await nextValueFromServer(key); assert_equals(result, "PASS", @@ -40,7 +40,7 @@ writeValueToServer(key, e.violatedDirective + ";" + e.blockedURI); }); - attachFencedFrame(await generateURN("resources/embeddee.html", [key])); + attachFencedFrame(await generateURN("resources/embeddee.html", [key]), "opaque-ads"); const result = await nextValueFromServer(key); assert_equals(result, "fenced-frame-src;",
diff --git a/third_party/blink/web_tests/wpt_internal/fenced_frame/resources/utils.js b/third_party/blink/web_tests/wpt_internal/fenced_frame/resources/utils.js index 42aa0ec..827be18 100644 --- a/third_party/blink/web_tests/wpt_internal/fenced_frame/resources/utils.js +++ b/third_party/blink/web_tests/wpt_internal/fenced_frame/resources/utils.js
@@ -187,13 +187,16 @@ return digest_slices.join('-'); } -function attachFencedFrame(url) { +function attachFencedFrame(url, mode='') { assert_implements( window.HTMLFencedFrameElement, 'The HTMLFencedFrameElement should be exposed on the window object'); const fenced_frame = document.createElement('fencedframe'); assert_true('mode' in fenced_frame); + if (mode) { + fenced_frame.mode = mode; + } fenced_frame.src = url; document.body.append(fenced_frame); return fenced_frame;
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/cts.https.html b/third_party/blink/web_tests/wpt_internal/webgpu/cts.https.html index 62c44aab..f1c299f 100644 --- a/third_party/blink/web_tests/wpt_internal/webgpu/cts.https.html +++ b/third_party/blink/web_tests/wpt_internal/webgpu/cts.https.html
@@ -2614,8 +2614,10 @@ <meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:command,renderPass,draw:*'> <meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:command,renderPass,renderBundle:*'> <meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:queue,writeBuffer:*'> -<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:queue,writeTexture:*'> -<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:queue,copyExternalImageToTexture:*'> +<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:queue,writeTexture,2d,uncompressed_format:*'> +<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:queue,writeTexture,2d,compressed_format:*'> +<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:queue,copyExternalImageToTexture,canvas:*'> +<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:queue,copyExternalImageToTexture,imageBitmap:*'> <meta name=variant content='?q=webgpu:api,validation,texture,destroy:base:*'> <meta name=variant content='?q=webgpu:api,validation,texture,destroy:twice:*'> <meta name=variant content='?q=webgpu:api,validation,texture,destroy:submit_a_destroyed_texture_as_attachment:*'>
diff --git a/third_party/libaom/README.chromium b/third_party/libaom/README.chromium index bcf117c..6b403c5 100644 --- a/third_party/libaom/README.chromium +++ b/third_party/libaom/README.chromium
@@ -3,9 +3,9 @@ URL: https://aomedia.googlesource.com/aom/ Version: 3.1.2 CPEPrefix: cpe:/a:aomedia:aomedia:3.1.2 -Date: Wednesday March 09 2022 +Date: Thursday March 17 2022 Branch: main -Commit: ee1ed1ccf2b9ecedd6aee438eafc7cc61c23342d +Commit: 24fa287e152b319d8998e24c0f174f4043138bfd License: BSD License File: source/libaom/LICENSE Security Critical: yes
diff --git a/third_party/libaom/libaom_srcs.gni b/third_party/libaom/libaom_srcs.gni index 7403ec3..c0c95aef 100644 --- a/third_party/libaom/libaom_srcs.gni +++ b/third_party/libaom/libaom_srcs.gni
@@ -371,6 +371,10 @@ aom_av1_rc_qmode_sources = [ "//third_party/libaom/source/libaom/av1/ratectrl_qmode.cc", "//third_party/libaom/source/libaom/av1/ratectrl_qmode.h", + "//third_party/libaom/source/libaom/av1/ratectrl_qmode_interface.cc", + "//third_party/libaom/source/libaom/av1/ratectrl_qmode_interface.h", + "//third_party/libaom/source/libaom/av1/reference_manager.cc", + "//third_party/libaom/source/libaom/av1/reference_manager.h", ] aom_dsp_common_asm_sse2 = [ @@ -712,6 +716,9 @@ "//third_party/libaom/source/libaom/common/webmenc.h", ] +av1_rc_qmode_sources = + [ "//third_party/libaom/source/libaom/test/ratectrl_qmode_test.cc" ] + # Files below this line are generated by the libaom build system. aom_rtcd_sources_gen = [
diff --git a/third_party/libaom/source/config/config/aom_version.h b/third_party/libaom/source/config/config/aom_version.h index 94c7ef0..50ea908 100644 --- a/third_party/libaom/source/config/config/aom_version.h +++ b/third_party/libaom/source/config/config/aom_version.h
@@ -12,8 +12,8 @@ #define VERSION_MAJOR 3 #define VERSION_MINOR 3 #define VERSION_PATCH 0 -#define VERSION_EXTRA "330-gee1ed1ccf" +#define VERSION_EXTRA "364-g24fa287e1" #define VERSION_PACKED \ ((VERSION_MAJOR << 16) | (VERSION_MINOR << 8) | (VERSION_PATCH)) -#define VERSION_STRING_NOSP "3.3.0-330-gee1ed1ccf" -#define VERSION_STRING " 3.3.0-330-gee1ed1ccf" +#define VERSION_STRING_NOSP "3.3.0-364-g24fa287e1" +#define VERSION_STRING " 3.3.0-364-g24fa287e1"
diff --git a/third_party/libaom/source/config/ios/arm-neon/config/aom_config.asm b/third_party/libaom/source/config/ios/arm-neon/config/aom_config.asm index 078e78c..cd9dfe35 100644 --- a/third_party/libaom/source/config/ios/arm-neon/config/aom_config.asm +++ b/third_party/libaom/source/config/ios/arm-neon/config/aom_config.asm
@@ -51,6 +51,7 @@ CONFIG_OS_SUPPORT equ 1 CONFIG_PARTITION_SEARCH_ORDER equ 0 CONFIG_PIC equ 0 +CONFIG_RATECTRL_LOG equ 0 CONFIG_RD_COMMAND equ 0 CONFIG_RD_DEBUG equ 0 CONFIG_REALTIME_ONLY equ 1
diff --git a/third_party/libaom/source/config/ios/arm-neon/config/aom_config.h b/third_party/libaom/source/config/ios/arm-neon/config/aom_config.h index c456f0a..9a0f04e2 100644 --- a/third_party/libaom/source/config/ios/arm-neon/config/aom_config.h +++ b/third_party/libaom/source/config/ios/arm-neon/config/aom_config.h
@@ -53,6 +53,7 @@ #define CONFIG_OS_SUPPORT 1 #define CONFIG_PARTITION_SEARCH_ORDER 0 #define CONFIG_PIC 0 +#define CONFIG_RATECTRL_LOG 0 #define CONFIG_RD_COMMAND 0 #define CONFIG_RD_DEBUG 0 #define CONFIG_REALTIME_ONLY 1
diff --git a/third_party/libaom/source/config/ios/arm-neon/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/ios/arm-neon/config/aom_dsp_rtcd.h index 01c7d9b..a1644504 100644 --- a/third_party/libaom/source/config/ios/arm-neon/config/aom_dsp_rtcd.h +++ b/third_party/libaom/source/config/ios/arm-neon/config/aom_dsp_rtcd.h
@@ -984,6 +984,20 @@ unsigned int aom_get_mb_ss_c(const int16_t*); #define aom_get_mb_ss aom_get_mb_ss_c +void aom_get_sse_sum_8x8_quad_c(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +void aom_get_sse_sum_8x8_quad_neon(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +#define aom_get_sse_sum_8x8_quad aom_get_sse_sum_8x8_quad_neon + void aom_h_predictor_16x16_c(uint8_t* dst, ptrdiff_t y_stride, const uint8_t* above,
diff --git a/third_party/libaom/source/config/ios/arm64/config/aom_config.asm b/third_party/libaom/source/config/ios/arm64/config/aom_config.asm index 078e78c..cd9dfe35 100644 --- a/third_party/libaom/source/config/ios/arm64/config/aom_config.asm +++ b/third_party/libaom/source/config/ios/arm64/config/aom_config.asm
@@ -51,6 +51,7 @@ CONFIG_OS_SUPPORT equ 1 CONFIG_PARTITION_SEARCH_ORDER equ 0 CONFIG_PIC equ 0 +CONFIG_RATECTRL_LOG equ 0 CONFIG_RD_COMMAND equ 0 CONFIG_RD_DEBUG equ 0 CONFIG_REALTIME_ONLY equ 1
diff --git a/third_party/libaom/source/config/ios/arm64/config/aom_config.h b/third_party/libaom/source/config/ios/arm64/config/aom_config.h index c456f0a..9a0f04e2 100644 --- a/third_party/libaom/source/config/ios/arm64/config/aom_config.h +++ b/third_party/libaom/source/config/ios/arm64/config/aom_config.h
@@ -53,6 +53,7 @@ #define CONFIG_OS_SUPPORT 1 #define CONFIG_PARTITION_SEARCH_ORDER 0 #define CONFIG_PIC 0 +#define CONFIG_RATECTRL_LOG 0 #define CONFIG_RD_COMMAND 0 #define CONFIG_RD_DEBUG 0 #define CONFIG_REALTIME_ONLY 1
diff --git a/third_party/libaom/source/config/ios/arm64/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/ios/arm64/config/aom_dsp_rtcd.h index 01c7d9b..a1644504 100644 --- a/third_party/libaom/source/config/ios/arm64/config/aom_dsp_rtcd.h +++ b/third_party/libaom/source/config/ios/arm64/config/aom_dsp_rtcd.h
@@ -984,6 +984,20 @@ unsigned int aom_get_mb_ss_c(const int16_t*); #define aom_get_mb_ss aom_get_mb_ss_c +void aom_get_sse_sum_8x8_quad_c(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +void aom_get_sse_sum_8x8_quad_neon(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +#define aom_get_sse_sum_8x8_quad aom_get_sse_sum_8x8_quad_neon + void aom_h_predictor_16x16_c(uint8_t* dst, ptrdiff_t y_stride, const uint8_t* above,
diff --git a/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_config.asm b/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_config.asm index eed135e5..78170e3 100644 --- a/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_config.asm +++ b/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_config.asm
@@ -51,6 +51,7 @@ CONFIG_OS_SUPPORT equ 1 CONFIG_PARTITION_SEARCH_ORDER equ 0 CONFIG_PIC equ 0 +CONFIG_RATECTRL_LOG equ 0 CONFIG_RD_COMMAND equ 0 CONFIG_RD_DEBUG equ 0 CONFIG_REALTIME_ONLY equ 1
diff --git a/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_config.h b/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_config.h index 1c9749c0..8e2471d 100644 --- a/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_config.h +++ b/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_config.h
@@ -53,6 +53,7 @@ #define CONFIG_OS_SUPPORT 1 #define CONFIG_PARTITION_SEARCH_ORDER 0 #define CONFIG_PIC 0 +#define CONFIG_RATECTRL_LOG 0 #define CONFIG_RD_COMMAND 0 #define CONFIG_RD_DEBUG 0 #define CONFIG_REALTIME_ONLY 1
diff --git a/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_dsp_rtcd.h index ce7ef6a..22f37d2 100644 --- a/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_dsp_rtcd.h +++ b/third_party/libaom/source/config/linux/arm-neon-cpu-detect/config/aom_dsp_rtcd.h
@@ -1072,6 +1072,25 @@ unsigned int aom_get_mb_ss_c(const int16_t*); #define aom_get_mb_ss aom_get_mb_ss_c +void aom_get_sse_sum_8x8_quad_c(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +void aom_get_sse_sum_8x8_quad_neon(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +RTCD_EXTERN void (*aom_get_sse_sum_8x8_quad)(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); + void aom_h_predictor_16x16_c(uint8_t* dst, ptrdiff_t y_stride, const uint8_t* above, @@ -4958,6 +4977,9 @@ aom_get8x8var = aom_get8x8var_c; if (flags & HAS_NEON) aom_get8x8var = aom_get8x8var_neon; + aom_get_sse_sum_8x8_quad = aom_get_sse_sum_8x8_quad_c; + if (flags & HAS_NEON) + aom_get_sse_sum_8x8_quad = aom_get_sse_sum_8x8_quad_neon; aom_h_predictor_16x16 = aom_h_predictor_16x16_c; if (flags & HAS_NEON) aom_h_predictor_16x16 = aom_h_predictor_16x16_neon;
diff --git a/third_party/libaom/source/config/linux/arm-neon/config/aom_config.asm b/third_party/libaom/source/config/linux/arm-neon/config/aom_config.asm index 078e78c..cd9dfe35 100644 --- a/third_party/libaom/source/config/linux/arm-neon/config/aom_config.asm +++ b/third_party/libaom/source/config/linux/arm-neon/config/aom_config.asm
@@ -51,6 +51,7 @@ CONFIG_OS_SUPPORT equ 1 CONFIG_PARTITION_SEARCH_ORDER equ 0 CONFIG_PIC equ 0 +CONFIG_RATECTRL_LOG equ 0 CONFIG_RD_COMMAND equ 0 CONFIG_RD_DEBUG equ 0 CONFIG_REALTIME_ONLY equ 1
diff --git a/third_party/libaom/source/config/linux/arm-neon/config/aom_config.h b/third_party/libaom/source/config/linux/arm-neon/config/aom_config.h index c456f0a..9a0f04e2 100644 --- a/third_party/libaom/source/config/linux/arm-neon/config/aom_config.h +++ b/third_party/libaom/source/config/linux/arm-neon/config/aom_config.h
@@ -53,6 +53,7 @@ #define CONFIG_OS_SUPPORT 1 #define CONFIG_PARTITION_SEARCH_ORDER 0 #define CONFIG_PIC 0 +#define CONFIG_RATECTRL_LOG 0 #define CONFIG_RD_COMMAND 0 #define CONFIG_RD_DEBUG 0 #define CONFIG_REALTIME_ONLY 1
diff --git a/third_party/libaom/source/config/linux/arm-neon/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/linux/arm-neon/config/aom_dsp_rtcd.h index 01c7d9b..a1644504 100644 --- a/third_party/libaom/source/config/linux/arm-neon/config/aom_dsp_rtcd.h +++ b/third_party/libaom/source/config/linux/arm-neon/config/aom_dsp_rtcd.h
@@ -984,6 +984,20 @@ unsigned int aom_get_mb_ss_c(const int16_t*); #define aom_get_mb_ss aom_get_mb_ss_c +void aom_get_sse_sum_8x8_quad_c(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +void aom_get_sse_sum_8x8_quad_neon(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +#define aom_get_sse_sum_8x8_quad aom_get_sse_sum_8x8_quad_neon + void aom_h_predictor_16x16_c(uint8_t* dst, ptrdiff_t y_stride, const uint8_t* above,
diff --git a/third_party/libaom/source/config/linux/arm/config/aom_config.asm b/third_party/libaom/source/config/linux/arm/config/aom_config.asm index 51451f7..6dfa8a6 100644 --- a/third_party/libaom/source/config/linux/arm/config/aom_config.asm +++ b/third_party/libaom/source/config/linux/arm/config/aom_config.asm
@@ -51,6 +51,7 @@ CONFIG_OS_SUPPORT equ 1 CONFIG_PARTITION_SEARCH_ORDER equ 0 CONFIG_PIC equ 0 +CONFIG_RATECTRL_LOG equ 0 CONFIG_RD_COMMAND equ 0 CONFIG_RD_DEBUG equ 0 CONFIG_REALTIME_ONLY equ 1
diff --git a/third_party/libaom/source/config/linux/arm/config/aom_config.h b/third_party/libaom/source/config/linux/arm/config/aom_config.h index 3469ff8f..fb2fc3da 100644 --- a/third_party/libaom/source/config/linux/arm/config/aom_config.h +++ b/third_party/libaom/source/config/linux/arm/config/aom_config.h
@@ -53,6 +53,7 @@ #define CONFIG_OS_SUPPORT 1 #define CONFIG_PARTITION_SEARCH_ORDER 0 #define CONFIG_PIC 0 +#define CONFIG_RATECTRL_LOG 0 #define CONFIG_RD_COMMAND 0 #define CONFIG_RD_DEBUG 0 #define CONFIG_REALTIME_ONLY 1
diff --git a/third_party/libaom/source/config/linux/arm/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/linux/arm/config/aom_dsp_rtcd.h index 5dd7d22..b1599c4 100644 --- a/third_party/libaom/source/config/linux/arm/config/aom_dsp_rtcd.h +++ b/third_party/libaom/source/config/linux/arm/config/aom_dsp_rtcd.h
@@ -875,6 +875,14 @@ unsigned int aom_get_mb_ss_c(const int16_t*); #define aom_get_mb_ss aom_get_mb_ss_c +void aom_get_sse_sum_8x8_quad_c(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +#define aom_get_sse_sum_8x8_quad aom_get_sse_sum_8x8_quad_c + void aom_h_predictor_16x16_c(uint8_t* dst, ptrdiff_t y_stride, const uint8_t* above,
diff --git a/third_party/libaom/source/config/linux/arm64/config/aom_config.asm b/third_party/libaom/source/config/linux/arm64/config/aom_config.asm index 078e78c..cd9dfe35 100644 --- a/third_party/libaom/source/config/linux/arm64/config/aom_config.asm +++ b/third_party/libaom/source/config/linux/arm64/config/aom_config.asm
@@ -51,6 +51,7 @@ CONFIG_OS_SUPPORT equ 1 CONFIG_PARTITION_SEARCH_ORDER equ 0 CONFIG_PIC equ 0 +CONFIG_RATECTRL_LOG equ 0 CONFIG_RD_COMMAND equ 0 CONFIG_RD_DEBUG equ 0 CONFIG_REALTIME_ONLY equ 1
diff --git a/third_party/libaom/source/config/linux/arm64/config/aom_config.h b/third_party/libaom/source/config/linux/arm64/config/aom_config.h index c456f0a..9a0f04e2 100644 --- a/third_party/libaom/source/config/linux/arm64/config/aom_config.h +++ b/third_party/libaom/source/config/linux/arm64/config/aom_config.h
@@ -53,6 +53,7 @@ #define CONFIG_OS_SUPPORT 1 #define CONFIG_PARTITION_SEARCH_ORDER 0 #define CONFIG_PIC 0 +#define CONFIG_RATECTRL_LOG 0 #define CONFIG_RD_COMMAND 0 #define CONFIG_RD_DEBUG 0 #define CONFIG_REALTIME_ONLY 1
diff --git a/third_party/libaom/source/config/linux/arm64/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/linux/arm64/config/aom_dsp_rtcd.h index 01c7d9b..a1644504 100644 --- a/third_party/libaom/source/config/linux/arm64/config/aom_dsp_rtcd.h +++ b/third_party/libaom/source/config/linux/arm64/config/aom_dsp_rtcd.h
@@ -984,6 +984,20 @@ unsigned int aom_get_mb_ss_c(const int16_t*); #define aom_get_mb_ss aom_get_mb_ss_c +void aom_get_sse_sum_8x8_quad_c(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +void aom_get_sse_sum_8x8_quad_neon(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +#define aom_get_sse_sum_8x8_quad aom_get_sse_sum_8x8_quad_neon + void aom_h_predictor_16x16_c(uint8_t* dst, ptrdiff_t y_stride, const uint8_t* above,
diff --git a/third_party/libaom/source/config/linux/generic/config/aom_config.asm b/third_party/libaom/source/config/linux/generic/config/aom_config.asm index 7febc43..446cdfe 100644 --- a/third_party/libaom/source/config/linux/generic/config/aom_config.asm +++ b/third_party/libaom/source/config/linux/generic/config/aom_config.asm
@@ -51,6 +51,7 @@ CONFIG_OS_SUPPORT equ 1 CONFIG_PARTITION_SEARCH_ORDER equ 0 CONFIG_PIC equ 0 +CONFIG_RATECTRL_LOG equ 0 CONFIG_RD_COMMAND equ 0 CONFIG_RD_DEBUG equ 0 CONFIG_REALTIME_ONLY equ 1
diff --git a/third_party/libaom/source/config/linux/generic/config/aom_config.h b/third_party/libaom/source/config/linux/generic/config/aom_config.h index 89f409e..6a54b6e 100644 --- a/third_party/libaom/source/config/linux/generic/config/aom_config.h +++ b/third_party/libaom/source/config/linux/generic/config/aom_config.h
@@ -53,6 +53,7 @@ #define CONFIG_OS_SUPPORT 1 #define CONFIG_PARTITION_SEARCH_ORDER 0 #define CONFIG_PIC 0 +#define CONFIG_RATECTRL_LOG 0 #define CONFIG_RD_COMMAND 0 #define CONFIG_RD_DEBUG 0 #define CONFIG_REALTIME_ONLY 1
diff --git a/third_party/libaom/source/config/linux/generic/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/linux/generic/config/aom_dsp_rtcd.h index 1fb294f..5113ef62 100644 --- a/third_party/libaom/source/config/linux/generic/config/aom_dsp_rtcd.h +++ b/third_party/libaom/source/config/linux/generic/config/aom_dsp_rtcd.h
@@ -875,6 +875,14 @@ unsigned int aom_get_mb_ss_c(const int16_t*); #define aom_get_mb_ss aom_get_mb_ss_c +void aom_get_sse_sum_8x8_quad_c(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +#define aom_get_sse_sum_8x8_quad aom_get_sse_sum_8x8_quad_c + void aom_h_predictor_16x16_c(uint8_t* dst, ptrdiff_t y_stride, const uint8_t* above,
diff --git a/third_party/libaom/source/config/linux/ia32/config/aom_config.asm b/third_party/libaom/source/config/linux/ia32/config/aom_config.asm index 16afb81..992f14d 100644 --- a/third_party/libaom/source/config/linux/ia32/config/aom_config.asm +++ b/third_party/libaom/source/config/linux/ia32/config/aom_config.asm
@@ -41,6 +41,7 @@ %define CONFIG_OS_SUPPORT 1 %define CONFIG_PARTITION_SEARCH_ORDER 0 %define CONFIG_PIC 1 +%define CONFIG_RATECTRL_LOG 0 %define CONFIG_RD_COMMAND 0 %define CONFIG_RD_DEBUG 0 %define CONFIG_REALTIME_ONLY 1
diff --git a/third_party/libaom/source/config/linux/ia32/config/aom_config.h b/third_party/libaom/source/config/linux/ia32/config/aom_config.h index df532d0..6677fab 100644 --- a/third_party/libaom/source/config/linux/ia32/config/aom_config.h +++ b/third_party/libaom/source/config/linux/ia32/config/aom_config.h
@@ -53,6 +53,7 @@ #define CONFIG_OS_SUPPORT 1 #define CONFIG_PARTITION_SEARCH_ORDER 0 #define CONFIG_PIC 1 +#define CONFIG_RATECTRL_LOG 0 #define CONFIG_RD_COMMAND 0 #define CONFIG_RD_DEBUG 0 #define CONFIG_REALTIME_ONLY 1
diff --git a/third_party/libaom/source/config/linux/ia32/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/linux/ia32/config/aom_dsp_rtcd.h index ceef0144..9c802bc2 100644 --- a/third_party/libaom/source/config/linux/ia32/config/aom_dsp_rtcd.h +++ b/third_party/libaom/source/config/linux/ia32/config/aom_dsp_rtcd.h
@@ -1979,6 +1979,31 @@ unsigned int aom_get_mb_ss_sse2(const int16_t*); #define aom_get_mb_ss aom_get_mb_ss_sse2 +void aom_get_sse_sum_8x8_quad_c(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +void aom_get_sse_sum_8x8_quad_sse2(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +void aom_get_sse_sum_8x8_quad_avx2(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +RTCD_EXTERN void (*aom_get_sse_sum_8x8_quad)(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); + void aom_h_predictor_16x16_c(uint8_t* dst, ptrdiff_t y_stride, const uint8_t* above, @@ -8612,6 +8637,9 @@ aom_get_blk_sse_sum = aom_get_blk_sse_sum_sse2; if (flags & HAS_AVX2) aom_get_blk_sse_sum = aom_get_blk_sse_sum_avx2; + aom_get_sse_sum_8x8_quad = aom_get_sse_sum_8x8_quad_sse2; + if (flags & HAS_AVX2) + aom_get_sse_sum_8x8_quad = aom_get_sse_sum_8x8_quad_avx2; aom_h_predictor_32x32 = aom_h_predictor_32x32_sse2; if (flags & HAS_AVX2) aom_h_predictor_32x32 = aom_h_predictor_32x32_avx2;
diff --git a/third_party/libaom/source/config/linux/x64/config/aom_config.asm b/third_party/libaom/source/config/linux/x64/config/aom_config.asm index 676c3dd8..b3a9b81 100644 --- a/third_party/libaom/source/config/linux/x64/config/aom_config.asm +++ b/third_party/libaom/source/config/linux/x64/config/aom_config.asm
@@ -41,6 +41,7 @@ %define CONFIG_OS_SUPPORT 1 %define CONFIG_PARTITION_SEARCH_ORDER 0 %define CONFIG_PIC 0 +%define CONFIG_RATECTRL_LOG 0 %define CONFIG_RD_COMMAND 0 %define CONFIG_RD_DEBUG 0 %define CONFIG_REALTIME_ONLY 1
diff --git a/third_party/libaom/source/config/linux/x64/config/aom_config.h b/third_party/libaom/source/config/linux/x64/config/aom_config.h index cb7e41a..63e5091 100644 --- a/third_party/libaom/source/config/linux/x64/config/aom_config.h +++ b/third_party/libaom/source/config/linux/x64/config/aom_config.h
@@ -53,6 +53,7 @@ #define CONFIG_OS_SUPPORT 1 #define CONFIG_PARTITION_SEARCH_ORDER 0 #define CONFIG_PIC 0 +#define CONFIG_RATECTRL_LOG 0 #define CONFIG_RD_COMMAND 0 #define CONFIG_RD_DEBUG 0 #define CONFIG_REALTIME_ONLY 1
diff --git a/third_party/libaom/source/config/linux/x64/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/linux/x64/config/aom_dsp_rtcd.h index 94a1db4..eed2759 100644 --- a/third_party/libaom/source/config/linux/x64/config/aom_dsp_rtcd.h +++ b/third_party/libaom/source/config/linux/x64/config/aom_dsp_rtcd.h
@@ -1982,6 +1982,31 @@ unsigned int aom_get_mb_ss_sse2(const int16_t*); #define aom_get_mb_ss aom_get_mb_ss_sse2 +void aom_get_sse_sum_8x8_quad_c(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +void aom_get_sse_sum_8x8_quad_sse2(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +void aom_get_sse_sum_8x8_quad_avx2(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +RTCD_EXTERN void (*aom_get_sse_sum_8x8_quad)(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); + void aom_h_predictor_16x16_c(uint8_t* dst, ptrdiff_t y_stride, const uint8_t* above, @@ -8651,6 +8676,9 @@ aom_get_blk_sse_sum = aom_get_blk_sse_sum_sse2; if (flags & HAS_AVX2) aom_get_blk_sse_sum = aom_get_blk_sse_sum_avx2; + aom_get_sse_sum_8x8_quad = aom_get_sse_sum_8x8_quad_sse2; + if (flags & HAS_AVX2) + aom_get_sse_sum_8x8_quad = aom_get_sse_sum_8x8_quad_avx2; aom_h_predictor_32x32 = aom_h_predictor_32x32_sse2; if (flags & HAS_AVX2) aom_h_predictor_32x32 = aom_h_predictor_32x32_avx2;
diff --git a/third_party/libaom/source/config/win/arm64/config/aom_config.asm b/third_party/libaom/source/config/win/arm64/config/aom_config.asm index 078e78c..cd9dfe35 100644 --- a/third_party/libaom/source/config/win/arm64/config/aom_config.asm +++ b/third_party/libaom/source/config/win/arm64/config/aom_config.asm
@@ -51,6 +51,7 @@ CONFIG_OS_SUPPORT equ 1 CONFIG_PARTITION_SEARCH_ORDER equ 0 CONFIG_PIC equ 0 +CONFIG_RATECTRL_LOG equ 0 CONFIG_RD_COMMAND equ 0 CONFIG_RD_DEBUG equ 0 CONFIG_REALTIME_ONLY equ 1
diff --git a/third_party/libaom/source/config/win/arm64/config/aom_config.h b/third_party/libaom/source/config/win/arm64/config/aom_config.h index a3d119e..6ffa438 100644 --- a/third_party/libaom/source/config/win/arm64/config/aom_config.h +++ b/third_party/libaom/source/config/win/arm64/config/aom_config.h
@@ -53,6 +53,7 @@ #define CONFIG_OS_SUPPORT 1 #define CONFIG_PARTITION_SEARCH_ORDER 0 #define CONFIG_PIC 0 +#define CONFIG_RATECTRL_LOG 0 #define CONFIG_RD_COMMAND 0 #define CONFIG_RD_DEBUG 0 #define CONFIG_REALTIME_ONLY 1
diff --git a/third_party/libaom/source/config/win/arm64/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/win/arm64/config/aom_dsp_rtcd.h index 01c7d9b..a1644504 100644 --- a/third_party/libaom/source/config/win/arm64/config/aom_dsp_rtcd.h +++ b/third_party/libaom/source/config/win/arm64/config/aom_dsp_rtcd.h
@@ -984,6 +984,20 @@ unsigned int aom_get_mb_ss_c(const int16_t*); #define aom_get_mb_ss aom_get_mb_ss_c +void aom_get_sse_sum_8x8_quad_c(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +void aom_get_sse_sum_8x8_quad_neon(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +#define aom_get_sse_sum_8x8_quad aom_get_sse_sum_8x8_quad_neon + void aom_h_predictor_16x16_c(uint8_t* dst, ptrdiff_t y_stride, const uint8_t* above,
diff --git a/third_party/libaom/source/config/win/ia32/config/aom_config.asm b/third_party/libaom/source/config/win/ia32/config/aom_config.asm index 71d7db2..f687b613c 100644 --- a/third_party/libaom/source/config/win/ia32/config/aom_config.asm +++ b/third_party/libaom/source/config/win/ia32/config/aom_config.asm
@@ -41,6 +41,7 @@ %define CONFIG_OS_SUPPORT 1 %define CONFIG_PARTITION_SEARCH_ORDER 0 %define CONFIG_PIC 1 +%define CONFIG_RATECTRL_LOG 0 %define CONFIG_RD_COMMAND 0 %define CONFIG_RD_DEBUG 0 %define CONFIG_REALTIME_ONLY 1
diff --git a/third_party/libaom/source/config/win/ia32/config/aom_config.h b/third_party/libaom/source/config/win/ia32/config/aom_config.h index d94b501..bc36f4b 100644 --- a/third_party/libaom/source/config/win/ia32/config/aom_config.h +++ b/third_party/libaom/source/config/win/ia32/config/aom_config.h
@@ -53,6 +53,7 @@ #define CONFIG_OS_SUPPORT 1 #define CONFIG_PARTITION_SEARCH_ORDER 0 #define CONFIG_PIC 1 +#define CONFIG_RATECTRL_LOG 0 #define CONFIG_RD_COMMAND 0 #define CONFIG_RD_DEBUG 0 #define CONFIG_REALTIME_ONLY 1
diff --git a/third_party/libaom/source/config/win/ia32/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/win/ia32/config/aom_dsp_rtcd.h index ceef0144..9c802bc2 100644 --- a/third_party/libaom/source/config/win/ia32/config/aom_dsp_rtcd.h +++ b/third_party/libaom/source/config/win/ia32/config/aom_dsp_rtcd.h
@@ -1979,6 +1979,31 @@ unsigned int aom_get_mb_ss_sse2(const int16_t*); #define aom_get_mb_ss aom_get_mb_ss_sse2 +void aom_get_sse_sum_8x8_quad_c(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +void aom_get_sse_sum_8x8_quad_sse2(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +void aom_get_sse_sum_8x8_quad_avx2(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +RTCD_EXTERN void (*aom_get_sse_sum_8x8_quad)(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); + void aom_h_predictor_16x16_c(uint8_t* dst, ptrdiff_t y_stride, const uint8_t* above, @@ -8612,6 +8637,9 @@ aom_get_blk_sse_sum = aom_get_blk_sse_sum_sse2; if (flags & HAS_AVX2) aom_get_blk_sse_sum = aom_get_blk_sse_sum_avx2; + aom_get_sse_sum_8x8_quad = aom_get_sse_sum_8x8_quad_sse2; + if (flags & HAS_AVX2) + aom_get_sse_sum_8x8_quad = aom_get_sse_sum_8x8_quad_avx2; aom_h_predictor_32x32 = aom_h_predictor_32x32_sse2; if (flags & HAS_AVX2) aom_h_predictor_32x32 = aom_h_predictor_32x32_avx2;
diff --git a/third_party/libaom/source/config/win/x64/config/aom_config.asm b/third_party/libaom/source/config/win/x64/config/aom_config.asm index d4892f7..5f04a0f 100644 --- a/third_party/libaom/source/config/win/x64/config/aom_config.asm +++ b/third_party/libaom/source/config/win/x64/config/aom_config.asm
@@ -41,6 +41,7 @@ %define CONFIG_OS_SUPPORT 1 %define CONFIG_PARTITION_SEARCH_ORDER 0 %define CONFIG_PIC 0 +%define CONFIG_RATECTRL_LOG 0 %define CONFIG_RD_COMMAND 0 %define CONFIG_RD_DEBUG 0 %define CONFIG_REALTIME_ONLY 1
diff --git a/third_party/libaom/source/config/win/x64/config/aom_config.h b/third_party/libaom/source/config/win/x64/config/aom_config.h index d92960bc..f212127f 100644 --- a/third_party/libaom/source/config/win/x64/config/aom_config.h +++ b/third_party/libaom/source/config/win/x64/config/aom_config.h
@@ -53,6 +53,7 @@ #define CONFIG_OS_SUPPORT 1 #define CONFIG_PARTITION_SEARCH_ORDER 0 #define CONFIG_PIC 0 +#define CONFIG_RATECTRL_LOG 0 #define CONFIG_RD_COMMAND 0 #define CONFIG_RD_DEBUG 0 #define CONFIG_REALTIME_ONLY 1
diff --git a/third_party/libaom/source/config/win/x64/config/aom_dsp_rtcd.h b/third_party/libaom/source/config/win/x64/config/aom_dsp_rtcd.h index 94a1db4..eed2759 100644 --- a/third_party/libaom/source/config/win/x64/config/aom_dsp_rtcd.h +++ b/third_party/libaom/source/config/win/x64/config/aom_dsp_rtcd.h
@@ -1982,6 +1982,31 @@ unsigned int aom_get_mb_ss_sse2(const int16_t*); #define aom_get_mb_ss aom_get_mb_ss_sse2 +void aom_get_sse_sum_8x8_quad_c(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +void aom_get_sse_sum_8x8_quad_sse2(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +void aom_get_sse_sum_8x8_quad_avx2(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); +RTCD_EXTERN void (*aom_get_sse_sum_8x8_quad)(const uint8_t* src_ptr, + int source_stride, + const uint8_t* ref_ptr, + int ref_stride, + unsigned int* sse, + int* sum); + void aom_h_predictor_16x16_c(uint8_t* dst, ptrdiff_t y_stride, const uint8_t* above, @@ -8651,6 +8676,9 @@ aom_get_blk_sse_sum = aom_get_blk_sse_sum_sse2; if (flags & HAS_AVX2) aom_get_blk_sse_sum = aom_get_blk_sse_sum_avx2; + aom_get_sse_sum_8x8_quad = aom_get_sse_sum_8x8_quad_sse2; + if (flags & HAS_AVX2) + aom_get_sse_sum_8x8_quad = aom_get_sse_sum_8x8_quad_avx2; aom_h_predictor_32x32 = aom_h_predictor_32x32_sse2; if (flags & HAS_AVX2) aom_h_predictor_32x32 = aom_h_predictor_32x32_avx2;
diff --git a/third_party/webgpu-cts/ts_sources.txt b/third_party/webgpu-cts/ts_sources.txt index 14dc8d6..b22e3c3 100644 --- a/third_party/webgpu-cts/ts_sources.txt +++ b/third_party/webgpu-cts/ts_sources.txt
@@ -3,10 +3,11 @@ src/common/internal/logging/log_message.ts src/common/internal/logging/result.ts src/common/internal/logging/logger.ts +src/common/util/types.ts +src/common/util/data_tables.ts src/common/util/timeout.ts src/common/util/util.ts src/common/internal/logging/test_case_recorder.ts -src/common/util/types.ts src/common/runtime/helper/options.ts src/common/internal/query/encode_selectively.ts src/common/internal/query/json_param_value.ts @@ -42,7 +43,6 @@ src/common/tools/version.ts src/common/util/collect_garbage.ts src/common/util/colors.ts -src/common/util/data_tables.ts src/common/util/preprocessor.ts src/unittests/unit_test.ts src/demo/a.spec.ts
diff --git a/third_party/xcbproto/README.chromium b/third_party/xcbproto/README.chromium index 0f97b77..8993074 100644 --- a/third_party/xcbproto/README.chromium +++ b/third_party/xcbproto/README.chromium
@@ -1,7 +1,7 @@ Name: xcbproto Short Name: xcbproto URL: https://gitlab.freedesktop.org/xorg/proto/xcbproto -Version: 496e3ce329c3cc9b32af4054c30fa0f306deb007 +Version: 70ca65fa35c3760661b090bc4b2601daa7a099b8 CPEPrefix: unknown Security Critical: no License: MIT
diff --git a/third_party/xcbproto/VERSION b/third_party/xcbproto/VERSION index b64ae9b0..626f0e2a 100644 --- a/third_party/xcbproto/VERSION +++ b/third_party/xcbproto/VERSION
@@ -1 +1 @@ -496e3ce329c3cc9b32af4054c30fa0f306deb007 +70ca65fa35c3760661b090bc4b2601daa7a099b8
diff --git a/third_party/xcbproto/patch.diff b/third_party/xcbproto/patch.diff index 2800338..1d72d6f 100644 --- a/third_party/xcbproto/patch.diff +++ b/third_party/xcbproto/patch.diff
@@ -1,6 +1,6 @@ diff -ru xcbproto/src/glx.xml src/src/glx.xml ---- xcbproto/src/glx.xml 2021-02-10 12:51:13.785903766 -0800 -+++ src/src/glx.xml 2021-02-04 15:13:24.836890047 -0800 +--- xcbproto/src/glx.xml 2022-03-25 16:30:06.066425385 -0700 ++++ src/src/glx.xml 2022-03-25 16:29:28.146096207 -0700 @@ -62,8 +62,6 @@ <type>glx:WINDOW</type> </xidunion> @@ -292,8 +292,8 @@ </list> </reply> diff -ru xcbproto/src/present.xml src/src/present.xml ---- xcbproto/src/present.xml 2021-02-10 12:51:13.785903766 -0800 -+++ src/src/present.xml 2021-02-10 12:51:07.933863954 -0800 +--- xcbproto/src/present.xml 2022-03-25 16:30:06.050425246 -0700 ++++ src/src/present.xml 2022-03-25 16:27:47.661223477 -0700 @@ -89,7 +89,7 @@ </reply> </request> @@ -304,8 +304,8 @@ <field type="WINDOW" name="window" /> <field type="PIXMAP" name="pixmap" /> diff -ru xcbproto/src/randr.xml src/src/randr.xml ---- xcbproto/src/randr.xml 2021-02-10 12:51:13.785903766 -0800 -+++ src/src/randr.xml 2021-02-04 11:51:16.530051106 -0800 +--- xcbproto/src/randr.xml 2022-03-25 16:30:06.050425246 -0700 ++++ src/src/randr.xml 2022-03-25 16:27:47.661223477 -0700 @@ -803,64 +803,6 @@ <item name="Lease"> <value>6</value></item> </enum> @@ -479,8 +479,8 @@ </event> </xcb> diff -ru xcbproto/src/shm.xml src/src/shm.xml ---- xcbproto/src/shm.xml 2021-02-10 12:51:13.789903793 -0800 -+++ src/src/shm.xml 2021-02-04 11:51:16.530051106 -0800 +--- xcbproto/src/shm.xml 2022-03-25 16:30:06.050425246 -0700 ++++ src/src/shm.xml 2022-03-25 16:27:47.661223477 -0700 @@ -78,7 +78,7 @@ <field type="INT16" name="dst_x" /> <field type="INT16" name="dst_y" /> @@ -491,8 +491,8 @@ <pad bytes="1" /> <field type="SEG" name="shmseg" /> diff -ru xcbproto/src/xinput.xml src/src/xinput.xml ---- xcbproto/src/xinput.xml 2021-02-10 12:51:13.789903793 -0800 -+++ src/src/xinput.xml 2021-02-08 17:31:46.995137506 -0800 +--- xcbproto/src/xinput.xml 2022-03-25 16:30:06.066425385 -0700 ++++ src/src/xinput.xml 2022-03-25 16:29:28.146096207 -0700 @@ -200,7 +200,12 @@ <list type="STR" name="names"> <fieldref>devices_len</fieldref> @@ -693,8 +693,8 @@ <!-- ⋅⋅⋅ Events (v2.3) ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ --> diff -ru xcbproto/src/xproto.xml src/src/xproto.xml ---- xcbproto/src/xproto.xml 2021-02-10 12:51:13.789903793 -0800 -+++ src/src/xproto.xml 2021-02-04 15:13:24.840890072 -0800 +--- xcbproto/src/xproto.xml 2022-03-25 16:30:06.070425420 -0700 ++++ src/src/xproto.xml 2022-03-25 16:29:28.150096242 -0700 @@ -963,6 +963,7 @@ <item name="CAP_HEIGHT"> <value>66</value> </item> <item name="WM_CLASS"> <value>67</value> </item> @@ -737,15 +737,10 @@ </request> <!-- Reply from SetPointerMapping or SetModifierMapping --> -Only in src/xcbgen: align.pyc -Only in src/xcbgen: error.pyc -Only in src/xcbgen: expr.pyc -Only in src/xcbgen: __init__.pyc -Only in src/xcbgen: matcher.pyc -Only in src/xcbgen: state.pyc +Only in src/xcbgen: __pycache__ diff -ru xcbproto/xcbgen/xtypes.py src/xcbgen/xtypes.py ---- xcbproto/xcbgen/xtypes.py 2021-02-10 12:51:13.789903793 -0800 -+++ src/xcbgen/xtypes.py 2021-02-04 15:13:24.840890072 -0800 +--- xcbproto/xcbgen/xtypes.py 2022-03-25 16:30:06.070425420 -0700 ++++ src/xcbgen/xtypes.py 2022-03-25 16:29:28.150096242 -0700 @@ -226,7 +226,7 @@ Derived class which represents a file descriptor. ''' @@ -755,4 +750,3 @@ self.is_fd = True def fixed_size(self): -Only in src/xcbgen: xtypes.pyc
diff --git a/third_party/xcbproto/src/doc/xml-xcb.txt b/third_party/xcbproto/src/doc/xml-xcb.txt index f5b9aed..baef734 100644 --- a/third_party/xcbproto/src/doc/xml-xcb.txt +++ b/third_party/xcbproto/src/doc/xml-xcb.txt
@@ -65,8 +65,8 @@ This element represents a data structure. The name attribute gives the name of the structure. The content represents the fields of the structure, and - consists of one or more of the field, pad, and list elements described in - the section "Structure Contents" below. + consists of one or more of the length, field, pad, and list elements described + in the section "Structure Contents" below. <union name="identifier">structure contents</union> @@ -215,6 +215,23 @@ declares the data type of the field, and the name attribute gives the name of the field. +<length>expression</length> + This element overrides the length of the data structure by specifying it + explicitly instead of it being defined by the layout of the structure. + This makes it possible to handle structures with conditional fields + (see the <switch> element) where the future revisions of protocols may + introduce new variants and old code must still properly ignore them. + + The content is an expression giving the length of the data structure in terms + of other fields in the structure. See the section "Expressions" for details + on the expression representation. + + The expression must not depend on conditional fields. + + Additionally, the length of the data structure must be at least such that it + includes the fields that the expression depends on. Smaller length is + considered a violation of the protocol. + <fd name="identifier" /> This element represents a file descriptor field passed with the request. The
diff --git a/third_party/xcbproto/src/src/glx.xml b/third_party/xcbproto/src/src/glx.xml index 70aa9f9..6e728e2 100644 --- a/third_party/xcbproto/src/src/glx.xml +++ b/third_party/xcbproto/src/src/glx.xml
@@ -489,6 +489,7 @@ <list type="char" name="gl_extension_string"> <fieldref>gl_str_len</fieldref> </list> + <pad align="4" /> <list type="char" name="glx_extension_string"> <fieldref>glx_str_len</fieldref> </list> @@ -525,6 +526,7 @@ <list type="char" name="gl_extension_string"> <fieldref>gl_str_len</fieldref> </list> + <pad align="4" /> <list type="char" name="glx_extension_string"> <fieldref>glx_str_len</fieldref> </list>
diff --git a/third_party/xcbproto/src/src/xcb.xsd b/third_party/xcbproto/src/src/xcb.xsd index dc3d7cc..86a51c5 100644 --- a/third_party/xcbproto/src/src/xcb.xsd +++ b/third_party/xcbproto/src/src/xcb.xsd
@@ -101,6 +101,13 @@ <!-- field replaces FIELD, PARAM, and REPLY. --> <xsd:element name="field" type="var" /> + <!-- Length of data structures --> + <xsd:element name="length"> + <xsd:complexType> + <xsd:group ref="expression" /> + </xsd:complexType> + </xsd:element> + <!-- fd passing parameter --> <xsd:element name="fd"> <xsd:complexType> @@ -210,6 +217,7 @@ <xsd:element ref="list" /> <xsd:element ref="fd" /> <xsd:element ref="required_start_align" /> + <xsd:element ref="length" /> </xsd:choice> </xsd:group>
diff --git a/third_party/xcbproto/src/src/xfixes.xml b/third_party/xcbproto/src/src/xfixes.xml index 0a3d5ff..a01cd7b0 100644 --- a/third_party/xcbproto/src/src/xfixes.xml +++ b/third_party/xcbproto/src/src/xfixes.xml
@@ -26,7 +26,7 @@ --> <!-- This file describes version 4 of XFixes. --> <xcb header="xfixes" extension-xname="XFIXES" extension-name="XFixes" - major-version="5" minor-version="0"> + major-version="6" minor-version="0"> <import>xproto</import> <import>render</import> <import>shape</import> @@ -359,4 +359,24 @@ <request name="DeletePointerBarrier" opcode="32"> <field type="BARRIER" name="barrier" /> </request> + + <!-- Version 6 --> + + <enum name="ClientDisconnectFlags"> + <item name="Default"><value>0</value></item> + <item name="Terminate"><bit>0</bit></item> + </enum> + + <request name="SetClientDisconnectMode" opcode="33"> + <field type="CARD32" name="disconnect_mode" mask="ClientDisconnectFlags" /> + </request> + + <request name="GetClientDisconnectMode" opcode="34"> + <reply> + <pad bytes="1" /> + <field type="CARD32" name="disconnect_mode" mask="ClientDisconnectFlags" /> + <pad bytes="20" /> + </reply> + </request> + </xcb>
diff --git a/third_party/xcbproto/src/src/xinput.xml b/third_party/xcbproto/src/src/xinput.xml index 33a93d2..90db8f9 100644 --- a/third_party/xcbproto/src/src/xinput.xml +++ b/third_party/xcbproto/src/src/xinput.xml
@@ -33,7 +33,7 @@ --> <xcb header="xinput" extension-xname="XInputExtension" extension-name="Input" - major-version="2" minor-version="3"> + major-version="2" minor-version="4"> <import>xfixes</import> <import>xproto</import> @@ -1607,6 +1607,7 @@ <item name="Valuator"> <value>2</value> </item> <item name="Scroll"> <value>3</value> </item> <item name="Touch"> <value>8</value> </item> + <item name="Gesture"> <value>9</value> </item> </enum> <enum name="DeviceType"> @@ -1680,6 +1681,14 @@ <field type="CARD8" name="num_touches" /> </struct> + <struct name="GestureClass"> + <field type="CARD16" name="type" enum="DeviceClassType" /> + <field type="CARD16" name="len" /> + <field type="DeviceId" name="sourceid" /> + <field type="CARD8" name="num_touches" /> + <pad bytes="1" /> + </struct> + <struct name="ValuatorClass"> <field type="CARD16" name="type" enum="DeviceClassType" /> <field type="CARD16" name="len" /> @@ -1695,6 +1704,12 @@ </struct> <struct name="DeviceClass"> + <length> + <op op="*"> + <fieldref>len</fieldref> + <value>4</value> + </op> + </length> <field type="CARD16" name="type" enum="DeviceClassType" /> <field type="CARD16" name="len" /> <field type="DeviceId" name="sourceid" /> @@ -1752,6 +1767,11 @@ <field type="CARD8" name="mode" enum="TouchMode" /> <field type="CARD8" name="num_touches" /> </case> + <case name="gesture"> + <enumref ref="DeviceClassType">Gesture</enumref> + <field type="CARD8" name="num_touches" /> + <pad bytes="1" /> + </case> </switch> </struct> @@ -1872,11 +1892,13 @@ </enum> <enum name="GrabType"> - <item name="Button"> <value>0</value> </item> - <item name="Keycode"> <value>1</value> </item> - <item name="Enter"> <value>2</value> </item> - <item name="FocusIn"> <value>3</value> </item> - <item name="TouchBegin"> <value>4</value> </item> + <item name="Button"> <value>0</value> </item> + <item name="Keycode"> <value>1</value> </item> + <item name="Enter"> <value>2</value> </item> + <item name="FocusIn"> <value>3</value> </item> + <item name="TouchBegin"> <value>4</value> </item> + <item name="GesturePinchBegin"> <value>5</value> </item> + <item name="GestureSwipeBegin"> <value>6</value> </item> </enum> <enum name="ModifierMask"> @@ -2485,6 +2507,72 @@ <eventcopy name="BarrierLeave" number="26" ref="BarrierHit" /> + <!-- ⋅⋅⋅ Events (v2.4) ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ --> + + <enum name="GesturePinchEventFlags"> + <item name="GesturePinchCancelled"> <bit>0</bit> </item> + </enum> + + <event name="GesturePinchBegin" number="27" xge="true"> + <field type="DeviceId" name="deviceid" altenum="Device" /> + <field type="TIMESTAMP" name="time" altenum="Time" /> + <!-- event specific fields --> + <field type="CARD32" name="detail" /> + <field type="WINDOW" name="root" /> + <field type="WINDOW" name="event" /> + <field type="WINDOW" name="child" /> + <!-- 32 byte boundary --> + <field type="FP1616" name="root_x" /> + <field type="FP1616" name="root_y" /> + <field type="FP1616" name="event_x" /> + <field type="FP1616" name="event_y" /> + <field type="FP1616" name="delta_x" /> + <field type="FP1616" name="delta_y" /> + <field type="FP1616" name="delta_unaccel_x" /> + <field type="FP1616" name="delta_unaccel_y" /> + <field type="FP1616" name="scale" /> + <field type="FP1616" name="delta_angle" /> + <field type="DeviceId" name="sourceid" altenum="Device" /> + <pad bytes="2" /> + <field type="ModifierInfo" name="mods" /> + <field type="GroupInfo" name="group" /> + <field type="CARD32" name="flags" mask="GesturePinchEventFlags" /> + </event> + + <eventcopy name="GesturePinchUpdate" number="28" ref="GesturePinchBegin" /> + <eventcopy name="GesturePinchEnd" number="29" ref="GesturePinchBegin" /> + + <enum name="GestureSwipeEventFlags"> + <item name="GestureSwipeCancelled"> <bit>0</bit> </item> + </enum> + + <event name="GestureSwipeBegin" number="30" xge="true"> + <field type="DeviceId" name="deviceid" altenum="Device" /> + <field type="TIMESTAMP" name="time" altenum="Time" /> + <!-- event specific fields --> + <field type="CARD32" name="detail" /> + <field type="WINDOW" name="root" /> + <field type="WINDOW" name="event" /> + <field type="WINDOW" name="child" /> + <!-- 32 byte boundary --> + <field type="FP1616" name="root_x" /> + <field type="FP1616" name="root_y" /> + <field type="FP1616" name="event_x" /> + <field type="FP1616" name="event_y" /> + <field type="FP1616" name="delta_x" /> + <field type="FP1616" name="delta_y" /> + <field type="FP1616" name="delta_unaccel_x" /> + <field type="FP1616" name="delta_unaccel_y" /> + <field type="DeviceId" name="sourceid" altenum="Device" /> + <pad bytes="2" /> + <field type="ModifierInfo" name="mods" /> + <field type="GroupInfo" name="group" /> + <field type="CARD32" name="flags" mask="GestureSwipeEventFlags" /> + </event> + + <eventcopy name="GestureSwipeUpdate" number="31" ref="GestureSwipeBegin" /> + <eventcopy name="GestureSwipeEnd" number="32" ref="GestureSwipeBegin" /> + <!-- ⋅⋅⋅ Requests that depend on events ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ --> <!-- SendExtensionEvent -->
diff --git a/third_party/xcbproto/src/src/xprint.xml b/third_party/xcbproto/src/src/xprint.xml index f9af65fa..fa3bb7f 100644 --- a/third_party/xcbproto/src/src/xprint.xml +++ b/third_party/xcbproto/src/src/xprint.xml
@@ -102,7 +102,7 @@ <list type="STRING8" name="printer_name"> <fieldref>printerNameLen</fieldref> </list> - <!-- There's some padding in here... --> + <pad align="4" /> <list type="STRING8" name="locale"> <fieldref>localeLen</fieldref> </list> @@ -125,7 +125,7 @@ <list type="STRING8" name="printerName"> <fieldref>printerNameLen</fieldref> </list> - <!-- padding --> + <pad align="4" /> <list type="STRING8" name="locale"> <fieldref>localeLen</fieldref> </list> @@ -177,11 +177,11 @@ <list type="BYTE" name="data"> <fieldref>len_data</fieldref> </list> - <!-- padding --> + <pad align="4" /> <list type="STRING8" name="doc_format"> <fieldref>len_fmt</fieldref> </list> - <!-- padding --> + <pad align="4" /> <list type="STRING8" name="options"> <fieldref>len_options</fieldref> </list>
diff --git a/third_party/xcbproto/src/src/xproto.xml b/third_party/xcbproto/src/src/xproto.xml index 928a1c39..21f332f 100644 --- a/third_party/xcbproto/src/src/xproto.xml +++ b/third_party/xcbproto/src/src/xproto.xml
@@ -2982,25 +2982,21 @@ ]]></description> <field name="owner_events"><![CDATA[ -If 1, the `grab_window` will still get the pointer events. If 0, events are not +If 1, the `grab_window` will still get the key events. If 0, events are not reported to the `grab_window`. ]]></field> <field name="grab_window"><![CDATA[ -Specifies the window on which the pointer should be grabbed. +Specifies the window on which the key should be grabbed. ]]></field> <field name="key"><![CDATA[ The keycode of the key to grab. The special value `XCB_GRAB_ANY` means grab any key. ]]></field> - <field name="cursor"><![CDATA[ -Specifies the cursor that should be displayed or `XCB_NONE` to not change the -cursor. - ]]></field> <field name="modifiers"><![CDATA[ The modifiers to grab. -Using the special value `XCB_MOD_MASK_ANY` means grab the pointer with all +Using the special value `XCB_MOD_MASK_ANY` means grab the key with all possible modifier combinations. ]]></field> <!-- the enum doc is sufficient. --> @@ -3011,7 +3007,8 @@ combination on the same window. ]]></error> <error type="Value"><![CDATA[ -TODO: reasons? +The key is not `XCB_GRAB_ANY` and not in the range specified by `min_keycode` +and `max_keycode` in the connection setup. ]]></error> <error type="Window"><![CDATA[ The specified `window` does not exist.
diff --git a/third_party/xcbproto/src/xcb-proto.pc.in b/third_party/xcbproto/src/xcb-proto.pc.in index a35f0bd..c7c8b47 100644 --- a/third_party/xcbproto/src/xcb-proto.pc.in +++ b/third_party/xcbproto/src/xcb-proto.pc.in
@@ -4,6 +4,7 @@ datadir=@datadir@ libdir=@libdir@ xcbincludedir=${pc_sysrootdir}@xcbincludedir@ +PYTHON_PREFIX=@PYTHON_PREFIX@ pythondir=${pc_sysrootdir}@pythondir@ Name: XCB Proto
diff --git a/third_party/xcbproto/src/xcbgen/xtypes.py b/third_party/xcbproto/src/xcbgen/xtypes.py index 0cabf2d7..3603233 100644 --- a/third_party/xcbproto/src/xcbgen/xtypes.py +++ b/third_party/xcbproto/src/xcbgen/xtypes.py
@@ -1,8 +1,15 @@ ''' This module contains the classes which represent XCB data types. ''' +import sys from xcbgen.expr import Field, Expression from xcbgen.align import Alignment, AlignmentLog + +if sys.version_info[:2] >= (3, 3): + from xml.etree.ElementTree import SubElement +else: + from xml.etree.cElementTree import SubElement + import __main__ verbose_align_log = False @@ -503,6 +510,8 @@ Public fields added: fields is an array of Field objects describing the structure fields. + length_expr is an expression that defines the length of the structure. + ''' def __init__(self, name, elt): Type.__init__(self, name) @@ -512,6 +521,7 @@ self.nmemb = 1 self.size = 0 self.lenfield_parent = [self] + self.length_expr = None # get required_start_alignment required_start_align_element = elt.find("required_start_align") @@ -573,6 +583,9 @@ type = module.get_type('INT32') type.make_fd_of(module, self, fd_name) continue + elif child.tag == 'length': + self.length_expr = Expression(list(child)[0], self) + continue else: # Hit this on Reply continue @@ -1346,6 +1359,15 @@ if self.required_start_align is None: self.required_start_align = Alignment(4,0) + # All errors are basically the same, but they still got different XML + # for historic reasons. This 'invents' the missing parts. + if len(self.elt) < 1: + SubElement(self.elt, "field", type="CARD32", name="bad_value") + if len(self.elt) < 2: + SubElement(self.elt, "field", type="CARD16", name="minor_opcode") + if len(self.elt) < 3: + SubElement(self.elt, "field", type="CARD8", name="major_opcode") + def add_opcode(self, opcode, name, main): self.opcodes[name] = opcode if main:
diff --git a/tools/binary_size/libsupersize/viewer/static/main.css b/tools/binary_size/libsupersize/viewer/static/main.css index 5d1dfe3..1956214e 100644 --- a/tools/binary_size/libsupersize/viewer/static/main.css +++ b/tools/binary_size/libsupersize/viewer/static/main.css
@@ -291,4 +291,10 @@ color: #0000d1; text-decoration: none; white-space: pre; +} + +#no-symbols-msg { + position: relative; + text-align: center; + top: -1em; } \ No newline at end of file
diff --git a/tools/binary_size/libsupersize/viewer/static/tree-ui.js b/tools/binary_size/libsupersize/viewer/static/tree-ui.js index 33dee93..de56ccb 100644 --- a/tools/binary_size/libsupersize/viewer/static/tree-ui.js +++ b/tools/binary_size/libsupersize/viewer/static/tree-ui.js
@@ -555,8 +555,7 @@ * @param {boolean} show */ function toggleNoSymbolsMessage(show) { - const errorModal = document.getElementById('error-modal'); - errorModal.querySelector('div').style.alignItems = 'center'; + const errorModal = document.getElementById('no-symbols-msg'); errorModal.style.display = show ? '' : 'none'; }
diff --git a/tools/binary_size/libsupersize/viewer/static/viewer.html b/tools/binary_size/libsupersize/viewer/static/viewer.html index bd65aff..60c324d 100644 --- a/tools/binary_size/libsupersize/viewer/static/viewer.html +++ b/tools/binary_size/libsupersize/viewer/static/viewer.html
@@ -410,6 +410,10 @@ </header> <ul id="symboltree" class="tree" role="tree" aria-labelledby="headline"></ul> </main> + <!-- Empty .sizediff Message--> + <div id="no-symbols-msg" style="display:none"> + This diff contains no symbols (no sizes changed). + </div> <!-- Metadata Node --> <div id="metadata-view" class="metadata" title="Metadata from .size/.sizediff file"> <a class="node" tabindex="0" role="presentation"> @@ -630,12 +634,6 @@ <button type="button" class="signin text-button filled-button">Sign Me In</button> </div> </div> - <!-- Modal Empty .sizediff Dialog --> - <div id="error-modal" class="modal" style="display:none"> - <div class="modal-content"> - <p>This diff contains no symbols (no sizes changed).</p> - </div> - </div> </body> </html>
diff --git a/tools/mb/mb.py b/tools/mb/mb.py index 65b4562..23bc524 100755 --- a/tools/mb/mb.py +++ b/tools/mb/mb.py
@@ -755,8 +755,9 @@ if ret != 0: return ret collect_json = json.loads(self.ReadFile(collect_output)) - # The exit_code field is not included if the task was successful. - ret = collect_json.get(task_id, {}).get('results', {}).get('exit_code', 0) + # The exit_code field might not be included if the task was successful. + ret = int( + collect_json.get(task_id, {}).get('results', {}).get('exit_code', 0)) finally: if json_dir: self.RemoveDirectory(json_dir)
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl index eee0fbb2..0572b8a1 100644 --- a/tools/mb/mb_config.pyl +++ b/tools/mb/mb_config.pyl
@@ -486,7 +486,7 @@ 'GPU Linux Builder (dbg)': 'gpu_tests_debug_bot_reclient', 'GPU Win x64 Builder': 'gpu_tests_release_bot_dcheck_always_on_resource_allowlisting', 'GPU Win x64 Builder Code Coverage': 'gpu_tests_release_trybot_resource_allowlisting_code_coverage', - 'GPU Win x64 Builder (dbg)': 'gpu_tests_debug_bot', + 'GPU Win x64 Builder (dbg)': 'gpu_tests_debug_bot_reclient', 'Android Release (Nexus 5X)': 'gpu_tests_android_release_bot_dcheck_always_on_arm64_fastbuild_reclient', }, @@ -508,15 +508,15 @@ 'GPU FYI Mac Builder (dbg)': 'gpu_fyi_tests_debug_trybot', 'GPU FYI Mac arm64 Builder': 'gpu_fyi_tests_release_trybot_arm64', 'GPU FYI Win Builder': 'gpu_fyi_tests_release_trybot_x86', - 'GPU FYI Win x64 Builder': 'gpu_fyi_tests_release_trybot', + 'GPU FYI Win x64 Builder': 'gpu_fyi_tests_release_trybot_reclient', 'GPU FYI Win x64 Builder (reclient shadow)': 'gpu_fyi_tests_release_trybot_reclient', - 'GPU FYI Win x64 Builder (dbg)': 'gpu_fyi_tests_debug_trybot', + 'GPU FYI Win x64 Builder (dbg)': 'gpu_fyi_tests_debug_trybot_reclient', 'GPU FYI Win x64 Builder (dbg) (reclient shadow)': 'gpu_fyi_tests_debug_trybot_reclient', - 'GPU FYI Win x64 DX12 Vulkan Builder': 'gpu_fyi_tests_dx12vk_release_trybot', + 'GPU FYI Win x64 DX12 Vulkan Builder': 'gpu_fyi_tests_dx12vk_release_trybot_reclient', 'GPU FYI Win x64 DX12 Vulkan Builder (reclient shadow)': 'gpu_fyi_tests_dx12vk_release_trybot_reclient', - 'GPU FYI Win x64 DX12 Vulkan Builder (dbg)': 'gpu_fyi_tests_dx12vk_debug_trybot', + 'GPU FYI Win x64 DX12 Vulkan Builder (dbg)': 'gpu_fyi_tests_dx12vk_debug_trybot_reclient', 'GPU FYI Win x64 DX12 Vulkan Builder (dbg) (reclient shadow)': 'gpu_fyi_tests_dx12vk_debug_trybot_reclient', - 'GPU FYI XR Win x64 Builder': 'gpu_fyi_tests_release_trybot', + 'GPU FYI XR Win x64 Builder': 'gpu_fyi_tests_release_trybot_reclient', 'GPU FYI XR Win x64 Builder (reclient shadow)': 'gpu_fyi_tests_release_trybot_reclient', 'GPU Win x64 Builder (dbg) (reclient shadow)': 'gpu_tests_debug_bot_reclient', 'Linux FYI GPU TSAN Release': 'gpu_fyi_tests_release_trybot_tsan_reclient',
diff --git a/tools/mb/mb_config_expectations/chromium.gpu.fyi.json b/tools/mb/mb_config_expectations/chromium.gpu.fyi.json index bf3779b..979c9acf 100644 --- a/tools/mb/mb_config_expectations/chromium.gpu.fyi.json +++ b/tools/mb/mb_config_expectations/chromium.gpu.fyi.json
@@ -209,7 +209,8 @@ "is_gpu_fyi_bot": true, "proprietary_codecs": true, "symbol_level": 1, - "use_goma": true + "use_rbe": true, + "use_remoteexec": true } }, "GPU FYI Win x64 Builder (dbg)": { @@ -222,7 +223,8 @@ "is_gpu_fyi_bot": true, "proprietary_codecs": true, "symbol_level": 1, - "use_goma": true + "use_rbe": true, + "use_remoteexec": true } }, "GPU FYI Win x64 Builder (dbg) (reclient shadow)": { @@ -268,7 +270,8 @@ "is_gpu_fyi_bot": true, "proprietary_codecs": true, "symbol_level": 1, - "use_goma": true + "use_rbe": true, + "use_remoteexec": true } }, "GPU FYI Win x64 DX12 Vulkan Builder (dbg)": { @@ -282,7 +285,8 @@ "is_gpu_fyi_bot": true, "proprietary_codecs": true, "symbol_level": 1, - "use_goma": true + "use_rbe": true, + "use_remoteexec": true } }, "GPU FYI Win x64 DX12 Vulkan Builder (dbg) (reclient shadow)": { @@ -329,7 +333,8 @@ "is_gpu_fyi_bot": true, "proprietary_codecs": true, "symbol_level": 1, - "use_goma": true + "use_rbe": true, + "use_remoteexec": true } }, "GPU FYI XR Win x64 Builder (reclient shadow)": {
diff --git a/tools/mb/mb_config_expectations/chromium.gpu.json b/tools/mb/mb_config_expectations/chromium.gpu.json index 4a18dc2a..101b845 100644 --- a/tools/mb/mb_config_expectations/chromium.gpu.json +++ b/tools/mb/mb_config_expectations/chromium.gpu.json
@@ -83,7 +83,8 @@ "is_debug": true, "proprietary_codecs": true, "symbol_level": 1, - "use_goma": true + "use_rbe": true, + "use_remoteexec": true } }, "GPU Win x64 Builder Code Coverage": {
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index d061bcb..e8590466 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml
@@ -8640,6 +8640,7 @@ label="RFH_BEFOREUNLOAD_HANDLER_NOT_ALLOWED_IN_FENCED_FRAME"/> <int value="271" label="MSDH_GET_OPEN_DEVICE_USE_WITHOUT_FEATURE"/> <int value="272" label="RFHI_SUBFRAME_NAV_WOULD_CHANGE_MAINFRAME_ORIGIN"/> + <int value="273" label="FF_CREATE_WHILE_PRERENDERING"/> </enum> <enum name="BadMessageReasonExtensions"> @@ -53261,6 +53262,7 @@ <int value="-1101334831" label="SyncSupportTrustedVaultPassphrase:disabled"/> <int value="-1101036307" label="SetTimeoutWithoutClamp:enabled"/> <int value="-1099618411" label="UpdatedCellularActivationUi:enabled"/> + <int value="-1099496822" label="CaptureModeSelfieCamera:enabled"/> <int value="-1099142083" label="V8Ignition:disabled"/> <int value="-1099135056" label="AsyncDns:enabled"/> <int value="-1097977406" @@ -53876,6 +53878,7 @@ <int value="-681434111" label="WebFeed:disabled"/> <int value="-680787130" label="ExperimentalVRFeatures:disabled"/> <int value="-680589442" label="MacRTL:disabled"/> + <int value="-679585025" label="CaptureModeSelfieCamera:disabled"/> <int value="-679500267" label="UseXpsForPrinting:disabled"/> <int value="-678184617" label="TranslateSubFrames:disabled"/> <int value="-677978627" label="ZeroCopyTabCapture:enabled"/>
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml index 098348b5..f68e0a7 100644 --- a/tools/metrics/histograms/metadata/ash/histograms.xml +++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -1641,6 +1641,21 @@ </summary> </histogram> +<histogram + name="Ash.DeviceActiveClient.Recorded{DeviceActivityClientTransitionMethod}Minute" + units="int" expires_after="2022-11-01"> + <owner>hirthanan@google.com</owner> + <owner>chromeos-data-team@google.com</owner> + <summary> + Emitted in the minute during the hour that DeviceActivityClient + {DeviceActivityClientTransitionMethod} is called. ChromeOS only. + </summary> + <token key="DeviceActivityClientTransitionMethod"> + <variant name="TransitionOutOfIdle" summary="transition-out-of-idle"/> + <variant name="TransitionToCheckIn" summary="transition-to-check-in"/> + </token> +</histogram> + <histogram name="Ash.DeviceActiveClient.Response.{DeviceActiveClientState}" enum="DeviceActiveClientPsmResponse" expires_after="2022-11-01"> <owner>hirthanan@google.com</owner> @@ -1664,21 +1679,6 @@ </summary> </histogram> -<histogram - name="Ash.DeviceActiveClient.{DeviceActivityClientTransitionMethod}Minute" - units="minutes" expires_after="2022-11-01"> - <owner>hirthanan@google.com</owner> - <owner>chromeos-data-team@google.com</owner> - <summary> - Emitted in the minute during the hour that DeviceActivityClient - {DeviceActivityClientTransitionMethod} is called. ChromeOS only. - </summary> - <token key="DeviceActivityClientTransitionMethod"> - <variant name="TransitionOutOfIdle" summary="transition-out-of-idle"/> - <variant name="TransitionToCheckIn" summary="transition-to-check-in"/> - </token> -</histogram> - <histogram name="Ash.DeviceActiveController.PsmDeviceActiveSecretIsSet" enum="BooleanSuccess" expires_after="2022-11-01"> <owner>hirthanan@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/chromeos_settings/histograms.xml b/tools/metrics/histograms/metadata/chromeos_settings/histograms.xml index f9c8b58..6963ee4 100644 --- a/tools/metrics/histograms/metadata/chromeos_settings/histograms.xml +++ b/tools/metrics/histograms/metadata/chromeos_settings/histograms.xml
@@ -75,7 +75,7 @@ </histogram> <histogram name="ChromeOS.Settings.Device.KeyboardFunctionKeys" - enum="BooleanToggled" expires_after="2022-03-15"> + enum="BooleanToggled" expires_after="2023-03-15"> <owner>jimmyxgong@chromium.org</owner> <owner>zentaro@chromium.org</owner> <owner>cros-peripherals@google.com</owner>
diff --git a/tools/perf/chrome-health-presets.yaml b/tools/perf/chrome-health-presets.yaml index f91bdd3..a53317b 100644 --- a/tools/perf/chrome-health-presets.yaml +++ b/tools/perf/chrome-health-presets.yaml
@@ -39,6 +39,10 @@ - motionmark_ramp_design - motionmark_ramp_design - motionmark_ramp_design + - motionmark_ramp_focus + - motionmark_ramp_focus + - motionmark_ramp_focus + - motionmark_ramp_focus - motionmark_ramp_images - motionmark_ramp_images - motionmark_ramp_images @@ -76,6 +80,10 @@ - motionmark_ramp_design - motionmark_ramp_design - motionmark_ramp_design + - motionmark_ramp_focus + - motionmark_ramp_focus + - motionmark_ramp_focus + - motionmark_ramp_focus - motionmark_ramp_images - motionmark_ramp_images - motionmark_ramp_images @@ -132,6 +140,10 @@ - motionmark_ramp_design - motionmark_ramp_design - motionmark_ramp_design + - motionmark_ramp_focus + - motionmark_ramp_focus + - motionmark_ramp_focus + - motionmark_ramp_focus - motionmark_ramp_images - motionmark_ramp_images - motionmark_ramp_images @@ -169,6 +181,10 @@ - motionmark_ramp_design - motionmark_ramp_design - motionmark_ramp_design + - motionmark_ramp_focus + - motionmark_ramp_focus + - motionmark_ramp_focus + - motionmark_ramp_focus - motionmark_ramp_images - motionmark_ramp_images - motionmark_ramp_images
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json index 83d7b63..ae54f585 100644 --- a/tools/perf/core/perfetto_binary_roller/binary_deps.json +++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -13,8 +13,8 @@ "remote_path": "perfetto_binaries/trace_processor_shell/linux_arm/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell" }, "mac": { - "hash": "32d9d37b055e4ac243193f6492b513fcfa386a31", - "remote_path": "perfetto_binaries/trace_processor_shell/mac/32214834f51712d2a9b80cd643aee86374270a73/trace_processor_shell" + "hash": "0b811b930b30cc65dfc06b66986db179b65e0e63", + "remote_path": "perfetto_binaries/trace_processor_shell/mac/3a5b2ad60d85cf76cc94677544bc08c6a0f42ac6/trace_processor_shell" }, "mac_arm64": { "hash": "c0397e87456ad6c6a7aa0133e5b81c97adbab4ab",
diff --git a/ui/gfx/geometry/matrix44.cc b/ui/gfx/geometry/matrix44.cc index 0c1e58b..000ce8f6 100644 --- a/ui/gfx/geometry/matrix44.cc +++ b/ui/gfx/geometry/matrix44.cc
@@ -165,74 +165,6 @@ this->setTypeMask(kIdentity_Mask); } -void Matrix44::set3x3(SkScalar m_00, - SkScalar m_10, - SkScalar m_20, - SkScalar m_01, - SkScalar m_11, - SkScalar m_21, - SkScalar m_02, - SkScalar m_12, - SkScalar m_22) { - fMat[0][0] = m_00; - fMat[0][1] = m_10; - fMat[0][2] = m_20; - fMat[0][3] = 0; - fMat[1][0] = m_01; - fMat[1][1] = m_11; - fMat[1][2] = m_21; - fMat[1][3] = 0; - fMat[2][0] = m_02; - fMat[2][1] = m_12; - fMat[2][2] = m_22; - fMat[2][3] = 0; - fMat[3][0] = 0; - fMat[3][1] = 0; - fMat[3][2] = 0; - fMat[3][3] = 1; - this->recomputeTypeMask(); -} - -void Matrix44::set3x3RowMajorf(const float src[]) { - fMat[0][0] = src[0]; - fMat[0][1] = src[3]; - fMat[0][2] = src[6]; - fMat[0][3] = 0; - fMat[1][0] = src[1]; - fMat[1][1] = src[4]; - fMat[1][2] = src[7]; - fMat[1][3] = 0; - fMat[2][0] = src[2]; - fMat[2][1] = src[5]; - fMat[2][2] = src[8]; - fMat[2][3] = 0; - fMat[3][0] = 0; - fMat[3][1] = 0; - fMat[3][2] = 0; - fMat[3][3] = 1; - this->recomputeTypeMask(); -} - -void Matrix44::set3x4RowMajorf(const float src[]) { - fMat[0][0] = src[0]; - fMat[1][0] = src[1]; - fMat[2][0] = src[2]; - fMat[3][0] = src[3]; - fMat[0][1] = src[4]; - fMat[1][1] = src[5]; - fMat[2][1] = src[6]; - fMat[3][1] = src[7]; - fMat[0][2] = src[8]; - fMat[1][2] = src[9]; - fMat[2][2] = src[10]; - fMat[3][2] = src[11]; - fMat[0][3] = 0; - fMat[1][3] = 0; - fMat[2][3] = 0; - fMat[3][3] = 1; - this->recomputeTypeMask(); -} - /////////////////////////////////////////////////////////////////////////////// Matrix44& Matrix44::setTranslate(SkScalar dx, SkScalar dy, SkScalar dz) { @@ -331,31 +263,14 @@ /////////////////////////////////////////////////////////////////////////////// -void Matrix44::setRotateAbout(SkScalar x, - SkScalar y, - SkScalar z, - SkScalar radians) { - double len2 = static_cast<double>(x) * x + static_cast<double>(y) * y + - static_cast<double>(z) * z; - if (1 != len2) { - if (0 == len2) { - this->setIdentity(); - return; - } - double scale = 1 / sqrt(len2); - x = SkScalar(x * scale); - y = SkScalar(y * scale); - z = SkScalar(z * scale); - } - this->setRotateAboutUnit(x, y, z, radians); -} - -void Matrix44::setRotateAboutUnit(SkScalar x, - SkScalar y, - SkScalar z, - SkScalar radians) { - double c = cos(radians); - double s = sin(radians); +void Matrix44::setRotateUnitSinCos(SkScalar x, + SkScalar y, + SkScalar z, + SkScalar sin_angle, + SkScalar cos_angle) { + // Use double precision for intermediate results. + double c = cos_angle; + double s = sin_angle; double C = 1 - c; double xs = x * s; double ys = y * s; @@ -367,18 +282,90 @@ double yzC = y * zC; double zxC = z * xC; - // if you're looking at wikipedia, remember that we're column major. - this->set3x3(SkScalar(x * xC + c), // scale x - SkScalar(xyC + zs), // skew x - SkScalar(zxC - ys), // trans x + fMat[0][0] = SkDoubleToScalar(x * xC + c); + fMat[0][1] = SkDoubleToScalar(xyC + zs); + fMat[0][2] = SkDoubleToScalar(zxC - ys); + fMat[0][3] = SkDoubleToScalar(0); + fMat[1][0] = SkDoubleToScalar(xyC - zs); + fMat[1][1] = SkDoubleToScalar(y * yC + c); + fMat[1][2] = SkDoubleToScalar(yzC + xs); + fMat[1][3] = SkDoubleToScalar(0); + fMat[2][0] = SkDoubleToScalar(zxC + ys); + fMat[2][1] = SkDoubleToScalar(yzC - xs); + fMat[2][2] = SkDoubleToScalar(z * zC + c); + fMat[2][3] = SkDoubleToScalar(0); + fMat[3][0] = SkDoubleToScalar(0); + fMat[3][1] = SkDoubleToScalar(0); + fMat[3][2] = SkDoubleToScalar(0); + fMat[3][3] = SkDoubleToScalar(1); - SkScalar(xyC - zs), // skew y - SkScalar(y * yC + c), // scale y - SkScalar(yzC + xs), // trans y + this->recomputeTypeMask(); +} - SkScalar(zxC + ys), // persp x - SkScalar(yzC - xs), // persp y - SkScalar(z * zC + c)); // persp 2 +void Matrix44::setRotateAboutXAxisSinCos(SkScalar sin_angle, + SkScalar cos_angle) { + fMat[0][0] = 1; + fMat[0][1] = 0; + fMat[0][2] = 0; + fMat[0][3] = 0; + fMat[1][0] = 0; + fMat[1][1] = cos_angle; + fMat[1][2] = sin_angle; + fMat[1][3] = 0; + fMat[2][0] = 0; + fMat[2][1] = -sin_angle; + fMat[2][2] = cos_angle; + fMat[2][3] = 0; + fMat[3][0] = 0; + fMat[3][1] = 0; + fMat[3][2] = 0; + fMat[3][3] = 1; + + this->recomputeTypeMask(); +} + +void Matrix44::setRotateAboutYAxisSinCos(SkScalar sin_angle, + SkScalar cos_angle) { + fMat[0][0] = cos_angle; + fMat[0][1] = 0; + fMat[0][2] = -sin_angle; + fMat[0][3] = 0; + fMat[1][0] = 0; + fMat[1][1] = 1; + fMat[1][2] = 0; + fMat[1][3] = 0; + fMat[2][0] = sin_angle; + fMat[2][1] = 0; + fMat[2][2] = cos_angle; + fMat[2][3] = 0; + fMat[3][0] = 0; + fMat[3][1] = 0; + fMat[3][2] = 0; + fMat[3][3] = 1; + + this->recomputeTypeMask(); +} + +void Matrix44::setRotateAboutZAxisSinCos(SkScalar sin_angle, + SkScalar cos_angle) { + fMat[0][0] = cos_angle; + fMat[0][1] = sin_angle; + fMat[0][2] = 0; + fMat[0][3] = 0; + fMat[1][0] = -sin_angle; + fMat[1][1] = cos_angle; + fMat[1][2] = 0; + fMat[1][3] = 0; + fMat[2][0] = 0; + fMat[2][1] = 0; + fMat[2][2] = 1; + fMat[2][3] = 0; + fMat[3][0] = 0; + fMat[3][1] = 0; + fMat[3][2] = 0; + fMat[3][3] = 1; + + this->recomputeTypeMask(); } ///////////////////////////////////////////////////////////////////////////////
diff --git a/ui/gfx/geometry/matrix44.h b/ui/gfx/geometry/matrix44.h index bed499d..223031d 100644 --- a/ui/gfx/geometry/matrix44.h +++ b/ui/gfx/geometry/matrix44.h
@@ -54,13 +54,6 @@ // This is the underlying data structure of Transform. Don't use this type // directly. The public methods can be called through Transform::matrix(). -// -// This class was originally SkMatrix44, then moved into Chromium as -// skia::Matrix44, then moved here. For now this class mostly follows the -// Skia coding style, especially the naming convention. This is to make the -// API of this class similar to SkM44 to ease experiment with different -// underlying matrix data structure of Transform. -// class GEOMETRY_SKIA_EXPORT Matrix44 { public: enum Uninitialized_Constructor { kUninitialized_Constructor }; @@ -109,7 +102,7 @@ * [ g h i ] [ 0 0 1 0 ] * [ g h 0 i ] */ - explicit Matrix44(const SkMatrix& sk_matrix); + explicit Matrix44(const SkMatrix&); // Inverse conversion of the above. SkMatrix asM33() const; @@ -216,20 +209,6 @@ CATransform3D ToCATransform3D() const; #endif - /* This sets the top-left of the matrix and clears the translation and - * perspective components (with [3][3] set to 1). m_ij is interpreted - * as the matrix entry at row = i, col = j. */ - void set3x3(SkScalar m_00, - SkScalar m_10, - SkScalar m_20, - SkScalar m_01, - SkScalar m_11, - SkScalar m_21, - SkScalar m_02, - SkScalar m_12, - SkScalar m_22); - void set3x3RowMajorf(const float[]); - Matrix44& setTranslate(SkScalar dx, SkScalar dy, SkScalar dz); Matrix44& preTranslate(SkScalar dx, SkScalar dy, SkScalar dz); Matrix44& postTranslate(SkScalar dx, SkScalar dy, SkScalar dz); @@ -248,21 +227,20 @@ return this->postScale(scale, scale, scale); } - void setRotateDegreesAbout(SkScalar x, - SkScalar y, - SkScalar z, - SkScalar degrees) { - this->setRotateAbout(x, y, z, degrees * SK_ScalarPI / 180); - } + // Sets this matrix to rotate about the specified unit-length axis vector, + // by an angle specified by its sin() and cos(). This does not attempt to + // verify that axis(x, y, z).length() == 1 or that the sin, cos values are + // correct. + void setRotateUnitSinCos(SkScalar x, + SkScalar y, + SkScalar z, + SkScalar sin_angle, + SkScalar cos_angle); - /** Rotate about the vector [x,y,z]. If that vector is not unit-length, - it will be automatically resized. - */ - void setRotateAbout(SkScalar x, SkScalar y, SkScalar z, SkScalar radians); - /** Rotate about the vector [x,y,z]. Does not check the length of the - vector, assuming it is unit-length. - */ - void setRotateAboutUnit(SkScalar x, SkScalar y, SkScalar z, SkScalar radians); + // Special case for x, y or z axis of the above function. + void setRotateAboutXAxisSinCos(SkScalar sin_angle, SkScalar cos_angle); + void setRotateAboutYAxisSinCos(SkScalar sin_angle, SkScalar cos_angle); + void setRotateAboutZAxisSinCos(SkScalar sin_angle, SkScalar cos_angle); void setConcat(const Matrix44& a, const Matrix44& b); inline void preConcat(const Matrix44& m) { this->setConcat(*this, m); } @@ -327,9 +305,6 @@ static constexpr int kAllPublic_Masks = 0xF; - void as3x4RowMajorf(float[]) const; - void set3x4RowMajorf(const float[]); - SkScalar transX() const { return fMat[3][0]; } SkScalar transY() const { return fMat[3][1]; } SkScalar transZ() const { return fMat[3][2]; }
diff --git a/ui/gfx/geometry/transform.cc b/ui/gfx/geometry/transform.cc index c089721..2b6442e 100644 --- a/ui/gfx/geometry/transform.cc +++ b/ui/gfx/geometry/transform.cc
@@ -79,74 +79,90 @@ matrix_.setRowMajor(data); } +// clang-format off Transform::Transform(const Quaternion& q) - : matrix_(Matrix44::kUninitialized_Constructor) { - double x = q.x(); - double y = q.y(); - double z = q.z(); - double w = q.w(); - - // Implicitly calls matrix.setIdentity() - matrix_.set3x3(SkDoubleToScalar(1.0 - 2.0 * (y * y + z * z)), - SkDoubleToScalar(2.0 * (x * y + z * w)), - SkDoubleToScalar(2.0 * (x * z - y * w)), - SkDoubleToScalar(2.0 * (x * y - z * w)), - SkDoubleToScalar(1.0 - 2.0 * (x * x + z * z)), - SkDoubleToScalar(2.0 * (y * z + x * w)), - SkDoubleToScalar(2.0 * (x * z + y * w)), - SkDoubleToScalar(2.0 * (y * z - x * w)), - SkDoubleToScalar(1.0 - 2.0 * (x * x + y * y))); -} + : matrix_( + // Row 1. + SkDoubleToScalar(1.0 - 2.0 * (q.y() * q.y() + q.z() * q.z())), + SkDoubleToScalar(2.0 * (q.x() * q.y() - q.z() * q.w())), + SkDoubleToScalar(2.0 * (q.x() * q.z() + q.y() * q.w())), + 0, + // Row 2. + SkDoubleToScalar(2.0 * (q.x() * q.y() + q.z() * q.w())), + SkDoubleToScalar(1.0 - 2.0 * (q.x() * q.x() + q.z() * q.z())), + SkDoubleToScalar(2.0 * (q.y() * q.z() - q.x() * q.w())), + 0, + // Row 3. + SkDoubleToScalar(2.0 * (q.x() * q.z() - q.y() * q.w())), + SkDoubleToScalar(2.0 * (q.y() * q.z() + q.x() * q.w())), + SkDoubleToScalar(1.0 - 2.0 * (q.x() * q.x() + q.y() * q.y())), + 0, + // row 4. + 0, 0, 0, 1) {} +// clang-format on void Transform::RotateAboutXAxis(double degrees) { double radians = gfx::DegToRad(degrees); - SkScalar cosTheta = SkDoubleToScalar(std::cos(radians)); - SkScalar sinTheta = SkDoubleToScalar(std::sin(radians)); + SkScalar sin_theta = SkDoubleToScalar(std::sin(radians)); + SkScalar cos_theta = SkDoubleToScalar(std::cos(radians)); if (matrix_.isIdentity()) { - matrix_.set3x3(1, 0, 0, 0, cosTheta, sinTheta, 0, -sinTheta, cosTheta); + matrix_.setRotateAboutXAxisSinCos(sin_theta, cos_theta); } else { Matrix44 rot(Matrix44::kUninitialized_Constructor); - rot.set3x3(1, 0, 0, 0, cosTheta, sinTheta, 0, -sinTheta, cosTheta); + rot.setRotateAboutXAxisSinCos(sin_theta, cos_theta); matrix_.preConcat(rot); } } void Transform::RotateAboutYAxis(double degrees) { double radians = gfx::DegToRad(degrees); - SkScalar cosTheta = SkDoubleToScalar(std::cos(radians)); - SkScalar sinTheta = SkDoubleToScalar(std::sin(radians)); + SkScalar sin_theta = SkDoubleToScalar(std::sin(radians)); + SkScalar cos_theta = SkDoubleToScalar(std::cos(radians)); if (matrix_.isIdentity()) { - // Note carefully the placement of the -sinTheta for rotation about - // y-axis is different than rotation about x-axis or z-axis. - matrix_.set3x3(cosTheta, 0, -sinTheta, 0, 1, 0, sinTheta, 0, cosTheta); + matrix_.setRotateAboutYAxisSinCos(sin_theta, cos_theta); } else { Matrix44 rot(Matrix44::kUninitialized_Constructor); - rot.set3x3(cosTheta, 0, -sinTheta, 0, 1, 0, sinTheta, 0, cosTheta); + rot.setRotateAboutYAxisSinCos(sin_theta, cos_theta); matrix_.preConcat(rot); } } void Transform::RotateAboutZAxis(double degrees) { double radians = gfx::DegToRad(degrees); - SkScalar cosTheta = SkDoubleToScalar(std::cos(radians)); - SkScalar sinTheta = SkDoubleToScalar(std::sin(radians)); + SkScalar sin_theta = SkDoubleToScalar(std::sin(radians)); + SkScalar cos_theta = SkDoubleToScalar(std::cos(radians)); if (matrix_.isIdentity()) { - matrix_.set3x3(cosTheta, sinTheta, 0, -sinTheta, cosTheta, 0, 0, 0, 1); + matrix_.setRotateAboutZAxisSinCos(sin_theta, cos_theta); } else { Matrix44 rot(Matrix44::kUninitialized_Constructor); - rot.set3x3(cosTheta, sinTheta, 0, -sinTheta, cosTheta, 0, 0, 0, 1); + rot.setRotateAboutZAxisSinCos(sin_theta, cos_theta); matrix_.preConcat(rot); } } void Transform::RotateAbout(const Vector3dF& axis, double degrees) { + double x = axis.x(); + double y = axis.y(); + double z = axis.z(); + double square_length = x * x + y * y + z * z; + if (square_length == 0) + return; + if (square_length != 1) { + double scale = 1 / sqrt(square_length); + x *= scale; + y *= scale; + z *= scale; + } + double radians = gfx::DegToRad(degrees); + SkScalar sin_theta = SkDoubleToScalar(std::sin(radians)); + SkScalar cos_theta = SkDoubleToScalar(std::cos(radians)); if (matrix_.isIdentity()) { - matrix_.setRotateDegreesAbout(axis.x(), axis.y(), axis.z(), - SkDoubleToScalar(degrees)); + matrix_.setRotateUnitSinCos(SkDoubleToScalar(x), SkDoubleToScalar(y), + SkDoubleToScalar(z), sin_theta, cos_theta); } else { Matrix44 rot(Matrix44::kUninitialized_Constructor); - rot.setRotateDegreesAbout(axis.x(), axis.y(), axis.z(), - SkDoubleToScalar(degrees)); + rot.setRotateUnitSinCos(SkDoubleToScalar(x), SkDoubleToScalar(y), + SkDoubleToScalar(z), sin_theta, cos_theta); matrix_.preConcat(rot); } }
diff --git a/ui/gfx/geometry/transform_unittest.cc b/ui/gfx/geometry/transform_unittest.cc index 0ca9c0c..eef7a30 100644 --- a/ui/gfx/geometry/transform_unittest.cc +++ b/ui/gfx/geometry/transform_unittest.cc
@@ -169,8 +169,8 @@ #define LOOSE_ERROR_THRESHOLD 1e-7 TEST(XFormTest, Equality) { - Transform lhs, rhs, interpolated; - rhs.matrix().set3x3(1, 2, 3, 4, 5, 6, 7, 8, 9); + Transform lhs, interpolated; + Transform rhs(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); interpolated = lhs; for (int i = 0; i <= 100; ++i) { for (int row = 0; row < 4; ++row) { @@ -1397,6 +1397,14 @@ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, transform); } +TEST(XFormTest, FromQuaternion) { + Transform t(Quaternion(1, 2, 3, 4)); + EXPECT_ROW1_EQ(-25.f, -20.f, 22.f, 0.f, t); + EXPECT_ROW2_EQ(28.f, -19.f, 4.f, 0.f, t); + EXPECT_ROW3_EQ(-10.f, 20.f, -9.f, 0.f, t); + EXPECT_ROW4_EQ(0.f, 0.f, 0.f, 1.f, t); +} + TEST(XFormTest, verifyAssignmentOperator) { Transform A; InitializeTestMatrix(&A); @@ -2663,7 +2671,7 @@ EXPECT_EQ(expected.ToString(), rrect.ToString()); Matrix44 rot(Matrix44::kUninitialized_Constructor); - rot.set3x3(0, 1, 0, -1, 0, 0, 0, 0, 1); + rot.setRotateAboutZAxisSinCos(1, 0); Transform rotation_90_Clock(rot); rrect = RRectF(gfx::RectF(0, 0, 20.f, 25.f),
diff --git a/ui/gfx/interpolated_transform.cc b/ui/gfx/interpolated_transform.cc index e92f587..81376f61 100644 --- a/ui/gfx/interpolated_transform.cc +++ b/ui/gfx/interpolated_transform.cc
@@ -41,17 +41,11 @@ // n should now be in the range [0, 3] // clang-format off if (n == 1) { - transform.matrix().set3x3( 0, 1, 0, - -1, 0, 0, - 0, 0, 1); + transform.matrix().setRotateAboutZAxisSinCos(1, 0); } else if (n == 2) { - transform.matrix().set3x3(-1, 0, 0, - 0, -1, 0, - 0, 0, 1); + transform.matrix().setRotateAboutZAxisSinCos(0, -1); } else if (n == 3) { - transform.matrix().set3x3( 0, -1, 0, - 1, 0, 0, - 0, 0, 1); + transform.matrix().setRotateAboutZAxisSinCos(-1, 0); } // clang-format on
diff --git a/ui/gfx/x/generated_protos/damage.cc b/ui/gfx/x/generated_protos/damage.cc index 81b2748..9504493 100644 --- a/ui/gfx/x/generated_protos/damage.cc +++ b/ui/gfx/x/generated_protos/damage.cc
@@ -56,7 +56,10 @@ std::string Damage::BadDamageError::ToString() const { std::stringstream ss_; ss_ << "Damage::BadDamageError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -67,6 +70,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -79,6 +85,15 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } template <>
diff --git a/ui/gfx/x/generated_protos/damage.h b/ui/gfx/x/generated_protos/damage.h index 581e8a6..ceada070 100644 --- a/ui/gfx/x/generated_protos/damage.h +++ b/ui/gfx/x/generated_protos/damage.h
@@ -92,6 +92,9 @@ struct BadDamageError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; };
diff --git a/ui/gfx/x/generated_protos/glx.cc b/ui/gfx/x/generated_protos/glx.cc index f0adfb92..dfb2fb3 100644 --- a/ui/gfx/x/generated_protos/glx.cc +++ b/ui/gfx/x/generated_protos/glx.cc
@@ -2797,6 +2797,9 @@ buf.Write(&gl_extension_string_elem); } + // pad0 + Align(&buf, 4); + // glx_extension_string DCHECK_EQ(static_cast<size_t>(glx_str_len), glx_extension_string.size()); for (auto& glx_extension_string_elem : glx_extension_string) { @@ -2956,6 +2959,9 @@ buf.Write(&gl_extension_string_elem); } + // pad0 + Align(&buf, 4); + // glx_extension_string DCHECK_EQ(static_cast<size_t>(glx_str_len), glx_extension_string.size()); for (auto& glx_extension_string_elem : glx_extension_string) {
diff --git a/ui/gfx/x/generated_protos/randr.cc b/ui/gfx/x/generated_protos/randr.cc index 1b83849a..efa0905 100644 --- a/ui/gfx/x/generated_protos/randr.cc +++ b/ui/gfx/x/generated_protos/randr.cc
@@ -56,7 +56,10 @@ std::string RandR::BadOutputError::ToString() const { std::stringstream ss_; ss_ << "RandR::BadOutputError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -67,6 +70,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -79,12 +85,24 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } std::string RandR::BadCrtcError::ToString() const { std::stringstream ss_; ss_ << "RandR::BadCrtcError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -95,6 +113,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -107,12 +128,24 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } std::string RandR::BadModeError::ToString() const { std::stringstream ss_; ss_ << "RandR::BadModeError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -123,6 +156,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -135,12 +171,24 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } std::string RandR::BadProviderError::ToString() const { std::stringstream ss_; ss_ << "RandR::BadProviderError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -151,6 +199,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -163,6 +214,15 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } template <>
diff --git a/ui/gfx/x/generated_protos/randr.h b/ui/gfx/x/generated_protos/randr.h index b79aa9a..97839536 100644 --- a/ui/gfx/x/generated_protos/randr.h +++ b/ui/gfx/x/generated_protos/randr.h
@@ -167,24 +167,36 @@ struct BadOutputError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; struct BadCrtcError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; struct BadModeError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; struct BadProviderError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; };
diff --git a/ui/gfx/x/generated_protos/read_event.cc b/ui/gfx/x/generated_protos/read_event.cc index bc58a45c..ec344cf9 100644 --- a/ui/gfx/x/generated_protos/read_event.cc +++ b/ui/gfx/x/generated_protos/read_event.cc
@@ -622,9 +622,43 @@ return; } + if (evtype == GeGenericEvent::opcode && conn->xinput().present() && + ge->extension == conn->xinput().major_opcode() && + (ge->event_type == Input::GesturePinchEvent::Begin || + ge->event_type == Input::GesturePinchEvent::Update || + ge->event_type == Input::GesturePinchEvent::End)) { + event->type_id_ = 38; + event->deleter_ = [](void* event) { + delete reinterpret_cast<Input::GesturePinchEvent*>(event); + }; + auto* event_ = new Input::GesturePinchEvent; + ReadEvent(event_, buffer); + event_->opcode = static_cast<decltype(event_->opcode)>(ge->event_type); + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + + if (evtype == GeGenericEvent::opcode && conn->xinput().present() && + ge->extension == conn->xinput().major_opcode() && + (ge->event_type == Input::GestureSwipeEvent::Begin || + ge->event_type == Input::GestureSwipeEvent::Update || + ge->event_type == Input::GestureSwipeEvent::End)) { + event->type_id_ = 39; + event->deleter_ = [](void* event) { + delete reinterpret_cast<Input::GestureSwipeEvent*>(event); + }; + auto* event_ = new Input::GestureSwipeEvent; + ReadEvent(event_, buffer); + event_->opcode = static_cast<decltype(event_->opcode)>(ge->event_type); + event->event_ = event_; + event->window_ = event_->GetWindow(); + return; + } + if (conn->xkb().present() && evtype - conn->xkb().first_event() == Xkb::NewKeyboardNotifyEvent::opcode) { - event->type_id_ = 38; + event->type_id_ = 40; event->deleter_ = [](void* event) { delete reinterpret_cast<Xkb::NewKeyboardNotifyEvent*>(event); }; @@ -637,7 +671,7 @@ if (conn->xkb().present() && evtype - conn->xkb().first_event() == Xkb::MapNotifyEvent::opcode) { - event->type_id_ = 39; + event->type_id_ = 41; event->deleter_ = [](void* event) { delete reinterpret_cast<Xkb::MapNotifyEvent*>(event); }; @@ -650,7 +684,7 @@ if (conn->xkb().present() && evtype - conn->xkb().first_event() == Xkb::StateNotifyEvent::opcode) { - event->type_id_ = 40; + event->type_id_ = 42; event->deleter_ = [](void* event) { delete reinterpret_cast<Xkb::StateNotifyEvent*>(event); }; @@ -663,7 +697,7 @@ if (conn->xkb().present() && evtype - conn->xkb().first_event() == Xkb::ControlsNotifyEvent::opcode) { - event->type_id_ = 41; + event->type_id_ = 43; event->deleter_ = [](void* event) { delete reinterpret_cast<Xkb::ControlsNotifyEvent*>(event); }; @@ -676,7 +710,7 @@ if (conn->xkb().present() && evtype - conn->xkb().first_event() == Xkb::IndicatorStateNotifyEvent::opcode) { - event->type_id_ = 42; + event->type_id_ = 44; event->deleter_ = [](void* event) { delete reinterpret_cast<Xkb::IndicatorStateNotifyEvent*>(event); }; @@ -689,7 +723,7 @@ if (conn->xkb().present() && evtype - conn->xkb().first_event() == Xkb::IndicatorMapNotifyEvent::opcode) { - event->type_id_ = 43; + event->type_id_ = 45; event->deleter_ = [](void* event) { delete reinterpret_cast<Xkb::IndicatorMapNotifyEvent*>(event); }; @@ -702,7 +736,7 @@ if (conn->xkb().present() && evtype - conn->xkb().first_event() == Xkb::NamesNotifyEvent::opcode) { - event->type_id_ = 44; + event->type_id_ = 46; event->deleter_ = [](void* event) { delete reinterpret_cast<Xkb::NamesNotifyEvent*>(event); }; @@ -715,7 +749,7 @@ if (conn->xkb().present() && evtype - conn->xkb().first_event() == Xkb::CompatMapNotifyEvent::opcode) { - event->type_id_ = 45; + event->type_id_ = 47; event->deleter_ = [](void* event) { delete reinterpret_cast<Xkb::CompatMapNotifyEvent*>(event); }; @@ -728,7 +762,7 @@ if (conn->xkb().present() && evtype - conn->xkb().first_event() == Xkb::BellNotifyEvent::opcode) { - event->type_id_ = 46; + event->type_id_ = 48; event->deleter_ = [](void* event) { delete reinterpret_cast<Xkb::BellNotifyEvent*>(event); }; @@ -741,7 +775,7 @@ if (conn->xkb().present() && evtype - conn->xkb().first_event() == Xkb::ActionMessageEvent::opcode) { - event->type_id_ = 47; + event->type_id_ = 49; event->deleter_ = [](void* event) { delete reinterpret_cast<Xkb::ActionMessageEvent*>(event); }; @@ -754,7 +788,7 @@ if (conn->xkb().present() && evtype - conn->xkb().first_event() == Xkb::AccessXNotifyEvent::opcode) { - event->type_id_ = 48; + event->type_id_ = 50; event->deleter_ = [](void* event) { delete reinterpret_cast<Xkb::AccessXNotifyEvent*>(event); }; @@ -767,7 +801,7 @@ if (conn->xkb().present() && evtype - conn->xkb().first_event() == Xkb::ExtensionDeviceNotifyEvent::opcode) { - event->type_id_ = 49; + event->type_id_ = 51; event->deleter_ = [](void* event) { delete reinterpret_cast<Xkb::ExtensionDeviceNotifyEvent*>(event); }; @@ -780,7 +814,7 @@ if (conn->xprint().present() && evtype - conn->xprint().first_event() == XPrint::NotifyEvent::opcode) { - event->type_id_ = 50; + event->type_id_ = 52; event->deleter_ = [](void* event) { delete reinterpret_cast<XPrint::NotifyEvent*>(event); }; @@ -793,7 +827,7 @@ if (conn->xprint().present() && evtype - conn->xprint().first_event() == XPrint::AttributNotifyEvent::opcode) { - event->type_id_ = 51; + event->type_id_ = 53; event->deleter_ = [](void* event) { delete reinterpret_cast<XPrint::AttributNotifyEvent*>(event); }; @@ -805,7 +839,7 @@ } if ((evtype == KeyEvent::Press || evtype == KeyEvent::Release)) { - event->type_id_ = 52; + event->type_id_ = 54; event->deleter_ = [](void* event) { delete reinterpret_cast<KeyEvent*>(event); }; @@ -818,7 +852,7 @@ } if ((evtype == ButtonEvent::Press || evtype == ButtonEvent::Release)) { - event->type_id_ = 53; + event->type_id_ = 55; event->deleter_ = [](void* event) { delete reinterpret_cast<ButtonEvent*>(event); }; @@ -831,7 +865,7 @@ } if (evtype == MotionNotifyEvent::opcode) { - event->type_id_ = 54; + event->type_id_ = 56; event->deleter_ = [](void* event) { delete reinterpret_cast<MotionNotifyEvent*>(event); }; @@ -844,7 +878,7 @@ if ((evtype == CrossingEvent::EnterNotify || evtype == CrossingEvent::LeaveNotify)) { - event->type_id_ = 55; + event->type_id_ = 57; event->deleter_ = [](void* event) { delete reinterpret_cast<CrossingEvent*>(event); }; @@ -857,7 +891,7 @@ } if ((evtype == FocusEvent::In || evtype == FocusEvent::Out)) { - event->type_id_ = 56; + event->type_id_ = 58; event->deleter_ = [](void* event) { delete reinterpret_cast<FocusEvent*>(event); }; @@ -870,7 +904,7 @@ } if (evtype == KeymapNotifyEvent::opcode) { - event->type_id_ = 57; + event->type_id_ = 59; event->deleter_ = [](void* event) { delete reinterpret_cast<KeymapNotifyEvent*>(event); }; @@ -882,7 +916,7 @@ } if (evtype == ExposeEvent::opcode) { - event->type_id_ = 58; + event->type_id_ = 60; event->deleter_ = [](void* event) { delete reinterpret_cast<ExposeEvent*>(event); }; @@ -894,7 +928,7 @@ } if (evtype == GraphicsExposureEvent::opcode) { - event->type_id_ = 59; + event->type_id_ = 61; event->deleter_ = [](void* event) { delete reinterpret_cast<GraphicsExposureEvent*>(event); }; @@ -906,7 +940,7 @@ } if (evtype == NoExposureEvent::opcode) { - event->type_id_ = 60; + event->type_id_ = 62; event->deleter_ = [](void* event) { delete reinterpret_cast<NoExposureEvent*>(event); }; @@ -918,7 +952,7 @@ } if (evtype == VisibilityNotifyEvent::opcode) { - event->type_id_ = 61; + event->type_id_ = 63; event->deleter_ = [](void* event) { delete reinterpret_cast<VisibilityNotifyEvent*>(event); }; @@ -930,7 +964,7 @@ } if (evtype == CreateNotifyEvent::opcode) { - event->type_id_ = 62; + event->type_id_ = 64; event->deleter_ = [](void* event) { delete reinterpret_cast<CreateNotifyEvent*>(event); }; @@ -942,7 +976,7 @@ } if (evtype == DestroyNotifyEvent::opcode) { - event->type_id_ = 63; + event->type_id_ = 65; event->deleter_ = [](void* event) { delete reinterpret_cast<DestroyNotifyEvent*>(event); }; @@ -954,7 +988,7 @@ } if (evtype == UnmapNotifyEvent::opcode) { - event->type_id_ = 64; + event->type_id_ = 66; event->deleter_ = [](void* event) { delete reinterpret_cast<UnmapNotifyEvent*>(event); }; @@ -966,7 +1000,7 @@ } if (evtype == MapNotifyEvent::opcode) { - event->type_id_ = 65; + event->type_id_ = 67; event->deleter_ = [](void* event) { delete reinterpret_cast<MapNotifyEvent*>(event); }; @@ -978,7 +1012,7 @@ } if (evtype == MapRequestEvent::opcode) { - event->type_id_ = 66; + event->type_id_ = 68; event->deleter_ = [](void* event) { delete reinterpret_cast<MapRequestEvent*>(event); }; @@ -990,7 +1024,7 @@ } if (evtype == ReparentNotifyEvent::opcode) { - event->type_id_ = 67; + event->type_id_ = 69; event->deleter_ = [](void* event) { delete reinterpret_cast<ReparentNotifyEvent*>(event); }; @@ -1002,7 +1036,7 @@ } if (evtype == ConfigureNotifyEvent::opcode) { - event->type_id_ = 68; + event->type_id_ = 70; event->deleter_ = [](void* event) { delete reinterpret_cast<ConfigureNotifyEvent*>(event); }; @@ -1014,7 +1048,7 @@ } if (evtype == ConfigureRequestEvent::opcode) { - event->type_id_ = 69; + event->type_id_ = 71; event->deleter_ = [](void* event) { delete reinterpret_cast<ConfigureRequestEvent*>(event); }; @@ -1026,7 +1060,7 @@ } if (evtype == GravityNotifyEvent::opcode) { - event->type_id_ = 70; + event->type_id_ = 72; event->deleter_ = [](void* event) { delete reinterpret_cast<GravityNotifyEvent*>(event); }; @@ -1038,7 +1072,7 @@ } if (evtype == ResizeRequestEvent::opcode) { - event->type_id_ = 71; + event->type_id_ = 73; event->deleter_ = [](void* event) { delete reinterpret_cast<ResizeRequestEvent*>(event); }; @@ -1050,7 +1084,7 @@ } if ((evtype == CirculateEvent::Notify || evtype == CirculateEvent::Request)) { - event->type_id_ = 72; + event->type_id_ = 74; event->deleter_ = [](void* event) { delete reinterpret_cast<CirculateEvent*>(event); }; @@ -1063,7 +1097,7 @@ } if (evtype == PropertyNotifyEvent::opcode) { - event->type_id_ = 73; + event->type_id_ = 75; event->deleter_ = [](void* event) { delete reinterpret_cast<PropertyNotifyEvent*>(event); }; @@ -1075,7 +1109,7 @@ } if (evtype == SelectionClearEvent::opcode) { - event->type_id_ = 74; + event->type_id_ = 76; event->deleter_ = [](void* event) { delete reinterpret_cast<SelectionClearEvent*>(event); }; @@ -1087,7 +1121,7 @@ } if (evtype == SelectionRequestEvent::opcode) { - event->type_id_ = 75; + event->type_id_ = 77; event->deleter_ = [](void* event) { delete reinterpret_cast<SelectionRequestEvent*>(event); }; @@ -1099,7 +1133,7 @@ } if (evtype == SelectionNotifyEvent::opcode) { - event->type_id_ = 76; + event->type_id_ = 78; event->deleter_ = [](void* event) { delete reinterpret_cast<SelectionNotifyEvent*>(event); }; @@ -1111,7 +1145,7 @@ } if (evtype == ColormapNotifyEvent::opcode) { - event->type_id_ = 77; + event->type_id_ = 79; event->deleter_ = [](void* event) { delete reinterpret_cast<ColormapNotifyEvent*>(event); }; @@ -1123,7 +1157,7 @@ } if (evtype == ClientMessageEvent::opcode) { - event->type_id_ = 78; + event->type_id_ = 80; event->deleter_ = [](void* event) { delete reinterpret_cast<ClientMessageEvent*>(event); }; @@ -1135,7 +1169,7 @@ } if (evtype == MappingNotifyEvent::opcode) { - event->type_id_ = 79; + event->type_id_ = 81; event->deleter_ = [](void* event) { delete reinterpret_cast<MappingNotifyEvent*>(event); }; @@ -1148,7 +1182,7 @@ if (conn->xv().present() && evtype - conn->xv().first_event() == Xv::VideoNotifyEvent::opcode) { - event->type_id_ = 81; + event->type_id_ = 83; event->deleter_ = [](void* event) { delete reinterpret_cast<Xv::VideoNotifyEvent*>(event); }; @@ -1161,7 +1195,7 @@ if (conn->xv().present() && evtype - conn->xv().first_event() == Xv::PortNotifyEvent::opcode) { - event->type_id_ = 82; + event->type_id_ = 84; event->deleter_ = [](void* event) { delete reinterpret_cast<Xv::PortNotifyEvent*>(event); };
diff --git a/ui/gfx/x/generated_protos/record.cc b/ui/gfx/x/generated_protos/record.cc index 356cce15..7465cae 100644 --- a/ui/gfx/x/generated_protos/record.cc +++ b/ui/gfx/x/generated_protos/record.cc
@@ -57,7 +57,9 @@ std::stringstream ss_; ss_ << "Record::BadContextError{"; ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; - ss_ << ".invalid_record = " << static_cast<uint64_t>(invalid_record); + ss_ << ".invalid_record = " << static_cast<uint64_t>(invalid_record) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -69,6 +71,8 @@ auto& sequence = (*error_).sequence; auto& invalid_record = (*error_).invalid_record; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -84,6 +88,12 @@ // invalid_record Read(&invalid_record, &buf); + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } Future<Record::QueryVersionReply> Record::QueryVersion(
diff --git a/ui/gfx/x/generated_protos/record.h b/ui/gfx/x/generated_protos/record.h index bc45d19..dccb50e 100644 --- a/ui/gfx/x/generated_protos/record.h +++ b/ui/gfx/x/generated_protos/record.h
@@ -158,6 +158,8 @@ struct BadContextError : public x11::Error { uint16_t sequence{}; uint32_t invalid_record{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; };
diff --git a/ui/gfx/x/generated_protos/render.cc b/ui/gfx/x/generated_protos/render.cc index 7784a7b..6910596 100644 --- a/ui/gfx/x/generated_protos/render.cc +++ b/ui/gfx/x/generated_protos/render.cc
@@ -56,7 +56,10 @@ std::string Render::PictFormatError::ToString() const { std::stringstream ss_; ss_ << "Render::PictFormatError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -67,6 +70,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -79,12 +85,24 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } std::string Render::PictureError::ToString() const { std::stringstream ss_; ss_ << "Render::PictureError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -95,6 +113,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -107,12 +128,24 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } std::string Render::PictOpError::ToString() const { std::stringstream ss_; ss_ << "Render::PictOpError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -123,6 +156,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -135,12 +171,24 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } std::string Render::GlyphSetError::ToString() const { std::stringstream ss_; ss_ << "Render::GlyphSetError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -151,6 +199,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -163,12 +214,24 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } std::string Render::GlyphError::ToString() const { std::stringstream ss_; ss_ << "Render::GlyphError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -179,6 +242,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -191,6 +257,15 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } Future<Render::QueryVersionReply> Render::QueryVersion(
diff --git a/ui/gfx/x/generated_protos/render.h b/ui/gfx/x/generated_protos/render.h index ea70403..cd1aba16 100644 --- a/ui/gfx/x/generated_protos/render.h +++ b/ui/gfx/x/generated_protos/render.h
@@ -197,30 +197,45 @@ struct PictFormatError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; struct PictureError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; struct PictOpError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; struct GlyphSetError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; struct GlyphError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; };
diff --git a/ui/gfx/x/generated_protos/xf86vidmode.cc b/ui/gfx/x/generated_protos/xf86vidmode.cc index 509cf8b..98fb120 100644 --- a/ui/gfx/x/generated_protos/xf86vidmode.cc +++ b/ui/gfx/x/generated_protos/xf86vidmode.cc
@@ -57,7 +57,10 @@ std::string XF86VidMode::BadClockError::ToString() const { std::stringstream ss_; ss_ << "XF86VidMode::BadClockError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -68,6 +71,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -80,12 +86,24 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } std::string XF86VidMode::BadHTimingsError::ToString() const { std::stringstream ss_; ss_ << "XF86VidMode::BadHTimingsError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -97,6 +115,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -109,12 +130,24 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } std::string XF86VidMode::BadVTimingsError::ToString() const { std::stringstream ss_; ss_ << "XF86VidMode::BadVTimingsError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -126,6 +159,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -138,12 +174,24 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } std::string XF86VidMode::ModeUnsuitableError::ToString() const { std::stringstream ss_; ss_ << "XF86VidMode::ModeUnsuitableError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -155,6 +203,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -167,12 +218,24 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } std::string XF86VidMode::ExtensionDisabledError::ToString() const { std::stringstream ss_; ss_ << "XF86VidMode::ExtensionDisabledError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -184,6 +247,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -196,12 +262,24 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } std::string XF86VidMode::ClientNotLocalError::ToString() const { std::stringstream ss_; ss_ << "XF86VidMode::ClientNotLocalError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -213,6 +291,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -225,12 +306,24 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } std::string XF86VidMode::ZoomLockedError::ToString() const { std::stringstream ss_; ss_ << "XF86VidMode::ZoomLockedError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -242,6 +335,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -254,6 +350,15 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } Future<XF86VidMode::QueryVersionReply> XF86VidMode::QueryVersion(
diff --git a/ui/gfx/x/generated_protos/xf86vidmode.h b/ui/gfx/x/generated_protos/xf86vidmode.h index f49b96c..b85bfc4 100644 --- a/ui/gfx/x/generated_protos/xf86vidmode.h +++ b/ui/gfx/x/generated_protos/xf86vidmode.h
@@ -135,42 +135,63 @@ struct BadClockError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; struct BadHTimingsError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; struct BadVTimingsError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; struct ModeUnsuitableError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; struct ExtensionDisabledError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; struct ClientNotLocalError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; struct ZoomLockedError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; };
diff --git a/ui/gfx/x/generated_protos/xfixes.cc b/ui/gfx/x/generated_protos/xfixes.cc index e3b44e6..f0ec6022 100644 --- a/ui/gfx/x/generated_protos/xfixes.cc +++ b/ui/gfx/x/generated_protos/xfixes.cc
@@ -147,7 +147,10 @@ std::string XFixes::BadRegionError::ToString() const { std::stringstream ss_; ss_ << "XFixes::BadRegionError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -158,6 +161,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -170,6 +176,15 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } Future<XFixes::QueryVersionReply> XFixes::QueryVersion( @@ -1985,4 +2000,110 @@ XFixes::DeletePointerBarrierRequest{barrier}); } +Future<void> XFixes::SetClientDisconnectMode( + const XFixes::SetClientDisconnectModeRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + auto& disconnect_mode = request.disconnect_mode; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 33; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + // disconnect_mode + uint32_t tmp10; + tmp10 = static_cast<uint32_t>(disconnect_mode); + buf.Write(&tmp10); + + Align(&buf, 4); + + return connection_->SendRequest<void>(&buf, "XFixes::SetClientDisconnectMode", + false); +} + +Future<void> XFixes::SetClientDisconnectMode( + const ClientDisconnectFlags& disconnect_mode) { + return XFixes::SetClientDisconnectMode( + XFixes::SetClientDisconnectModeRequest{disconnect_mode}); +} + +Future<XFixes::GetClientDisconnectModeReply> XFixes::GetClientDisconnectMode( + const XFixes::GetClientDisconnectModeRequest& request) { + if (!connection_->Ready() || !present()) + return {}; + + WriteBuffer buf; + + // major_opcode + uint8_t major_opcode = info_.major_opcode; + buf.Write(&major_opcode); + + // minor_opcode + uint8_t minor_opcode = 34; + buf.Write(&minor_opcode); + + // length + // Caller fills in length for writes. + Pad(&buf, sizeof(uint16_t)); + + Align(&buf, 4); + + return connection_->SendRequest<XFixes::GetClientDisconnectModeReply>( + &buf, "XFixes::GetClientDisconnectMode", false); +} + +Future<XFixes::GetClientDisconnectModeReply> XFixes::GetClientDisconnectMode() { + return XFixes::GetClientDisconnectMode( + XFixes::GetClientDisconnectModeRequest{}); +} + +template <> +COMPONENT_EXPORT(X11) +std::unique_ptr<XFixes::GetClientDisconnectModeReply> detail::ReadReply< + XFixes::GetClientDisconnectModeReply>(ReadBuffer* buffer) { + auto& buf = *buffer; + auto reply = std::make_unique<XFixes::GetClientDisconnectModeReply>(); + + auto& sequence = (*reply).sequence; + auto& disconnect_mode = (*reply).disconnect_mode; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // pad0 + Pad(&buf, 1); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // disconnect_mode + uint32_t tmp11; + Read(&tmp11, &buf); + disconnect_mode = static_cast<XFixes::ClientDisconnectFlags>(tmp11); + + // pad1 + Pad(&buf, 20); + + Align(&buf, 4); + DCHECK_EQ(buf.offset < 32 ? 0 : buf.offset - 32, 4 * length); + + return reply; +} + } // namespace x11
diff --git a/ui/gfx/x/generated_protos/xfixes.h b/ui/gfx/x/generated_protos/xfixes.h index 75385d0..b0aa4b9 100644 --- a/ui/gfx/x/generated_protos/xfixes.h +++ b/ui/gfx/x/generated_protos/xfixes.h
@@ -70,7 +70,7 @@ class COMPONENT_EXPORT(X11) XFixes { public: - static constexpr unsigned major_version = 5; + static constexpr unsigned major_version = 6; static constexpr unsigned minor_version = 0; XFixes(Connection* connection, const x11::QueryExtensionReply& info); @@ -130,6 +130,11 @@ NegativeY = 1 << 3, }; + enum class ClientDisconnectFlags : int { + Default = 0, + Terminate = 1 << 0, + }; + struct SelectionNotifyEvent { static constexpr int type_id = 18; static constexpr uint8_t opcode = 0; @@ -159,6 +164,9 @@ struct BadRegionError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; @@ -641,6 +649,33 @@ Future<void> DeletePointerBarrier(const Barrier& barrier = {}); + struct SetClientDisconnectModeRequest { + ClientDisconnectFlags disconnect_mode{}; + }; + + using SetClientDisconnectModeResponse = Response<void>; + + Future<void> SetClientDisconnectMode( + const SetClientDisconnectModeRequest& request); + + Future<void> SetClientDisconnectMode( + const ClientDisconnectFlags& disconnect_mode = {}); + + struct GetClientDisconnectModeRequest {}; + + struct GetClientDisconnectModeReply { + uint16_t sequence{}; + ClientDisconnectFlags disconnect_mode{}; + }; + + using GetClientDisconnectModeResponse = + Response<GetClientDisconnectModeReply>; + + Future<GetClientDisconnectModeReply> GetClientDisconnectMode( + const GetClientDisconnectModeRequest& request); + + Future<GetClientDisconnectModeReply> GetClientDisconnectMode(); + private: Connection* const connection_; x11::QueryExtensionReply info_{}; @@ -790,4 +825,20 @@ static_cast<T>(r)); } +inline constexpr x11::XFixes::ClientDisconnectFlags operator|( + x11::XFixes::ClientDisconnectFlags l, + x11::XFixes::ClientDisconnectFlags r) { + using T = std::underlying_type_t<x11::XFixes::ClientDisconnectFlags>; + return static_cast<x11::XFixes::ClientDisconnectFlags>(static_cast<T>(l) | + static_cast<T>(r)); +} + +inline constexpr x11::XFixes::ClientDisconnectFlags operator&( + x11::XFixes::ClientDisconnectFlags l, + x11::XFixes::ClientDisconnectFlags r) { + using T = std::underlying_type_t<x11::XFixes::ClientDisconnectFlags>; + return static_cast<x11::XFixes::ClientDisconnectFlags>(static_cast<T>(l) & + static_cast<T>(r)); +} + #endif // UI_GFX_X_GENERATED_PROTOS_XFIXES_H_
diff --git a/ui/gfx/x/generated_protos/xinput.cc b/ui/gfx/x/generated_protos/xinput.cc index a89a198..2c4abd6 100644 --- a/ui/gfx/x/generated_protos/xinput.cc +++ b/ui/gfx/x/generated_protos/xinput.cc
@@ -734,6 +734,16 @@ // num_touches Read(&num_touches, &buf); } + if (CaseEq(data_expr, Input::DeviceClassType::Gesture)) { + data.gesture.emplace(); + auto& num_touches = (*data.gesture).num_touches; + + // num_touches + Read(&num_touches, &buf); + + // pad2 + Pad(&buf, 1); + } } } @@ -1485,10 +1495,305 @@ DCHECK_EQ(buf.offset, 32 + 4 * length); } +template <> +COMPONENT_EXPORT(X11) +void ReadEvent<Input::GesturePinchEvent>(Input::GesturePinchEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*event_).sequence; + auto& deviceid = (*event_).deviceid; + auto& time = (*event_).time; + auto& detail = (*event_).detail; + auto& root = (*event_).root; + auto& event = (*event_).event; + auto& child = (*event_).child; + auto& root_x = (*event_).root_x; + auto& root_y = (*event_).root_y; + auto& event_x = (*event_).event_x; + auto& event_y = (*event_).event_y; + auto& delta_x = (*event_).delta_x; + auto& delta_y = (*event_).delta_y; + auto& delta_unaccel_x = (*event_).delta_unaccel_x; + auto& delta_unaccel_y = (*event_).delta_unaccel_y; + auto& scale = (*event_).scale; + auto& delta_angle = (*event_).delta_angle; + auto& sourceid = (*event_).sourceid; + auto& mods = (*event_).mods; + auto& group = (*event_).group; + auto& flags = (*event_).flags; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // extension + uint8_t extension; + Read(&extension, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // event_type + uint16_t event_type; + Read(&event_type, &buf); + + // deviceid + Read(&deviceid, &buf); + + // time + Read(&time, &buf); + + // detail + Read(&detail, &buf); + + // root + Read(&root, &buf); + + // event + Read(&event, &buf); + + // child + Read(&child, &buf); + + // root_x + Read(&root_x, &buf); + + // root_y + Read(&root_y, &buf); + + // event_x + Read(&event_x, &buf); + + // event_y + Read(&event_y, &buf); + + // delta_x + Read(&delta_x, &buf); + + // delta_y + Read(&delta_y, &buf); + + // delta_unaccel_x + Read(&delta_unaccel_x, &buf); + + // delta_unaccel_y + Read(&delta_unaccel_y, &buf); + + // scale + Read(&scale, &buf); + + // delta_angle + Read(&delta_angle, &buf); + + // sourceid + Read(&sourceid, &buf); + + // pad0 + Pad(&buf, 2); + + // mods + { + auto& base = mods.base; + auto& latched = mods.latched; + auto& locked = mods.locked; + auto& effective = mods.effective; + + // base + Read(&base, &buf); + + // latched + Read(&latched, &buf); + + // locked + Read(&locked, &buf); + + // effective + Read(&effective, &buf); + } + + // group + { + auto& base = group.base; + auto& latched = group.latched; + auto& locked = group.locked; + auto& effective = group.effective; + + // base + Read(&base, &buf); + + // latched + Read(&latched, &buf); + + // locked + Read(&locked, &buf); + + // effective + Read(&effective, &buf); + } + + // flags + uint32_t tmp27; + Read(&tmp27, &buf); + flags = static_cast<Input::GesturePinchEventFlags>(tmp27); + + Align(&buf, 4); + DCHECK_EQ(buf.offset, 32 + 4 * length); +} + +template <> +COMPONENT_EXPORT(X11) +void ReadEvent<Input::GestureSwipeEvent>(Input::GestureSwipeEvent* event_, + ReadBuffer* buffer) { + auto& buf = *buffer; + + auto& sequence = (*event_).sequence; + auto& deviceid = (*event_).deviceid; + auto& time = (*event_).time; + auto& detail = (*event_).detail; + auto& root = (*event_).root; + auto& event = (*event_).event; + auto& child = (*event_).child; + auto& root_x = (*event_).root_x; + auto& root_y = (*event_).root_y; + auto& event_x = (*event_).event_x; + auto& event_y = (*event_).event_y; + auto& delta_x = (*event_).delta_x; + auto& delta_y = (*event_).delta_y; + auto& delta_unaccel_x = (*event_).delta_unaccel_x; + auto& delta_unaccel_y = (*event_).delta_unaccel_y; + auto& sourceid = (*event_).sourceid; + auto& mods = (*event_).mods; + auto& group = (*event_).group; + auto& flags = (*event_).flags; + + // response_type + uint8_t response_type; + Read(&response_type, &buf); + + // extension + uint8_t extension; + Read(&extension, &buf); + + // sequence + Read(&sequence, &buf); + + // length + uint32_t length; + Read(&length, &buf); + + // event_type + uint16_t event_type; + Read(&event_type, &buf); + + // deviceid + Read(&deviceid, &buf); + + // time + Read(&time, &buf); + + // detail + Read(&detail, &buf); + + // root + Read(&root, &buf); + + // event + Read(&event, &buf); + + // child + Read(&child, &buf); + + // root_x + Read(&root_x, &buf); + + // root_y + Read(&root_y, &buf); + + // event_x + Read(&event_x, &buf); + + // event_y + Read(&event_y, &buf); + + // delta_x + Read(&delta_x, &buf); + + // delta_y + Read(&delta_y, &buf); + + // delta_unaccel_x + Read(&delta_unaccel_x, &buf); + + // delta_unaccel_y + Read(&delta_unaccel_y, &buf); + + // sourceid + Read(&sourceid, &buf); + + // pad0 + Pad(&buf, 2); + + // mods + { + auto& base = mods.base; + auto& latched = mods.latched; + auto& locked = mods.locked; + auto& effective = mods.effective; + + // base + Read(&base, &buf); + + // latched + Read(&latched, &buf); + + // locked + Read(&locked, &buf); + + // effective + Read(&effective, &buf); + } + + // group + { + auto& base = group.base; + auto& latched = group.latched; + auto& locked = group.locked; + auto& effective = group.effective; + + // base + Read(&base, &buf); + + // latched + Read(&latched, &buf); + + // locked + Read(&locked, &buf); + + // effective + Read(&effective, &buf); + } + + // flags + uint32_t tmp28; + Read(&tmp28, &buf); + flags = static_cast<Input::GestureSwipeEventFlags>(tmp28); + + Align(&buf, 4); + DCHECK_EQ(buf.offset, 32 + 4 * length); +} + std::string Input::DeviceError::ToString() const { std::stringstream ss_; ss_ << "Input::DeviceError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -1499,6 +1804,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -1511,12 +1819,24 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } std::string Input::EventError::ToString() const { std::stringstream ss_; ss_ << "Input::EventError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -1527,6 +1847,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -1539,12 +1862,24 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } std::string Input::ModeError::ToString() const { std::stringstream ss_; ss_ << "Input::ModeError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -1554,6 +1889,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -1566,12 +1904,24 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } std::string Input::DeviceBusyError::ToString() const { std::stringstream ss_; ss_ << "Input::DeviceBusyError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -1582,6 +1932,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -1594,12 +1947,24 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } std::string Input::ClassError::ToString() const { std::stringstream ss_; ss_ << "Input::ClassError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -1610,6 +1975,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -1622,6 +1990,15 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } Future<Input::GetExtensionVersionReply> Input::GetExtensionVersion( @@ -1801,9 +2178,9 @@ Read(&num_class_info, &buf); // device_use - uint8_t tmp27; - Read(&tmp27, &buf); - device_use = static_cast<Input::DeviceUse>(tmp27); + uint8_t tmp29; + Read(&tmp29, &buf); + device_use = static_cast<Input::DeviceUse>(tmp29); // pad0 Pad(&buf, 1); @@ -1811,7 +2188,7 @@ } // infos - auto sum28_ = SumOf( + auto sum30_ = SumOf( [](auto& listelem_ref) { auto& device_type = listelem_ref.device_type; auto& device_id = listelem_ref.device_id; @@ -1821,7 +2198,7 @@ return num_class_info; }, devices); - infos.resize(sum28_); + infos.resize(sum30_); for (auto& infos_elem : infos) { // infos_elem { @@ -1830,9 +2207,9 @@ auto& info = infos_elem; // class_id - uint8_t tmp29; - Read(&tmp29, &buf); - class_id = static_cast<Input::InputClass>(tmp29); + uint8_t tmp31; + Read(&tmp31, &buf); + class_id = static_cast<Input::InputClass>(tmp31); // len Read(&len, &buf); @@ -1875,9 +2252,9 @@ Read(&axes_len, &buf); // mode - uint8_t tmp30; - Read(&tmp30, &buf); - mode = static_cast<Input::ValuatorMode>(tmp30); + uint8_t tmp32; + Read(&tmp32, &buf); + mode = static_cast<Input::ValuatorMode>(tmp32); // motion_size Read(&motion_size, &buf); @@ -2013,9 +2390,9 @@ auto& event_type_base = class_info_elem.event_type_base; // class_id - uint8_t tmp31; - Read(&tmp31, &buf); - class_id = static_cast<Input::InputClass>(tmp31); + uint8_t tmp33; + Read(&tmp33, &buf); + class_id = static_cast<Input::InputClass>(tmp33); // event_type_base Read(&event_type_base, &buf); @@ -2092,9 +2469,9 @@ buf.Write(&device_id); // mode - uint8_t tmp32; - tmp32 = static_cast<uint8_t>(mode); - buf.Write(&tmp32); + uint8_t tmp34; + tmp34 = static_cast<uint8_t>(mode); + buf.Write(&tmp34); // pad0 Pad(&buf, 2); @@ -2137,9 +2514,9 @@ Read(&length, &buf); // status - uint8_t tmp33; - Read(&tmp33, &buf); - status = static_cast<GrabStatus>(tmp33); + uint8_t tmp35; + Read(&tmp35, &buf); + status = static_cast<GrabStatus>(tmp35); // pad0 Pad(&buf, 23); @@ -2333,9 +2710,9 @@ buf.Write(&num_classes); // mode - uint8_t tmp34; - tmp34 = static_cast<uint8_t>(mode); - buf.Write(&tmp34); + uint8_t tmp36; + tmp36 = static_cast<uint8_t>(mode); + buf.Write(&tmp36); // pad0 Pad(&buf, 1); @@ -2529,9 +2906,9 @@ Read(&num_axes, &buf); // device_mode - uint8_t tmp35; - Read(&tmp35, &buf); - device_mode = static_cast<Input::ValuatorMode>(tmp35); + uint8_t tmp37; + Read(&tmp37, &buf); + device_mode = static_cast<Input::ValuatorMode>(tmp37); // pad0 Pad(&buf, 18); @@ -2628,9 +3005,9 @@ Read(&length, &buf); // status - uint8_t tmp36; - Read(&tmp36, &buf); - status = static_cast<GrabStatus>(tmp36); + uint8_t tmp38; + Read(&tmp38, &buf); + status = static_cast<GrabStatus>(tmp38); // pad0 Pad(&buf, 23); @@ -2716,9 +3093,9 @@ Read(&length, &buf); // status - uint8_t tmp37; - Read(&tmp37, &buf); - status = static_cast<GrabStatus>(tmp37); + uint8_t tmp39; + Read(&tmp39, &buf); + status = static_cast<GrabStatus>(tmp39); // pad0 Pad(&buf, 23); @@ -2769,14 +3146,14 @@ buf.Write(&num_classes); // this_device_mode - uint8_t tmp38; - tmp38 = static_cast<uint8_t>(this_device_mode); - buf.Write(&tmp38); + uint8_t tmp40; + tmp40 = static_cast<uint8_t>(this_device_mode); + buf.Write(&tmp40); // other_device_mode - uint8_t tmp39; - tmp39 = static_cast<uint8_t>(other_device_mode); - buf.Write(&tmp39); + uint8_t tmp41; + tmp41 = static_cast<uint8_t>(other_device_mode); + buf.Write(&tmp41); // owner_events buf.Write(&owner_events); @@ -2839,9 +3216,9 @@ Read(&length, &buf); // status - uint8_t tmp40; - Read(&tmp40, &buf); - status = static_cast<GrabStatus>(tmp40); + uint8_t tmp42; + Read(&tmp42, &buf); + status = static_cast<GrabStatus>(tmp42); // pad0 Pad(&buf, 23); @@ -2929,9 +3306,9 @@ buf.Write(&num_classes); // modifiers - uint16_t tmp41; - tmp41 = static_cast<uint16_t>(modifiers); - buf.Write(&tmp41); + uint16_t tmp43; + tmp43 = static_cast<uint16_t>(modifiers); + buf.Write(&tmp43); // modifier_device buf.Write(&modifier_device); @@ -2943,14 +3320,14 @@ buf.Write(&key); // this_device_mode - uint8_t tmp42; - tmp42 = static_cast<uint8_t>(this_device_mode); - buf.Write(&tmp42); + uint8_t tmp44; + tmp44 = static_cast<uint8_t>(this_device_mode); + buf.Write(&tmp44); // other_device_mode - uint8_t tmp43; - tmp43 = static_cast<uint8_t>(other_device_mode); - buf.Write(&tmp43); + uint8_t tmp45; + tmp45 = static_cast<uint8_t>(other_device_mode); + buf.Write(&tmp45); // owner_events buf.Write(&owner_events); @@ -3013,9 +3390,9 @@ buf.Write(&grabWindow); // modifiers - uint16_t tmp44; - tmp44 = static_cast<uint16_t>(modifiers); - buf.Write(&tmp44); + uint16_t tmp46; + tmp46 = static_cast<uint16_t>(modifiers); + buf.Write(&tmp46); // modifier_device buf.Write(&modifier_device); @@ -3085,19 +3462,19 @@ buf.Write(&num_classes); // modifiers - uint16_t tmp45; - tmp45 = static_cast<uint16_t>(modifiers); - buf.Write(&tmp45); + uint16_t tmp47; + tmp47 = static_cast<uint16_t>(modifiers); + buf.Write(&tmp47); // this_device_mode - uint8_t tmp46; - tmp46 = static_cast<uint8_t>(this_device_mode); - buf.Write(&tmp46); + uint8_t tmp48; + tmp48 = static_cast<uint8_t>(this_device_mode); + buf.Write(&tmp48); // other_device_mode - uint8_t tmp47; - tmp47 = static_cast<uint8_t>(other_device_mode); - buf.Write(&tmp47); + uint8_t tmp49; + tmp49 = static_cast<uint8_t>(other_device_mode); + buf.Write(&tmp49); // button buf.Write(&button); @@ -3163,9 +3540,9 @@ buf.Write(&grab_window); // modifiers - uint16_t tmp48; - tmp48 = static_cast<uint16_t>(modifiers); - buf.Write(&tmp48); + uint16_t tmp50; + tmp50 = static_cast<uint16_t>(modifiers); + buf.Write(&tmp50); // modifier_device buf.Write(&modifier_device); @@ -3221,9 +3598,9 @@ buf.Write(&time); // mode - uint8_t tmp49; - tmp49 = static_cast<uint8_t>(mode); - buf.Write(&tmp49); + uint8_t tmp51; + tmp51 = static_cast<uint8_t>(mode); + buf.Write(&tmp51); // device_id buf.Write(&device_id); @@ -3316,9 +3693,9 @@ Read(&time, &buf); // revert_to - uint8_t tmp50; - Read(&tmp50, &buf); - revert_to = static_cast<InputFocus>(tmp50); + uint8_t tmp52; + Read(&tmp52, &buf); + revert_to = static_cast<InputFocus>(tmp52); // pad0 Pad(&buf, 15); @@ -3360,9 +3737,9 @@ buf.Write(&time); // revert_to - uint8_t tmp51; - tmp51 = static_cast<uint8_t>(revert_to); - buf.Write(&tmp51); + uint8_t tmp53; + tmp53 = static_cast<uint8_t>(revert_to); + buf.Write(&tmp53); // device_id buf.Write(&device_id); @@ -3465,9 +3842,9 @@ auto& data = feedbacks_elem; // class_id - uint8_t tmp52; - Read(&tmp52, &buf); - class_id = static_cast<Input::FeedbackClass>(tmp52); + uint8_t tmp54; + Read(&tmp54, &buf); + class_id = static_cast<Input::FeedbackClass>(tmp54); // feedback_id Read(&feedback_id, &buf); @@ -3635,9 +4012,9 @@ Pad(&buf, sizeof(uint16_t)); // mask - uint32_t tmp53; - tmp53 = static_cast<uint32_t>(mask); - buf.Write(&tmp53); + uint32_t tmp55; + tmp55 = static_cast<uint32_t>(mask); + buf.Write(&tmp55); // device_id buf.Write(&device_id); @@ -3665,9 +4042,9 @@ &class_id); SwitchVar(FeedbackClass::Led, data.led.has_value(), false, &class_id); SwitchVar(FeedbackClass::Bell, data.bell.has_value(), false, &class_id); - uint8_t tmp54; - tmp54 = static_cast<uint8_t>(class_id); - buf.Write(&tmp54); + uint8_t tmp56; + tmp56 = static_cast<uint8_t>(class_id); + buf.Write(&tmp56); // feedback_id buf.Write(&feedback_id); @@ -4117,9 +4494,9 @@ Read(&length, &buf); // status - uint8_t tmp55; - Read(&tmp55, &buf); - status = static_cast<MappingStatus>(tmp55); + uint8_t tmp57; + Read(&tmp57, &buf); + status = static_cast<MappingStatus>(tmp57); // pad0 Pad(&buf, 23); @@ -4298,9 +4675,9 @@ Read(&length, &buf); // status - uint8_t tmp56; - Read(&tmp56, &buf); - status = static_cast<MappingStatus>(tmp56); + uint8_t tmp58; + Read(&tmp58, &buf); + status = static_cast<MappingStatus>(tmp58); // pad0 Pad(&buf, 23); @@ -4392,9 +4769,9 @@ auto& data = classes_elem; // class_id - uint8_t tmp57; - Read(&tmp57, &buf); - class_id = static_cast<Input::InputClass>(tmp57); + uint8_t tmp59; + Read(&tmp59, &buf); + class_id = static_cast<Input::InputClass>(tmp59); // len Read(&len, &buf); @@ -4448,9 +4825,9 @@ Read(&num_valuators, &buf); // mode - uint8_t tmp58; - Read(&tmp58, &buf); - mode = static_cast<Input::ValuatorStateModeMask>(tmp58); + uint8_t tmp60; + Read(&tmp60, &buf); + mode = static_cast<Input::ValuatorStateModeMask>(tmp60); // valuators valuators.resize(num_valuators); @@ -4601,9 +4978,9 @@ Read(&length, &buf); // status - uint8_t tmp59; - Read(&tmp59, &buf); - status = static_cast<GrabStatus>(tmp59); + uint8_t tmp61; + Read(&tmp61, &buf); + status = static_cast<GrabStatus>(tmp61); // pad0 Pad(&buf, 23); @@ -4637,9 +5014,9 @@ Pad(&buf, sizeof(uint16_t)); // control_id - uint16_t tmp60; - tmp60 = static_cast<uint16_t>(control_id); - buf.Write(&tmp60); + uint16_t tmp62; + tmp62 = static_cast<uint16_t>(control_id); + buf.Write(&tmp62); // device_id buf.Write(&device_id); @@ -4699,9 +5076,9 @@ auto& data = control; // control_id - uint16_t tmp61; - Read(&tmp61, &buf); - control_id = static_cast<Input::DeviceControl>(tmp61); + uint16_t tmp63; + Read(&tmp63, &buf); + control_id = static_cast<Input::DeviceControl>(tmp63); // len Read(&len, &buf); @@ -4860,9 +5237,9 @@ Pad(&buf, sizeof(uint16_t)); // control_id - uint16_t tmp62; - tmp62 = static_cast<uint16_t>(control_id); - buf.Write(&tmp62); + uint16_t tmp64; + tmp64 = static_cast<uint16_t>(control_id); + buf.Write(&tmp64); // device_id buf.Write(&device_id); @@ -4886,9 +5263,9 @@ &control_id); SwitchVar(DeviceControl::abs_area, data.abs_area.has_value(), false, &control_id); - uint16_t tmp63; - tmp63 = static_cast<uint16_t>(control_id); - buf.Write(&tmp63); + uint16_t tmp65; + tmp65 = static_cast<uint16_t>(control_id); + buf.Write(&tmp65); // len buf.Write(&len); @@ -5174,14 +5551,14 @@ SwitchVar(PropertyFormat::c_8Bits, items.data8.has_value(), false, &format); SwitchVar(PropertyFormat::c_16Bits, items.data16.has_value(), false, &format); SwitchVar(PropertyFormat::c_32Bits, items.data32.has_value(), false, &format); - uint8_t tmp64; - tmp64 = static_cast<uint8_t>(format); - buf.Write(&tmp64); + uint8_t tmp66; + tmp66 = static_cast<uint8_t>(format); + buf.Write(&tmp66); // mode - uint8_t tmp65; - tmp65 = static_cast<uint8_t>(mode); - buf.Write(&tmp65); + uint8_t tmp67; + tmp67 = static_cast<uint8_t>(mode); + buf.Write(&tmp67); // pad0 Pad(&buf, 1); @@ -5397,9 +5774,9 @@ Read(&num_items, &buf); // format - uint8_t tmp66; - Read(&tmp66, &buf); - format = static_cast<Input::PropertyFormat>(tmp66); + uint8_t tmp68; + Read(&tmp68, &buf); + format = static_cast<Input::PropertyFormat>(tmp68); // device_id Read(&device_id, &buf); @@ -5787,9 +6164,9 @@ false, &type); SwitchVar(HierarchyChangeType::DetachSlave, data.detach_slave.has_value(), false, &type); - uint16_t tmp67; - tmp67 = static_cast<uint16_t>(type); - buf.Write(&tmp67); + uint16_t tmp69; + tmp69 = static_cast<uint16_t>(type); + buf.Write(&tmp69); // len buf.Write(&len); @@ -5832,9 +6209,9 @@ buf.Write(&deviceid); // return_mode - uint8_t tmp68; - tmp68 = static_cast<uint8_t>(return_mode); - buf.Write(&tmp68); + uint8_t tmp70; + tmp70 = static_cast<uint8_t>(return_mode); + buf.Write(&tmp70); // pad1 Pad(&buf, 1); @@ -6053,9 +6430,9 @@ DCHECK_EQ(static_cast<size_t>(mask_len), mask.size()); for (auto& mask_elem : mask) { // mask_elem - uint32_t tmp69; - tmp69 = static_cast<uint32_t>(mask_elem); - buf.Write(&tmp69); + uint32_t tmp71; + tmp71 = static_cast<uint32_t>(mask_elem); + buf.Write(&tmp71); } } } @@ -6240,9 +6617,9 @@ Read(&deviceid, &buf); // type - uint16_t tmp70; - Read(&tmp70, &buf); - type = static_cast<Input::DeviceType>(tmp70); + uint16_t tmp72; + Read(&tmp72, &buf); + type = static_cast<Input::DeviceType>(tmp72); // attachment Read(&attachment, &buf); @@ -6280,9 +6657,9 @@ auto& data = classes_elem; // type - uint16_t tmp71; - Read(&tmp71, &buf); - type = static_cast<Input::DeviceClassType>(tmp71); + uint16_t tmp73; + Read(&tmp73, &buf); + type = static_cast<Input::DeviceClassType>(tmp73); // len Read(&len, &buf); @@ -6389,9 +6766,9 @@ Read(&resolution, &buf); // mode - uint8_t tmp72; - Read(&tmp72, &buf); - mode = static_cast<Input::ValuatorMode>(tmp72); + uint8_t tmp74; + Read(&tmp74, &buf); + mode = static_cast<Input::ValuatorMode>(tmp74); // pad0 Pad(&buf, 3); @@ -6407,17 +6784,17 @@ Read(&number, &buf); // scroll_type - uint16_t tmp73; - Read(&tmp73, &buf); - scroll_type = static_cast<Input::ScrollType>(tmp73); + uint16_t tmp75; + Read(&tmp75, &buf); + scroll_type = static_cast<Input::ScrollType>(tmp75); // pad1 Pad(&buf, 2); // flags - uint32_t tmp74; - Read(&tmp74, &buf); - flags = static_cast<Input::ScrollFlags>(tmp74); + uint32_t tmp76; + Read(&tmp76, &buf); + flags = static_cast<Input::ScrollFlags>(tmp76); // increment { @@ -6437,13 +6814,23 @@ auto& num_touches = (*data.touch).num_touches; // mode - uint8_t tmp75; - Read(&tmp75, &buf); - mode = static_cast<Input::TouchMode>(tmp75); + uint8_t tmp77; + Read(&tmp77, &buf); + mode = static_cast<Input::TouchMode>(tmp77); // num_touches Read(&num_touches, &buf); } + if (CaseEq(data_expr, Input::DeviceClassType::Gesture)) { + data.gesture.emplace(); + auto& num_touches = (*data.gesture).num_touches; + + // num_touches + Read(&num_touches, &buf); + + // pad2 + Pad(&buf, 1); + } } } } @@ -6615,19 +7002,19 @@ buf.Write(&deviceid); // mode - uint8_t tmp76; - tmp76 = static_cast<uint8_t>(mode); - buf.Write(&tmp76); + uint8_t tmp78; + tmp78 = static_cast<uint8_t>(mode); + buf.Write(&tmp78); // paired_device_mode - uint8_t tmp77; - tmp77 = static_cast<uint8_t>(paired_device_mode); - buf.Write(&tmp77); + uint8_t tmp79; + tmp79 = static_cast<uint8_t>(paired_device_mode); + buf.Write(&tmp79); // owner_events - uint8_t tmp78; - tmp78 = static_cast<uint8_t>(owner_events); - buf.Write(&tmp78); + uint8_t tmp80; + tmp80 = static_cast<uint8_t>(owner_events); + buf.Write(&tmp80); // pad0 Pad(&buf, 1); @@ -6688,9 +7075,9 @@ Read(&length, &buf); // status - uint8_t tmp79; - Read(&tmp79, &buf); - status = static_cast<GrabStatus>(tmp79); + uint8_t tmp81; + Read(&tmp81, &buf); + status = static_cast<GrabStatus>(tmp81); // pad1 Pad(&buf, 23); @@ -6772,9 +7159,9 @@ buf.Write(&deviceid); // event_mode - uint8_t tmp80; - tmp80 = static_cast<uint8_t>(event_mode); - buf.Write(&tmp80); + uint8_t tmp82; + tmp82 = static_cast<uint8_t>(event_mode); + buf.Write(&tmp82); // pad0 Pad(&buf, 1); @@ -6857,25 +7244,25 @@ buf.Write(&mask_len); // grab_type - uint8_t tmp81; - tmp81 = static_cast<uint8_t>(grab_type); - buf.Write(&tmp81); - - // grab_mode - uint8_t tmp82; - tmp82 = static_cast<uint8_t>(grab_mode); - buf.Write(&tmp82); - - // paired_device_mode uint8_t tmp83; - tmp83 = static_cast<uint8_t>(paired_device_mode); + tmp83 = static_cast<uint8_t>(grab_type); buf.Write(&tmp83); - // owner_events + // grab_mode uint8_t tmp84; - tmp84 = static_cast<uint8_t>(owner_events); + tmp84 = static_cast<uint8_t>(grab_mode); buf.Write(&tmp84); + // paired_device_mode + uint8_t tmp85; + tmp85 = static_cast<uint8_t>(paired_device_mode); + buf.Write(&tmp85); + + // owner_events + uint8_t tmp86; + tmp86 = static_cast<uint8_t>(owner_events); + buf.Write(&tmp86); + // pad0 Pad(&buf, 2); @@ -6960,9 +7347,9 @@ Read(&modifiers, &buf); // status - uint8_t tmp85; - Read(&tmp85, &buf); - status = static_cast<GrabStatus>(tmp85); + uint8_t tmp87; + Read(&tmp87, &buf); + status = static_cast<GrabStatus>(tmp87); // pad0 Pad(&buf, 3); @@ -7016,9 +7403,9 @@ buf.Write(&num_modifiers); // grab_type - uint8_t tmp86; - tmp86 = static_cast<uint8_t>(grab_type); - buf.Write(&tmp86); + uint8_t tmp88; + tmp88 = static_cast<uint8_t>(grab_type); + buf.Write(&tmp88); // pad0 Pad(&buf, 3); @@ -7160,17 +7547,17 @@ buf.Write(&deviceid); // mode - uint8_t tmp87; - tmp87 = static_cast<uint8_t>(mode); - buf.Write(&tmp87); + uint8_t tmp89; + tmp89 = static_cast<uint8_t>(mode); + buf.Write(&tmp89); // format SwitchVar(PropertyFormat::c_8Bits, items.data8.has_value(), false, &format); SwitchVar(PropertyFormat::c_16Bits, items.data16.has_value(), false, &format); SwitchVar(PropertyFormat::c_32Bits, items.data32.has_value(), false, &format); - uint8_t tmp88; - tmp88 = static_cast<uint8_t>(format); - buf.Write(&tmp88); + uint8_t tmp90; + tmp90 = static_cast<uint8_t>(format); + buf.Write(&tmp90); // property buf.Write(&property); @@ -7384,9 +7771,9 @@ Read(&num_items, &buf); // format - uint8_t tmp89; - Read(&tmp89, &buf); - format = static_cast<Input::PropertyFormat>(tmp89); + uint8_t tmp91; + Read(&tmp91, &buf); + format = static_cast<Input::PropertyFormat>(tmp91); // pad1 Pad(&buf, 11); @@ -7528,9 +7915,9 @@ mask.resize(mask_len); for (auto& mask_elem : mask) { // mask_elem - uint32_t tmp90; - Read(&tmp90, &buf); - mask_elem = static_cast<Input::XIEventMask>(tmp90); + uint32_t tmp92; + Read(&tmp92, &buf); + mask_elem = static_cast<Input::XIEventMask>(tmp92); } } }
diff --git a/ui/gfx/x/generated_protos/xinput.h b/ui/gfx/x/generated_protos/xinput.h index 1c9b676d..31651f2 100644 --- a/ui/gfx/x/generated_protos/xinput.h +++ b/ui/gfx/x/generated_protos/xinput.h
@@ -70,7 +70,7 @@ class COMPONENT_EXPORT(X11) Input { public: static constexpr unsigned major_version = 2; - static constexpr unsigned minor_version = 3; + static constexpr unsigned minor_version = 4; Input(Connection* connection, const x11::QueryExtensionReply& info); @@ -226,6 +226,7 @@ Valuator = 2, Scroll = 3, Touch = 8, + Gesture = 9, }; enum class DeviceType : int { @@ -279,6 +280,8 @@ Enter = 2, FocusIn = 3, TouchBegin = 4, + GesturePinchBegin = 5, + GestureSwipeBegin = 6, }; enum class ModifierMask : int { @@ -375,6 +378,14 @@ DeviceIsGrabbed = 1 << 1, }; + enum class GesturePinchEventFlags : int { + GesturePinchCancelled = 1 << 0, + }; + + enum class GestureSwipeEventFlags : int { + GestureSwipeCancelled = 1 << 0, + }; + struct Fp3232 { bool operator==(const Fp3232& other) const { return integral == other.integral && frac == other.frac; @@ -1222,6 +1233,18 @@ uint8_t num_touches{}; }; + struct GestureClass { + bool operator==(const GestureClass& other) const { + return type == other.type && len == other.len && + sourceid == other.sourceid && num_touches == other.num_touches; + } + + DeviceClassType type{}; + uint16_t len{}; + DeviceId sourceid{}; + uint8_t num_touches{}; + }; + struct ValuatorClass { bool operator==(const ValuatorClass& other) const { return type == other.type && len == other.len && @@ -1272,11 +1295,15 @@ TouchMode mode{}; uint8_t num_touches{}; }; + struct Gesture { + uint8_t num_touches{}; + }; absl::optional<Key> key{}; absl::optional<Button> button{}; absl::optional<Valuator> valuator{}; absl::optional<Scroll> scroll{}; absl::optional<Touch> touch{}; + absl::optional<Gesture> gesture{}; }; struct XIDeviceInfo { @@ -1630,33 +1657,110 @@ x11::Window* GetWindow() { return reinterpret_cast<x11::Window*>(&event); } }; + struct GesturePinchEvent { + static constexpr int type_id = 38; + enum Opcode { + Begin = 27, + Update = 28, + End = 29, + } opcode{}; + uint16_t sequence{}; + DeviceId deviceid{}; + Time time{}; + uint32_t detail{}; + Window root{}; + Window event{}; + Window child{}; + Fp1616 root_x{}; + Fp1616 root_y{}; + Fp1616 event_x{}; + Fp1616 event_y{}; + Fp1616 delta_x{}; + Fp1616 delta_y{}; + Fp1616 delta_unaccel_x{}; + Fp1616 delta_unaccel_y{}; + Fp1616 scale{}; + Fp1616 delta_angle{}; + DeviceId sourceid{}; + ModifierInfo mods{}; + GroupInfo group{}; + GesturePinchEventFlags flags{}; + + x11::Window* GetWindow() { return reinterpret_cast<x11::Window*>(&event); } + }; + + struct GestureSwipeEvent { + static constexpr int type_id = 39; + enum Opcode { + Begin = 30, + Update = 31, + End = 32, + } opcode{}; + uint16_t sequence{}; + DeviceId deviceid{}; + Time time{}; + uint32_t detail{}; + Window root{}; + Window event{}; + Window child{}; + Fp1616 root_x{}; + Fp1616 root_y{}; + Fp1616 event_x{}; + Fp1616 event_y{}; + Fp1616 delta_x{}; + Fp1616 delta_y{}; + Fp1616 delta_unaccel_x{}; + Fp1616 delta_unaccel_y{}; + DeviceId sourceid{}; + ModifierInfo mods{}; + GroupInfo group{}; + GestureSwipeEventFlags flags{}; + + x11::Window* GetWindow() { return reinterpret_cast<x11::Window*>(&event); } + }; + using EventForSend = std::array<uint8_t, 32>; struct DeviceError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; struct EventError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; struct ModeError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; struct DeviceBusyError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; struct ClassError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; @@ -3501,4 +3605,36 @@ static_cast<T>(r)); } +inline constexpr x11::Input::GesturePinchEventFlags operator|( + x11::Input::GesturePinchEventFlags l, + x11::Input::GesturePinchEventFlags r) { + using T = std::underlying_type_t<x11::Input::GesturePinchEventFlags>; + return static_cast<x11::Input::GesturePinchEventFlags>(static_cast<T>(l) | + static_cast<T>(r)); +} + +inline constexpr x11::Input::GesturePinchEventFlags operator&( + x11::Input::GesturePinchEventFlags l, + x11::Input::GesturePinchEventFlags r) { + using T = std::underlying_type_t<x11::Input::GesturePinchEventFlags>; + return static_cast<x11::Input::GesturePinchEventFlags>(static_cast<T>(l) & + static_cast<T>(r)); +} + +inline constexpr x11::Input::GestureSwipeEventFlags operator|( + x11::Input::GestureSwipeEventFlags l, + x11::Input::GestureSwipeEventFlags r) { + using T = std::underlying_type_t<x11::Input::GestureSwipeEventFlags>; + return static_cast<x11::Input::GestureSwipeEventFlags>(static_cast<T>(l) | + static_cast<T>(r)); +} + +inline constexpr x11::Input::GestureSwipeEventFlags operator&( + x11::Input::GestureSwipeEventFlags l, + x11::Input::GestureSwipeEventFlags r) { + using T = std::underlying_type_t<x11::Input::GestureSwipeEventFlags>; + return static_cast<x11::Input::GestureSwipeEventFlags>(static_cast<T>(l) & + static_cast<T>(r)); +} + #endif // UI_GFX_X_GENERATED_PROTOS_XINPUT_H_
diff --git a/ui/gfx/x/generated_protos/xkb.h b/ui/gfx/x/generated_protos/xkb.h index 79e326ca..6270f287 100644 --- a/ui/gfx/x/generated_protos/xkb.h +++ b/ui/gfx/x/generated_protos/xkb.h
@@ -1194,7 +1194,7 @@ static_assert(std::is_trivially_copyable<Action>::value, ""); struct NewKeyboardNotifyEvent { - static constexpr int type_id = 38; + static constexpr int type_id = 40; static constexpr uint8_t opcode = 0; uint8_t xkbType{}; uint16_t sequence{}; @@ -1213,7 +1213,7 @@ }; struct MapNotifyEvent { - static constexpr int type_id = 39; + static constexpr int type_id = 41; static constexpr uint8_t opcode = 1; uint8_t xkbType{}; uint16_t sequence{}; @@ -1243,7 +1243,7 @@ }; struct StateNotifyEvent { - static constexpr int type_id = 40; + static constexpr int type_id = 42; static constexpr uint8_t opcode = 2; uint8_t xkbType{}; uint16_t sequence{}; @@ -1273,7 +1273,7 @@ }; struct ControlsNotifyEvent { - static constexpr int type_id = 41; + static constexpr int type_id = 43; static constexpr uint8_t opcode = 3; uint8_t xkbType{}; uint16_t sequence{}; @@ -1292,7 +1292,7 @@ }; struct IndicatorStateNotifyEvent { - static constexpr int type_id = 42; + static constexpr int type_id = 44; static constexpr uint8_t opcode = 4; uint8_t xkbType{}; uint16_t sequence{}; @@ -1305,7 +1305,7 @@ }; struct IndicatorMapNotifyEvent { - static constexpr int type_id = 43; + static constexpr int type_id = 45; static constexpr uint8_t opcode = 5; uint8_t xkbType{}; uint16_t sequence{}; @@ -1318,7 +1318,7 @@ }; struct NamesNotifyEvent { - static constexpr int type_id = 44; + static constexpr int type_id = 46; static constexpr uint8_t opcode = 6; uint8_t xkbType{}; uint16_t sequence{}; @@ -1341,7 +1341,7 @@ }; struct CompatMapNotifyEvent { - static constexpr int type_id = 45; + static constexpr int type_id = 47; static constexpr uint8_t opcode = 7; uint8_t xkbType{}; uint16_t sequence{}; @@ -1356,7 +1356,7 @@ }; struct BellNotifyEvent { - static constexpr int type_id = 46; + static constexpr int type_id = 48; static constexpr uint8_t opcode = 8; uint8_t xkbType{}; uint16_t sequence{}; @@ -1375,7 +1375,7 @@ }; struct ActionMessageEvent { - static constexpr int type_id = 47; + static constexpr int type_id = 49; static constexpr uint8_t opcode = 9; uint8_t xkbType{}; uint16_t sequence{}; @@ -1392,7 +1392,7 @@ }; struct AccessXNotifyEvent { - static constexpr int type_id = 48; + static constexpr int type_id = 50; static constexpr uint8_t opcode = 10; uint8_t xkbType{}; uint16_t sequence{}; @@ -1407,7 +1407,7 @@ }; struct ExtensionDeviceNotifyEvent { - static constexpr int type_id = 49; + static constexpr int type_id = 51; static constexpr uint8_t opcode = 11; uint8_t xkbType{}; uint16_t sequence{};
diff --git a/ui/gfx/x/generated_protos/xprint.cc b/ui/gfx/x/generated_protos/xprint.cc index 01f586a..e2c236af 100644 --- a/ui/gfx/x/generated_protos/xprint.cc +++ b/ui/gfx/x/generated_protos/xprint.cc
@@ -112,7 +112,10 @@ std::string XPrint::BadContextError::ToString() const { std::stringstream ss_; ss_ << "XPrint::BadContextError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -123,6 +126,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -135,12 +141,24 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } std::string XPrint::BadSequenceError::ToString() const { std::stringstream ss_; ss_ << "XPrint::BadSequenceError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -151,6 +169,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -163,6 +184,15 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } Future<XPrint::PrintQueryVersionReply> XPrint::PrintQueryVersion( @@ -272,6 +302,9 @@ buf.Write(&printer_name_elem); } + // pad0 + Align(&buf, 4); + // locale DCHECK_EQ(static_cast<size_t>(localeLen), locale.size()); for (auto& locale_elem : locale) { @@ -445,6 +478,9 @@ buf.Write(&printerName_elem); } + // pad0 + Align(&buf, 4); + // locale DCHECK_EQ(static_cast<size_t>(localeLen), locale.size()); for (auto& locale_elem : locale) { @@ -839,6 +875,9 @@ buf.Write(&data_elem); } + // pad0 + Align(&buf, 4); + // doc_format DCHECK_EQ(static_cast<size_t>(len_fmt), doc_format.size()); for (auto& doc_format_elem : doc_format) { @@ -846,6 +885,9 @@ buf.Write(&doc_format_elem); } + // pad1 + Align(&buf, 4); + // options DCHECK_EQ(static_cast<size_t>(len_options), options.size()); for (auto& options_elem : options) {
diff --git a/ui/gfx/x/generated_protos/xprint.h b/ui/gfx/x/generated_protos/xprint.h index 76f4138..ec0170f 100644 --- a/ui/gfx/x/generated_protos/xprint.h +++ b/ui/gfx/x/generated_protos/xprint.h
@@ -124,7 +124,7 @@ }; struct NotifyEvent { - static constexpr int type_id = 50; + static constexpr int type_id = 52; static constexpr uint8_t opcode = 0; uint8_t detail{}; uint16_t sequence{}; @@ -135,7 +135,7 @@ }; struct AttributNotifyEvent { - static constexpr int type_id = 51; + static constexpr int type_id = 53; static constexpr uint8_t opcode = 1; uint8_t detail{}; uint16_t sequence{}; @@ -146,12 +146,18 @@ struct BadContextError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; struct BadSequenceError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; };
diff --git a/ui/gfx/x/generated_protos/xproto.h b/ui/gfx/x/generated_protos/xproto.h index f6349a3..5d6daa7 100644 --- a/ui/gfx/x/generated_protos/xproto.h +++ b/ui/gfx/x/generated_protos/xproto.h
@@ -895,7 +895,7 @@ }; struct KeyEvent { - static constexpr int type_id = 52; + static constexpr int type_id = 54; enum Opcode { Press = 2, Release = 3, @@ -917,7 +917,7 @@ }; struct ButtonEvent { - static constexpr int type_id = 53; + static constexpr int type_id = 55; enum Opcode { Press = 4, Release = 5, @@ -939,7 +939,7 @@ }; struct MotionNotifyEvent { - static constexpr int type_id = 54; + static constexpr int type_id = 56; static constexpr uint8_t opcode = 6; Motion detail{}; uint16_t sequence{}; @@ -958,7 +958,7 @@ }; struct CrossingEvent { - static constexpr int type_id = 55; + static constexpr int type_id = 57; enum Opcode { EnterNotify = 7, LeaveNotify = 8, @@ -981,7 +981,7 @@ }; struct FocusEvent { - static constexpr int type_id = 56; + static constexpr int type_id = 58; enum Opcode { In = 9, Out = 10, @@ -995,7 +995,7 @@ }; struct KeymapNotifyEvent { - static constexpr int type_id = 57; + static constexpr int type_id = 59; static constexpr uint8_t opcode = 11; std::array<uint8_t, 31> keys{}; @@ -1003,7 +1003,7 @@ }; struct ExposeEvent { - static constexpr int type_id = 58; + static constexpr int type_id = 60; static constexpr uint8_t opcode = 12; uint16_t sequence{}; Window window{}; @@ -1017,7 +1017,7 @@ }; struct GraphicsExposureEvent { - static constexpr int type_id = 59; + static constexpr int type_id = 61; static constexpr uint8_t opcode = 13; uint16_t sequence{}; Drawable drawable{}; @@ -1033,7 +1033,7 @@ }; struct NoExposureEvent { - static constexpr int type_id = 60; + static constexpr int type_id = 62; static constexpr uint8_t opcode = 14; uint16_t sequence{}; Drawable drawable{}; @@ -1044,7 +1044,7 @@ }; struct VisibilityNotifyEvent { - static constexpr int type_id = 61; + static constexpr int type_id = 63; static constexpr uint8_t opcode = 15; uint16_t sequence{}; Window window{}; @@ -1054,7 +1054,7 @@ }; struct CreateNotifyEvent { - static constexpr int type_id = 62; + static constexpr int type_id = 64; static constexpr uint8_t opcode = 16; uint16_t sequence{}; Window parent{}; @@ -1070,7 +1070,7 @@ }; struct DestroyNotifyEvent { - static constexpr int type_id = 63; + static constexpr int type_id = 65; static constexpr uint8_t opcode = 17; uint16_t sequence{}; Window event{}; @@ -1080,7 +1080,7 @@ }; struct UnmapNotifyEvent { - static constexpr int type_id = 64; + static constexpr int type_id = 66; static constexpr uint8_t opcode = 18; uint16_t sequence{}; Window event{}; @@ -1091,7 +1091,7 @@ }; struct MapNotifyEvent { - static constexpr int type_id = 65; + static constexpr int type_id = 67; static constexpr uint8_t opcode = 19; uint16_t sequence{}; Window event{}; @@ -1102,7 +1102,7 @@ }; struct MapRequestEvent { - static constexpr int type_id = 66; + static constexpr int type_id = 68; static constexpr uint8_t opcode = 20; uint16_t sequence{}; Window parent{}; @@ -1112,7 +1112,7 @@ }; struct ReparentNotifyEvent { - static constexpr int type_id = 67; + static constexpr int type_id = 69; static constexpr uint8_t opcode = 21; uint16_t sequence{}; Window event{}; @@ -1126,7 +1126,7 @@ }; struct ConfigureNotifyEvent { - static constexpr int type_id = 68; + static constexpr int type_id = 70; static constexpr uint8_t opcode = 22; uint16_t sequence{}; Window event{}; @@ -1143,7 +1143,7 @@ }; struct ConfigureRequestEvent { - static constexpr int type_id = 69; + static constexpr int type_id = 71; static constexpr uint8_t opcode = 23; StackMode stack_mode{}; uint16_t sequence{}; @@ -1161,7 +1161,7 @@ }; struct GravityNotifyEvent { - static constexpr int type_id = 70; + static constexpr int type_id = 72; static constexpr uint8_t opcode = 24; uint16_t sequence{}; Window event{}; @@ -1173,7 +1173,7 @@ }; struct ResizeRequestEvent { - static constexpr int type_id = 71; + static constexpr int type_id = 73; static constexpr uint8_t opcode = 25; uint16_t sequence{}; Window window{}; @@ -1184,7 +1184,7 @@ }; struct CirculateEvent { - static constexpr int type_id = 72; + static constexpr int type_id = 74; enum Opcode { Notify = 26, Request = 27, @@ -1198,7 +1198,7 @@ }; struct PropertyNotifyEvent { - static constexpr int type_id = 73; + static constexpr int type_id = 75; static constexpr uint8_t opcode = 28; uint16_t sequence{}; Window window{}; @@ -1210,7 +1210,7 @@ }; struct SelectionClearEvent { - static constexpr int type_id = 74; + static constexpr int type_id = 76; static constexpr uint8_t opcode = 29; uint16_t sequence{}; Time time{}; @@ -1221,7 +1221,7 @@ }; struct SelectionRequestEvent { - static constexpr int type_id = 75; + static constexpr int type_id = 77; static constexpr uint8_t opcode = 30; uint16_t sequence{}; Time time{}; @@ -1235,7 +1235,7 @@ }; struct SelectionNotifyEvent { - static constexpr int type_id = 76; + static constexpr int type_id = 78; static constexpr uint8_t opcode = 31; uint16_t sequence{}; Time time{}; @@ -1250,7 +1250,7 @@ }; struct ColormapNotifyEvent { - static constexpr int type_id = 77; + static constexpr int type_id = 79; static constexpr uint8_t opcode = 32; uint16_t sequence{}; Window window{}; @@ -1271,7 +1271,7 @@ static_assert(std::is_trivially_copyable<ClientMessageData>::value, ""); struct ClientMessageEvent { - static constexpr int type_id = 78; + static constexpr int type_id = 80; static constexpr uint8_t opcode = 33; uint8_t format{}; uint16_t sequence{}; @@ -1283,7 +1283,7 @@ }; struct MappingNotifyEvent { - static constexpr int type_id = 79; + static constexpr int type_id = 81; static constexpr uint8_t opcode = 34; uint16_t sequence{}; Mapping request{}; @@ -1294,7 +1294,7 @@ }; struct GeGenericEvent { - static constexpr int type_id = 80; + static constexpr int type_id = 82; static constexpr uint8_t opcode = 35; uint16_t sequence{};
diff --git a/ui/gfx/x/generated_protos/xv.cc b/ui/gfx/x/generated_protos/xv.cc index c206f2d..9150830 100644 --- a/ui/gfx/x/generated_protos/xv.cc +++ b/ui/gfx/x/generated_protos/xv.cc
@@ -56,7 +56,10 @@ std::string Xv::BadPortError::ToString() const { std::stringstream ss_; ss_ << "Xv::BadPortError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -66,6 +69,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -78,12 +84,24 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } std::string Xv::BadEncodingError::ToString() const { std::stringstream ss_; ss_ << "Xv::BadEncodingError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -94,6 +112,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -106,12 +127,24 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } std::string Xv::BadControlError::ToString() const { std::stringstream ss_; ss_ << "Xv::BadControlError{"; - ss_ << ".sequence = " << static_cast<uint64_t>(sequence); + ss_ << ".sequence = " << static_cast<uint64_t>(sequence) << ", "; + ss_ << ".bad_value = " << static_cast<uint64_t>(bad_value) << ", "; + ss_ << ".minor_opcode = " << static_cast<uint64_t>(minor_opcode) << ", "; + ss_ << ".major_opcode = " << static_cast<uint64_t>(major_opcode); ss_ << "}"; return ss_.str(); } @@ -122,6 +155,9 @@ auto& buf = *buffer; auto& sequence = (*error_).sequence; + auto& bad_value = (*error_).bad_value; + auto& minor_opcode = (*error_).minor_opcode; + auto& major_opcode = (*error_).major_opcode; // response_type uint8_t response_type; @@ -134,6 +170,15 @@ // sequence Read(&sequence, &buf); + // bad_value + Read(&bad_value, &buf); + + // minor_opcode + Read(&minor_opcode, &buf); + + // major_opcode + Read(&major_opcode, &buf); + DCHECK_LE(buf.offset, 32ul); } template <>
diff --git a/ui/gfx/x/generated_protos/xv.h b/ui/gfx/x/generated_protos/xv.h index 0c5f76bf..0800d15f 100644 --- a/ui/gfx/x/generated_protos/xv.h +++ b/ui/gfx/x/generated_protos/xv.h
@@ -248,24 +248,33 @@ struct BadPortError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; struct BadEncodingError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; struct BadControlError : public x11::Error { uint16_t sequence{}; + uint32_t bad_value{}; + uint16_t minor_opcode{}; + uint8_t major_opcode{}; std::string ToString() const override; }; struct VideoNotifyEvent { - static constexpr int type_id = 81; + static constexpr int type_id = 83; static constexpr uint8_t opcode = 0; VideoNotifyReason reason{}; uint16_t sequence{}; @@ -279,7 +288,7 @@ }; struct PortNotifyEvent { - static constexpr int type_id = 82; + static constexpr int type_id = 84; static constexpr uint8_t opcode = 1; uint16_t sequence{}; Time time{};
diff --git a/ui/views/controls/menu/menu_config.h b/ui/views/controls/menu/menu_config.h index be3fd27..10a4fc6 100644 --- a/ui/views/controls/menu/menu_config.h +++ b/ui/views/controls/menu/menu_config.h
@@ -193,6 +193,9 @@ // Shadow elevation of touchable menus. int touchable_menu_shadow_elevation = 12; + // Shadow elevation of touchable submenus. + int touchable_submenu_shadow_elevation = 16; + // Vertical padding for touchable menus. int vertical_touchable_menu_item_padding = 8;
diff --git a/ui/views/controls/menu/menu_controller.cc b/ui/views/controls/menu/menu_controller.cc index e477fc35..2dbf14bc 100644 --- a/ui/views/controls/menu/menu_controller.cc +++ b/ui/views/controls/menu/menu_controller.cc
@@ -2503,9 +2503,18 @@ const MenuConfig& menu_config = MenuConfig::instance(); // Shadow insets are built into MenuScrollView's preferred size so it must be // compensated for when determining the bounds of touchable menus. + BubbleBorder::Shadow shadow_type = BubbleBorder::STANDARD_SHADOW; + int elevation = menu_config.touchable_menu_shadow_elevation; +#if BUILDFLAG(IS_CHROMEOS_ASH) + if (use_ash_system_ui_layout_) { + shadow_type = BubbleBorder::CHROMEOS_SYSTEM_UI_SHADOW; + elevation = item->GetParentMenuItem() + ? menu_config.touchable_submenu_shadow_elevation + : menu_config.touchable_menu_shadow_elevation; + } +#endif const gfx::Insets border_and_shadow_insets = - BubbleBorder::GetBorderAndShadowInsets( - menu_config.touchable_menu_shadow_elevation); + BubbleBorder::GetBorderAndShadowInsets(elevation, shadow_type); const gfx::Rect& monitor_bounds = state_.monitor_bounds;
diff --git a/ui/views/controls/menu/menu_controller_unittest.cc b/ui/views/controls/menu/menu_controller_unittest.cc index 56d601e..62f63513 100644 --- a/ui/views/controls/menu/menu_controller_unittest.cc +++ b/ui/views/controls/menu/menu_controller_unittest.cc
@@ -505,8 +505,7 @@ // Adjust the final bounds to not include the shadow and border. const gfx::Insets border_and_shadow_insets = - BubbleBorder::GetBorderAndShadowInsets( - MenuConfig::instance().touchable_menu_shadow_elevation); + GetBorderAndShadowInsets(/*is_submenu=*/false); final_bounds.Inset(border_and_shadow_insets); // Test that the menu will show on screen. @@ -552,8 +551,7 @@ // Adjust the final bounds to not include the shadow and border. const gfx::Insets border_and_shadow_insets = - BubbleBorder::GetBorderAndShadowInsets( - MenuConfig::instance().touchable_menu_shadow_elevation); + GetBorderAndShadowInsets(/*is_submenu=*/false); final_bounds.Inset(border_and_shadow_insets); // Test that the menu is within the monitor bounds. @@ -603,8 +601,7 @@ // Adjust the final bounds to not include the shadow and border. const gfx::Insets border_and_shadow_insets = - BubbleBorder::GetBorderAndShadowInsets( - MenuConfig::instance().touchable_menu_shadow_elevation); + GetBorderAndShadowInsets(/*is_submenu=*/false); options.anchor_bounds = gfx::Rect(monitor_bounds.origin(), anchor_size); gfx::Rect final_bounds = CalculateBubbleMenuBounds(options); @@ -655,8 +652,7 @@ // Adjust the final bounds to not include the shadow and border. const gfx::Insets border_and_shadow_insets = - BubbleBorder::GetBorderAndShadowInsets( - MenuConfig::instance().touchable_menu_shadow_elevation); + GetBorderAndShadowInsets(/*is_submenu=*/true); MenuItemView* parent_item = item->GetParentMenuItem(); SubmenuView* sub_menu = parent_item->GetSubmenu(); @@ -878,6 +874,23 @@ menu_controller_->OpenMenuImpl(parent, true); } + gfx::Insets GetBorderAndShadowInsets(bool is_submenu) { + const MenuConfig& menu_config = MenuConfig::instance(); + int elevation = menu_config.touchable_menu_shadow_elevation; + BubbleBorder::Shadow shadow_type = BubbleBorder::STANDARD_SHADOW; +#if BUILDFLAG(IS_CHROMEOS_ASH) + // Increase the submenu shadow elevation and change the shadow style to + // ChromeOS system UI shadow style when using Ash System UI layout. + if (menu_controller_->use_ash_system_ui_layout()) { + if (is_submenu) + elevation = menu_config.touchable_submenu_shadow_elevation; + + shadow_type = BubbleBorder::CHROMEOS_SYSTEM_UI_SHADOW; + } +#endif + return BubbleBorder::GetBorderAndShadowInsets(elevation, shadow_type); + } + private: void Init() { owner_ = std::make_unique<GestureTestWidget>(); @@ -1063,8 +1076,7 @@ constexpr gfx::Rect monitor_bounds(0, 0, 500, 500); constexpr gfx::Size menu_size(100, 200); const gfx::Insets border_and_shadow_insets = - BubbleBorder::GetBorderAndShadowInsets( - MenuConfig::instance().touchable_menu_shadow_elevation); + GetBorderAndShadowInsets(/*is_submenu=*/false); // Calculate the suitable anchor point to ensure that if the menu shows below // the anchor point, the bottom of the menu should be one pixel off the @@ -1128,8 +1140,7 @@ constexpr gfx::Rect kMonitorBounds(0, 0, 500, 500); constexpr gfx::Size kMenuSize(100, 200); const gfx::Insets border_and_shadow_insets = - BubbleBorder::GetBorderAndShadowInsets( - MenuConfig::instance().touchable_menu_shadow_elevation); + GetBorderAndShadowInsets(/*is_submenu=*/false); // Calculate the suitable anchor point to ensure that if the menu shows below // the anchor point, the bottom of the menu should be one pixel off the
diff --git a/ui/views/controls/menu/menu_scroll_view_container.cc b/ui/views/controls/menu/menu_scroll_view_container.cc index eff2c484..1725c53 100644 --- a/ui/views/controls/menu/menu_scroll_view_container.cc +++ b/ui/views/controls/menu/menu_scroll_view_container.cc
@@ -385,23 +385,32 @@ void MenuScrollViewContainer::CreateBubbleBorder() { const MenuConfig& menu_config = MenuConfig::instance(); - const int border_radius = menu_config.CornerRadiusForMenu( - content_view_->GetMenuItem()->GetMenuController()); + auto* menu_controller = content_view_->GetMenuItem()->GetMenuController(); + const int border_radius = menu_config.CornerRadiusForMenu(menu_controller); + const bool use_ash_system_ui_layout = + menu_controller->use_ash_system_ui_layout(); + ui::ColorId id = ui::kColorMenuBackground; + BubbleBorder::Shadow shadow_type = BubbleBorder::STANDARD_SHADOW; #if BUILDFLAG(IS_CHROMEOS_ASH) id = ui::kColorAshSystemUIMenuBackground; + // For ash system ui, we use chromeos system ui shadow. + if (use_ash_system_ui_layout) + shadow_type = BubbleBorder::CHROMEOS_SYSTEM_UI_SHADOW; #endif + const SkColor color = GetWidget() ? GetColorProvider()->GetColor(id) : gfx::kPlaceholderColor; - auto bubble_border = std::make_unique<BubbleBorder>( - arrow_, BubbleBorder::STANDARD_SHADOW, color); - if (content_view_->GetMenuItem() - ->GetMenuController() - ->use_ash_system_ui_layout() || - border_radius > 0) { + auto bubble_border = + std::make_unique<BubbleBorder>(arrow_, shadow_type, color); + if (use_ash_system_ui_layout || border_radius > 0) { bubble_border->SetCornerRadius(border_radius); + + const bool is_top_menu = !content_view_->GetMenuItem()->GetParentMenuItem(); bubble_border->set_md_shadow_elevation( - menu_config.touchable_menu_shadow_elevation); + is_top_menu ? menu_config.touchable_menu_shadow_elevation + : menu_config.touchable_submenu_shadow_elevation); + gfx::Insets insets(menu_config.vertical_touchable_menu_item_padding, 0); if (GetFootnote()) insets.Set(menu_config.vertical_touchable_menu_item_padding, 0, 0, 0);
diff --git a/ui/views/view_unittest.cc b/ui/views/view_unittest.cc index f643d3f..0c39a2e2 100644 --- a/ui/views/view_unittest.cc +++ b/ui/views/view_unittest.cc
@@ -1345,19 +1345,11 @@ namespace { void RotateCounterclockwise(gfx::Transform* transform) { - // clang-format off - transform->matrix().set3x3(0, -1, 0, - 1, 0, 0, - 0, 0, 1); - // clang-format on + transform->matrix().setRotateAboutZAxisSinCos(-1, 0); } void RotateClockwise(gfx::Transform* transform) { - // clang-format off - transform->matrix().set3x3( 0, 1, 0, // NOLINT - -1, 0, 0, - 0, 0, 1); - // clang-format on + transform->matrix().setRotateAboutZAxisSinCos(1, 0); } } // namespace