From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- gfx/layers/apz/test/gtest/APZCBasicTester.h | 102 + gfx/layers/apz/test/gtest/APZCTreeManagerTester.h | 223 +++ gfx/layers/apz/test/gtest/APZTestAccess.cpp | 27 + gfx/layers/apz/test/gtest/APZTestAccess.h | 36 + gfx/layers/apz/test/gtest/APZTestCommon.cpp | 15 + gfx/layers/apz/test/gtest/APZTestCommon.h | 1051 +++++++++++ gfx/layers/apz/test/gtest/InputUtils.h | 149 ++ gfx/layers/apz/test/gtest/MockHitTester.cpp | 38 + gfx/layers/apz/test/gtest/MockHitTester.h | 37 + gfx/layers/apz/test/gtest/TestAxisLock.cpp | 645 +++++++ gfx/layers/apz/test/gtest/TestBasic.cpp | 639 +++++++ gfx/layers/apz/test/gtest/TestEventRegions.cpp | 199 ++ gfx/layers/apz/test/gtest/TestEventResult.cpp | 476 +++++ .../apz/test/gtest/TestFlingAcceleration.cpp | 252 +++ gfx/layers/apz/test/gtest/TestGestureDetector.cpp | 849 +++++++++ gfx/layers/apz/test/gtest/TestHitTesting.cpp | 352 ++++ gfx/layers/apz/test/gtest/TestInputQueue.cpp | 45 + gfx/layers/apz/test/gtest/TestOverscroll.cpp | 1991 ++++++++++++++++++++ gfx/layers/apz/test/gtest/TestPanning.cpp | 251 +++ gfx/layers/apz/test/gtest/TestPinching.cpp | 675 +++++++ .../apz/test/gtest/TestPointerEventsConsumable.cpp | 500 +++++ gfx/layers/apz/test/gtest/TestScrollHandoff.cpp | 809 ++++++++ gfx/layers/apz/test/gtest/TestSnapping.cpp | 305 +++ .../apz/test/gtest/TestSnappingOnMomentum.cpp | 104 + .../apz/test/gtest/TestTransformNotifications.cpp | 567 ++++++ gfx/layers/apz/test/gtest/TestTreeManager.cpp | 347 ++++ gfx/layers/apz/test/gtest/TestWRScrollData.cpp | 273 +++ gfx/layers/apz/test/gtest/TestWRScrollData.h | 63 + gfx/layers/apz/test/gtest/moz.build | 39 + .../test/gtest/mvm/TestMobileViewportManager.cpp | 220 +++ gfx/layers/apz/test/gtest/mvm/moz.build | 13 + .../test/mochitest/FissionTestHelperChild.sys.mjs | 157 ++ .../test/mochitest/FissionTestHelperParent.sys.mjs | 103 + .../test/mochitest/apz_test_native_event_utils.js | 1881 ++++++++++++++++++ gfx/layers/apz/test/mochitest/apz_test_utils.js | 1287 +++++++++++++ gfx/layers/apz/test/mochitest/browser.ini | 64 + .../browser_test_animations_without_apz_sampler.js | 134 ++ ...test_autoscrolling_in_extension_popup_window.js | 189 ++ .../browser_test_autoscrolling_in_oop_frame.js | 120 ++ .../browser_test_background_tab_load_scroll.js | 117 ++ .../browser_test_background_tab_scroll.js | 66 + .../browser_test_content_response_timeout.js | 88 + .../test/mochitest/browser_test_group_fission.js | 150 ++ .../test/mochitest/browser_test_position_sticky.js | 105 ++ .../mochitest/browser_test_reset_scaling_zoom.js | 44 + .../browser_test_scroll_thumb_dragging.js | 77 + ...ser_test_scrollbar_in_extension_popup_window.js | 138 ++ ...ser_test_scrolling_in_extension_popup_window.js | 128 ++ ..._inactive_scroller_in_extension_popup_window.js | 137 ++ .../browser_test_select_popup_position.js | 130 ++ .../apz/test/mochitest/browser_test_select_zoom.js | 195 ++ .../test/mochitest/browser_test_tab_drag_zoom.js | 103 + gfx/layers/apz/test/mochitest/green100x100.png | Bin 0 -> 255 bytes .../helper_background_tab_load_scroll.html | 147 ++ .../mochitest/helper_background_tab_scroll.html | 9 + .../test/mochitest/helper_basic_onetouchpinch.html | 90 + .../apz/test/mochitest/helper_basic_pan.html | 73 + .../apz/test/mochitest/helper_basic_scrollend.html | 92 + .../apz/test/mochitest/helper_basic_zoom.html | 71 + .../test/mochitest/helper_browser_test_utils.js | 11 + .../apz/test/mochitest/helper_bug1162771.html | 107 ++ .../apz/test/mochitest/helper_bug1271432.html | 573 ++++++ .../apz/test/mochitest/helper_bug1280013.html | 73 + .../apz/test/mochitest/helper_bug1285070.html | 44 + .../apz/test/mochitest/helper_bug1299195.html | 47 + .../apz/test/mochitest/helper_bug1326290.html | 63 + .../apz/test/mochitest/helper_bug1331693.html | 71 + .../apz/test/mochitest/helper_bug1346632.html | 89 + .../apz/test/mochitest/helper_bug1414336.html | 97 + .../apz/test/mochitest/helper_bug1462961.html | 74 + .../apz/test/mochitest/helper_bug1473108.html | 50 + .../apz/test/mochitest/helper_bug1490393-2.html | 65 + .../apz/test/mochitest/helper_bug1490393.html | 64 + .../helper_bug1502010_unconsumed_pan.html | 76 + ...per_bug1506497_touch_action_fixed_on_fixed.html | 96 + .../apz/test/mochitest/helper_bug1509575.html | 71 + .../helper_bug1519339_hidden_smoothscroll.html | 61 + ...elper_bug1544966_zoom_on_touch_action_none.html | 89 + .../apz/test/mochitest/helper_bug1550510.html | 66 + .../helper_bug1637113_main_thread_hit_test.html | 70 + .../helper_bug1637135_narrow_viewport.html | 60 + .../helper_bug1638441_fixed_pos_hit_test.html | 67 + .../mochitest/helper_bug1638458_contextmenu.html | 82 + ...elper_bug1648491_no_pointercancel_with_dtc.html | 89 + .../apz/test/mochitest/helper_bug1662800.html | 61 + ...3731_no_pointercancel_on_second_touchstart.html | 82 + .../apz/test/mochitest/helper_bug1669625.html | 79 + .../apz/test/mochitest/helper_bug1674935.html | 76 + ...170_pointercancel_on_touchaction_pinchzoom.html | 75 + .../apz/test/mochitest/helper_bug1695598.html | 123 ++ .../helper_bug1714934_mouseevent_buttons.html | 40 + .../apz/test/mochitest/helper_bug1719330.html | 65 + .../apz/test/mochitest/helper_bug1756529.html | 226 +++ .../apz/test/mochitest/helper_bug1780701.html | 70 + .../apz/test/mochitest/helper_bug1783936.html | 74 + .../apz/test/mochitest/helper_bug982141.html | 130 ++ .../apz/test/mochitest/helper_check_dp_size.html | 124 ++ .../helper_checkerboard_apzforcedisabled.html | 93 + .../helper_checkerboard_no_multiplier.html | 57 + .../mochitest/helper_checkerboard_scrollinfo.html | 91 + .../helper_checkerboard_zoom_during_load.html | 55 + .../helper_checkerboard_zoomoverflowhidden.html | 150 ++ gfx/layers/apz/test/mochitest/helper_click.html | 42 + .../helper_click_interrupt_animation.html | 96 + .../mochitest/helper_content_response_timeout.html | 26 + ...elper_disallow_doubletap_zoom_inside_oopif.html | 58 + .../test/mochitest/helper_displayport_expiry.html | 77 + gfx/layers/apz/test/mochitest/helper_div_pan.html | 43 + .../apz/test/mochitest/helper_dommousescroll.html | 33 + .../apz/test/mochitest/helper_doubletap_zoom.html | 50 + .../helper_doubletap_zoom_bug1702464.html | 90 + .../mochitest/helper_doubletap_zoom_fixedpos.html | 88 + .../helper_doubletap_zoom_fixedpos_overflow.html | 113 ++ .../mochitest/helper_doubletap_zoom_gencon.html | 101 + .../helper_doubletap_zoom_horizontal_center.html | 50 + .../helper_doubletap_zoom_hscrollable.html | 85 + .../helper_doubletap_zoom_hscrollable2.html | 109 ++ .../helper_doubletap_zoom_htmlelement.html | 76 + .../test/mochitest/helper_doubletap_zoom_img.html | 43 + .../helper_doubletap_zoom_large_overflow.html | 300 +++ .../mochitest/helper_doubletap_zoom_noscroll.html | 59 + .../mochitest/helper_doubletap_zoom_nothing.html | 46 + .../helper_doubletap_zoom_nothing_listener.html | 47 + .../mochitest/helper_doubletap_zoom_oopif.html | 50 + ...per_doubletap_zoom_scrolled_overflowhidden.html | 82 + .../mochitest/helper_doubletap_zoom_shadowdom.html | 69 + .../mochitest/helper_doubletap_zoom_small.html | 43 + .../mochitest/helper_doubletap_zoom_smooth.html | 161 ++ .../mochitest/helper_doubletap_zoom_square.html | 61 + .../mochitest/helper_doubletap_zoom_tablecell.html | 110 ++ .../mochitest/helper_doubletap_zoom_tallwide.html | 85 + .../mochitest/helper_doubletap_zoom_textarea.html | 43 + .../apz/test/mochitest/helper_drag_bug1719913.html | 91 + .../apz/test/mochitest/helper_drag_click.html | 69 + .../test/mochitest/helper_drag_root_scrollbar.html | 61 + .../apz/test/mochitest/helper_drag_scroll.html | 653 +++++++ .../mochitest/helper_drag_scrollbar_hittest.html | 100 + gfx/layers/apz/test/mochitest/helper_empty.html | 4 + .../helper_fission_animation_styling_in_oopif.html | 166 ++ ...ion_animation_styling_in_transformed_oopif.html | 130 ++ .../apz/test/mochitest/helper_fission_basic.html | 40 + .../helper_fission_checkerboard_severity.html | 138 ++ .../apz/test/mochitest/helper_fission_empty.html | 34 + .../helper_fission_event_region_override.html | 84 + .../helper_fission_force_empty_hit_region.html | 82 + ...fission_inactivescroller_positionedcontent.html | 120 ++ ...elper_fission_inactivescroller_under_oopif.html | 88 + .../helper_fission_initial_displayport.html | 105 ++ .../mochitest/helper_fission_irregular_areas.html | 101 + .../mochitest/helper_fission_large_subframe.html | 67 + .../mochitest/helper_fission_scroll_handoff.html | 50 + .../mochitest/helper_fission_scroll_oopif.html | 158 ++ .../mochitest/helper_fission_setResolution.html | 59 + .../apz/test/mochitest/helper_fission_tap.html | 87 + ...per_fission_tap_in_nested_iframe_on_zoomed.html | 106 ++ .../mochitest/helper_fission_tap_on_zoomed.html | 93 + .../apz/test/mochitest/helper_fission_touch.html | 99 + .../test/mochitest/helper_fission_transforms.html | 89 + .../apz/test/mochitest/helper_fission_utils.js | 130 ++ .../test/mochitest/helper_fixed_html_hittest.html | 61 + .../mochitest/helper_fixed_pos_displayport.html | 101 + .../helper_fixed_position_scroll_hittest.html | 51 + .../apz/test/mochitest/helper_fullscreen.html | 53 + .../mochitest/helper_hittest_backface_hidden.html | 67 + .../apz/test/mochitest/helper_hittest_basic.html | 141 ++ .../test/mochitest/helper_hittest_bug1119497.html | 54 + .../test/mochitest/helper_hittest_bug1257288.html | 74 + .../test/mochitest/helper_hittest_bug1715187.html | 69 + .../mochitest/helper_hittest_bug1715187_oopif.html | 13 + .../test/mochitest/helper_hittest_bug1715369.html | 74 + .../helper_hittest_bug1715369_iframe.html | 13 + .../mochitest/helper_hittest_bug1715369_oopif.html | 13 + .../mochitest/helper_hittest_bug1730606-1.html | 124 ++ .../mochitest/helper_hittest_bug1730606-2.html | 157 ++ .../mochitest/helper_hittest_bug1730606-3.html | 56 + .../mochitest/helper_hittest_bug1730606-4.html | 194 ++ .../mochitest/helper_hittest_checkerboard.html | 57 + .../test/mochitest/helper_hittest_clippath.html | 118 ++ .../helper_hittest_clipped_fixed_modal.html | 85 + .../mochitest/helper_hittest_deep_scene_stack.html | 57 + .../apz/test/mochitest/helper_hittest_fixed-2.html | 74 + .../apz/test/mochitest/helper_hittest_fixed-3.html | 113 ++ .../apz/test/mochitest/helper_hittest_fixed.html | 82 + .../test/mochitest/helper_hittest_fixed_bg.html | 53 + ...helper_hittest_fixed_in_scrolled_transform.html | 91 + .../helper_hittest_fixed_item_over_oop_iframe.html | 61 + .../mochitest/helper_hittest_float_bug1434846.html | 56 + .../mochitest/helper_hittest_float_bug1443518.html | 56 + ...helper_hittest_hidden_inactive_scrollframe.html | 55 + .../helper_hittest_hoisted_scrollinfo.html | 81 + .../helper_hittest_iframe_perspective-2.html | 69 + .../helper_hittest_iframe_perspective-3.html | 70 + .../helper_hittest_iframe_perspective.html | 60 + .../helper_hittest_iframe_perspective_child.html | 13 + ...elper_hittest_nested_transforms_bug1459696.html | 80 + .../test/mochitest/helper_hittest_obscuration.html | 77 + .../test/mochitest/helper_hittest_overscroll.html | 249 +++ .../helper_hittest_overscroll_contextmenu.html | 129 ++ .../helper_hittest_overscroll_subframe.html | 132 ++ .../helper_hittest_pointerevents_svg.html | 177 ++ .../apz/test/mochitest/helper_hittest_spam.html | 100 + .../helper_hittest_sticky_bug1478304.html | 58 + .../test/mochitest/helper_hittest_touchaction.html | 353 ++++ .../mochitest/helper_horizontal_checkerboard.html | 65 + gfx/layers/apz/test/mochitest/helper_iframe1.html | 14 + gfx/layers/apz/test/mochitest/helper_iframe2.html | 14 + .../apz/test/mochitest/helper_iframe_pan.html | 49 + .../apz/test/mochitest/helper_iframe_textarea.html | 12 + .../test/mochitest/helper_interrupted_reflow.html | 712 +++++++ .../apz/test/mochitest/helper_key_scroll.html | 109 ++ gfx/layers/apz/test/mochitest/helper_long_tap.html | 166 ++ ...helper_main_thread_smooth_scroll_scrollend.html | 47 + .../helper_mainthread_scroll_bug1662379.html | 168 ++ .../test/mochitest/helper_minimum_scale_1_0.html | 46 + .../helper_no_scalable_with_initial_scale.html | 48 + .../mochitest/helper_onetouchpinch_nested.html | 103 + .../test/mochitest/helper_overflowhidden_zoom.html | 83 + .../apz/test/mochitest/helper_override_root.html | 62 + .../apz/test/mochitest/helper_override_subdoc.html | 15 + .../helper_overscroll_behavior_bug1425573.html | 44 + .../helper_overscroll_behavior_bug1425603.html | 76 + .../helper_overscroll_behavior_bug1494440.html | 50 + .../helper_overscroll_in_apz_test_data.html | 29 + .../helper_overscroll_in_subscroller.html | 165 ++ .../helper_position_fixed_scroll_handoff-1.html | 88 + .../helper_position_fixed_scroll_handoff-2.html | 65 + .../helper_position_fixed_scroll_handoff-3.html | 77 + .../helper_position_fixed_scroll_handoff-4.html | 79 + .../helper_position_fixed_scroll_handoff-5.html | 110 ++ .../mochitest/helper_position_sticky_flicker.html | 25 + .../helper_position_sticky_scroll_handoff.html | 88 + .../helper_programmatic_scroll_behavior.html | 81 + .../helper_relative_scroll_smoothness.html | 141 ++ .../mochitest/helper_reset_zoom_bug1818967.html | 55 + .../helper_scroll_anchoring_on_wheel.html | 59 + .../helper_scroll_anchoring_smooth_scroll.html | 54 + ...l_anchoring_smooth_scroll_with_set_timeout.html | 56 + .../helper_scroll_inactive_perspective.html | 45 + .../mochitest/helper_scroll_inactive_zindex.html | 46 + .../helper_scroll_into_view_bug1516056.html | 62 + .../helper_scroll_into_view_bug1562757.html | 64 + .../helper_scroll_linked_effect_by_wheel.html | 65 + .../helper_scroll_linked_effect_detector.html | 108 ++ .../mochitest/helper_scroll_on_position_fixed.html | 60 + .../mochitest/helper_scroll_over_scrollbar.html | 48 + .../helper_scroll_snap_no_valid_snap_position.html | 45 + ...lper_scroll_snap_not_resnap_during_panning.html | 93 + ..._snap_not_resnap_during_scrollbar_dragging.html | 105 ++ .../helper_scroll_snap_on_page_down_scroll.html | 84 + ...lper_scroll_snap_resnap_after_async_scroll.html | 81 + ...er_scroll_snap_resnap_after_async_scrollBy.html | 72 + .../helper_scroll_tables_perspective.html | 66 + .../mochitest/helper_scroll_thumb_dragging.html | 20 + .../helper_scrollbar_snap_bug1501062.html | 135 ++ .../mochitest/helper_scrollbarbutton_repeat.html | 101 + .../helper_scrollbarbuttonclick_checkerboard.html | 75 + .../test/mochitest/helper_scrollby_bug1531796.html | 36 + .../test/mochitest/helper_scrollend_bubbles.html | 99 + .../helper_scrollframe_activation_on_load.html | 89 + .../apz/test/mochitest/helper_scrollto_tap.html | 59 + .../apz/test/mochitest/helper_self_closer.html | 12 + .../test/mochitest/helper_smoothscroll_spam.html | 51 + .../helper_smoothscroll_spam_interleaved.html | 57 + .../apz/test/mochitest/helper_subframe_style.css | 15 + gfx/layers/apz/test/mochitest/helper_tall.html | 504 +++++ gfx/layers/apz/test/mochitest/helper_tap.html | 32 + .../test/mochitest/helper_tap_default_passive.html | 81 + .../apz/test/mochitest/helper_tap_fullzoom.html | 33 + .../apz/test/mochitest/helper_tap_passive.html | 66 + .../helper_test_autoscrolling_in_oop_frame.html | 9 + .../mochitest/helper_test_reset_scaling_zoom.html | 23 + .../helper_test_select_popup_position.html | 23 + ...elect_popup_position_transformed_in_parent.html | 25 + .../helper_test_select_popup_position_zoomed.html | 25 + .../test/mochitest/helper_test_select_zoom.html | 43 + .../test/mochitest/helper_test_tab_drag_zoom.html | 18 + .../apz/test/mochitest/helper_touch_action.html | 123 ++ .../mochitest/helper_touch_action_complex.html | 137 ++ .../helper_touch_action_ordering_block.html | 39 + .../helper_touch_action_ordering_zindex.html | 37 + .../mochitest/helper_touch_action_regions.html | 345 ++++ ...elper_touch_action_zero_opacity_bug1500864.html | 45 + .../helper_touch_drag_root_scrollbar.html | 51 + .../mochitest/helper_touchpad_pinch_and_pan.html | 49 + .../helper_transform_end_on_keyboard_scroll.html | 58 + .../helper_transform_end_on_wheel_scroll.html | 28 + .../helper_visual_scrollbars_pagescroll.html | 119 ++ .../mochitest/helper_visual_smooth_scroll.html | 53 + .../helper_visualscroll_clamp_restore.html | 63 + .../mochitest/helper_visualscroll_nonrcd_rsf.html | 88 + .../helper_wheelevents_handoff_on_iframe.html | 52 + ...helper_wheelevents_handoff_on_iframe_child.html | 11 + ...eelevents_handoff_on_non_scrollable_iframe.html | 113 ++ .../mochitest/helper_wide_crossorigin_iframe.html | 33 + .../helper_wide_crossorigin_iframe_child.html | 71 + ...helper_zoomToFocusedInput_fixed_bug1673511.html | 42 + .../helper_zoomToFocusedInput_iframe.html | 68 + .../helper_zoomToFocusedInput_multiline.html | 94 + .../helper_zoomToFocusedInput_nozoom.html | 39 + ...elper_zoomToFocusedInput_nozoom_bug1738696.html | 51 + .../helper_zoomToFocusedInput_scroll.html | 51 + .../helper_zoomToFocusedInput_touch-action.html | 67 + .../mochitest/helper_zoomToFocusedInput_zoom.html | 39 + .../helper_zoom_after_gpu_process_restart.html | 63 + .../test/mochitest/helper_zoom_keyboardscroll.html | 74 + .../apz/test/mochitest/helper_zoom_oopif.html | 54 + .../helper_zoom_out_clamped_scrollpos.html | 85 + .../helper_zoom_out_with_mainthread_clamping.html | 110 ++ .../apz/test/mochitest/helper_zoom_prevented.html | 75 + .../helper_zoom_restore_position_tabswitch.html | 74 + .../helper_zoom_with_dynamic_toolbar.html | 45 + .../test/mochitest/helper_zoom_with_touchpad.html | 110 ++ .../apz/test/mochitest/helper_zoomed_pan.html | 79 + gfx/layers/apz/test/mochitest/mochitest.ini | 125 ++ ...test_abort_smooth_scroll_by_instant_scroll.html | 51 + gfx/layers/apz/test/mochitest/test_bug1151667.html | 63 + gfx/layers/apz/test/mochitest/test_bug1253683.html | 59 + gfx/layers/apz/test/mochitest/test_bug1277814.html | 105 ++ .../apz/test/mochitest/test_bug1304689-2.html | 130 ++ gfx/layers/apz/test/mochitest/test_bug1304689.html | 134 ++ .../test/mochitest/test_frame_reconstruction.html | 218 +++ .../apz/test/mochitest/test_group_bug1534549.html | 37 + .../test/mochitest/test_group_checkerboarding.html | 83 + .../apz/test/mochitest/test_group_displayport.html | 31 + .../mochitest/test_group_double_tap_zoom-2.html | 89 + .../test/mochitest/test_group_double_tap_zoom.html | 66 + .../apz/test/mochitest/test_group_fullscreen.html | 32 + .../apz/test/mochitest/test_group_hittest-1.html | 59 + .../apz/test/mochitest/test_group_hittest-2.html | 72 + .../apz/test/mochitest/test_group_hittest-3.html | 50 + .../mochitest/test_group_hittest-overscroll.html | 54 + .../apz/test/mochitest/test_group_keyboard-2.html | 46 + .../apz/test/mochitest/test_group_keyboard.html | 60 + .../apz/test/mochitest/test_group_mainthread.html | 51 + .../mochitest/test_group_minimum_scale_size.html | 48 + .../apz/test/mochitest/test_group_mouseevents.html | 82 + .../apz/test/mochitest/test_group_overrides.html | 37 + .../apz/test/mochitest/test_group_overscroll.html | 35 + .../mochitest/test_group_overscroll_handoff.html | 46 + .../test/mochitest/test_group_pointerevents.html | 43 + .../test_group_programmatic_scroll_behavior.html | 38 + .../mochitest/test_group_scroll_linked_effect.html | 33 + .../apz/test/mochitest/test_group_scroll_snap.html | 67 + .../apz/test/mochitest/test_group_scrollend.html | 58 + .../test_group_scrollframe_activation.html | 49 + .../test/mochitest/test_group_touchevents-2.html | 69 + .../test/mochitest/test_group_touchevents-3.html | 47 + .../test/mochitest/test_group_touchevents-4.html | 54 + .../test/mochitest/test_group_touchevents-5.html | 51 + .../apz/test/mochitest/test_group_touchevents.html | 55 + .../apz/test/mochitest/test_group_wheelevents.html | 84 + .../apz/test/mochitest/test_group_zoom-2.html | 81 + gfx/layers/apz/test/mochitest/test_group_zoom.html | 80 + .../mochitest/test_group_zoomToFocusedInput.html | 49 + .../test/mochitest/test_interrupted_reflow.html | 38 + .../apz/test/mochitest/test_layerization.html | 312 +++ .../apz/test/mochitest/test_relative_update.html | 92 + .../mochitest/test_scroll_inactive_bug1190112.html | 553 ++++++ .../test_scroll_inactive_flattened_frame.html | 50 + .../mochitest/test_scroll_subframe_scrollbar.html | 116 ++ gfx/layers/apz/test/mochitest/test_smoothness.html | 71 + .../test_touch_listeners_impacting_wheel.html | 119 ++ .../apz/test/mochitest/test_wheel_scroll.html | 109 ++ .../test/mochitest/test_wheel_transactions.html | 150 ++ .../apz/test/reftest/async-scrollbar-1-h-ref.html | 8 + .../test/reftest/async-scrollbar-1-h-rtl-ref.html | 9 + .../apz/test/reftest/async-scrollbar-1-h-rtl.html | 13 + .../apz/test/reftest/async-scrollbar-1-h.html | 12 + .../reftest/async-scrollbar-1-v-fullzoom-ref.html | 8 + .../test/reftest/async-scrollbar-1-v-fullzoom.html | 14 + .../apz/test/reftest/async-scrollbar-1-v-ref.html | 8 + .../test/reftest/async-scrollbar-1-v-rtl-ref.html | 9 + .../apz/test/reftest/async-scrollbar-1-v-rtl.html | 13 + .../apz/test/reftest/async-scrollbar-1-v.html | 12 + .../apz/test/reftest/async-scrollbar-1-vh-ref.html | 8 + .../test/reftest/async-scrollbar-1-vh-rtl-ref.html | 9 + .../apz/test/reftest/async-scrollbar-1-vh-rtl.html | 13 + .../apz/test/reftest/async-scrollbar-1-vh.html | 12 + .../frame-reconstruction-scroll-clamping-ref.html | 27 + .../frame-reconstruction-scroll-clamping.html | 53 + .../apz/test/reftest/iframe-zoomed-child.html | 12 + gfx/layers/apz/test/reftest/iframe-zoomed-ref.html | 20 + gfx/layers/apz/test/reftest/iframe-zoomed.html | 25 + .../apz/test/reftest/initial-scale-1-ref.html | 9 + gfx/layers/apz/test/reftest/initial-scale-1.html | 9 + .../reftest/pinch-zoom-position-fixed-ref.html | 23 + .../test/reftest/pinch-zoom-position-fixed.html | 37 + .../reftest/pinch-zoom-position-sticky-ref.html | 27 + .../test/reftest/pinch-zoom-position-sticky.html | 30 + gfx/layers/apz/test/reftest/reftest.list | 50 + .../root-scrollbar-async-zoomed-in-ref.html | 8 + .../reftest/root-scrollbar-async-zoomed-in.html | 13 + .../root-scrollbar-async-zoomed-out-ref.html | 8 + .../reftest/root-scrollbar-async-zoomed-out.html | 13 + .../root-scrollbar-zoomed-in-async-scroll.html | 12 + .../test/reftest/root-scrollbar-zoomed-in-ref.html | 8 + .../apz/test/reftest/root-scrollbar-zoomed-in.html | 8 + .../root-scrollbar-zoomed-out-async-scroll.html | 12 + .../reftest/root-scrollbar-zoomed-out-ref.html | 8 + .../test/reftest/root-scrollbar-zoomed-out.html | 8 + .../apz/test/reftest/root-scrollbars-1-ref.html | 14 + gfx/layers/apz/test/reftest/root-scrollbars-1.html | 14 + .../apz/test/reftest/scaled-iframe-zoomed-ref.html | 21 + .../apz/test/reftest/scaled-iframe-zoomed.html | 26 + ...frame-scrollbar-zoomed-in-async-scroll-ref.html | 10 + .../subframe-scrollbar-zoomed-in-async-scroll.html | 15 + ...rame-scrollbar-zoomed-out-async-scroll-ref.html | 10 + ...subframe-scrollbar-zoomed-out-async-scroll.html | 15 + 408 files changed, 43863 insertions(+) create mode 100644 gfx/layers/apz/test/gtest/APZCBasicTester.h create mode 100644 gfx/layers/apz/test/gtest/APZCTreeManagerTester.h create mode 100644 gfx/layers/apz/test/gtest/APZTestAccess.cpp create mode 100644 gfx/layers/apz/test/gtest/APZTestAccess.h create mode 100644 gfx/layers/apz/test/gtest/APZTestCommon.cpp create mode 100644 gfx/layers/apz/test/gtest/APZTestCommon.h create mode 100644 gfx/layers/apz/test/gtest/InputUtils.h create mode 100644 gfx/layers/apz/test/gtest/MockHitTester.cpp create mode 100644 gfx/layers/apz/test/gtest/MockHitTester.h create mode 100644 gfx/layers/apz/test/gtest/TestAxisLock.cpp create mode 100644 gfx/layers/apz/test/gtest/TestBasic.cpp create mode 100644 gfx/layers/apz/test/gtest/TestEventRegions.cpp create mode 100644 gfx/layers/apz/test/gtest/TestEventResult.cpp create mode 100644 gfx/layers/apz/test/gtest/TestFlingAcceleration.cpp create mode 100644 gfx/layers/apz/test/gtest/TestGestureDetector.cpp create mode 100644 gfx/layers/apz/test/gtest/TestHitTesting.cpp create mode 100644 gfx/layers/apz/test/gtest/TestInputQueue.cpp create mode 100644 gfx/layers/apz/test/gtest/TestOverscroll.cpp create mode 100644 gfx/layers/apz/test/gtest/TestPanning.cpp create mode 100644 gfx/layers/apz/test/gtest/TestPinching.cpp create mode 100644 gfx/layers/apz/test/gtest/TestPointerEventsConsumable.cpp create mode 100644 gfx/layers/apz/test/gtest/TestScrollHandoff.cpp create mode 100644 gfx/layers/apz/test/gtest/TestSnapping.cpp create mode 100644 gfx/layers/apz/test/gtest/TestSnappingOnMomentum.cpp create mode 100644 gfx/layers/apz/test/gtest/TestTransformNotifications.cpp create mode 100644 gfx/layers/apz/test/gtest/TestTreeManager.cpp create mode 100644 gfx/layers/apz/test/gtest/TestWRScrollData.cpp create mode 100644 gfx/layers/apz/test/gtest/TestWRScrollData.h create mode 100644 gfx/layers/apz/test/gtest/moz.build create mode 100644 gfx/layers/apz/test/gtest/mvm/TestMobileViewportManager.cpp create mode 100644 gfx/layers/apz/test/gtest/mvm/moz.build create mode 100644 gfx/layers/apz/test/mochitest/FissionTestHelperChild.sys.mjs create mode 100644 gfx/layers/apz/test/mochitest/FissionTestHelperParent.sys.mjs create mode 100644 gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js create mode 100644 gfx/layers/apz/test/mochitest/apz_test_utils.js create mode 100644 gfx/layers/apz/test/mochitest/browser.ini create mode 100644 gfx/layers/apz/test/mochitest/browser_test_animations_without_apz_sampler.js create mode 100644 gfx/layers/apz/test/mochitest/browser_test_autoscrolling_in_extension_popup_window.js create mode 100644 gfx/layers/apz/test/mochitest/browser_test_autoscrolling_in_oop_frame.js create mode 100644 gfx/layers/apz/test/mochitest/browser_test_background_tab_load_scroll.js create mode 100644 gfx/layers/apz/test/mochitest/browser_test_background_tab_scroll.js create mode 100644 gfx/layers/apz/test/mochitest/browser_test_content_response_timeout.js create mode 100644 gfx/layers/apz/test/mochitest/browser_test_group_fission.js create mode 100644 gfx/layers/apz/test/mochitest/browser_test_position_sticky.js create mode 100644 gfx/layers/apz/test/mochitest/browser_test_reset_scaling_zoom.js create mode 100644 gfx/layers/apz/test/mochitest/browser_test_scroll_thumb_dragging.js create mode 100644 gfx/layers/apz/test/mochitest/browser_test_scrollbar_in_extension_popup_window.js create mode 100644 gfx/layers/apz/test/mochitest/browser_test_scrolling_in_extension_popup_window.js create mode 100644 gfx/layers/apz/test/mochitest/browser_test_scrolling_on_inactive_scroller_in_extension_popup_window.js create mode 100644 gfx/layers/apz/test/mochitest/browser_test_select_popup_position.js create mode 100644 gfx/layers/apz/test/mochitest/browser_test_select_zoom.js create mode 100644 gfx/layers/apz/test/mochitest/browser_test_tab_drag_zoom.js create mode 100644 gfx/layers/apz/test/mochitest/green100x100.png create mode 100644 gfx/layers/apz/test/mochitest/helper_background_tab_load_scroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_background_tab_scroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_basic_onetouchpinch.html create mode 100644 gfx/layers/apz/test/mochitest/helper_basic_pan.html create mode 100644 gfx/layers/apz/test/mochitest/helper_basic_scrollend.html create mode 100644 gfx/layers/apz/test/mochitest/helper_basic_zoom.html create mode 100644 gfx/layers/apz/test/mochitest/helper_browser_test_utils.js create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1162771.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1271432.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1280013.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1285070.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1299195.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1326290.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1331693.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1346632.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1414336.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1462961.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1473108.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1490393-2.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1490393.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1502010_unconsumed_pan.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1506497_touch_action_fixed_on_fixed.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1509575.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1519339_hidden_smoothscroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1544966_zoom_on_touch_action_none.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1550510.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1637113_main_thread_hit_test.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1637135_narrow_viewport.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1638441_fixed_pos_hit_test.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1638458_contextmenu.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1648491_no_pointercancel_with_dtc.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1662800.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1663731_no_pointercancel_on_second_touchstart.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1669625.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1674935.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1682170_pointercancel_on_touchaction_pinchzoom.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1695598.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1714934_mouseevent_buttons.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1719330.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1756529.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1780701.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1783936.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug982141.html create mode 100644 gfx/layers/apz/test/mochitest/helper_check_dp_size.html create mode 100644 gfx/layers/apz/test/mochitest/helper_checkerboard_apzforcedisabled.html create mode 100644 gfx/layers/apz/test/mochitest/helper_checkerboard_no_multiplier.html create mode 100644 gfx/layers/apz/test/mochitest/helper_checkerboard_scrollinfo.html create mode 100644 gfx/layers/apz/test/mochitest/helper_checkerboard_zoom_during_load.html create mode 100644 gfx/layers/apz/test/mochitest/helper_checkerboard_zoomoverflowhidden.html create mode 100644 gfx/layers/apz/test/mochitest/helper_click.html create mode 100644 gfx/layers/apz/test/mochitest/helper_click_interrupt_animation.html create mode 100644 gfx/layers/apz/test/mochitest/helper_content_response_timeout.html create mode 100644 gfx/layers/apz/test/mochitest/helper_disallow_doubletap_zoom_inside_oopif.html create mode 100644 gfx/layers/apz/test/mochitest/helper_displayport_expiry.html create mode 100644 gfx/layers/apz/test/mochitest/helper_div_pan.html create mode 100644 gfx/layers/apz/test/mochitest/helper_dommousescroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom_bug1702464.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom_fixedpos.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom_fixedpos_overflow.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom_gencon.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom_horizontal_center.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom_hscrollable.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom_hscrollable2.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom_htmlelement.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom_img.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom_large_overflow.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom_noscroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom_nothing.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom_nothing_listener.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom_oopif.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom_scrolled_overflowhidden.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom_shadowdom.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom_small.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom_smooth.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom_square.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom_tablecell.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom_tallwide.html create mode 100644 gfx/layers/apz/test/mochitest/helper_doubletap_zoom_textarea.html create mode 100644 gfx/layers/apz/test/mochitest/helper_drag_bug1719913.html create mode 100644 gfx/layers/apz/test/mochitest/helper_drag_click.html create mode 100644 gfx/layers/apz/test/mochitest/helper_drag_root_scrollbar.html create mode 100644 gfx/layers/apz/test/mochitest/helper_drag_scroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_drag_scrollbar_hittest.html create mode 100644 gfx/layers/apz/test/mochitest/helper_empty.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_animation_styling_in_oopif.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_animation_styling_in_transformed_oopif.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_basic.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_checkerboard_severity.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_empty.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_event_region_override.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_force_empty_hit_region.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_inactivescroller_positionedcontent.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_inactivescroller_under_oopif.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_initial_displayport.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_irregular_areas.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_large_subframe.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_scroll_handoff.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_scroll_oopif.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_setResolution.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_tap.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_tap_in_nested_iframe_on_zoomed.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_tap_on_zoomed.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_touch.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_transforms.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_utils.js create mode 100644 gfx/layers/apz/test/mochitest/helper_fixed_html_hittest.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fixed_pos_displayport.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fixed_position_scroll_hittest.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fullscreen.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_backface_hidden.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_basic.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_bug1119497.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_bug1257288.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_bug1715187.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_bug1715187_oopif.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_bug1715369.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_bug1715369_iframe.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_bug1715369_oopif.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_bug1730606-1.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_bug1730606-2.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_bug1730606-3.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_bug1730606-4.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_checkerboard.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_clippath.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_clipped_fixed_modal.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_deep_scene_stack.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_fixed-2.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_fixed-3.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_fixed.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_fixed_bg.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_fixed_in_scrolled_transform.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_fixed_item_over_oop_iframe.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_float_bug1434846.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_float_bug1443518.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_hidden_inactive_scrollframe.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_hoisted_scrollinfo.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective-2.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective-3.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective_child.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_nested_transforms_bug1459696.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_obscuration.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_overscroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_overscroll_contextmenu.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_overscroll_subframe.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_pointerevents_svg.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_spam.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_sticky_bug1478304.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_touchaction.html create mode 100644 gfx/layers/apz/test/mochitest/helper_horizontal_checkerboard.html create mode 100644 gfx/layers/apz/test/mochitest/helper_iframe1.html create mode 100644 gfx/layers/apz/test/mochitest/helper_iframe2.html create mode 100644 gfx/layers/apz/test/mochitest/helper_iframe_pan.html create mode 100644 gfx/layers/apz/test/mochitest/helper_iframe_textarea.html create mode 100644 gfx/layers/apz/test/mochitest/helper_interrupted_reflow.html create mode 100644 gfx/layers/apz/test/mochitest/helper_key_scroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_long_tap.html create mode 100644 gfx/layers/apz/test/mochitest/helper_main_thread_smooth_scroll_scrollend.html create mode 100644 gfx/layers/apz/test/mochitest/helper_mainthread_scroll_bug1662379.html create mode 100644 gfx/layers/apz/test/mochitest/helper_minimum_scale_1_0.html create mode 100644 gfx/layers/apz/test/mochitest/helper_no_scalable_with_initial_scale.html create mode 100644 gfx/layers/apz/test/mochitest/helper_onetouchpinch_nested.html create mode 100644 gfx/layers/apz/test/mochitest/helper_overflowhidden_zoom.html create mode 100644 gfx/layers/apz/test/mochitest/helper_override_root.html create mode 100644 gfx/layers/apz/test/mochitest/helper_override_subdoc.html create mode 100644 gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1425573.html create mode 100644 gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1425603.html create mode 100644 gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1494440.html create mode 100644 gfx/layers/apz/test/mochitest/helper_overscroll_in_apz_test_data.html create mode 100644 gfx/layers/apz/test/mochitest/helper_overscroll_in_subscroller.html create mode 100644 gfx/layers/apz/test/mochitest/helper_position_fixed_scroll_handoff-1.html create mode 100644 gfx/layers/apz/test/mochitest/helper_position_fixed_scroll_handoff-2.html create mode 100644 gfx/layers/apz/test/mochitest/helper_position_fixed_scroll_handoff-3.html create mode 100644 gfx/layers/apz/test/mochitest/helper_position_fixed_scroll_handoff-4.html create mode 100644 gfx/layers/apz/test/mochitest/helper_position_fixed_scroll_handoff-5.html create mode 100644 gfx/layers/apz/test/mochitest/helper_position_sticky_flicker.html create mode 100644 gfx/layers/apz/test/mochitest/helper_position_sticky_scroll_handoff.html create mode 100644 gfx/layers/apz/test/mochitest/helper_programmatic_scroll_behavior.html create mode 100644 gfx/layers/apz/test/mochitest/helper_relative_scroll_smoothness.html create mode 100644 gfx/layers/apz/test/mochitest/helper_reset_zoom_bug1818967.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_anchoring_on_wheel.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_anchoring_smooth_scroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_anchoring_smooth_scroll_with_set_timeout.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_inactive_perspective.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_inactive_zindex.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_into_view_bug1516056.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_into_view_bug1562757.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_linked_effect_by_wheel.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_linked_effect_detector.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_over_scrollbar.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_snap_no_valid_snap_position.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_snap_not_resnap_during_panning.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_snap_not_resnap_during_scrollbar_dragging.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_snap_on_page_down_scroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_snap_resnap_after_async_scroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_snap_resnap_after_async_scrollBy.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_tables_perspective.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_thumb_dragging.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scrollbar_snap_bug1501062.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scrollbarbutton_repeat.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scrollbarbuttonclick_checkerboard.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scrollby_bug1531796.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scrollend_bubbles.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scrollframe_activation_on_load.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scrollto_tap.html create mode 100644 gfx/layers/apz/test/mochitest/helper_self_closer.html create mode 100644 gfx/layers/apz/test/mochitest/helper_smoothscroll_spam.html create mode 100644 gfx/layers/apz/test/mochitest/helper_smoothscroll_spam_interleaved.html create mode 100644 gfx/layers/apz/test/mochitest/helper_subframe_style.css create mode 100644 gfx/layers/apz/test/mochitest/helper_tall.html create mode 100644 gfx/layers/apz/test/mochitest/helper_tap.html create mode 100644 gfx/layers/apz/test/mochitest/helper_tap_default_passive.html create mode 100644 gfx/layers/apz/test/mochitest/helper_tap_fullzoom.html create mode 100644 gfx/layers/apz/test/mochitest/helper_tap_passive.html create mode 100644 gfx/layers/apz/test/mochitest/helper_test_autoscrolling_in_oop_frame.html create mode 100644 gfx/layers/apz/test/mochitest/helper_test_reset_scaling_zoom.html create mode 100644 gfx/layers/apz/test/mochitest/helper_test_select_popup_position.html create mode 100644 gfx/layers/apz/test/mochitest/helper_test_select_popup_position_transformed_in_parent.html create mode 100644 gfx/layers/apz/test/mochitest/helper_test_select_popup_position_zoomed.html create mode 100644 gfx/layers/apz/test/mochitest/helper_test_select_zoom.html create mode 100644 gfx/layers/apz/test/mochitest/helper_test_tab_drag_zoom.html create mode 100644 gfx/layers/apz/test/mochitest/helper_touch_action.html create mode 100644 gfx/layers/apz/test/mochitest/helper_touch_action_complex.html create mode 100644 gfx/layers/apz/test/mochitest/helper_touch_action_ordering_block.html create mode 100644 gfx/layers/apz/test/mochitest/helper_touch_action_ordering_zindex.html create mode 100644 gfx/layers/apz/test/mochitest/helper_touch_action_regions.html create mode 100644 gfx/layers/apz/test/mochitest/helper_touch_action_zero_opacity_bug1500864.html create mode 100644 gfx/layers/apz/test/mochitest/helper_touch_drag_root_scrollbar.html create mode 100644 gfx/layers/apz/test/mochitest/helper_touchpad_pinch_and_pan.html create mode 100644 gfx/layers/apz/test/mochitest/helper_transform_end_on_keyboard_scroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_transform_end_on_wheel_scroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_visual_scrollbars_pagescroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_visual_smooth_scroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_visualscroll_clamp_restore.html create mode 100644 gfx/layers/apz/test/mochitest/helper_visualscroll_nonrcd_rsf.html create mode 100644 gfx/layers/apz/test/mochitest/helper_wheelevents_handoff_on_iframe.html create mode 100644 gfx/layers/apz/test/mochitest/helper_wheelevents_handoff_on_iframe_child.html create mode 100644 gfx/layers/apz/test/mochitest/helper_wheelevents_handoff_on_non_scrollable_iframe.html create mode 100644 gfx/layers/apz/test/mochitest/helper_wide_crossorigin_iframe.html create mode 100644 gfx/layers/apz/test/mochitest/helper_wide_crossorigin_iframe_child.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_fixed_bug1673511.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_iframe.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_multiline.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_nozoom.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_nozoom_bug1738696.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_scroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_touch-action.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_zoom.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoom_after_gpu_process_restart.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoom_keyboardscroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoom_oopif.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoom_out_clamped_scrollpos.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoom_out_with_mainthread_clamping.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoom_prevented.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoom_restore_position_tabswitch.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoom_with_dynamic_toolbar.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoom_with_touchpad.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoomed_pan.html create mode 100644 gfx/layers/apz/test/mochitest/mochitest.ini create mode 100644 gfx/layers/apz/test/mochitest/test_abort_smooth_scroll_by_instant_scroll.html create mode 100644 gfx/layers/apz/test/mochitest/test_bug1151667.html create mode 100644 gfx/layers/apz/test/mochitest/test_bug1253683.html create mode 100644 gfx/layers/apz/test/mochitest/test_bug1277814.html create mode 100644 gfx/layers/apz/test/mochitest/test_bug1304689-2.html create mode 100644 gfx/layers/apz/test/mochitest/test_bug1304689.html create mode 100644 gfx/layers/apz/test/mochitest/test_frame_reconstruction.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_bug1534549.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_checkerboarding.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_displayport.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_double_tap_zoom-2.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_double_tap_zoom.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_fullscreen.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_hittest-1.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_hittest-2.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_hittest-3.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_hittest-overscroll.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_keyboard-2.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_keyboard.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_mainthread.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_minimum_scale_size.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_mouseevents.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_overrides.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_overscroll.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_overscroll_handoff.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_pointerevents.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_programmatic_scroll_behavior.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_scroll_linked_effect.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_scroll_snap.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_scrollend.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_scrollframe_activation.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_touchevents-2.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_touchevents-3.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_touchevents-4.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_touchevents-5.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_touchevents.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_wheelevents.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_zoom-2.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_zoom.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_zoomToFocusedInput.html create mode 100644 gfx/layers/apz/test/mochitest/test_interrupted_reflow.html create mode 100644 gfx/layers/apz/test/mochitest/test_layerization.html create mode 100644 gfx/layers/apz/test/mochitest/test_relative_update.html create mode 100644 gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html create mode 100644 gfx/layers/apz/test/mochitest/test_scroll_inactive_flattened_frame.html create mode 100644 gfx/layers/apz/test/mochitest/test_scroll_subframe_scrollbar.html create mode 100644 gfx/layers/apz/test/mochitest/test_smoothness.html create mode 100644 gfx/layers/apz/test/mochitest/test_touch_listeners_impacting_wheel.html create mode 100644 gfx/layers/apz/test/mochitest/test_wheel_scroll.html create mode 100644 gfx/layers/apz/test/mochitest/test_wheel_transactions.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-h-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-h.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-v-fullzoom-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-v-fullzoom.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-v-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-v.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-vh-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-vh.html create mode 100644 gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping-ref.html create mode 100644 gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping.html create mode 100644 gfx/layers/apz/test/reftest/iframe-zoomed-child.html create mode 100644 gfx/layers/apz/test/reftest/iframe-zoomed-ref.html create mode 100644 gfx/layers/apz/test/reftest/iframe-zoomed.html create mode 100644 gfx/layers/apz/test/reftest/initial-scale-1-ref.html create mode 100644 gfx/layers/apz/test/reftest/initial-scale-1.html create mode 100644 gfx/layers/apz/test/reftest/pinch-zoom-position-fixed-ref.html create mode 100644 gfx/layers/apz/test/reftest/pinch-zoom-position-fixed.html create mode 100644 gfx/layers/apz/test/reftest/pinch-zoom-position-sticky-ref.html create mode 100644 gfx/layers/apz/test/reftest/pinch-zoom-position-sticky.html create mode 100644 gfx/layers/apz/test/reftest/reftest.list create mode 100644 gfx/layers/apz/test/reftest/root-scrollbar-async-zoomed-in-ref.html create mode 100644 gfx/layers/apz/test/reftest/root-scrollbar-async-zoomed-in.html create mode 100644 gfx/layers/apz/test/reftest/root-scrollbar-async-zoomed-out-ref.html create mode 100644 gfx/layers/apz/test/reftest/root-scrollbar-async-zoomed-out.html create mode 100644 gfx/layers/apz/test/reftest/root-scrollbar-zoomed-in-async-scroll.html create mode 100644 gfx/layers/apz/test/reftest/root-scrollbar-zoomed-in-ref.html create mode 100644 gfx/layers/apz/test/reftest/root-scrollbar-zoomed-in.html create mode 100644 gfx/layers/apz/test/reftest/root-scrollbar-zoomed-out-async-scroll.html create mode 100644 gfx/layers/apz/test/reftest/root-scrollbar-zoomed-out-ref.html create mode 100644 gfx/layers/apz/test/reftest/root-scrollbar-zoomed-out.html create mode 100644 gfx/layers/apz/test/reftest/root-scrollbars-1-ref.html create mode 100644 gfx/layers/apz/test/reftest/root-scrollbars-1.html create mode 100644 gfx/layers/apz/test/reftest/scaled-iframe-zoomed-ref.html create mode 100644 gfx/layers/apz/test/reftest/scaled-iframe-zoomed.html create mode 100644 gfx/layers/apz/test/reftest/subframe-scrollbar-zoomed-in-async-scroll-ref.html create mode 100644 gfx/layers/apz/test/reftest/subframe-scrollbar-zoomed-in-async-scroll.html create mode 100644 gfx/layers/apz/test/reftest/subframe-scrollbar-zoomed-out-async-scroll-ref.html create mode 100644 gfx/layers/apz/test/reftest/subframe-scrollbar-zoomed-out-async-scroll.html (limited to 'gfx/layers/apz/test') diff --git a/gfx/layers/apz/test/gtest/APZCBasicTester.h b/gfx/layers/apz/test/gtest/APZCBasicTester.h new file mode 100644 index 0000000000..621a3d37be --- /dev/null +++ b/gfx/layers/apz/test/gtest/APZCBasicTester.h @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_APZCBasicTester_h +#define mozilla_layers_APZCBasicTester_h + +/** + * Defines a test fixture used for testing a single APZC. + */ + +#include "APZTestCommon.h" + +#include "mozilla/layers/APZSampler.h" +#include "mozilla/layers/APZUpdater.h" + +class APZCBasicTester : public APZCTesterBase { + public: + explicit APZCBasicTester( + AsyncPanZoomController::GestureBehavior aGestureBehavior = + AsyncPanZoomController::DEFAULT_GESTURES) + : mGestureBehavior(aGestureBehavior) {} + + protected: + virtual void SetUp() { + APZCTesterBase::SetUp(); + APZThreadUtils::SetThreadAssertionsEnabled(false); + APZThreadUtils::SetControllerThread(NS_GetCurrentThread()); + + tm = new TestAPZCTreeManager(mcc); + updater = new APZUpdater(tm, false); + sampler = new APZSampler(tm, false); + apzc = + new TestAsyncPanZoomController(LayersId{0}, mcc, tm, mGestureBehavior); + apzc->SetFrameMetrics(TestFrameMetrics()); + apzc->GetScrollMetadata().SetIsLayersIdRoot(true); + } + + /** + * Get the APZC's scroll range in CSS pixels. + */ + CSSRect GetScrollRange() const { + const FrameMetrics& metrics = apzc->GetFrameMetrics(); + return CSSRect(metrics.GetScrollableRect().TopLeft(), + metrics.GetScrollableRect().Size() - + metrics.CalculateCompositedSizeInCssPixels()); + } + + virtual void TearDown() { + while (mcc->RunThroughDelayedTasks()) + ; + apzc->Destroy(); + tm->ClearTree(); + tm->ClearContentController(); + + APZCTesterBase::TearDown(); + } + + void MakeApzcWaitForMainThread() { apzc->SetWaitForMainThread(); } + + void MakeApzcZoomable() { + apzc->UpdateZoomConstraints(ZoomConstraints( + true, true, CSSToParentLayerScale(0.25f), CSSToParentLayerScale(4.0f))); + } + + void MakeApzcUnzoomable() { + apzc->UpdateZoomConstraints(ZoomConstraints(false, false, + CSSToParentLayerScale(1.0f), + CSSToParentLayerScale(1.0f))); + } + + /** + * Sample animations once, 1 ms later than the last sample. + */ + void SampleAnimationOnce() { + const TimeDuration increment = TimeDuration::FromMilliseconds(1); + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + mcc->AdvanceBy(increment); + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + } + /** + * Sample animations one frame, 17 ms later than the last sample. + */ + void SampleAnimationOneFrame() { + const TimeDuration increment = TimeDuration::FromMilliseconds(17); + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + mcc->AdvanceBy(increment); + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + } + + AsyncPanZoomController::GestureBehavior mGestureBehavior; + RefPtr tm; + RefPtr sampler; + RefPtr updater; + RefPtr apzc; +}; + +#endif // mozilla_layers_APZCBasicTester_h diff --git a/gfx/layers/apz/test/gtest/APZCTreeManagerTester.h b/gfx/layers/apz/test/gtest/APZCTreeManagerTester.h new file mode 100644 index 0000000000..8a2f9d749b --- /dev/null +++ b/gfx/layers/apz/test/gtest/APZCTreeManagerTester.h @@ -0,0 +1,223 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_APZCTreeManagerTester_h +#define mozilla_layers_APZCTreeManagerTester_h + +/** + * Defines a test fixture used for testing multiple APZCs interacting in + * an APZCTreeManager. + */ + +#include "APZTestAccess.h" +#include "APZTestCommon.h" +#include "gfxPlatform.h" +#include "MockHitTester.h" +#include "apz/src/WRHitTester.h" + +#include "mozilla/layers/APZSampler.h" +#include "mozilla/layers/APZUpdater.h" +#include "mozilla/layers/WebRenderScrollDataWrapper.h" + +class APZCTreeManagerTester : public APZCTesterBase { + protected: + APZCTreeManagerTester() : mHitTester(MakeUnique()) {} + + virtual void SetUp() { + APZCTesterBase::SetUp(); + + APZThreadUtils::SetThreadAssertionsEnabled(false); + APZThreadUtils::SetControllerThread(NS_GetCurrentThread()); + + manager = new TestAPZCTreeManager(mcc, std::move(mHitTester)); + updater = new APZUpdater(manager, false); + sampler = new APZSampler(manager, false); + } + + virtual void TearDown() { + while (mcc->RunThroughDelayedTasks()) + ; + manager->ClearTree(); + manager->ClearContentController(); + + APZCTesterBase::TearDown(); + } + + /** + * Sample animations once for all APZCs, 1 ms later than the last sample and + * return whether there is still any active animations or not. + */ + bool SampleAnimationsOnce() { + const TimeDuration increment = TimeDuration::FromMilliseconds(1); + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + mcc->AdvanceBy(increment); + + bool activeAnimations = false; + + for (size_t i = 0; i < layers.GetLayerCount(); ++i) { + if (TestAsyncPanZoomController* apzc = ApzcOf(layers[i])) { + activeAnimations |= + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + } + } + + return activeAnimations; + } + + // A convenience function for letting a test modify the frame metrics + // stored on a particular layer. + template + void ModifyFrameMetrics(WebRenderLayerScrollData* aLayer, + Callback aCallback) { + MOZ_ASSERT(aLayer->GetScrollMetadataCount() == 1); + ScrollMetadata& metadataRef = + APZTestAccess::GetScrollMetadataMut(*aLayer, layers, 0); + aCallback(metadataRef, metadataRef.GetMetrics()); + } + + // A convenience wrapper for manager->UpdateHitTestingTree(). + void UpdateHitTestingTree(uint32_t aPaintSequenceNumber = 0) { + manager->UpdateHitTestingTree(WebRenderScrollDataWrapper{*updater, &layers}, + /* is first paint = */ false, LayersId{0}, + aPaintSequenceNumber); + } + + void CreateScrollData(const char* aTreeShape, + const LayerIntRegion* aVisibleRegions = nullptr, + const gfx::Matrix4x4* aTransforms = nullptr) { + layers = TestWRScrollData::Create(aTreeShape, *updater, aVisibleRegions, + aTransforms); + root = layers[0]; + } + + void CreateMockHitTester() { + mHitTester = MakeUnique(); + // Save a pointer in a separate variable, because SetUp() will + // move the value out of mHitTester. + mMockHitTester = static_cast(mHitTester.get()); + } + void QueueMockHitResult(ScrollableLayerGuid::ViewID aScrollId, + gfx::CompositorHitTestInfo aHitInfo = + gfx::CompositorHitTestFlags::eVisibleToHitTest) { + MOZ_ASSERT(mMockHitTester); + mMockHitTester->QueueHitResult(aScrollId, aHitInfo); + } + + RefPtr manager; + RefPtr sampler; + RefPtr updater; + TestWRScrollData layers; + WebRenderLayerScrollData* root = nullptr; + + UniquePtr mHitTester; + MockHitTester* mMockHitTester = nullptr; + + protected: + static ScrollMetadata BuildScrollMetadata( + ScrollableLayerGuid::ViewID aScrollId, const CSSRect& aScrollableRect, + const ParentLayerRect& aCompositionBounds) { + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetScrollId(aScrollId); + // By convention in this test file, START_SCROLL_ID is the root, so mark it + // as such. + if (aScrollId == ScrollableLayerGuid::START_SCROLL_ID) { + metadata.SetIsLayersIdRoot(true); + } + metrics.SetCompositionBounds(aCompositionBounds); + metrics.SetScrollableRect(aScrollableRect); + metrics.SetLayoutScrollOffset(CSSPoint(0, 0)); + metadata.SetPageScrollAmount(LayoutDeviceIntSize(50, 100)); + metadata.SetLineScrollAmount(LayoutDeviceIntSize(5, 10)); + return metadata; + } + + void SetScrollMetadata(WebRenderLayerScrollData* aLayer, + const ScrollMetadata& aMetadata) { + MOZ_ASSERT(aLayer->GetScrollMetadataCount() <= 1, + "This function does not support multiple ScrollMetadata on a " + "single layer"); + if (aLayer->GetScrollMetadataCount() == 0) { + // Add new metrics + aLayer->AppendScrollMetadata(layers, aMetadata); + } else { + // Overwrite existing metrics + ModifyFrameMetrics( + aLayer, [&](ScrollMetadata& aSm, FrameMetrics&) { aSm = aMetadata; }); + } + } + + void SetScrollMetadata(WebRenderLayerScrollData* aLayer, + const nsTArray& aMetadata) { + // The reason for this restriction is that WebRenderLayerScrollData does not + // have an API to *remove* previous metadata. + MOZ_ASSERT(aLayer->GetScrollMetadataCount() == 0, + "This function can only be used on layers which do not yet have " + "scroll metadata"); + for (const ScrollMetadata& metadata : aMetadata) { + aLayer->AppendScrollMetadata(layers, metadata); + } + } + + void SetScrollableFrameMetrics(WebRenderLayerScrollData* aLayer, + ScrollableLayerGuid::ViewID aScrollId, + CSSRect aScrollableRect = CSSRect(-1, -1, -1, + -1)) { + auto localTransform = aLayer->GetTransformTyped() * AsyncTransformMatrix(); + ParentLayerIntRect compositionBounds = + RoundedToInt(localTransform.TransformBounds( + LayerRect(aLayer->GetVisibleRegion().GetBounds()))); + ScrollMetadata metadata = BuildScrollMetadata( + aScrollId, aScrollableRect, ParentLayerRect(compositionBounds)); + SetScrollMetadata(aLayer, metadata); + } + + bool HasScrollableFrameMetrics(const WebRenderLayerScrollData* aLayer) const { + for (uint32_t i = 0; i < aLayer->GetScrollMetadataCount(); i++) { + if (aLayer->GetScrollMetadata(layers, i).GetMetrics().IsScrollable()) { + return true; + } + } + return false; + } + + void SetScrollHandoff(WebRenderLayerScrollData* aChild, + WebRenderLayerScrollData* aParent) { + ModifyFrameMetrics(aChild, [&](ScrollMetadata& aSm, FrameMetrics&) { + aSm.SetScrollParentId( + aParent->GetScrollMetadata(layers, 0).GetMetrics().GetScrollId()); + }); + } + + TestAsyncPanZoomController* ApzcOf(WebRenderLayerScrollData* aLayer) { + EXPECT_EQ(1u, aLayer->GetScrollMetadataCount()); + return ApzcOf(aLayer, 0); + } + + TestAsyncPanZoomController* ApzcOf(WebRenderLayerScrollData* aLayer, + uint32_t aIndex) { + EXPECT_LT(aIndex, aLayer->GetScrollMetadataCount()); + // Unlike Layer, WebRenderLayerScrollData does not store the associated + // APZCs, so look it up using the tree manager instead. + RefPtr apzc = manager->GetTargetAPZC( + LayersId{0}, + aLayer->GetScrollMetadata(layers, aIndex).GetMetrics().GetScrollId()); + return (TestAsyncPanZoomController*)apzc.get(); + } + + void CreateSimpleScrollingLayer() { + const char* treeShape = "x"; + LayerIntRegion layerVisibleRegion[] = { + LayerIntRect(0, 0, 200, 200), + }; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(layers[0], ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 500, 500)); + } +}; + +#endif // mozilla_layers_APZCTreeManagerTester_h diff --git a/gfx/layers/apz/test/gtest/APZTestAccess.cpp b/gfx/layers/apz/test/gtest/APZTestAccess.cpp new file mode 100644 index 0000000000..d55d7711f8 --- /dev/null +++ b/gfx/layers/apz/test/gtest/APZTestAccess.cpp @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZTestAccess.h" +#include "mozilla/layers/WebRenderScrollData.h" + +namespace mozilla { +namespace layers { + +/*static*/ +void APZTestAccess::InitializeForTest(WebRenderLayerScrollData& aLayer, + int32_t aDescendantCount) { + aLayer.InitializeForTest(aDescendantCount); +} + +/*static*/ +ScrollMetadata& APZTestAccess::GetScrollMetadataMut( + WebRenderLayerScrollData& aLayer, WebRenderScrollData& aOwner, + size_t aIndex) { + return aLayer.GetScrollMetadataMut(aOwner, aIndex); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/test/gtest/APZTestAccess.h b/gfx/layers/apz/test/gtest/APZTestAccess.h new file mode 100644 index 0000000000..a56fb10a1a --- /dev/null +++ b/gfx/layers/apz/test/gtest/APZTestAccess.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_APZTestAccess_h +#define mozilla_layers_APZTestAccess_h + +#include // for size_t +#include // for int32_t + +namespace mozilla { +namespace layers { + +struct ScrollMetadata; +class WebRenderLayerScrollData; +class WebRenderScrollData; + +// The only purpose of this class is to serve as a single type that can be +// the target of a "friend class" declaration in APZ classes that want to +// give APZ test code access to their private members. +// APZ test code can then access those members via this class. +class APZTestAccess { + public: + static void InitializeForTest(WebRenderLayerScrollData& aLayer, + int32_t aDescendantCount); + static ScrollMetadata& GetScrollMetadataMut(WebRenderLayerScrollData& aLayer, + WebRenderScrollData& aOwner, + size_t aIndex); +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/apz/test/gtest/APZTestCommon.cpp b/gfx/layers/apz/test/gtest/APZTestCommon.cpp new file mode 100644 index 0000000000..5276531a26 --- /dev/null +++ b/gfx/layers/apz/test/gtest/APZTestCommon.cpp @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZTestCommon.h" + +AsyncPanZoomController* TestAPZCTreeManager::NewAPZCInstance( + LayersId aLayersId, GeckoContentController* aController) { + MockContentControllerDelayed* mcc = + static_cast(aController); + return new TestAsyncPanZoomController( + aLayersId, mcc, this, AsyncPanZoomController::USE_GESTURE_DETECTOR); +} diff --git a/gfx/layers/apz/test/gtest/APZTestCommon.h b/gfx/layers/apz/test/gtest/APZTestCommon.h new file mode 100644 index 0000000000..e4552bdd11 --- /dev/null +++ b/gfx/layers/apz/test/gtest/APZTestCommon.h @@ -0,0 +1,1051 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_APZTestCommon_h +#define mozilla_layers_APZTestCommon_h + +/** + * Defines a set of mock classes and utility functions/classes for + * writing APZ gtests. + */ + +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +#include "mozilla/Attributes.h" +#include "mozilla/layers/GeckoContentController.h" +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/APZThreadUtils.h" +#include "mozilla/layers/MatrixMessage.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/TypedEnumBits.h" +#include "mozilla/UniquePtr.h" +#include "apz/src/APZCTreeManager.h" +#include "apz/src/AsyncPanZoomController.h" +#include "apz/src/HitTestingTreeNode.h" +#include "base/task.h" +#include "gfxPlatform.h" +#include "TestWRScrollData.h" +#include "UnitTransforms.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::layers; +using ::testing::_; +using ::testing::AtLeast; +using ::testing::AtMost; +using ::testing::InSequence; +using ::testing::MockFunction; +using ::testing::NiceMock; +typedef mozilla::layers::GeckoContentController::TapType TapType; + +inline TimeStamp GetStartupTime() { + static TimeStamp sStartupTime = TimeStamp::Now(); + return sStartupTime; +} + +inline uint32_t MillisecondsSinceStartup(TimeStamp aTime) { + return (aTime - GetStartupTime()).ToMilliseconds(); +} + +// Some helper functions for constructing input event objects suitable to be +// passed either to an APZC (which expects an transformed point), or to an APZTM +// (which expects an untransformed point). We handle both cases by setting both +// the transformed and untransformed fields to the same value. +inline SingleTouchData CreateSingleTouchData(int32_t aIdentifier, + const ScreenIntPoint& aPoint) { + SingleTouchData touch(aIdentifier, aPoint, ScreenSize(0, 0), 0, 0); + touch.mLocalScreenPoint = ParentLayerPoint(aPoint.x, aPoint.y); + return touch; +} + +// Convenience wrapper for CreateSingleTouchData() that takes loose coordinates. +inline SingleTouchData CreateSingleTouchData(int32_t aIdentifier, + ScreenIntCoord aX, + ScreenIntCoord aY) { + return CreateSingleTouchData(aIdentifier, ScreenIntPoint(aX, aY)); +} + +inline PinchGestureInput CreatePinchGestureInput( + PinchGestureInput::PinchGestureType aType, const ScreenPoint& aFocus, + float aCurrentSpan, float aPreviousSpan, TimeStamp timestamp) { + ParentLayerPoint localFocus(aFocus.x, aFocus.y); + PinchGestureInput result(aType, PinchGestureInput::UNKNOWN, timestamp, + ExternalPoint(0, 0), aFocus, aCurrentSpan, + aPreviousSpan, 0); + return result; +} + +template +class ScopedGfxSetting { + public: + ScopedGfxSetting(const std::function& aGetPrefFunc, + const std::function& aSetPrefFunc, SetArg aVal) + : mSetPrefFunc(aSetPrefFunc) { + mOldVal = aGetPrefFunc(); + aSetPrefFunc(aVal); + } + + ~ScopedGfxSetting() { mSetPrefFunc(mOldVal); } + + private: + std::function mSetPrefFunc; + Storage mOldVal; +}; + +static inline constexpr auto kDefaultTouchBehavior = + AllowedTouchBehavior::VERTICAL_PAN | AllowedTouchBehavior::HORIZONTAL_PAN | + AllowedTouchBehavior::PINCH_ZOOM | AllowedTouchBehavior::ANIMATING_ZOOM; + +#define FRESH_PREF_VAR_PASTE(id, line) id##line +#define FRESH_PREF_VAR_EXPAND(id, line) FRESH_PREF_VAR_PASTE(id, line) +#define FRESH_PREF_VAR FRESH_PREF_VAR_EXPAND(pref, __LINE__) + +#define SCOPED_GFX_PREF_BOOL(prefName, prefValue) \ + ScopedGfxSetting FRESH_PREF_VAR( \ + [=]() { return Preferences::GetBool(prefName); }, \ + [=](bool aPrefValue) { Preferences::SetBool(prefName, aPrefValue); }, \ + prefValue) + +#define SCOPED_GFX_PREF_INT(prefName, prefValue) \ + ScopedGfxSetting FRESH_PREF_VAR( \ + [=]() { return Preferences::GetInt(prefName); }, \ + [=](int32_t aPrefValue) { Preferences::SetInt(prefName, aPrefValue); }, \ + prefValue) + +#define SCOPED_GFX_PREF_FLOAT(prefName, prefValue) \ + ScopedGfxSetting FRESH_PREF_VAR( \ + [=]() { return Preferences::GetFloat(prefName); }, \ + [=](float aPrefValue) { Preferences::SetFloat(prefName, aPrefValue); }, \ + prefValue) + +class MockContentController : public GeckoContentController { + public: + MOCK_METHOD1(NotifyLayerTransforms, void(nsTArray&&)); + MOCK_METHOD1(RequestContentRepaint, void(const RepaintRequest&)); + MOCK_METHOD5(HandleTap, void(TapType, const LayoutDevicePoint&, Modifiers, + const ScrollableLayerGuid&, uint64_t)); + MOCK_METHOD5(NotifyPinchGesture, + void(PinchGestureInput::PinchGestureType, + const ScrollableLayerGuid&, const LayoutDevicePoint&, + LayoutDeviceCoord, Modifiers)); + // Can't use the macros with already_AddRefed :( + void PostDelayedTask(already_AddRefed aTask, int aDelayMs) { + RefPtr task = aTask; + } + bool IsRepaintThread() { return NS_IsMainThread(); } + void DispatchToRepaintThread(already_AddRefed aTask) { + NS_DispatchToMainThread(std::move(aTask)); + } + MOCK_METHOD4(NotifyAPZStateChange, + void(const ScrollableLayerGuid& aGuid, APZStateChange aChange, + int aArg, Maybe aInputBlockId)); + MOCK_METHOD0(NotifyFlushComplete, void()); + MOCK_METHOD3(NotifyAsyncScrollbarDragInitiated, + void(uint64_t, const ScrollableLayerGuid::ViewID&, + ScrollDirection aDirection)); + MOCK_METHOD1(NotifyAsyncScrollbarDragRejected, + void(const ScrollableLayerGuid::ViewID&)); + MOCK_METHOD1(NotifyAsyncAutoscrollRejected, + void(const ScrollableLayerGuid::ViewID&)); + MOCK_METHOD1(CancelAutoscroll, void(const ScrollableLayerGuid&)); + MOCK_METHOD2(NotifyScaleGestureComplete, + void(const ScrollableLayerGuid&, float aScale)); + MOCK_METHOD4(UpdateOverscrollVelocity, + void(const ScrollableLayerGuid&, float, float, bool)); + MOCK_METHOD4(UpdateOverscrollOffset, + void(const ScrollableLayerGuid&, float, float, bool)); +}; + +class MockContentControllerDelayed : public MockContentController { + public: + MockContentControllerDelayed() + : mTime(SampleTime::FromTest(GetStartupTime())) {} + + const TimeStamp& Time() { return mTime.Time(); } + const SampleTime& GetSampleTime() { return mTime; } + + void AdvanceByMillis(int aMillis) { + AdvanceBy(TimeDuration::FromMilliseconds(aMillis)); + } + + void AdvanceBy(const TimeDuration& aIncrement) { + SampleTime target = mTime + aIncrement; + while (mTaskQueue.Length() > 0 && mTaskQueue[0].second <= target) { + RunNextDelayedTask(); + } + mTime = target; + } + + void PostDelayedTask(already_AddRefed aTask, int aDelayMs) { + RefPtr task = aTask; + SampleTime runAtTime = mTime + TimeDuration::FromMilliseconds(aDelayMs); + int insIndex = mTaskQueue.Length(); + while (insIndex > 0) { + if (mTaskQueue[insIndex - 1].second <= runAtTime) { + break; + } + insIndex--; + } + mTaskQueue.InsertElementAt(insIndex, std::make_pair(task, runAtTime)); + } + + // Run all the tasks in the queue, returning the number of tasks + // run. Note that if a task queues another task while running, that + // new task will not be run. Therefore, there may be still be tasks + // in the queue after this function is called. Only when the return + // value is 0 is the queue guaranteed to be empty. + int RunThroughDelayedTasks() { + nsTArray, SampleTime>> runQueue = + std::move(mTaskQueue); + int numTasks = runQueue.Length(); + for (int i = 0; i < numTasks; i++) { + mTime = runQueue[i].second; + runQueue[i].first->Run(); + + // Deleting the task is important in order to release the reference to + // the callee object. + runQueue[i].first = nullptr; + } + return numTasks; + } + + private: + void RunNextDelayedTask() { + std::pair, SampleTime> next = mTaskQueue[0]; + mTaskQueue.RemoveElementAt(0); + mTime = next.second; + next.first->Run(); + // Deleting the task is important in order to release the reference to + // the callee object. + next.first = nullptr; + } + + // The following array is sorted by timestamp (tasks are inserted in order by + // timestamp). + nsTArray, SampleTime>> mTaskQueue; + SampleTime mTime; +}; + +class TestAPZCTreeManager : public APZCTreeManager { + public: + explicit TestAPZCTreeManager(MockContentControllerDelayed* aMcc, + UniquePtr aHitTester = nullptr) + : APZCTreeManager(LayersId{0}, std::move(aHitTester)), mcc(aMcc) {} + + RefPtr GetInputQueue() const { return mInputQueue; } + + void ClearContentController() { mcc = nullptr; } + + /** + * This function is not currently implemented. + * See bug 1468804 for more information. + **/ + void CancelAnimation() { EXPECT_TRUE(false); } + + APZEventResult ReceiveInputEvent( + InputData& aEvent, + InputBlockCallback&& aCallback = InputBlockCallback()) override { + APZEventResult result = + APZCTreeManager::ReceiveInputEvent(aEvent, std::move(aCallback)); + if (aEvent.mInputType == PANGESTURE_INPUT && + // In the APZCTreeManager::ReceiveInputEvent some type of pan gesture + // events are marked as `mHandledByAPZ = false` (e.g. with Ctrl key + // modifier which causes reflow zoom), in such cases the events will + // never be processed by InputQueue so we shouldn't try to invoke + // AllowsSwipe() here. + aEvent.AsPanGestureInput().mHandledByAPZ && + aEvent.AsPanGestureInput().AllowsSwipe()) { + SetBrowserGestureResponse(result.mInputBlockId, + BrowserGestureResponse::NotConsumed); + } + return result; + } + + protected: + AsyncPanZoomController* NewAPZCInstance( + LayersId aLayersId, GeckoContentController* aController) override; + + SampleTime GetFrameTime() override { return mcc->GetSampleTime(); } + + private: + RefPtr mcc; +}; + +class TestAsyncPanZoomController : public AsyncPanZoomController { + public: + TestAsyncPanZoomController(LayersId aLayersId, + MockContentControllerDelayed* aMcc, + TestAPZCTreeManager* aTreeManager, + GestureBehavior aBehavior = DEFAULT_GESTURES) + : AsyncPanZoomController(aLayersId, aTreeManager, + aTreeManager->GetInputQueue(), aMcc, aBehavior), + mWaitForMainThread(false), + mcc(aMcc) {} + + APZEventResult ReceiveInputEvent( + InputData& aEvent, + const Maybe>& aTouchBehaviors = Nothing()) { + // This is a function whose signature matches exactly the ReceiveInputEvent + // on APZCTreeManager. This allows us to templates for functions like + // TouchDown, TouchUp, etc so that we can reuse the code for dispatching + // events into both APZC and APZCTM. + APZEventResult result = GetInputQueue()->ReceiveInputEvent( + this, TargetConfirmationFlags{!mWaitForMainThread}, aEvent, + aTouchBehaviors); + + if (aEvent.mInputType == PANGESTURE_INPUT && + aEvent.AsPanGestureInput().AllowsSwipe()) { + GetInputQueue()->SetBrowserGestureResponse( + result.mInputBlockId, BrowserGestureResponse::NotConsumed); + } + return result; + } + + void ContentReceivedInputBlock(uint64_t aInputBlockId, bool aPreventDefault) { + GetInputQueue()->ContentReceivedInputBlock(aInputBlockId, aPreventDefault); + } + + void ConfirmTarget(uint64_t aInputBlockId) { + RefPtr target = this; + GetInputQueue()->SetConfirmedTargetApzc(aInputBlockId, target); + } + + void SetAllowedTouchBehavior(uint64_t aInputBlockId, + const nsTArray& aBehaviors) { + GetInputQueue()->SetAllowedTouchBehavior(aInputBlockId, aBehaviors); + } + + void SetFrameMetrics(const FrameMetrics& metrics) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + Metrics() = metrics; + } + + void SetScrollMetadata(const ScrollMetadata& aMetadata) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + mScrollMetadata = aMetadata; + } + + FrameMetrics& GetFrameMetrics() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return mScrollMetadata.GetMetrics(); + } + + ScrollMetadata& GetScrollMetadata() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return mScrollMetadata; + } + + const FrameMetrics& GetFrameMetrics() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return mScrollMetadata.GetMetrics(); + } + + using AsyncPanZoomController::GetOverscrollAmount; + using AsyncPanZoomController::GetVelocityVector; + + void AssertStateIsReset() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + EXPECT_EQ(NOTHING, mState); + } + + void AssertStateIsFling() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + EXPECT_EQ(FLING, mState); + } + + void AssertStateIsSmoothScroll() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + EXPECT_EQ(SMOOTH_SCROLL, mState); + } + + void AssertStateIsSmoothMsdScroll() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + EXPECT_EQ(SMOOTHMSD_SCROLL, mState); + } + + void AssertStateIsPanningLockedY() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + EXPECT_EQ(PANNING_LOCKED_Y, mState); + } + + void AssertStateIsPanningLockedX() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + EXPECT_EQ(PANNING_LOCKED_X, mState); + } + + void AssertStateIsPanning() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + EXPECT_EQ(PANNING, mState); + } + + void AssertStateIsPanMomentum() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + EXPECT_EQ(PAN_MOMENTUM, mState); + } + + void SetAxisLocked(ScrollDirections aDirections, bool aLockValue) { + if (aDirections.contains(ScrollDirection::eVertical)) { + mY.SetAxisLocked(aLockValue); + } + if (aDirections.contains(ScrollDirection::eHorizontal)) { + mX.SetAxisLocked(aLockValue); + } + } + + void AssertNotAxisLocked() const { + EXPECT_FALSE(mY.IsAxisLocked()); + EXPECT_FALSE(mX.IsAxisLocked()); + } + + void AssertAxisLocked(ScrollDirection aDirection) const { + switch (aDirection) { + case ScrollDirection::eHorizontal: + EXPECT_TRUE(mY.IsAxisLocked()); + EXPECT_FALSE(mX.IsAxisLocked()); + break; + case ScrollDirection::eVertical: + EXPECT_TRUE(mX.IsAxisLocked()); + EXPECT_FALSE(mY.IsAxisLocked()); + break; + default: + FAIL() << "input direction must be either vertical or horizontal"; + } + } + + void AdvanceAnimationsUntilEnd( + const TimeDuration& aIncrement = TimeDuration::FromMilliseconds(10)) { + while (AdvanceAnimations(mcc->GetSampleTime())) { + mcc->AdvanceBy(aIncrement); + } + } + + bool SampleContentTransformForFrame( + AsyncTransform* aOutTransform, ParentLayerPoint& aScrollOffset, + const TimeDuration& aIncrement = TimeDuration::FromMilliseconds(0)) { + mcc->AdvanceBy(aIncrement); + bool ret = AdvanceAnimations(mcc->GetSampleTime()); + if (aOutTransform) { + *aOutTransform = + GetCurrentAsyncTransform(AsyncPanZoomController::eForHitTesting); + } + aScrollOffset = + GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting); + return ret; + } + + CSSPoint GetCompositedScrollOffset() const { + return GetCurrentAsyncScrollOffset( + AsyncPanZoomController::eForCompositing) / + GetFrameMetrics().GetZoom(); + } + + void SetWaitForMainThread() { mWaitForMainThread = true; } + + bool IsOverscrollAnimationRunning() const { + return mState == PanZoomState::OVERSCROLL_ANIMATION; + } + + private: + bool mWaitForMainThread; + MockContentControllerDelayed* mcc; +}; + +class APZCTesterBase : public ::testing::Test { + public: + APZCTesterBase() { mcc = new NiceMock(); } + + void SetUp() override { + gfxPlatform::GetPlatform(); + // This pref is changed in Pan() without using SCOPED_GFX_PREF + // because the modified value needs to be in place until the touch + // events are processed, which may not happen until the input queue + // is flushed in TearDown(). So, we save and restore its value here. + mTouchStartTolerance = StaticPrefs::apz_touch_start_tolerance(); + } + + void TearDown() override { + Preferences::SetFloat("apz.touch_start_tolerance", mTouchStartTolerance); + } + + enum class PanOptions { + None = 0, + KeepFingerDown = 0x1, + /* + * Do not adjust the touch-start coordinates to overcome the touch-start + * tolerance threshold. If this option is passed, it's up to the caller + * to pass in coordinates that are sufficient to overcome the touch-start + * tolerance *and* cause the desired amount of scrolling. + */ + ExactCoordinates = 0x2, + NoFling = 0x4 + }; + + enum class PinchOptions { + None = 0, + LiftFinger1 = 0x1, + LiftFinger2 = 0x2, + /* + * The bitwise OR result of (LiftFinger1 | LiftFinger2). + * Defined explicitly here because it is used as the default + * argument for PinchWithTouchInput which is defined BEFORE the + * definition of operator| for this class. + */ + LiftBothFingers = 0x3 + }; + + template + APZEventResult Tap(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, TimeDuration aTapLength, + nsEventStatus (*aOutEventStatuses)[2] = nullptr, + uint64_t* aOutInputBlockId = nullptr); + + template + void TapAndCheckStatus(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, TimeDuration aTapLength); + + template + void Pan(const RefPtr& aTarget, + const ScreenIntPoint& aTouchStart, const ScreenIntPoint& aTouchEnd, + PanOptions aOptions = PanOptions::None, + nsTArray* aAllowedTouchBehaviors = nullptr, + nsEventStatus (*aOutEventStatuses)[4] = nullptr, + uint64_t* aOutInputBlockId = nullptr); + + /* + * A version of Pan() that only takes y coordinates rather than (x, y) points + * for the touch start and end points, and uses 10 for the x coordinates. + * This is for convenience, as most tests only need to pan in one direction. + */ + template + void Pan(const RefPtr& aTarget, int aTouchStartY, + int aTouchEndY, PanOptions aOptions = PanOptions::None, + nsTArray* aAllowedTouchBehaviors = nullptr, + nsEventStatus (*aOutEventStatuses)[4] = nullptr, + uint64_t* aOutInputBlockId = nullptr); + + /* + * Dispatches mock touch events to the apzc and checks whether apzc properly + * consumed them and triggered scrolling behavior. + */ + template + void PanAndCheckStatus(const RefPtr& aTarget, int aTouchStartY, + int aTouchEndY, bool aExpectConsumed, + nsTArray* aAllowedTouchBehaviors, + uint64_t* aOutInputBlockId = nullptr); + + template + void DoubleTap(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, + nsEventStatus (*aOutEventStatuses)[4] = nullptr, + uint64_t (*aOutInputBlockIds)[2] = nullptr); + + template + void DoubleTapAndCheckStatus(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, + uint64_t (*aOutInputBlockIds)[2] = nullptr); + + template + void PinchWithTouchInput( + const RefPtr& aTarget, const ScreenIntPoint& aFocus, + const ScreenIntPoint& aSecondFocus, float aScale, int& inputId, + nsTArray* aAllowedTouchBehaviors = nullptr, + nsEventStatus (*aOutEventStatuses)[4] = nullptr, + uint64_t* aOutInputBlockId = nullptr, + PinchOptions aOptions = PinchOptions::LiftBothFingers, + bool aVertical = false); + + // Pinch with one focus point. Zooms in place with no panning + template + void PinchWithTouchInput( + const RefPtr& aTarget, const ScreenIntPoint& aFocus, + float aScale, int& inputId, + nsTArray* aAllowedTouchBehaviors = nullptr, + nsEventStatus (*aOutEventStatuses)[4] = nullptr, + uint64_t* aOutInputBlockId = nullptr, + PinchOptions aOptions = PinchOptions::LiftBothFingers, + bool aVertical = false); + + template + void PinchWithTouchInputAndCheckStatus( + const RefPtr& aTarget, const ScreenIntPoint& aFocus, + float aScale, int& inputId, bool aShouldTriggerPinch, + nsTArray* aAllowedTouchBehaviors); + + template + void PinchWithPinchInput(const RefPtr& aTarget, + const ScreenIntPoint& aFocus, + const ScreenIntPoint& aSecondFocus, float aScale, + nsEventStatus (*aOutEventStatuses)[3] = nullptr); + + template + void PinchWithPinchInputAndCheckStatus(const RefPtr& aTarget, + const ScreenIntPoint& aFocus, + float aScale, + bool aShouldTriggerPinch); + + protected: + RefPtr mcc; + + private: + float mTouchStartTolerance; +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(APZCTesterBase::PanOptions) +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(APZCTesterBase::PinchOptions) + +template +APZEventResult APZCTesterBase::Tap(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, + TimeDuration aTapLength, + nsEventStatus (*aOutEventStatuses)[2], + uint64_t* aOutInputBlockId) { + APZEventResult touchDownResult = TouchDown(aTarget, aPoint, mcc->Time()); + if (aOutEventStatuses) { + (*aOutEventStatuses)[0] = touchDownResult.GetStatus(); + } + if (aOutInputBlockId) { + *aOutInputBlockId = touchDownResult.mInputBlockId; + } + mcc->AdvanceBy(aTapLength); + + // If touch-action is enabled then simulate the allowed touch behaviour + // notification that the main thread is supposed to deliver. + if (touchDownResult.GetStatus() != nsEventStatus_eConsumeNoDefault) { + SetDefaultAllowedTouchBehavior(aTarget, touchDownResult.mInputBlockId); + } + + APZEventResult touchUpResult = TouchUp(aTarget, aPoint, mcc->Time()); + if (aOutEventStatuses) { + (*aOutEventStatuses)[1] = touchUpResult.GetStatus(); + } + return touchDownResult; +} + +template +void APZCTesterBase::TapAndCheckStatus(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, + TimeDuration aTapLength) { + nsEventStatus statuses[2]; + Tap(aTarget, aPoint, aTapLength, &statuses); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[1]); +} + +template +void APZCTesterBase::Pan(const RefPtr& aTarget, + const ScreenIntPoint& aTouchStart, + const ScreenIntPoint& aTouchEnd, PanOptions aOptions, + nsTArray* aAllowedTouchBehaviors, + nsEventStatus (*aOutEventStatuses)[4], + uint64_t* aOutInputBlockId) { + // Reduce the move tolerance to a tiny value. + // We can't use a scoped pref because this value might be read at some later + // time when the events are actually processed, rather than when we deliver + // them. + const float touchStartTolerance = 0.1f; + const float panThreshold = touchStartTolerance * aTarget->GetDPI(); + Preferences::SetFloat("apz.touch_start_tolerance", touchStartTolerance); + Preferences::SetFloat("apz.touch_move_tolerance", 0.0f); + int overcomeTouchToleranceX = 0; + int overcomeTouchToleranceY = 0; + if (!(aOptions & PanOptions::ExactCoordinates)) { + // Have the direction of the adjustment to overcome the touch tolerance + // match the direction of the entire gesture, otherwise we run into + // trouble such as accidentally activating the axis lock. + if (aTouchStart.x != aTouchEnd.x && aTouchStart.y != aTouchEnd.y) { + // Tests that need to avoid rounding error here can arrange for + // panThreshold to be 10 (by setting the DPI to 100), which makes sure + // that these are the legs in a Pythagorean triple where panThreshold is + // the hypotenuse. Watch out for changes of APZCPinchTester::mDPI. + overcomeTouchToleranceX = panThreshold / 10 * 6; + overcomeTouchToleranceY = panThreshold / 10 * 8; + } else if (aTouchStart.x != aTouchEnd.x) { + overcomeTouchToleranceX = panThreshold; + } else if (aTouchStart.y != aTouchEnd.y) { + overcomeTouchToleranceY = panThreshold; + } + } + + const TimeDuration TIME_BETWEEN_TOUCH_EVENT = + TimeDuration::FromMilliseconds(20); + + // Even if the caller doesn't care about the block id, we need it to set the + // allowed touch behaviour below, so make sure aOutInputBlockId is non-null. + uint64_t blockId; + if (!aOutInputBlockId) { + aOutInputBlockId = &blockId; + } + + // Make sure the move is large enough to not be handled as a tap + APZEventResult result = + TouchDown(aTarget, + ScreenIntPoint(aTouchStart.x + overcomeTouchToleranceX, + aTouchStart.y + overcomeTouchToleranceY), + mcc->Time()); + if (aOutInputBlockId) { + *aOutInputBlockId = result.mInputBlockId; + } + if (aOutEventStatuses) { + (*aOutEventStatuses)[0] = result.GetStatus(); + } + + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + // Allowed touch behaviours must be set after sending touch-start. + if (result.GetStatus() != nsEventStatus_eConsumeNoDefault) { + if (aAllowedTouchBehaviors) { + EXPECT_EQ(1UL, aAllowedTouchBehaviors->Length()); + aTarget->SetAllowedTouchBehavior(*aOutInputBlockId, + *aAllowedTouchBehaviors); + } else { + SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId); + } + } + + result = TouchMove(aTarget, aTouchStart, mcc->Time()); + if (aOutEventStatuses) { + (*aOutEventStatuses)[1] = result.GetStatus(); + } + + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + const int numSteps = 3; + auto stepVector = (aTouchEnd - aTouchStart) / numSteps; + for (int k = 1; k < numSteps; k++) { + auto stepPoint = aTouchStart + stepVector * k; + Unused << TouchMove(aTarget, stepPoint, mcc->Time()); + + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + } + + result = TouchMove(aTarget, aTouchEnd, mcc->Time()); + if (aOutEventStatuses) { + (*aOutEventStatuses)[2] = result.GetStatus(); + } + + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + if (!(aOptions & PanOptions::KeepFingerDown)) { + result = TouchUp(aTarget, aTouchEnd, mcc->Time()); + } else { + result.SetStatusAsIgnore(); + } + if (aOutEventStatuses) { + (*aOutEventStatuses)[3] = result.GetStatus(); + } + + if ((aOptions & PanOptions::NoFling)) { + aTarget->CancelAnimation(); + } + + // Don't increment the time here. Animations started on touch-up, such as + // flings, are affected by elapsed time, and we want to be able to sample + // them immediately after they start, without time having elapsed. +} + +template +void APZCTesterBase::Pan(const RefPtr& aTarget, int aTouchStartY, + int aTouchEndY, PanOptions aOptions, + nsTArray* aAllowedTouchBehaviors, + nsEventStatus (*aOutEventStatuses)[4], + uint64_t* aOutInputBlockId) { + Pan(aTarget, ScreenIntPoint(10, aTouchStartY), ScreenIntPoint(10, aTouchEndY), + aOptions, aAllowedTouchBehaviors, aOutEventStatuses, aOutInputBlockId); +} + +template +void APZCTesterBase::PanAndCheckStatus( + const RefPtr& aTarget, int aTouchStartY, int aTouchEndY, + bool aExpectConsumed, nsTArray* aAllowedTouchBehaviors, + uint64_t* aOutInputBlockId) { + nsEventStatus statuses[4]; // down, move, move, up + Pan(aTarget, aTouchStartY, aTouchEndY, PanOptions::None, + aAllowedTouchBehaviors, &statuses, aOutInputBlockId); + + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]); + + nsEventStatus touchMoveStatus; + if (aExpectConsumed) { + touchMoveStatus = nsEventStatus_eConsumeDoDefault; + } else { + touchMoveStatus = nsEventStatus_eIgnore; + } + EXPECT_EQ(touchMoveStatus, statuses[1]); + EXPECT_EQ(touchMoveStatus, statuses[2]); +} + +template +void APZCTesterBase::DoubleTap(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, + nsEventStatus (*aOutEventStatuses)[4], + uint64_t (*aOutInputBlockIds)[2]) { + APZEventResult result = TouchDown(aTarget, aPoint, mcc->Time()); + if (aOutEventStatuses) { + (*aOutEventStatuses)[0] = result.GetStatus(); + } + if (aOutInputBlockIds) { + (*aOutInputBlockIds)[0] = result.mInputBlockId; + } + mcc->AdvanceByMillis(10); + + // If touch-action is enabled then simulate the allowed touch behaviour + // notification that the main thread is supposed to deliver. + if (result.GetStatus() != nsEventStatus_eConsumeNoDefault) { + SetDefaultAllowedTouchBehavior(aTarget, result.mInputBlockId); + } + + result = TouchUp(aTarget, aPoint, mcc->Time()); + if (aOutEventStatuses) { + (*aOutEventStatuses)[1] = result.GetStatus(); + } + mcc->AdvanceByMillis(10); + result = TouchDown(aTarget, aPoint, mcc->Time()); + if (aOutEventStatuses) { + (*aOutEventStatuses)[2] = result.GetStatus(); + } + if (aOutInputBlockIds) { + (*aOutInputBlockIds)[1] = result.mInputBlockId; + } + mcc->AdvanceByMillis(10); + + if (result.GetStatus() != nsEventStatus_eConsumeNoDefault) { + SetDefaultAllowedTouchBehavior(aTarget, result.mInputBlockId); + } + + result = TouchUp(aTarget, aPoint, mcc->Time()); + if (aOutEventStatuses) { + (*aOutEventStatuses)[3] = result.GetStatus(); + } +} + +template +void APZCTesterBase::DoubleTapAndCheckStatus( + const RefPtr& aTarget, const ScreenIntPoint& aPoint, + uint64_t (*aOutInputBlockIds)[2]) { + nsEventStatus statuses[4]; + DoubleTap(aTarget, aPoint, &statuses, aOutInputBlockIds); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[1]); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[2]); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[3]); +} + +template +void APZCTesterBase::PinchWithTouchInput( + const RefPtr& aTarget, const ScreenIntPoint& aFocus, + float aScale, int& inputId, nsTArray* aAllowedTouchBehaviors, + nsEventStatus (*aOutEventStatuses)[4], uint64_t* aOutInputBlockId, + PinchOptions aOptions, bool aVertical) { + // Perform a pinch gesture with the same start & end focus point + PinchWithTouchInput(aTarget, aFocus, aFocus, aScale, inputId, + aAllowedTouchBehaviors, aOutEventStatuses, + aOutInputBlockId, aOptions, aVertical); +} + +template +void APZCTesterBase::PinchWithTouchInput( + const RefPtr& aTarget, const ScreenIntPoint& aFocus, + const ScreenIntPoint& aSecondFocus, float aScale, int& inputId, + nsTArray* aAllowedTouchBehaviors, + nsEventStatus (*aOutEventStatuses)[4], uint64_t* aOutInputBlockId, + PinchOptions aOptions, bool aVertical) { + // Having pinch coordinates in float type may cause problems with + // high-precision scale values since SingleTouchData accepts integer value. + // But for trivial tests it should be ok. + const float pinchLength = 100.0; + const float pinchLengthScaled = pinchLength * aScale; + + const float pinchLengthX = aVertical ? 0 : pinchLength; + const float pinchLengthScaledX = aVertical ? 0 : pinchLengthScaled; + const float pinchLengthY = aVertical ? pinchLength : 0; + const float pinchLengthScaledY = aVertical ? pinchLengthScaled : 0; + + // Even if the caller doesn't care about the block id, we need it to set the + // allowed touch behaviour below, so make sure aOutInputBlockId is non-null. + uint64_t blockId; + if (!aOutInputBlockId) { + aOutInputBlockId = &blockId; + } + + const TimeDuration TIME_BETWEEN_TOUCH_EVENT = + TimeDuration::FromMilliseconds(20); + + MultiTouchInput mtiStart = + MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, mcc->Time(), 0); + mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocus)); + mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocus)); + APZEventResult result; + result = aTarget->ReceiveInputEvent(mtiStart); + if (aOutInputBlockId) { + *aOutInputBlockId = result.mInputBlockId; + } + if (aOutEventStatuses) { + (*aOutEventStatuses)[0] = result.GetStatus(); + } + + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + if (aAllowedTouchBehaviors) { + EXPECT_EQ(2UL, aAllowedTouchBehaviors->Length()); + aTarget->SetAllowedTouchBehavior(*aOutInputBlockId, + *aAllowedTouchBehaviors); + } else { + SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId, 2); + } + + ScreenIntPoint pinchStartPoint1(aFocus.x - int32_t(pinchLengthX), + aFocus.y - int32_t(pinchLengthY)); + ScreenIntPoint pinchStartPoint2(aFocus.x + int32_t(pinchLengthX), + aFocus.y + int32_t(pinchLengthY)); + + MultiTouchInput mtiMove1 = + MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0); + mtiMove1.mTouches.AppendElement( + CreateSingleTouchData(inputId, pinchStartPoint1)); + mtiMove1.mTouches.AppendElement( + CreateSingleTouchData(inputId + 1, pinchStartPoint2)); + result = aTarget->ReceiveInputEvent(mtiMove1); + if (aOutEventStatuses) { + (*aOutEventStatuses)[1] = result.GetStatus(); + } + + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + // Pinch instantly but move in steps. + const int numSteps = 3; + auto stepVector = (aSecondFocus - aFocus) / numSteps; + for (int k = 1; k < numSteps; k++) { + ScreenIntPoint stepFocus = aFocus + stepVector * k; + ScreenIntPoint stepPoint1(stepFocus.x - int32_t(pinchLengthScaledX), + stepFocus.y - int32_t(pinchLengthScaledY)); + ScreenIntPoint stepPoint2(stepFocus.x + int32_t(pinchLengthScaledX), + stepFocus.y + int32_t(pinchLengthScaledY)); + MultiTouchInput mtiMoveStep = + MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0); + mtiMoveStep.mTouches.AppendElement( + CreateSingleTouchData(inputId, stepPoint1)); + mtiMoveStep.mTouches.AppendElement( + CreateSingleTouchData(inputId + 1, stepPoint2)); + Unused << aTarget->ReceiveInputEvent(mtiMoveStep); + + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + } + + ScreenIntPoint pinchEndPoint1(aSecondFocus.x - int32_t(pinchLengthScaledX), + aSecondFocus.y - int32_t(pinchLengthScaledY)); + ScreenIntPoint pinchEndPoint2(aSecondFocus.x + int32_t(pinchLengthScaledX), + aSecondFocus.y + int32_t(pinchLengthScaledY)); + + MultiTouchInput mtiMove2 = + MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0); + mtiMove2.mTouches.AppendElement( + CreateSingleTouchData(inputId, pinchEndPoint1)); + mtiMove2.mTouches.AppendElement( + CreateSingleTouchData(inputId + 1, pinchEndPoint2)); + result = aTarget->ReceiveInputEvent(mtiMove2); + if (aOutEventStatuses) { + (*aOutEventStatuses)[2] = result.GetStatus(); + } + + if (aOptions & (PinchOptions::LiftFinger1 | PinchOptions::LiftFinger2)) { + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + MultiTouchInput mtiEnd = + MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, mcc->Time(), 0); + if (aOptions & PinchOptions::LiftFinger1) { + mtiEnd.mTouches.AppendElement( + CreateSingleTouchData(inputId, pinchEndPoint1)); + } + if (aOptions & PinchOptions::LiftFinger2) { + mtiEnd.mTouches.AppendElement( + CreateSingleTouchData(inputId + 1, pinchEndPoint2)); + } + result = aTarget->ReceiveInputEvent(mtiEnd); + if (aOutEventStatuses) { + (*aOutEventStatuses)[3] = result.GetStatus(); + } + } + + inputId += 2; +} + +template +void APZCTesterBase::PinchWithTouchInputAndCheckStatus( + const RefPtr& aTarget, const ScreenIntPoint& aFocus, + float aScale, int& inputId, bool aShouldTriggerPinch, + nsTArray* aAllowedTouchBehaviors) { + nsEventStatus statuses[4]; // down, move, move, up + PinchWithTouchInput(aTarget, aFocus, aScale, inputId, aAllowedTouchBehaviors, + &statuses); + + nsEventStatus expectedMoveStatus = aShouldTriggerPinch + ? nsEventStatus_eConsumeDoDefault + : nsEventStatus_eIgnore; + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]); + EXPECT_EQ(expectedMoveStatus, statuses[1]); + EXPECT_EQ(expectedMoveStatus, statuses[2]); +} + +template +void APZCTesterBase::PinchWithPinchInput( + const RefPtr& aTarget, const ScreenIntPoint& aFocus, + const ScreenIntPoint& aSecondFocus, float aScale, + nsEventStatus (*aOutEventStatuses)[3]) { + const TimeDuration TIME_BETWEEN_PINCH_INPUT = + TimeDuration::FromMilliseconds(50); + + auto event = CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_START, + aFocus, 10.0, 10.0, mcc->Time()); + APZEventResult actual = aTarget->ReceiveInputEvent(event); + if (aOutEventStatuses) { + (*aOutEventStatuses)[0] = actual.GetStatus(); + } + mcc->AdvanceBy(TIME_BETWEEN_PINCH_INPUT); + + event = + CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE, + aSecondFocus, 10.0 * aScale, 10.0, mcc->Time()); + actual = aTarget->ReceiveInputEvent(event); + if (aOutEventStatuses) { + (*aOutEventStatuses)[1] = actual.GetStatus(); + } + mcc->AdvanceBy(TIME_BETWEEN_PINCH_INPUT); + + event = + CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_END, aSecondFocus, + 10.0 * aScale, 10.0 * aScale, mcc->Time()); + actual = aTarget->ReceiveInputEvent(event); + if (aOutEventStatuses) { + (*aOutEventStatuses)[2] = actual.GetStatus(); + } +} + +template +void APZCTesterBase::PinchWithPinchInputAndCheckStatus( + const RefPtr& aTarget, const ScreenIntPoint& aFocus, + float aScale, bool aShouldTriggerPinch) { + nsEventStatus statuses[3]; // scalebegin, scale, scaleend + PinchWithPinchInput(aTarget, aFocus, aFocus, aScale, &statuses); + + nsEventStatus expectedStatus = aShouldTriggerPinch + ? nsEventStatus_eConsumeDoDefault + : nsEventStatus_eIgnore; + EXPECT_EQ(expectedStatus, statuses[0]); + EXPECT_EQ(expectedStatus, statuses[1]); +} + +inline FrameMetrics TestFrameMetrics() { + FrameMetrics fm; + + fm.SetDisplayPort(CSSRect(0, 0, 10, 10)); + fm.SetCompositionBounds(ParentLayerRect(0, 0, 10, 10)); + fm.SetScrollableRect(CSSRect(0, 0, 100, 100)); + + return fm; +} + +#endif // mozilla_layers_APZTestCommon_h diff --git a/gfx/layers/apz/test/gtest/InputUtils.h b/gfx/layers/apz/test/gtest/InputUtils.h new file mode 100644 index 0000000000..74f4b640b3 --- /dev/null +++ b/gfx/layers/apz/test/gtest/InputUtils.h @@ -0,0 +1,149 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_InputUtils_h +#define mozilla_layers_InputUtils_h + +/** + * Defines a set of utility functions for generating input events + * to an APZC/APZCTM during APZ gtests. + */ + +#include "APZTestCommon.h" + +/* The InputReceiver template parameter used in the helper functions below needs + * to be a class that implements functions with the signatures: + * APZEventResult ReceiveInputEvent(const InputData& aEvent); + * void SetAllowedTouchBehavior(uint64_t aInputBlockId, + * const nsTArray& aBehaviours); + * The classes that currently implement these are APZCTreeManager and + * TestAsyncPanZoomController. Using this template allows us to test individual + * APZC instances in isolation and also an entire APZ tree, while using the same + * code to dispatch input events. + */ + +template +void SetDefaultAllowedTouchBehavior(const RefPtr& aTarget, + uint64_t aInputBlockId, + int touchPoints = 1) { + nsTArray defaultBehaviors; + // use the default value where everything is allowed + for (int i = 0; i < touchPoints; i++) { + defaultBehaviors.AppendElement( + mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN | + mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN | + mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM | + mozilla::layers::AllowedTouchBehavior::ANIMATING_ZOOM); + } + aTarget->SetAllowedTouchBehavior(aInputBlockId, defaultBehaviors); +} + +inline MultiTouchInput CreateMultiTouchInput( + MultiTouchInput::MultiTouchType aType, TimeStamp aTime) { + return MultiTouchInput(aType, MillisecondsSinceStartup(aTime), aTime, 0); +} + +template +APZEventResult TouchDown(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, TimeStamp aTime) { + MultiTouchInput mti = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, aTime); + mti.mTouches.AppendElement(CreateSingleTouchData(0, aPoint)); + return aTarget->ReceiveInputEvent(mti); +} + +template +APZEventResult TouchMove(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, TimeStamp aTime) { + MultiTouchInput mti = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime); + mti.mTouches.AppendElement(CreateSingleTouchData(0, aPoint)); + return aTarget->ReceiveInputEvent(mti); +} + +template +APZEventResult TouchUp(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, TimeStamp aTime) { + MultiTouchInput mti = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, aTime); + mti.mTouches.AppendElement(CreateSingleTouchData(0, aPoint)); + return aTarget->ReceiveInputEvent(mti); +} + +template +APZEventResult Wheel(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, const ScreenPoint& aDelta, + TimeStamp aTime) { + ScrollWheelInput input(aTime, 0, ScrollWheelInput::SCROLLMODE_INSTANT, + ScrollWheelInput::SCROLLDELTA_PIXEL, aPoint, aDelta.x, + aDelta.y, false, WheelDeltaAdjustmentStrategy::eNone); + return aTarget->ReceiveInputEvent(input); +} + +template +APZEventResult SmoothWheel(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, + const ScreenPoint& aDelta, TimeStamp aTime) { + ScrollWheelInput input(aTime, 0, ScrollWheelInput::SCROLLMODE_SMOOTH, + ScrollWheelInput::SCROLLDELTA_LINE, aPoint, aDelta.x, + aDelta.y, false, WheelDeltaAdjustmentStrategy::eNone); + return aTarget->ReceiveInputEvent(input); +} + +template +APZEventResult MouseDown(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, TimeStamp aTime) { + MouseInput input(MouseInput::MOUSE_DOWN, + MouseInput::ButtonType::PRIMARY_BUTTON, 0, 0, aPoint, aTime, + 0); + return aTarget->ReceiveInputEvent(input); +} + +template +APZEventResult MouseMove(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, TimeStamp aTime) { + MouseInput input(MouseInput::MOUSE_MOVE, + MouseInput::ButtonType::PRIMARY_BUTTON, 0, 0, aPoint, aTime, + 0); + return aTarget->ReceiveInputEvent(input); +} + +template +APZEventResult MouseUp(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, TimeStamp aTime) { + MouseInput input(MouseInput::MOUSE_UP, MouseInput::ButtonType::PRIMARY_BUTTON, + 0, 0, aPoint, aTime, 0); + return aTarget->ReceiveInputEvent(input); +} + +template +APZEventResult PanGesture(PanGestureInput::PanGestureType aType, + const RefPtr& aTarget, + const ScreenIntPoint& aPoint, + const ScreenPoint& aDelta, TimeStamp aTime, + Modifiers aModifiers = MODIFIER_NONE, + bool aSimulateMomentum = false) { + PanGestureInput input(aType, aTime, aPoint, aDelta, aModifiers); + input.mSimulateMomentum = aSimulateMomentum; + if constexpr (std::is_same_v) { + // In the case of TestAsyncPanZoomController we know for sure that the + // event will be handled by APZ so set it explicitly. + input.mHandledByAPZ = true; + } + return aTarget->ReceiveInputEvent(input); +} + +template +APZEventResult PanGestureWithModifiers(PanGestureInput::PanGestureType aType, + Modifiers aModifiers, + const RefPtr& aTarget, + const ScreenIntPoint& aPoint, + const ScreenPoint& aDelta, + TimeStamp aTime) { + return PanGesture(aType, aTarget, aPoint, aDelta, aTime, aModifiers); +} + +#endif // mozilla_layers_InputUtils_h diff --git a/gfx/layers/apz/test/gtest/MockHitTester.cpp b/gfx/layers/apz/test/gtest/MockHitTester.cpp new file mode 100644 index 0000000000..025866ca65 --- /dev/null +++ b/gfx/layers/apz/test/gtest/MockHitTester.cpp @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MockHitTester.h" +#include "apz/src/AsyncPanZoomController.h" +#include "mozilla/layers/ScrollableLayerGuid.h" + +namespace mozilla::layers { + +IAPZHitTester::HitTestResult MockHitTester::GetAPZCAtPoint( + const ScreenPoint& aHitTestPoint, + const RecursiveMutexAutoLock& aProofOfTreeLock) { + MOZ_ASSERT(!mQueuedResults.empty()); + HitTestResult result = std::move(mQueuedResults.front()); + mQueuedResults.pop(); + return result; +} + +void MockHitTester::QueueHitResult(ScrollableLayerGuid::ViewID aScrollId, + gfx::CompositorHitTestInfo aHitInfo) { + LayersId layersId = GetRootLayersId(); // currently this is all the tests use + RefPtr node = + GetTargetNode(ScrollableLayerGuid(layersId, 0, aScrollId), + ScrollableLayerGuid::EqualsIgnoringPresShell); + MOZ_ASSERT(node); + AsyncPanZoomController* apzc = node->GetApzc(); + MOZ_ASSERT(apzc); + HitTestResult result; + result.mTargetApzc = apzc; + result.mHitResult = aHitInfo; + result.mLayersId = layersId; + mQueuedResults.push(std::move(result)); +} + +} // namespace mozilla::layers diff --git a/gfx/layers/apz/test/gtest/MockHitTester.h b/gfx/layers/apz/test/gtest/MockHitTester.h new file mode 100644 index 0000000000..9c91b31152 --- /dev/null +++ b/gfx/layers/apz/test/gtest/MockHitTester.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_MockHitTester_h +#define mozilla_layers_MockHitTester_h + +#include "apz/src/IAPZHitTester.h" +#include "mozilla/gfx/CompositorHitTestInfo.h" + +#include + +namespace mozilla::layers { + +// IAPZHitTester implementation for APZ gtests. +// This does not actually perform hit-testing, it just allows +// the test code to specify the expected hit test results. +class MockHitTester final : public IAPZHitTester { + public: + HitTestResult GetAPZCAtPoint( + const ScreenPoint& aHitTestPoint, + const RecursiveMutexAutoLock& aProofOfTreeLock) override; + + // Queue a hit test result whose target APZC is the APZC + // with scroll id |aScrollId|, and the provided hit test flags. + void QueueHitResult(ScrollableLayerGuid::ViewID aScrollId, + gfx::CompositorHitTestInfo aHitInfo); + + private: + std::queue mQueuedResults; +}; + +} // namespace mozilla::layers + +#endif // define mozilla_layers_MockHitTester_h diff --git a/gfx/layers/apz/test/gtest/TestAxisLock.cpp b/gfx/layers/apz/test/gtest/TestAxisLock.cpp new file mode 100644 index 0000000000..8b0df3e8a2 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestAxisLock.cpp @@ -0,0 +1,645 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCTreeManagerTester.h" +#include "APZTestCommon.h" + +#include "InputUtils.h" +#include "gtest/gtest.h" + +#include + +class APZCAxisLockCompatTester : public APZCTreeManagerTester, + public testing::WithParamInterface { + public: + APZCAxisLockCompatTester() : oldAxisLockMode(0) { CreateMockHitTester(); } + + int oldAxisLockMode; + + UniquePtr registration; + + RefPtr apzc; + + void SetUp() { + APZCTreeManagerTester::SetUp(); + + oldAxisLockMode = Preferences::GetInt("apz.axis_lock.mode"); + + Preferences::SetInt("apz.axis_lock.mode", GetParam()); + } + + void TearDown() { + APZCTreeManagerTester::TearDown(); + + Preferences::SetInt("apz.axis_lock.mode", oldAxisLockMode); + } + + static std::string PrintFromParam(const testing::TestParamInfo& info) { + switch (info.param) { + case 0: + return "FREE"; + case 1: + return "STANDARD"; + case 2: + return "STICKY"; + case 3: + return "DOMINANT_AXIS"; + default: + return "UNKNOWN"; + } + } +}; + +class APZCAxisLockTester : public APZCTreeManagerTester { + public: + APZCAxisLockTester() { CreateMockHitTester(); } + + UniquePtr registration; + + RefPtr apzc; + + void SetupBasicTest() { + const char* treeShape = "x"; + LayerIntRegion layerVisibleRegion[] = { + LayerIntRect(0, 0, 100, 100), + }; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 500, 500)); + + registration = MakeUnique(LayersId{0}, mcc); + + UpdateHitTestingTree(); + } + + void BreakStickyAxisLockTestGesture(const ScrollDirections& aDirections) { + float panX = 0; + float panY = 0; + + if (aDirections.contains(ScrollDirection::eVertical)) { + panY = 30; + } + if (aDirections.contains(ScrollDirection::eHorizontal)) { + panX = 30; + } + + // Kick off the gesture that may lock onto an axis + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 50), + ScreenPoint(panX, panY), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 50), + ScreenPoint(panX, panY), mcc->Time()); + } + + void BreakStickyAxisLockTest(const ScrollDirections& aDirections) { + // Create the gesture for the test. + BreakStickyAxisLockTestGesture(aDirections); + + // Based on the scroll direction(s) ensure the state is what we expect. + if (aDirections == ScrollDirection::eVertical) { + apzc->AssertStateIsPanningLockedY(); + apzc->AssertAxisLocked(ScrollDirection::eVertical); + EXPECT_GT(apzc->GetVelocityVector().y, 0); + EXPECT_EQ(apzc->GetVelocityVector().x, 0); + } else if (aDirections == ScrollDirection::eHorizontal) { + apzc->AssertStateIsPanningLockedX(); + apzc->AssertAxisLocked(ScrollDirection::eHorizontal); + EXPECT_GT(apzc->GetVelocityVector().x, 0); + EXPECT_EQ(apzc->GetVelocityVector().y, 0); + } else { + apzc->AssertStateIsPanning(); + apzc->AssertNotAxisLocked(); + EXPECT_GT(apzc->GetVelocityVector().x, 0); + EXPECT_GT(apzc->GetVelocityVector().y, 0); + } + + // Cleanup for next test. + apzc->AdvanceAnimationsUntilEnd(); + } +}; + +TEST_F(APZCAxisLockTester, BasicDominantAxisUse) { + SCOPED_GFX_PREF_INT("apz.axis_lock.mode", 1); + SCOPED_GFX_PREF_FLOAT("apz.axis_lock.lock_angle", M_PI / 4.0f); + + SetupBasicTest(); + + apzc = ApzcOf(root); + + // Kick off the initial gesture that triggers the momentum scroll. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 50), + ScreenIntPoint(1, 2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 50), + ScreenPoint(15, 30), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 50), + ScreenPoint(15, 30), mcc->Time()); + + // Should be in a PANNING_LOCKED_Y state with no horizontal velocity. + apzc->AssertStateIsPanningLockedY(); + apzc->AssertAxisLocked(ScrollDirection::eVertical); + EXPECT_GT(apzc->GetVelocityVector().y, 0); + EXPECT_EQ(apzc->GetVelocityVector().x, 0); + + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_END, manager, ScreenIntPoint(50, 50), + ScreenPoint(0, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + // Ensure that we have not panned on the horizontal axis. + ParentLayerPoint panEndOffset = apzc->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::AsyncTransformConsumer::eForHitTesting); + EXPECT_EQ(panEndOffset.x, 0); + + // The lock onto the Y axis extends into momentum scroll. + apzc->AssertAxisLocked(ScrollDirection::eVertical); + + // Start the momentum scroll. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, manager, + ScreenIntPoint(50, 50), ScreenPoint(30, 90), mcc->Time()); + mcc->AdvanceByMillis(10); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, manager, + ScreenIntPoint(50, 50), ScreenPoint(10, 30), mcc->Time()); + mcc->AdvanceByMillis(10); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, manager, + ScreenIntPoint(50, 50), ScreenPoint(10, 30), mcc->Time()); + mcc->AdvanceByMillis(10); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + // In momentum locking mode, we should still be locked onto the Y axis. + apzc->AssertStateIsPanMomentum(); + apzc->AssertAxisLocked(ScrollDirection::eVertical); + EXPECT_GT(apzc->GetVelocityVector().y, 0); + EXPECT_EQ(apzc->GetVelocityVector().x, 0); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMEND, manager, + ScreenIntPoint(50, 50), ScreenPoint(0, 0), mcc->Time()); + + // After momentum scroll end, ensure we are no longer locked onto an axis. + apzc->AssertNotAxisLocked(); + + // Wait until the end of the animation and ensure the final state is + // reasonable. + apzc->AdvanceAnimationsUntilEnd(); + ParentLayerPoint finalOffset = apzc->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::AsyncTransformConsumer::eForHitTesting); + + // Ensure we have scrolled some amount on the Y axis in momentum scroll. + EXPECT_GT(finalOffset.y, panEndOffset.y); + EXPECT_EQ(finalOffset.x, 0.0f); +} + +TEST_F(APZCAxisLockTester, NewGestureBreaksMomentumAxisLock) { + SCOPED_GFX_PREF_INT("apz.axis_lock.mode", 1); + SCOPED_GFX_PREF_FLOAT("apz.axis_lock.lock_angle", M_PI / 4.0f); + + SetupBasicTest(); + + apzc = ApzcOf(root); + + // Kick off the initial gesture that triggers the momentum scroll. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 50), + ScreenIntPoint(2, 1), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 50), + ScreenPoint(30, 15), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 50), + ScreenPoint(30, 15), mcc->Time()); + + // Should be in a PANNING_LOCKED_X state with no vertical velocity. + apzc->AssertStateIsPanningLockedX(); + apzc->AssertAxisLocked(ScrollDirection::eHorizontal); + EXPECT_GT(apzc->GetVelocityVector().x, 0); + EXPECT_EQ(apzc->GetVelocityVector().y, 0); + + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_END, manager, ScreenIntPoint(50, 50), + ScreenPoint(0, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + // Double check that we have not panned on the vertical axis. + ParentLayerPoint panEndOffset = apzc->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::AsyncTransformConsumer::eForHitTesting); + EXPECT_EQ(panEndOffset.y, 0); + + // Ensure that the axis locks extends into momentum scroll. + apzc->AssertAxisLocked(ScrollDirection::eHorizontal); + + // Start the momentum scroll. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, manager, + ScreenIntPoint(50, 50), ScreenPoint(80, 40), mcc->Time()); + mcc->AdvanceByMillis(10); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, manager, + ScreenIntPoint(50, 50), ScreenPoint(20, 10), mcc->Time()); + mcc->AdvanceByMillis(10); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, manager, + ScreenIntPoint(50, 50), ScreenPoint(20, 10), mcc->Time()); + mcc->AdvanceByMillis(10); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + // In momentum locking mode, we should still be locked onto the X axis. + apzc->AssertStateIsPanMomentum(); + apzc->AssertAxisLocked(ScrollDirection::eHorizontal); + EXPECT_GT(apzc->GetVelocityVector().x, 0); + EXPECT_EQ(apzc->GetVelocityVector().y, 0); + + ParentLayerPoint beforeBreakOffset = apzc->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::AsyncTransformConsumer::eForHitTesting); + EXPECT_EQ(beforeBreakOffset.y, 0); + // Ensure we have scrolled some amount on the X axis in momentum scroll. + EXPECT_GT(beforeBreakOffset.x, panEndOffset.x); + + // Kick off the gesture that breaks the lock onto the X axis. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 50), + ScreenIntPoint(1, 2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + ParentLayerPoint afterBreakOffset = apzc->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::AsyncTransformConsumer::eForHitTesting); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 50), + ScreenPoint(15, 30), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 50), + ScreenPoint(15, 30), mcc->Time()); + + // The lock onto the X axis should be broken and we now should be locked + // onto the Y axis. + apzc->AssertStateIsPanningLockedY(); + apzc->AssertAxisLocked(ScrollDirection::eVertical); + EXPECT_GT(apzc->GetVelocityVector().y, 0); + EXPECT_EQ(apzc->GetVelocityVector().x, 0); + + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_END, manager, ScreenIntPoint(50, 50), + ScreenPoint(0, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + // The lock onto the Y axis extends into momentum scroll. + apzc->AssertAxisLocked(ScrollDirection::eVertical); + + // Wait until the end of the animation and ensure the final state is + // reasonable. + apzc->AdvanceAnimationsUntilEnd(); + ParentLayerPoint finalOffset = apzc->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::AsyncTransformConsumer::eForHitTesting); + + EXPECT_GT(finalOffset.y, 0); + // Ensure that we did not scroll on the X axis after the vertical scroll + // started. + EXPECT_EQ(finalOffset.x, afterBreakOffset.x); +} + +TEST_F(APZCAxisLockTester, BreakStickyAxisLock) { + SCOPED_GFX_PREF_INT("apz.axis_lock.mode", 2); + SCOPED_GFX_PREF_FLOAT("apz.axis_lock.lock_angle", M_PI / 6.0f); + SCOPED_GFX_PREF_FLOAT("apz.axis_lock.breakout_angle", M_PI / 6.0f); + + SetupBasicTest(); + + apzc = ApzcOf(root); + + // Start a gesture to get us locked onto the Y axis. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 50), + ScreenIntPoint(0, 2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + // Ensure that we have locked onto the Y axis. + apzc->AssertStateIsPanningLockedY(); + + // Test switch to locking onto the X axis. + BreakStickyAxisLockTest(ScrollDirection::eHorizontal); + + // Test switch back to locking onto the Y axis. + BreakStickyAxisLockTest(ScrollDirection::eVertical); + + // Test breaking all axis locks from a Y axis lock. + BreakStickyAxisLockTest(ScrollDirections(ScrollDirection::eHorizontal, + ScrollDirection::eVertical)); + + // We should be in a panning state. + apzc->AssertStateIsPanning(); + apzc->AssertNotAxisLocked(); + + // Lock back to the X axis. + BreakStickyAxisLockTestGesture(ScrollDirection::eHorizontal); + + // End the gesture. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_END, manager, ScreenIntPoint(50, 50), + ScreenPoint(0, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + // Start a gesture to get us locked onto the X axis. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 50), + ScreenIntPoint(2, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + // Ensure that we have locked onto the X axis. + apzc->AssertStateIsPanningLockedX(); + + // Test breaking all axis locks from a X axis lock. + BreakStickyAxisLockTest(ScrollDirections(ScrollDirection::eHorizontal, + ScrollDirection::eVertical)); + + // We should be in a panning state. + apzc->AssertStateIsPanning(); + apzc->AssertNotAxisLocked(); + + // Test switch back to locking onto the Y axis. + BreakStickyAxisLockTest(ScrollDirection::eVertical); +} + +TEST_F(APZCAxisLockTester, BreakAxisLockByLockAngle) { + SCOPED_GFX_PREF_INT("apz.axis_lock.mode", 2); + SCOPED_GFX_PREF_FLOAT("apz.axis_lock.lock_angle", M_PI / 4.0f); + SCOPED_GFX_PREF_FLOAT("apz.axis_lock.breakout_angle", M_PI / 8.0f); + + SetupBasicTest(); + + apzc = ApzcOf(root); + + // Start a gesture to get us locked onto the Y axis. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 50), + ScreenIntPoint(1, 10), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + // Ensure that we have locked onto the Y axis. + apzc->AssertStateIsPanningLockedY(); + + // Stay within 45 degrees from the X axis, and more than 22.5 degrees from + // the Y axis. This should break the Y lock and lock us to the X axis. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_PAN, manager, ScreenIntPoint(50, 50), + ScreenIntPoint(12, 10), mcc->Time()); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + // Ensure that we have locked onto the X axis. + apzc->AssertStateIsPanningLockedX(); + + // End the gesture. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_END, manager, ScreenIntPoint(50, 50), + ScreenPoint(0, 0), mcc->Time()); + apzc->AdvanceAnimations(mcc->GetSampleTime()); +} + +TEST_F(APZCAxisLockTester, TestDominantAxisScrolling) { + SCOPED_GFX_PREF_INT("apz.axis_lock.mode", 3); + + int panY; + int panX; + + SetupBasicTest(); + + apzc = ApzcOf(root); + + ParentLayerPoint lastOffset = + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting); + + // In dominant axis mode, test pan gesture events with varying gesture + // angles and ensure that we only pan on one axis. + for (panX = 0, panY = 50; panY >= 0; panY -= 10, panX += 5) { + // Gesture that should be locked onto one axis + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_START, manager, + ScreenIntPoint(50, 50), ScreenIntPoint(panX, panY), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 50), + ScreenPoint(static_cast(panX), static_cast(panY)), + mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_END, manager, ScreenIntPoint(50, 50), + ScreenPoint(0, 0), mcc->Time()); + apzc->AdvanceAnimationsUntilEnd(); + + ParentLayerPoint scrollOffset = apzc->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::eForHitTesting); + + if (panX > panY) { + // If we're closer to the X axis ensure that we moved on the horizontal + // axis and there was no movement on the vertical axis. + EXPECT_GT(scrollOffset.x, lastOffset.x); + EXPECT_EQ(scrollOffset.y, lastOffset.y); + } else { + // If we're closer to the Y axis ensure that we moved on the vertical + // axis and there was no movement on the horizontal axis. + EXPECT_GT(scrollOffset.y, lastOffset.y); + EXPECT_EQ(scrollOffset.x, lastOffset.x); + } + + lastOffset = scrollOffset; + } +} + +TEST_F(APZCAxisLockTester, TestCanScrollWithAxisLock) { + SCOPED_GFX_PREF_INT("apz.axis_lock.mode", 2); + + SetupBasicTest(); + + apzc = ApzcOf(root); + + // The axis locks do not impact CanScroll() + apzc->SetAxisLocked(ScrollDirection::eHorizontal, true); + EXPECT_EQ(apzc->CanScroll(ParentLayerPoint(10, 0)), true); + + apzc->SetAxisLocked(ScrollDirection::eHorizontal, false); + apzc->SetAxisLocked(ScrollDirection::eVertical, true); + EXPECT_EQ(apzc->CanScroll(ParentLayerPoint(0, 10)), true); +} + +TEST_F(APZCAxisLockTester, TestScrollHandoffAxisLockConflict) { + SCOPED_GFX_PREF_INT("apz.axis_lock.mode", 2); + + // Create two scrollable frames. One parent frame with one child. + const char* treeShape = "x(x)"; + LayerIntRegion layerVisibleRegion[] = { + LayerIntRect(0, 0, 100, 100), + LayerIntRect(0, 0, 100, 100), + }; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 500, 500)); + SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 500, 500)); + SetScrollHandoff(layers[1], root); + + registration = MakeUnique(LayersId{0}, mcc); + + UpdateHitTestingTree(); + + RefPtr rootApzc = ApzcOf(root); + apzc = ApzcOf(layers[1]); + + // Create a gesture on the y-axis that should lock the x axis. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 50), + ScreenIntPoint(0, 2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 50), + ScreenPoint(0, 15), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + PanGesture(PanGestureInput::PANGESTURE_END, manager, ScreenIntPoint(50, 50), + ScreenPoint(0, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimationsUntilEnd(); + + // We are locked onto the y-axis. + apzc->AssertAxisLocked(ScrollDirection::eVertical); + + // There should be movement in the child. + ParentLayerPoint childCurrentOffset = apzc->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::AsyncTransformConsumer::eForHitTesting); + EXPECT_GT(childCurrentOffset.y, 0); + EXPECT_EQ(childCurrentOffset.x, 0); + + // There should be no movement in the parent. + ParentLayerPoint parentCurrentOffset = rootApzc->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::AsyncTransformConsumer::eForHitTesting); + EXPECT_EQ(parentCurrentOffset.y, 0); + EXPECT_EQ(parentCurrentOffset.x, 0); + + // Create a gesture on the x-axis, that should be directed + // at the child, even if the x-axis is locked. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 50), + ScreenIntPoint(2, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 50), + ScreenPoint(15, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + PanGesture(PanGestureInput::PANGESTURE_END, manager, ScreenIntPoint(50, 50), + ScreenPoint(0, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimationsUntilEnd(); + + // We broke the y-axis lock and are now locked onto the x-axis. + apzc->AssertAxisLocked(ScrollDirection::eHorizontal); + + // There should be some movement in the child on the x-axis. + ParentLayerPoint childFinalOffset = apzc->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::AsyncTransformConsumer::eForHitTesting); + EXPECT_GT(childFinalOffset.x, 0); + + // There should still be no movement in the parent. + ParentLayerPoint parentFinalOffset = rootApzc->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::AsyncTransformConsumer::eForHitTesting); + EXPECT_EQ(parentFinalOffset.y, 0); + EXPECT_EQ(parentFinalOffset.x, 0); +} + +// The delta from the initial pan gesture should be reflected in the +// current offset for all axis locking modes. +TEST_P(APZCAxisLockCompatTester, TestPanGestureStart) { + const char* treeShape = "x"; + LayerIntRegion layerVisibleRegion[] = { + LayerIntRect(0, 0, 100, 100), + }; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 500, 500)); + + registration = MakeUnique(LayersId{0}, mcc); + + UpdateHitTestingTree(); + + apzc = ApzcOf(root); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 50), + ScreenIntPoint(0, 10), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimationsUntilEnd(); + ParentLayerPoint currentOffset = apzc->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::AsyncTransformConsumer::eForHitTesting); + + EXPECT_EQ(currentOffset.x, 0); + EXPECT_EQ(currentOffset.y, 10); +} + +// All APZCAxisLockCompatTester tests should be run for each apz.axis_lock.mode. +// If another mode is added, the value should be added to this list. +INSTANTIATE_TEST_SUITE_P(APZCAxisLockCompat, APZCAxisLockCompatTester, + testing::Values(0, 1, 2, 3), + APZCAxisLockCompatTester::PrintFromParam); diff --git a/gfx/layers/apz/test/gtest/TestBasic.cpp b/gfx/layers/apz/test/gtest/TestBasic.cpp new file mode 100644 index 0000000000..52cebfccd4 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestBasic.cpp @@ -0,0 +1,639 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCBasicTester.h" +#include "APZTestCommon.h" + +#include "InputUtils.h" + +static ScrollGenerationCounter sGenerationCounter; + +TEST_F(APZCBasicTester, Overzoom) { + // the visible area of the document in CSS pixels is x=10 y=0 w=100 h=100 + FrameMetrics fm; + fm.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + fm.SetScrollableRect(CSSRect(0, 0, 125, 150)); + fm.SetVisualScrollOffset(CSSPoint(10, 0)); + fm.SetZoom(CSSToParentLayerScale(1.0)); + fm.SetIsRootContent(true); + apzc->SetFrameMetrics(fm); + + MakeApzcZoomable(); + + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); + + PinchWithPinchInputAndCheckStatus(apzc, ScreenIntPoint(50, 50), 0.5, true); + + fm = apzc->GetFrameMetrics(); + EXPECT_EQ(0.8f, fm.GetZoom().scale); + // bug 936721 - PGO builds introduce rounding error so + // use a fuzzy match instead + EXPECT_LT(std::abs(fm.GetVisualScrollOffset().x), 1e-5); + EXPECT_LT(std::abs(fm.GetVisualScrollOffset().y), 1e-5); +} + +TEST_F(APZCBasicTester, ZoomLimits) { + SCOPED_GFX_PREF_FLOAT("apz.min_zoom", 0.9f); + SCOPED_GFX_PREF_FLOAT("apz.max_zoom", 2.0f); + + // the visible area of the document in CSS pixels is x=10 y=0 w=100 h=100 + FrameMetrics fm; + fm.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + fm.SetScrollableRect(CSSRect(0, 0, 125, 150)); + fm.SetZoom(CSSToParentLayerScale(1.0)); + fm.SetIsRootContent(true); + apzc->SetFrameMetrics(fm); + + MakeApzcZoomable(); + + // This should take the zoom scale to 0.8, but we've capped it at 0.9. + PinchWithPinchInputAndCheckStatus(apzc, ScreenIntPoint(50, 50), 0.5, true); + + fm = apzc->GetFrameMetrics(); + EXPECT_EQ(0.9f, fm.GetZoom().scale); + + // This should take the zoom scale to 2.7, but we've capped it at 2. + PinchWithPinchInputAndCheckStatus(apzc, ScreenIntPoint(50, 50), 3, true); + + fm = apzc->GetFrameMetrics(); + EXPECT_EQ(2.0f, fm.GetZoom().scale); +} + +TEST_F(APZCBasicTester, SimpleTransform) { + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + + EXPECT_EQ(ParentLayerPoint(), pointOut); + EXPECT_EQ(AsyncTransform(), viewTransformOut); +} + +TEST_F(APZCBasicTester, ComplexTransform) { + // This test assumes there is a page that gets rendered to + // two layers. In CSS pixels, the first layer is 50x50 and + // the second layer is 25x50. The widget scale factor is 3.0 + // and the presShell resolution is 2.0. Therefore, these layers + // end up being 300x300 and 150x300 in layer pixels. + // + // The second (child) layer has an additional CSS transform that + // stretches it by 2.0 on the x-axis. Therefore, after applying + // CSS transforms, the two layers are the same size in screen + // pixels. + // + // The screen itself is 24x24 in screen pixels (therefore 4x4 in + // CSS pixels). The displayport is 1 extra CSS pixel on all + // sides. + + RefPtr childApzc = + new TestAsyncPanZoomController(LayersId{0}, mcc, tm); + + const char* treeShape = "x(x)"; + // LayerID 0 1 + LayerIntRegion layerVisibleRegion[] = { + LayerIntRect(0, 0, 300, 300), + LayerIntRect(0, 0, 150, 300), + }; + Matrix4x4 transforms[] = { + Matrix4x4(), + Matrix4x4(), + }; + transforms[0].PostScale( + 0.5f, 0.5f, + 1.0f); // this results from the 2.0 resolution on the root layer + transforms[1].PostScale( + 2.0f, 1.0f, + 1.0f); // this is the 2.0 x-axis CSS transform on the child layer + + auto layers = TestWRScrollData::Create(treeShape, *updater, + layerVisibleRegion, transforms); + + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetCompositionBounds(ParentLayerRect(0, 0, 24, 24)); + metrics.SetDisplayPort(CSSRect(-1, -1, 6, 6)); + metrics.SetVisualScrollOffset(CSSPoint(10, 10)); + metrics.SetLayoutViewport(CSSRect(10, 10, 8, 8)); + metrics.SetScrollableRect(CSSRect(0, 0, 50, 50)); + metrics.SetCumulativeResolution(LayoutDeviceToLayerScale(2)); + metrics.SetPresShellResolution(2.0f); + metrics.SetZoom(CSSToParentLayerScale(6)); + metrics.SetDevPixelsPerCSSPixel(CSSToLayoutDeviceScale(3)); + metrics.SetScrollId(ScrollableLayerGuid::START_SCROLL_ID); + + ScrollMetadata childMetadata = metadata; + FrameMetrics& childMetrics = childMetadata.GetMetrics(); + childMetrics.SetScrollId(ScrollableLayerGuid::START_SCROLL_ID + 1); + + layers[0]->AppendScrollMetadata(layers, metadata); + layers[1]->AppendScrollMetadata(layers, childMetadata); + + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + + // Both the parent and child layer should behave exactly the same here, + // because the CSS transform on the child layer does not affect the + // SampleContentTransformForFrame code + + // initial transform + apzc->SetFrameMetrics(metrics); + apzc->NotifyLayersUpdated(metadata, true, true); + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()), + viewTransformOut); + EXPECT_EQ(ParentLayerPoint(60, 60), pointOut); + + childApzc->SetFrameMetrics(childMetrics); + childApzc->NotifyLayersUpdated(childMetadata, true, true); + childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()), + viewTransformOut); + EXPECT_EQ(ParentLayerPoint(60, 60), pointOut); + + // do an async scroll by 5 pixels and check the transform + metrics.ScrollBy(CSSPoint(5, 0)); + apzc->SetFrameMetrics(metrics); + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + EXPECT_EQ( + AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint(-30, 0)), + viewTransformOut); + EXPECT_EQ(ParentLayerPoint(90, 60), pointOut); + + childMetrics.ScrollBy(CSSPoint(5, 0)); + childApzc->SetFrameMetrics(childMetrics); + childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + EXPECT_EQ( + AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint(-30, 0)), + viewTransformOut); + EXPECT_EQ(ParentLayerPoint(90, 60), pointOut); + + // do an async zoom of 1.5x and check the transform + metrics.ZoomBy(1.5f); + apzc->SetFrameMetrics(metrics); + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + EXPECT_EQ( + AsyncTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)), + viewTransformOut); + EXPECT_EQ(ParentLayerPoint(135, 90), pointOut); + + childMetrics.ZoomBy(1.5f); + childApzc->SetFrameMetrics(childMetrics); + childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + EXPECT_EQ( + AsyncTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)), + viewTransformOut); + EXPECT_EQ(ParentLayerPoint(135, 90), pointOut); + + childApzc->Destroy(); +} + +TEST_F(APZCBasicTester, Fling) { + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + int touchStart = 50; + int touchEnd = 10; + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + + // Fling down. Each step scroll further down + Pan(apzc, touchStart, touchEnd); + ParentLayerPoint lastPoint; + for (int i = 1; i < 50; i += 1) { + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut, + TimeDuration::FromMilliseconds(1)); + EXPECT_GT(pointOut.y, lastPoint.y); + lastPoint = pointOut; + } +} + +#ifndef MOZ_WIDGET_ANDROID // Maybe fails on Android +TEST_F(APZCBasicTester, ResumeInterruptedTouchDrag_Bug1592435) { + // Start a touch-drag and scroll some amount, not lifting the finger. + SCOPED_GFX_PREF_FLOAT("apz.touch_start_tolerance", 1.0f / 1000.0f); + ScreenIntPoint touchPos(10, 50); + uint64_t touchBlock = TouchDown(apzc, touchPos, mcc->Time()).mInputBlockId; + SetDefaultAllowedTouchBehavior(apzc, touchBlock); + for (int i = 0; i < 20; ++i) { + touchPos.y -= 1; + mcc->AdvanceByMillis(1); + TouchMove(apzc, touchPos, mcc->Time()); + } + + // Take note of the scroll offset before the interruption. + CSSPoint scrollOffsetBeforeInterruption = + apzc->GetFrameMetrics().GetVisualScrollOffset(); + + // Have the main thread interrupt the touch-drag by sending + // a main thread scroll update to a nearby location. + CSSPoint mainThreadOffset = scrollOffsetBeforeInterruption; + mainThreadOffset.y -= 5; + ScrollMetadata metadata = apzc->GetScrollMetadata(); + metadata.GetMetrics().SetLayoutScrollOffset(mainThreadOffset); + nsTArray scrollUpdates; + scrollUpdates.AppendElement(ScrollPositionUpdate::NewScroll( + ScrollOrigin::Other, CSSPoint::ToAppUnits(mainThreadOffset))); + metadata.SetScrollUpdates(scrollUpdates); + metadata.GetMetrics().SetScrollGeneration( + scrollUpdates.LastElement().GetGeneration()); + apzc->NotifyLayersUpdated(metadata, false, true); + + // Continue and finish the touch-drag gesture. + for (int i = 0; i < 20; ++i) { + touchPos.y -= 1; + mcc->AdvanceByMillis(1); + TouchMove(apzc, touchPos, mcc->Time()); + } + + // Check that the portion of the touch-drag that occurred after + // the interruption caused additional scrolling. + CSSPoint finalScrollOffset = apzc->GetFrameMetrics().GetVisualScrollOffset(); + EXPECT_GT(finalScrollOffset.y, scrollOffsetBeforeInterruption.y); + + // Now do the same thing, but for a visual scroll update. + scrollOffsetBeforeInterruption = + apzc->GetFrameMetrics().GetVisualScrollOffset(); + mainThreadOffset = scrollOffsetBeforeInterruption; + mainThreadOffset.y -= 5; + metadata = apzc->GetScrollMetadata(); + metadata.GetMetrics().SetVisualDestination(mainThreadOffset); + metadata.GetMetrics().SetScrollGeneration( + sGenerationCounter.NewMainThreadGeneration()); + metadata.GetMetrics().SetVisualScrollUpdateType(FrameMetrics::eMainThread); + scrollUpdates.Clear(); + metadata.SetScrollUpdates(scrollUpdates); + apzc->NotifyLayersUpdated(metadata, false, true); + for (int i = 0; i < 20; ++i) { + touchPos.y -= 1; + mcc->AdvanceByMillis(1); + TouchMove(apzc, touchPos, mcc->Time()); + } + finalScrollOffset = apzc->GetFrameMetrics().GetVisualScrollOffset(); + EXPECT_GT(finalScrollOffset.y, scrollOffsetBeforeInterruption.y); + + // Clean up by ending the touch gesture. + mcc->AdvanceByMillis(1); + TouchUp(apzc, touchPos, mcc->Time()); +} +#endif + +TEST_F(APZCBasicTester, RelativeScrollOffset) { + // Set up initial conditions: zoomed in, layout offset at (100, 100), + // visual offset at (120, 120); the relative offset is therefore (20, 20). + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetScrollableRect(CSSRect(0, 0, 1000, 1000)); + metrics.SetLayoutViewport(CSSRect(100, 100, 100, 100)); + metrics.SetZoom(CSSToParentLayerScale(2.0)); + metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + metrics.SetVisualScrollOffset(CSSPoint(120, 120)); + metrics.SetIsRootContent(true); + apzc->SetFrameMetrics(metrics); + + // Scroll the layout viewport to (200, 200). + ScrollMetadata mainThreadMetadata = metadata; + FrameMetrics& mainThreadMetrics = mainThreadMetadata.GetMetrics(); + mainThreadMetrics.SetLayoutScrollOffset(CSSPoint(200, 200)); + nsTArray scrollUpdates; + scrollUpdates.AppendElement(ScrollPositionUpdate::NewScroll( + ScrollOrigin::Other, CSSPoint::ToAppUnits(CSSPoint(200, 200)))); + mainThreadMetadata.SetScrollUpdates(scrollUpdates); + mainThreadMetrics.SetScrollGeneration( + scrollUpdates.LastElement().GetGeneration()); + apzc->NotifyLayersUpdated(mainThreadMetadata, /*isFirstPaint=*/false, + /*thisLayerTreeUpdated=*/true); + + // Check that the relative offset has been preserved. + metrics = apzc->GetFrameMetrics(); + EXPECT_EQ(metrics.GetLayoutScrollOffset(), CSSPoint(200, 200)); + EXPECT_EQ(metrics.GetVisualScrollOffset(), CSSPoint(220, 220)); +} + +TEST_F(APZCBasicTester, MultipleSmoothScrollsSmooth) { + SCOPED_GFX_PREF_BOOL("general.smoothScroll", true); + // We want to test that if we send multiple smooth scroll requests that we + // still smoothly animate, ie that we get non-zero change every frame while + // the animation is running. + + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetScrollableRect(CSSRect(0, 0, 100, 10000)); + metrics.SetLayoutViewport(CSSRect(0, 0, 100, 100)); + metrics.SetZoom(CSSToParentLayerScale(1.0)); + metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + metrics.SetVisualScrollOffset(CSSPoint(0, 0)); + metrics.SetIsRootContent(true); + apzc->SetFrameMetrics(metrics); + + // Structure of this test. + // -send a pure relative smooth scroll request via NotifyLayersUpdated + // -advance animations a few times, check that scroll offset is increasing + // after the first few advances + // -send a pure relative smooth scroll request via NotifyLayersUpdated + // -advance animations a few times, check that scroll offset is increasing + // -send a pure relative smooth scroll request via NotifyLayersUpdated + // -advance animations a few times, check that scroll offset is increasing + + ScrollMetadata metadata2 = metadata; + nsTArray scrollUpdates2; + scrollUpdates2.AppendElement(ScrollPositionUpdate::NewPureRelativeScroll( + ScrollOrigin::Other, ScrollMode::Smooth, + CSSPoint::ToAppUnits(CSSPoint(0, 200)))); + metadata2.SetScrollUpdates(scrollUpdates2); + metadata2.GetMetrics().SetScrollGeneration( + scrollUpdates2.LastElement().GetGeneration()); + apzc->NotifyLayersUpdated(metadata2, /*isFirstPaint=*/false, + /*thisLayerTreeUpdated=*/true); + + // Get the animation going + for (uint32_t i = 0; i < 3; i++) { + SampleAnimationOneFrame(); + } + + float offset = + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForCompositing) + .y; + ASSERT_GT(offset, 0); + float lastOffset = offset; + + for (uint32_t i = 0; i < 2; i++) { + for (uint32_t j = 0; j < 3; j++) { + SampleAnimationOneFrame(); + offset = apzc->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::eForCompositing) + .y; + ASSERT_GT(offset, lastOffset); + lastOffset = offset; + } + + ScrollMetadata metadata3 = metadata; + nsTArray scrollUpdates3; + scrollUpdates3.AppendElement(ScrollPositionUpdate::NewPureRelativeScroll( + ScrollOrigin::Other, ScrollMode::Smooth, + CSSPoint::ToAppUnits(CSSPoint(0, 200)))); + metadata3.SetScrollUpdates(scrollUpdates3); + metadata3.GetMetrics().SetScrollGeneration( + scrollUpdates3.LastElement().GetGeneration()); + apzc->NotifyLayersUpdated(metadata3, /*isFirstPaint=*/false, + /*thisLayerTreeUpdated=*/true); + } + + for (uint32_t j = 0; j < 7; j++) { + SampleAnimationOneFrame(); + offset = apzc->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::eForCompositing) + .y; + ASSERT_GT(offset, lastOffset); + lastOffset = offset; + } +} + +class APZCSmoothScrollTester : public APZCBasicTester { + public: + // Test that a smooth scroll animation correctly handles its destination + // being updated by a relative scroll delta. + void TestSmoothScrollDestinationUpdate() { + // Set up scroll frame. Starting scroll position is (0, 0). + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetScrollableRect(CSSRect(0, 0, 100, 10000)); + metrics.SetLayoutViewport(CSSRect(0, 0, 100, 100)); + metrics.SetZoom(CSSToParentLayerScale(1.0)); + metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + metrics.SetVisualScrollOffset(CSSPoint(0, 0)); + metrics.SetIsRootContent(true); + apzc->SetFrameMetrics(metrics); + + // Start smooth scroll via main-thread request. + nsTArray scrollUpdates; + scrollUpdates.AppendElement(ScrollPositionUpdate::NewPureRelativeScroll( + ScrollOrigin::Other, ScrollMode::Smooth, + CSSPoint::ToAppUnits(CSSPoint(0, 1000)))); + metadata.SetScrollUpdates(scrollUpdates); + metrics.SetScrollGeneration(scrollUpdates.LastElement().GetGeneration()); + apzc->NotifyLayersUpdated(metadata, false, true); + + // Sample the smooth scroll animation until we get past y=500. + apzc->AssertStateIsSmoothScroll(); + float y = 0; + while (y < 500) { + SampleAnimationOneFrame(); + y = apzc->GetFrameMetrics().GetVisualScrollOffset().y; + } + + // Send a relative scroll of y = -400. + scrollUpdates.Clear(); + scrollUpdates.AppendElement(ScrollPositionUpdate::NewRelativeScroll( + CSSPoint::ToAppUnits(CSSPoint(0, 500)), + CSSPoint::ToAppUnits(CSSPoint(0, 100)))); + metadata.SetScrollUpdates(scrollUpdates); + metrics.SetScrollGeneration(scrollUpdates.LastElement().GetGeneration()); + apzc->NotifyLayersUpdated(metadata, false, false); + + // Verify the relative scroll was applied but didn't cancel the animation. + float y2 = apzc->GetFrameMetrics().GetVisualScrollOffset().y; + ASSERT_EQ(y2, y - 400); + apzc->AssertStateIsSmoothScroll(); + + // Sample the animation again and check that it respected the relative + // scroll. + SampleAnimationOneFrame(); + float y3 = apzc->GetFrameMetrics().GetVisualScrollOffset().y; + ASSERT_GT(y3, y2); + ASSERT_LT(y3, 500); + + // Continue animation until done and check that it ended up at a correctly + // adjusted destination. + apzc->AdvanceAnimationsUntilEnd(); + float y4 = apzc->GetFrameMetrics().GetVisualScrollOffset().y; + ASSERT_EQ(y4, 600); // 1000 (initial destination) - 400 (relative scroll) + } +}; + +TEST_F(APZCSmoothScrollTester, SmoothScrollDestinationUpdateBezier) { + SCOPED_GFX_PREF_BOOL("general.smoothScroll", true); + SCOPED_GFX_PREF_BOOL("general.smoothScroll.msdPhysics.enabled", false); + TestSmoothScrollDestinationUpdate(); +} + +TEST_F(APZCSmoothScrollTester, SmoothScrollDestinationUpdateMsd) { + SCOPED_GFX_PREF_BOOL("general.smoothScroll", true); + SCOPED_GFX_PREF_BOOL("general.smoothScroll.msdPhysics.enabled", true); + TestSmoothScrollDestinationUpdate(); +} + +TEST_F(APZCBasicTester, ZoomAndScrollableRectChangeAfterZoomChange) { + // We want to check that a small scrollable rect change (which causes us to + // reclamp our scroll position, including in the sampled state) does not move + // the scroll offset in the sample state based the zoom in the apzc, only + // based on the zoom in the sampled state. + + // First we zoom in to the right hand side. Then start zooming out, then send + // a scrollable rect change and check that it doesn't change the sampled state + // scroll offset. + + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + metrics.SetScrollableRect(CSSRect(0, 0, 100, 1000)); + metrics.SetLayoutViewport(CSSRect(0, 0, 100, 100)); + metrics.SetVisualScrollOffset(CSSPoint(0, 0)); + metrics.SetZoom(CSSToParentLayerScale(1.0)); + metrics.SetIsRootContent(true); + apzc->SetFrameMetrics(metrics); + + MakeApzcZoomable(); + + // Zoom to right side. + ZoomTarget zoomTarget{CSSRect(75, 25, 25, 25)}; + apzc->ZoomToRect(zoomTarget, 0); + + // Run the animation to completion, should take 250ms/16.67ms = 15 frames, but + // do extra to make sure. + for (uint32_t i = 0; i < 30; i++) { + SampleAnimationOneFrame(); + } + + EXPECT_FALSE(apzc->IsAsyncZooming()); + + // Zoom out. + ZoomTarget zoomTarget2{CSSRect(0, 0, 100, 100)}; + apzc->ZoomToRect(zoomTarget2, 0); + + // Run the animation a few times to get it going. + for (uint32_t i = 0; i < 2; i++) { + SampleAnimationOneFrame(); + } + + // Check that it is decreasing in scale. + float prevScale = + apzc->GetCurrentPinchZoomScale(AsyncPanZoomController::eForCompositing) + .scale; + for (uint32_t i = 0; i < 2; i++) { + SampleAnimationOneFrame(); + float scale = + apzc->GetCurrentPinchZoomScale(AsyncPanZoomController::eForCompositing) + .scale; + ASSERT_GT(prevScale, scale); + prevScale = scale; + } + + float offset = + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForCompositing) + .x; + + // Change the scrollable rect slightly to trigger a reclamp. + ScrollMetadata metadata2 = metadata; + metadata2.GetMetrics().SetScrollableRect(CSSRect(0, 0, 100, 1000.2)); + apzc->NotifyLayersUpdated(metadata2, /*isFirstPaint=*/false, + /*thisLayerTreeUpdated=*/true); + + float newOffset = + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForCompositing) + .x; + + ASSERT_EQ(newOffset, offset); +} + +TEST_F(APZCBasicTester, ZoomToRectAndCompositionBoundsChange) { + // We want to check that content sending a composition bounds change (due to + // addition of scrollbars) during a zoom animation does not cause us to take + // the out of date content resolution. + + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + metrics.SetCompositionBoundsWidthIgnoringScrollbars(ParentLayerCoord{100}); + metrics.SetScrollableRect(CSSRect(0, 0, 100, 1000)); + metrics.SetLayoutViewport(CSSRect(0, 0, 100, 100)); + metrics.SetVisualScrollOffset(CSSPoint(0, 0)); + metrics.SetZoom(CSSToParentLayerScale(1.0)); + metrics.SetIsRootContent(true); + apzc->SetFrameMetrics(metrics); + + MakeApzcZoomable(); + + // Start a zoom to a rect. + ZoomTarget zoomTarget{CSSRect(25, 25, 25, 25)}; + apzc->ZoomToRect(zoomTarget, 0); + + // Run the animation a few times to get it going. + // Check that it is increasing in scale. + float prevScale = + apzc->GetCurrentPinchZoomScale(AsyncPanZoomController::eForCompositing) + .scale; + for (uint32_t i = 0; i < 3; i++) { + SampleAnimationOneFrame(); + float scale = + apzc->GetCurrentPinchZoomScale(AsyncPanZoomController::eForCompositing) + .scale; + ASSERT_GE(scale, prevScale); + prevScale = scale; + } + + EXPECT_TRUE(apzc->IsAsyncZooming()); + + // Simulate the appearance of a scrollbar by reducing the width of + // the composition bounds, while keeping + // mCompositionBoundsWidthIgnoringScrollbars unchanged. + ScrollMetadata metadata2 = metadata; + metadata2.GetMetrics().SetCompositionBounds(ParentLayerRect(0, 0, 90, 100)); + apzc->NotifyLayersUpdated(metadata2, /*isFirstPaint=*/false, + /*thisLayerTreeUpdated=*/true); + + float scale = + apzc->GetCurrentPinchZoomScale(AsyncPanZoomController::eForCompositing) + .scale; + + ASSERT_EQ(scale, prevScale); + + // Run the rest of the animation to completion, should take 250ms/16.67ms = 15 + // frames total, but do extra to make sure. + for (uint32_t i = 0; i < 30; i++) { + SampleAnimationOneFrame(); + scale = + apzc->GetCurrentPinchZoomScale(AsyncPanZoomController::eForCompositing) + .scale; + ASSERT_GE(scale, prevScale); + prevScale = scale; + } + + EXPECT_FALSE(apzc->IsAsyncZooming()); +} + +TEST_F(APZCBasicTester, StartTolerance) { + SCOPED_GFX_PREF_FLOAT("apz.touch_start_tolerance", 10 / tm->GetDPI()); + + FrameMetrics fm; + fm.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + fm.SetScrollableRect(CSSRect(0, 0, 100, 300)); + fm.SetVisualScrollOffset(CSSPoint(0, 50)); + fm.SetIsRootContent(true); + apzc->SetFrameMetrics(fm); + + uint64_t touchBlock = TouchDown(apzc, {50, 50}, mcc->Time()).mInputBlockId; + SetDefaultAllowedTouchBehavior(apzc, touchBlock); + + CSSPoint initialScrollOffset = + apzc->GetFrameMetrics().GetVisualScrollOffset(); + + mcc->AdvanceByMillis(1); + TouchMove(apzc, {50, 70}, mcc->Time()); + + // Expect 10 pixels of scrolling: the distance from (50,50) to (50,70) + // minus the 10-pixel touch start tolerance. + ASSERT_EQ(initialScrollOffset.y - 10, + apzc->GetFrameMetrics().GetVisualScrollOffset().y); + + mcc->AdvanceByMillis(1); + TouchMove(apzc, {50, 90}, mcc->Time()); + + // Expect 30 pixels of scrolling: the distance from (50,50) to (50,90) + // minus the 10-pixel touch start tolerance. + ASSERT_EQ(initialScrollOffset.y - 30, + apzc->GetFrameMetrics().GetVisualScrollOffset().y); + + // Clean up by ending the touch gesture. + mcc->AdvanceByMillis(1); + TouchUp(apzc, {50, 90}, mcc->Time()); +} diff --git a/gfx/layers/apz/test/gtest/TestEventRegions.cpp b/gfx/layers/apz/test/gtest/TestEventRegions.cpp new file mode 100644 index 0000000000..0b4564b49f --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestEventRegions.cpp @@ -0,0 +1,199 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCTreeManagerTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" +#include "mozilla/layers/LayersTypes.h" + +class APZEventRegionsTester : public APZCTreeManagerTester { + protected: + UniquePtr registration; + TestAsyncPanZoomController* rootApzc; + + void CreateEventRegionsLayerTree1() { + const char* treeShape = "x(xx)"; + LayerIntRegion layerVisibleRegions[] = { + LayerIntRect(0, 0, 200, 200), // root + LayerIntRect(0, 0, 100, 200), // left half + LayerIntRect(0, 100, 200, 100), // bottom half + }; + CreateScrollData(treeShape, layerVisibleRegions); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID); + SetScrollableFrameMetrics(layers[1], + ScrollableLayerGuid::START_SCROLL_ID + 1); + SetScrollableFrameMetrics(layers[2], + ScrollableLayerGuid::START_SCROLL_ID + 2); + SetScrollHandoff(layers[1], root); + SetScrollHandoff(layers[2], root); + + registration = MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + rootApzc = ApzcOf(root); + } + + void CreateEventRegionsLayerTree2() { + const char* treeShape = "x(x)"; + LayerIntRegion layerVisibleRegions[] = { + LayerIntRect(0, 0, 100, 500), + LayerIntRect(0, 150, 100, 100), + }; + CreateScrollData(treeShape, layerVisibleRegions); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID); + + registration = MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + rootApzc = ApzcOf(root); + } + + void CreateBug1117712LayerTree() { + const char* treeShape = "x(x(x)x)"; + // LayerID 0 1 2 3 + // 0 is the root + // 1 is a container layer whose sole purpose to make a non-empty ancestor + // transform for 2, so that 2's screen-to-apzc and apzc-to-gecko + // transforms are different from 3's. + // 2 is a small layer that is the actual target + // 3 is a big layer obscuring 2 with a dispatch-to-content region + LayerIntRegion layerVisibleRegions[] = { + LayerIntRect(0, 0, 100, 100), + LayerIntRect(0, 0, 0, 0), + LayerIntRect(0, 0, 10, 10), + LayerIntRect(0, 0, 100, 100), + }; + Matrix4x4 layerTransforms[] = { + Matrix4x4(), + Matrix4x4::Translation(50, 0, 0), + Matrix4x4(), + Matrix4x4(), + }; + CreateScrollData(treeShape, layerVisibleRegions, layerTransforms); + + SetScrollableFrameMetrics(layers[2], ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 10, 10)); + SetScrollableFrameMetrics(layers[3], + ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 100, 100)); + SetScrollHandoff(layers[3], layers[2]); + + registration = MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + } +}; + +class APZEventRegionsTesterMock : public APZEventRegionsTester { + public: + APZEventRegionsTesterMock() { CreateMockHitTester(); } +}; + +TEST_F(APZEventRegionsTesterMock, HitRegionImmediateResponse) { + CreateEventRegionsLayerTree1(); + + TestAsyncPanZoomController* root = ApzcOf(layers[0]); + TestAsyncPanZoomController* left = ApzcOf(layers[1]); + TestAsyncPanZoomController* bottom = ApzcOf(layers[2]); + + MockFunction check; + { + InSequence s; + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, left->GetGuid(), _)) + .Times(1); + EXPECT_CALL(check, Call("Tapped on left")); + EXPECT_CALL(*mcc, + HandleTap(TapType::eSingleTap, _, _, bottom->GetGuid(), _)) + .Times(1); + EXPECT_CALL(check, Call("Tapped on bottom")); + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, root->GetGuid(), _)) + .Times(1); + EXPECT_CALL(check, Call("Tapped on root")); + EXPECT_CALL(check, Call("Tap pending on d-t-c region")); + EXPECT_CALL(*mcc, + HandleTap(TapType::eSingleTap, _, _, bottom->GetGuid(), _)) + .Times(1); + EXPECT_CALL(check, Call("Tapped on bottom again")); + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, left->GetGuid(), _)) + .Times(1); + EXPECT_CALL(check, Call("Tapped on left this time")); + } + + TimeDuration tapDuration = TimeDuration::FromMilliseconds(100); + + // Tap in the exposed hit regions of each of the layers once and ensure + // the clicks are dispatched right away + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + Tap(manager, ScreenIntPoint(10, 10), tapDuration); + mcc->RunThroughDelayedTasks(); // this runs the tap event + check.Call("Tapped on left"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 2); + Tap(manager, ScreenIntPoint(110, 110), tapDuration); + mcc->RunThroughDelayedTasks(); // this runs the tap event + check.Call("Tapped on bottom"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + Tap(manager, ScreenIntPoint(110, 10), tapDuration); + mcc->RunThroughDelayedTasks(); // this runs the tap event + check.Call("Tapped on root"); + + // Now tap on the dispatch-to-content region where the layers overlap + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 2, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + Tap(manager, ScreenIntPoint(10, 110), tapDuration); + mcc->RunThroughDelayedTasks(); // this runs the main-thread timeout + check.Call("Tap pending on d-t-c region"); + mcc->RunThroughDelayedTasks(); // this runs the tap event + check.Call("Tapped on bottom again"); + + // Now let's do that again, but simulate a main-thread response + uint64_t inputBlockId = 0; + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 2, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + Tap(manager, ScreenIntPoint(10, 110), tapDuration, nullptr, &inputBlockId); + nsTArray targets; + targets.AppendElement(left->GetGuid()); + manager->SetTargetAPZC(inputBlockId, targets); + while (mcc->RunThroughDelayedTasks()) + ; // this runs the tap event + check.Call("Tapped on left this time"); +} + +TEST_F(APZEventRegionsTesterMock, HitRegionAccumulatesChildren) { + CreateEventRegionsLayerTree2(); + + // Tap in the area of the child layer that's not directly included in the + // parent layer's hit region. Verify that it comes out of the APZC's + // content controller, which indicates the input events got routed correctly + // to the APZC. + EXPECT_CALL(*mcc, + HandleTap(TapType::eSingleTap, _, _, rootApzc->GetGuid(), _)) + .Times(1); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + Tap(manager, ScreenIntPoint(10, 160), TimeDuration::FromMilliseconds(100)); +} + +TEST_F(APZEventRegionsTesterMock, Bug1117712) { + CreateBug1117712LayerTree(); + + TestAsyncPanZoomController* apzc2 = ApzcOf(layers[2]); + + // These touch events should hit the dispatch-to-content region of layers[3] + // and so get queued with that APZC as the tentative target. + uint64_t inputBlockId = 0; + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + Tap(manager, ScreenIntPoint(55, 5), TimeDuration::FromMilliseconds(100), + nullptr, &inputBlockId); + // But now we tell the APZ that really it hit layers[2], and expect the tap + // to be delivered at the correct coordinates. + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(55, 5), 0, + apzc2->GetGuid(), _)) + .Times(1); + + nsTArray targets; + targets.AppendElement(apzc2->GetGuid()); + manager->SetTargetAPZC(inputBlockId, targets); +} diff --git a/gfx/layers/apz/test/gtest/TestEventResult.cpp b/gfx/layers/apz/test/gtest/TestEventResult.cpp new file mode 100644 index 0000000000..90d17ee511 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestEventResult.cpp @@ -0,0 +1,476 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCTreeManagerTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" +#include "mozilla/EventForwards.h" +#include "mozilla/layers/LayersTypes.h" +#include + +class APZEventResultTester : public APZCTreeManagerTester { + protected: + UniquePtr registration; + + void UpdateOverscrollBehavior(OverscrollBehavior aX, OverscrollBehavior aY) { + ModifyFrameMetrics(root, [aX, aY](ScrollMetadata& sm, FrameMetrics& _) { + OverscrollBehaviorInfo overscroll; + overscroll.mBehaviorX = aX; + overscroll.mBehaviorY = aY; + sm.SetOverscrollBehavior(overscroll); + }); + UpdateHitTestingTree(); + } + + void SetScrollOffsetOnMainThread(const CSSPoint& aPoint) { + RefPtr apzc = ApzcOf(root); + + ScrollMetadata metadata = apzc->GetScrollMetadata(); + metadata.GetMetrics().SetLayoutScrollOffset(aPoint); + nsTArray scrollUpdates; + scrollUpdates.AppendElement(ScrollPositionUpdate::NewScroll( + ScrollOrigin::Other, CSSPoint::ToAppUnits(aPoint))); + metadata.SetScrollUpdates(scrollUpdates); + metadata.GetMetrics().SetScrollGeneration( + scrollUpdates.LastElement().GetGeneration()); + apzc->NotifyLayersUpdated(metadata, /*aIsFirstPaint=*/false, + /*aThisLayerTreeUpdated=*/true); + } + + void CreateScrollableRootLayer() { + const char* treeShape = "x"; + LayerIntRegion layerVisibleRegions[] = { + LayerIntRect(0, 0, 100, 100), + }; + CreateScrollData(treeShape, layerVisibleRegions); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 200, 200)); + ModifyFrameMetrics(root, [](ScrollMetadata& sm, FrameMetrics& metrics) { + metrics.SetIsRootContent(true); + }); + registration = MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + } + + enum class PreventDefaultFlag { No, Yes }; + std::tuple TapDispatchToContent( + const ScreenIntPoint& aPoint, PreventDefaultFlag aPreventDefaultFlag) { + APZEventResult result = + Tap(manager, aPoint, TimeDuration::FromMilliseconds(100)); + + APZHandledResult delayedAnswer{APZHandledPlace::Invalid, SideBits::eNone, + ScrollDirections()}; + manager->AddInputBlockCallback( + result.mInputBlockId, + {result.GetStatus(), [&](uint64_t id, const APZHandledResult& answer) { + EXPECT_EQ(id, result.mInputBlockId); + delayedAnswer = answer; + }}); + manager->SetAllowedTouchBehavior(result.mInputBlockId, + {AllowedTouchBehavior::VERTICAL_PAN}); + manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid}); + manager->ContentReceivedInputBlock( + result.mInputBlockId, aPreventDefaultFlag == PreventDefaultFlag::Yes); + return {result, delayedAnswer}; + } + + void OverscrollDirectionsWithEventHandlerTest( + PreventDefaultFlag aPreventDefaultFlag) { + UpdateHitTestingTree(); + + APZHandledPlace expectedPlace = + aPreventDefaultFlag == PreventDefaultFlag::No + ? APZHandledPlace::HandledByRoot + : APZHandledPlace::HandledByContent; + { + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + auto [result, delayedHandledResult] = + TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag); + EXPECT_EQ(result.GetHandledResult(), Nothing()); + EXPECT_EQ( + delayedHandledResult, + (APZHandledResult{expectedPlace, SideBits::eBottom | SideBits::eRight, + EitherScrollDirection})); + } + + // overscroll-behavior: contain, contain. + UpdateOverscrollBehavior(OverscrollBehavior::Contain, + OverscrollBehavior::Contain); + { + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + auto [result, delayedHandledResult] = + TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag); + EXPECT_EQ(result.GetHandledResult(), Nothing()); + EXPECT_EQ( + delayedHandledResult, + (APZHandledResult{expectedPlace, SideBits::eBottom | SideBits::eRight, + ScrollDirections()})); + } + + // overscroll-behavior: none, none. + UpdateOverscrollBehavior(OverscrollBehavior::None, + OverscrollBehavior::None); + { + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + auto [result, delayedHandledResult] = + TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag); + EXPECT_EQ(result.GetHandledResult(), Nothing()); + EXPECT_EQ( + delayedHandledResult, + (APZHandledResult{expectedPlace, SideBits::eBottom | SideBits::eRight, + ScrollDirections()})); + } + + // overscroll-behavior: auto, none. + UpdateOverscrollBehavior(OverscrollBehavior::Auto, + OverscrollBehavior::None); + { + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + auto [result, delayedHandledResult] = + TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag); + EXPECT_EQ(result.GetHandledResult(), Nothing()); + EXPECT_EQ( + delayedHandledResult, + (APZHandledResult{expectedPlace, SideBits::eBottom | SideBits::eRight, + HorizontalScrollDirection})); + } + + // overscroll-behavior: none, auto. + UpdateOverscrollBehavior(OverscrollBehavior::None, + OverscrollBehavior::Auto); + { + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + auto [result, delayedHandledResult] = + TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag); + EXPECT_EQ(result.GetHandledResult(), Nothing()); + EXPECT_EQ( + delayedHandledResult, + (APZHandledResult{expectedPlace, SideBits::eBottom | SideBits::eRight, + VerticalScrollDirection})); + } + } + + void ScrollableDirectionsWithEventHandlerTest( + PreventDefaultFlag aPreventDefaultFlag) { + UpdateHitTestingTree(); + + APZHandledPlace expectedPlace = + aPreventDefaultFlag == PreventDefaultFlag::No + ? APZHandledPlace::HandledByRoot + : APZHandledPlace::HandledByContent; + { + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + auto [result, delayedHandledResult] = + TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag); + EXPECT_EQ(result.GetHandledResult(), Nothing()); + EXPECT_EQ( + delayedHandledResult, + (APZHandledResult{expectedPlace, SideBits::eBottom | SideBits::eRight, + EitherScrollDirection})); + } + + // scroll down a bit. + SetScrollOffsetOnMainThread(CSSPoint(0, 10)); + { + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + auto [result, delayedHandledResult] = + TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag); + EXPECT_EQ(result.GetHandledResult(), Nothing()); + EXPECT_EQ(delayedHandledResult, + (APZHandledResult{ + expectedPlace, + SideBits::eTop | SideBits::eBottom | SideBits::eRight, + EitherScrollDirection})); + } + + // scroll to the bottom edge + SetScrollOffsetOnMainThread(CSSPoint(0, 100)); + { + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + auto [result, delayedHandledResult] = + TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag); + EXPECT_EQ(result.GetHandledResult(), Nothing()); + EXPECT_EQ( + delayedHandledResult, + (APZHandledResult{expectedPlace, SideBits::eRight | SideBits::eTop, + EitherScrollDirection})); + } + + // scroll to right a bit. + SetScrollOffsetOnMainThread(CSSPoint(10, 100)); + { + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + auto [result, delayedHandledResult] = + TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag); + EXPECT_EQ(result.GetHandledResult(), Nothing()); + EXPECT_EQ( + delayedHandledResult, + (APZHandledResult{expectedPlace, + SideBits::eLeft | SideBits::eRight | SideBits::eTop, + EitherScrollDirection})); + } + + // scroll to the right edge. + SetScrollOffsetOnMainThread(CSSPoint(100, 100)); + { + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + auto [result, delayedHandledResult] = + TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag); + EXPECT_EQ(result.GetHandledResult(), Nothing()); + EXPECT_EQ( + delayedHandledResult, + (APZHandledResult{expectedPlace, SideBits::eTop | SideBits::eLeft, + EitherScrollDirection})); + } + } +}; + +TEST_F(APZEventResultTester, OverscrollDirections) { + CreateScrollableRootLayer(); + + TimeDuration tapDuration = TimeDuration::FromMilliseconds(100); + + // The default value of overscroll-behavior is auto. + APZEventResult result = Tap(manager, ScreenIntPoint(50, 50), tapDuration); + EXPECT_EQ(result.GetHandledResult()->mOverscrollDirections, + EitherScrollDirection); + + // overscroll-behavior: contain, contain. + UpdateOverscrollBehavior(OverscrollBehavior::Contain, + OverscrollBehavior::Contain); + result = Tap(manager, ScreenIntPoint(50, 50), tapDuration); + EXPECT_EQ(result.GetHandledResult()->mOverscrollDirections, + ScrollDirections()); + + // overscroll-behavior: none, none. + UpdateOverscrollBehavior(OverscrollBehavior::None, OverscrollBehavior::None); + result = Tap(manager, ScreenIntPoint(50, 50), tapDuration); + EXPECT_EQ(result.GetHandledResult()->mOverscrollDirections, + ScrollDirections()); + + // overscroll-behavior: auto, none. + UpdateOverscrollBehavior(OverscrollBehavior::Auto, OverscrollBehavior::None); + result = Tap(manager, ScreenIntPoint(50, 50), tapDuration); + EXPECT_EQ(result.GetHandledResult()->mOverscrollDirections, + HorizontalScrollDirection); + + // overscroll-behavior: none, auto. + UpdateOverscrollBehavior(OverscrollBehavior::None, OverscrollBehavior::Auto); + result = Tap(manager, ScreenIntPoint(50, 50), tapDuration); + EXPECT_EQ(result.GetHandledResult()->mOverscrollDirections, + VerticalScrollDirection); +} + +TEST_F(APZEventResultTester, ScrollableDirections) { + CreateScrollableRootLayer(); + + TimeDuration tapDuration = TimeDuration::FromMilliseconds(100); + + APZEventResult result = Tap(manager, ScreenIntPoint(50, 50), tapDuration); + // scrollable to down/right. + EXPECT_EQ(result.GetHandledResult()->mScrollableDirections, + SideBits::eBottom | SideBits::eRight); + + // scroll down a bit. + SetScrollOffsetOnMainThread(CSSPoint(0, 10)); + result = Tap(manager, ScreenIntPoint(50, 50), tapDuration); + // also scrollable toward top. + EXPECT_EQ(result.GetHandledResult()->mScrollableDirections, + SideBits::eTop | SideBits::eBottom | SideBits::eRight); + + // scroll to the bottom edge + SetScrollOffsetOnMainThread(CSSPoint(0, 100)); + result = Tap(manager, ScreenIntPoint(50, 50), tapDuration); + EXPECT_EQ(result.GetHandledResult()->mScrollableDirections, + SideBits::eRight | SideBits::eTop); + + // scroll to right a bit. + SetScrollOffsetOnMainThread(CSSPoint(10, 100)); + result = Tap(manager, ScreenIntPoint(50, 50), tapDuration); + EXPECT_EQ(result.GetHandledResult()->mScrollableDirections, + SideBits::eLeft | SideBits::eRight | SideBits::eTop); + + // scroll to the right edge. + SetScrollOffsetOnMainThread(CSSPoint(100, 100)); + result = Tap(manager, ScreenIntPoint(50, 50), tapDuration); + EXPECT_EQ(result.GetHandledResult()->mScrollableDirections, + SideBits::eLeft | SideBits::eTop); +} + +class APZEventResultTesterMock : public APZEventResultTester { + public: + APZEventResultTesterMock() { CreateMockHitTester(); } +}; + +TEST_F(APZEventResultTesterMock, OverscrollDirectionsWithEventHandler) { + CreateScrollableRootLayer(); + + OverscrollDirectionsWithEventHandlerTest(PreventDefaultFlag::No); +} + +TEST_F(APZEventResultTesterMock, + OverscrollDirectionsWithPreventDefaultEventHandler) { + CreateScrollableRootLayer(); + + OverscrollDirectionsWithEventHandlerTest(PreventDefaultFlag::Yes); +} + +TEST_F(APZEventResultTesterMock, ScrollableDirectionsWithEventHandler) { + CreateScrollableRootLayer(); + + ScrollableDirectionsWithEventHandlerTest(PreventDefaultFlag::No); +} + +TEST_F(APZEventResultTesterMock, + ScrollableDirectionsWithPreventDefaultEventHandler) { + CreateScrollableRootLayer(); + + ScrollableDirectionsWithEventHandlerTest(PreventDefaultFlag::Yes); +} + +// Test that APZEventResult::GetHandledResult() is correctly +// populated. +TEST_F(APZEventResultTesterMock, HandledByRootApzcFlag) { + // Create simple layer tree containing a dispatch-to-content region + // that covers part but not all of its area. + const char* treeShape = "x"; + LayerIntRegion layerVisibleRegions[] = { + LayerIntRect(0, 0, 100, 100), + }; + CreateScrollData(treeShape, layerVisibleRegions); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 100, 200)); + ModifyFrameMetrics(root, [](ScrollMetadata& sm, FrameMetrics& metrics) { + metrics.SetIsRootContent(true); + }); + // away from the scrolling container layer. + registration = MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + + // Tap the top half and check that we report that the event was + // handled by the root APZC. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + APZEventResult result = + TouchDown(manager, ScreenIntPoint(50, 25), mcc->Time()); + TouchUp(manager, ScreenIntPoint(50, 25), mcc->Time()); + EXPECT_EQ(result.GetHandledResult(), + Some(APZHandledResult{APZHandledPlace::HandledByRoot, + SideBits::eBottom, EitherScrollDirection})); + + // Tap the bottom half and check that we report that we're not + // sure whether the event was handled by the root APZC. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + result = TouchDown(manager, ScreenIntPoint(50, 75), mcc->Time()); + TouchUp(manager, ScreenIntPoint(50, 75), mcc->Time()); + EXPECT_EQ(result.GetHandledResult(), Nothing()); + + // Register an input block callback that will tell us the + // delayed answer. + APZHandledResult delayedAnswer{APZHandledPlace::Invalid, SideBits::eNone, + ScrollDirections()}; + manager->AddInputBlockCallback( + result.mInputBlockId, + {result.GetStatus(), [&](uint64_t id, const APZHandledResult& answer) { + EXPECT_EQ(id, result.mInputBlockId); + delayedAnswer = answer; + }}); + + // Send APZ the relevant notifications to allow it to process the + // input block. + manager->SetAllowedTouchBehavior(result.mInputBlockId, + {AllowedTouchBehavior::VERTICAL_PAN}); + manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid}); + manager->ContentReceivedInputBlock(result.mInputBlockId, + /*aPreventDefault=*/false); + + // Check that we received the delayed answer and it is what we expect. + EXPECT_EQ(delayedAnswer, + (APZHandledResult{APZHandledPlace::HandledByRoot, SideBits::eBottom, + EitherScrollDirection})); + + // Now repeat the tap on the bottom half, but simulate a prevent-default. + // This time, we expect a delayed answer of `HandledByContent`. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + result = TouchDown(manager, ScreenIntPoint(50, 75), mcc->Time()); + TouchUp(manager, ScreenIntPoint(50, 75), mcc->Time()); + EXPECT_EQ(result.GetHandledResult(), Nothing()); + manager->AddInputBlockCallback( + result.mInputBlockId, + {result.GetStatus(), [&](uint64_t id, const APZHandledResult& answer) { + EXPECT_EQ(id, result.mInputBlockId); + delayedAnswer = answer; + }}); + manager->SetAllowedTouchBehavior(result.mInputBlockId, + {AllowedTouchBehavior::VERTICAL_PAN}); + manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid}); + manager->ContentReceivedInputBlock(result.mInputBlockId, + /*aPreventDefault=*/true); + EXPECT_EQ(delayedAnswer, + (APZHandledResult{APZHandledPlace::HandledByContent, + SideBits::eBottom, EitherScrollDirection})); + + // Shrink the scrollable area, now it's no longer scrollable. + ModifyFrameMetrics(root, [](ScrollMetadata& sm, FrameMetrics& metrics) { + metrics.SetScrollableRect(CSSRect(0, 0, 100, 100)); + }); + UpdateHitTestingTree(); + // Now repeat the tap on the bottom half with an event handler. + // This time, we expect a delayed answer of `Unhandled`. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + result = TouchDown(manager, ScreenIntPoint(50, 75), mcc->Time()); + TouchUp(manager, ScreenIntPoint(50, 75), mcc->Time()); + EXPECT_EQ(result.GetHandledResult(), Nothing()); + manager->AddInputBlockCallback( + result.mInputBlockId, + {result.GetStatus(), [&](uint64_t id, const APZHandledResult& answer) { + EXPECT_EQ(id, result.mInputBlockId); + delayedAnswer = answer; + }}); + manager->SetAllowedTouchBehavior(result.mInputBlockId, + {AllowedTouchBehavior::VERTICAL_PAN}); + manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid}); + manager->ContentReceivedInputBlock(result.mInputBlockId, + /*aPreventDefault=*/false); + EXPECT_EQ(delayedAnswer, + (APZHandledResult{APZHandledPlace::Unhandled, SideBits::eNone, + ScrollDirections()})); + + // Repeat the tap on the bottom half, with no event handler. + // Make sure we get an eager answer of `Unhandled`. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + result = TouchDown(manager, ScreenIntPoint(50, 75), mcc->Time()); + TouchUp(manager, ScreenIntPoint(50, 75), mcc->Time()); + EXPECT_EQ(result.GetStatus(), nsEventStatus_eIgnore); + EXPECT_EQ(result.GetHandledResult(), + Some(APZHandledResult{APZHandledPlace::Unhandled, SideBits::eNone, + EitherScrollDirection})); +} diff --git a/gfx/layers/apz/test/gtest/TestFlingAcceleration.cpp b/gfx/layers/apz/test/gtest/TestFlingAcceleration.cpp new file mode 100644 index 0000000000..986025bddc --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestFlingAcceleration.cpp @@ -0,0 +1,252 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include "APZCTreeManagerTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" + +class APZCFlingAccelerationTester : public APZCTreeManagerTester { + protected: + void SetUp() { + APZCTreeManagerTester::SetUp(); + const char* treeShape = "x"; + LayerIntRegion layerVisibleRegion[] = { + LayerIntRect(0, 0, 800, 1000), + }; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 800, 50000)); + // Scroll somewhere into the middle of the scroll range, so that we have + // lots of space to scroll in both directions. + ModifyFrameMetrics(root, [](ScrollMetadata& aSm, FrameMetrics& aMetrics) { + aMetrics.SetVisualScrollUpdateType( + FrameMetrics::ScrollOffsetUpdateType::eMainThread); + aMetrics.SetVisualDestination(CSSPoint(0, 25000)); + }); + + registration = MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + + apzc = ApzcOf(root); + } + + void ExecutePanGesture100Hz(const ScreenIntPoint& aStartPoint, + std::initializer_list aYDeltas) { + APZEventResult result = TouchDown(apzc, aStartPoint, mcc->Time()); + + // Allowed touch behaviours must be set after sending touch-start. + if (result.GetStatus() != nsEventStatus_eConsumeNoDefault) { + SetDefaultAllowedTouchBehavior(apzc, result.mInputBlockId); + } + + const TimeDuration kTouchTimeDelta100Hz = + TimeDuration::FromMilliseconds(10); + + ScreenIntPoint currentLocation = aStartPoint; + for (int32_t delta : aYDeltas) { + mcc->AdvanceBy(kTouchTimeDelta100Hz); + if (delta != 0) { + currentLocation.y += delta; + Unused << TouchMove(apzc, currentLocation, mcc->Time()); + } + } + + Unused << TouchUp(apzc, currentLocation, mcc->Time()); + } + + void ExecuteWait(const TimeDuration& aDuration) { + TimeDuration remaining = aDuration; + const TimeDuration TIME_BETWEEN_FRAMES = + TimeDuration::FromSeconds(1) / int64_t(60); + while (remaining.ToMilliseconds() > 0) { + mcc->AdvanceBy(TIME_BETWEEN_FRAMES); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + remaining -= TIME_BETWEEN_FRAMES; + } + } + + RefPtr apzc; + UniquePtr registration; +}; + +enum class UpOrDown : uint8_t { Up, Down }; + +// This is a macro so that the assertions print useful line numbers. +#define CHECK_VELOCITY(aUpOrDown, aLowerBound, aUpperBound) \ + do { \ + auto vel = apzc->GetVelocityVector(); \ + if (UpOrDown::aUpOrDown == UpOrDown::Up) { \ + EXPECT_LT(vel.y, 0.0); \ + } else { \ + EXPECT_GT(vel.y, 0.0); \ + } \ + EXPECT_GE(vel.Length(), aLowerBound); \ + EXPECT_LE(vel.Length(), aUpperBound); \ + } while (0) + +// These tests have the following pattern: Two flings are executed, with a bit +// of wait time in between. The deltas in each pan gesture have been captured +// from a real phone, from touch events triggered by real fingers. +// We check the velocity at the end to detect whether the fling was accelerated +// or not. As an additional safety precaution, we also check the velocities for +// the first fling, so that changes in behavior are easier to analyze. +// One added challenge of this test is the fact that it has to work with on +// multiple platforms, and we use different velocity estimation strategies and +// different fling physics depending on the platform. +// The upper and lower bounds for the velocities were chosen in such a way that +// the test passes on all platforms. At the time of writing, we usually end up +// with higher velocities on Android than on Desktop, so the observed velocities +// on Android became the upper bounds and the observed velocities on Desktop +// becaume the lower bounds, each rounded out to a multiple of 0.1. + +TEST_F(APZCFlingAccelerationTester, TwoNormalFlingsShouldAccelerate) { + ExecutePanGesture100Hz(ScreenIntPoint{665, 1244}, + {0, 0, -21, -44, -52, -55, -53, -49, -46, -47}); + CHECK_VELOCITY(Down, 4.5, 6.8); + + ExecuteWait(TimeDuration::FromMilliseconds(375)); + CHECK_VELOCITY(Down, 2.2, 5.1); + + ExecutePanGesture100Hz(ScreenIntPoint{623, 1211}, + {-6, -51, -55, 0, -53, -57, -60, -60, -56}); + CHECK_VELOCITY(Down, 9.0, 14.0); +} + +TEST_F(APZCFlingAccelerationTester, TwoFastFlingsShouldAccelerate) { + ExecutePanGesture100Hz(ScreenIntPoint{764, 714}, + {9, 30, 49, 60, 64, 64, 62, 59, 51}); + CHECK_VELOCITY(Up, 5.0, 7.5); + + ExecuteWait(TimeDuration::FromMilliseconds(447)); + CHECK_VELOCITY(Up, 2.3, 5.2); + + ExecutePanGesture100Hz(ScreenIntPoint{743, 739}, + {7, 0, 38, 66, 75, 146, 0, 119}); + CHECK_VELOCITY(Up, 13.0, 20.0); +} + +TEST_F(APZCFlingAccelerationTester, + FlingsInOppositeDirectionShouldNotAccelerate) { + ExecutePanGesture100Hz(ScreenIntPoint{728, 1381}, + {0, 0, 0, -12, -24, -32, -43, -46, 0}); + CHECK_VELOCITY(Down, 2.9, 5.3); + + ExecuteWait(TimeDuration::FromMilliseconds(153)); + CHECK_VELOCITY(Down, 2.1, 4.8); + + ExecutePanGesture100Hz(ScreenIntPoint{698, 1059}, + {0, 0, 14, 61, 41, 0, 45, 35}); + CHECK_VELOCITY(Up, 3.2, 4.3); +} + +TEST_F(APZCFlingAccelerationTester, + ShouldNotAccelerateWhenPreviousFlingHasSlowedDown) { + ExecutePanGesture100Hz(ScreenIntPoint{748, 1046}, + {0, 9, 15, 23, 31, 30, 0, 34, 31, 29, 28, 24, 24, 11}); + CHECK_VELOCITY(Up, 2.2, 3.0); + ExecuteWait(TimeDuration::FromMilliseconds(498)); + CHECK_VELOCITY(Up, 0.5, 1.0); + ExecutePanGesture100Hz(ScreenIntPoint{745, 1056}, + {0, 10, 17, 29, 29, 33, 33, 0, 31, 27, 13}); + CHECK_VELOCITY(Up, 1.8, 2.7); +} + +TEST_F(APZCFlingAccelerationTester, ShouldNotAccelerateWhenPausedAtStartOfPan) { + ExecutePanGesture100Hz( + ScreenIntPoint{711, 1468}, + {0, 0, 0, 0, -8, 0, -18, -32, -50, -57, -66, -68, -63, -60}); + CHECK_VELOCITY(Down, 6.2, 8.6); + + ExecuteWait(TimeDuration::FromMilliseconds(285)); + CHECK_VELOCITY(Down, 3.4, 7.4); + + ExecutePanGesture100Hz( + ScreenIntPoint{658, 1352}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, -8, -18, -34, -53, -70, -75, -75, -64}); + CHECK_VELOCITY(Down, 6.7, 9.1); +} + +TEST_F(APZCFlingAccelerationTester, ShouldNotAccelerateWhenPausedDuringPan) { + ExecutePanGesture100Hz( + ScreenIntPoint{732, 1423}, + {0, 0, 0, -5, 0, -15, -41, -71, -90, -93, -85, -64, -44}); + CHECK_VELOCITY(Down, 7.5, 10.1); + + ExecuteWait(TimeDuration::FromMilliseconds(204)); + CHECK_VELOCITY(Down, 4.8, 9.4); + + ExecutePanGesture100Hz( + ScreenIntPoint{651, 1372}, + {0, 0, 0, -6, 0, -16, -26, -41, -49, -65, -66, -61, -50, -35, -24, + -17, -11, -8, -6, -5, -4, -3, -2, -2, -2, -2, -2, -2, -2, -2, + -3, -4, -5, -7, -9, -10, -10, -12, -18, -25, -23, -28, -30, -24}); + CHECK_VELOCITY(Down, 2.5, 3.4); +} + +TEST_F(APZCFlingAccelerationTester, + ShouldNotAccelerateWhenOppositeDirectionDuringPan) { + ExecutePanGesture100Hz(ScreenIntPoint{663, 1371}, + {0, 0, 0, -5, -18, -31, -49, -56, -61, -54, -55}); + CHECK_VELOCITY(Down, 5.4, 7.1); + + ExecuteWait(TimeDuration::FromMilliseconds(255)); + CHECK_VELOCITY(Down, 3.1, 6.0); + + ExecutePanGesture100Hz( + ScreenIntPoint{726, 930}, + {0, 0, 0, 0, 30, 0, 19, 24, 32, 30, 37, 33, + 33, 32, 25, 23, 23, 18, 13, 9, 5, 3, 1, 0, + -7, -19, -38, -53, -68, -79, -85, -73, -64, -54}); + CHECK_VELOCITY(Down, 7.0, 10.0); +} + +TEST_F(APZCFlingAccelerationTester, + ShouldAccelerateAfterLongWaitIfVelocityStillHigh) { + // Reduce friction with the "Desktop" fling physics a little, so that it + // behaves more similarly to the Android fling physics, and has enough + // velocity after the wait time to allow for acceleration. + SCOPED_GFX_PREF_FLOAT("apz.fling_friction", 0.0012); + + ExecutePanGesture100Hz(ScreenIntPoint{739, 1424}, + {0, 0, -5, -10, -20, 0, -110, -86, 0, -102, -105}); + CHECK_VELOCITY(Down, 6.3, 9.4); + + ExecuteWait(TimeDuration::FromMilliseconds(1117)); + CHECK_VELOCITY(Down, 1.6, 3.3); + + ExecutePanGesture100Hz(ScreenIntPoint{726, 1380}, + {0, -8, 0, -30, -60, -87, -104, -111}); + CHECK_VELOCITY(Down, 13.0, 23.0); +} + +TEST_F(APZCFlingAccelerationTester, ShouldNotAccelerateAfterCanceledWithTap) { + // First, build up a lot of speed. + ExecutePanGesture100Hz(ScreenIntPoint{569, 710}, + {11, 2, 107, 18, 148, 57, 133, 159, 21}); + ExecuteWait(TimeDuration::FromMilliseconds(154)); + ExecutePanGesture100Hz(ScreenIntPoint{581, 650}, + {12, 68, 0, 162, 78, 140, 167}); + ExecuteWait(TimeDuration::FromMilliseconds(123)); + ExecutePanGesture100Hz(ScreenIntPoint{568, 723}, {11, 0, 79, 91, 131, 171}); + ExecuteWait(TimeDuration::FromMilliseconds(123)); + ExecutePanGesture100Hz(ScreenIntPoint{598, 678}, + {8, 55, 22, 87, 117, 220, 54}); + ExecuteWait(TimeDuration::FromMilliseconds(134)); + ExecutePanGesture100Hz(ScreenIntPoint{585, 854}, {45, 137, 107, 102, 79}); + ExecuteWait(TimeDuration::FromMilliseconds(246)); + + // Then, interrupt with a tap. + ExecutePanGesture100Hz(ScreenIntPoint{566, 812}, {0, 0, 0, 0}); + ExecuteWait(TimeDuration::FromMilliseconds(869)); + + // Then do a regular fling. + ExecutePanGesture100Hz(ScreenIntPoint{599, 819}, + {0, 0, 8, 35, 8, 38, 29, 37}); + + CHECK_VELOCITY(Up, 2.8, 4.2); +} diff --git a/gfx/layers/apz/test/gtest/TestGestureDetector.cpp b/gfx/layers/apz/test/gtest/TestGestureDetector.cpp new file mode 100644 index 0000000000..ad5f379ba8 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestGestureDetector.cpp @@ -0,0 +1,849 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCBasicTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" +#include "mozilla/StaticPrefs_apz.h" + +// Note: There are additional tests that test gesture detection behaviour +// with multiple APZCs in TestTreeManager.cpp. + +class APZCGestureDetectorTester : public APZCBasicTester { + public: + APZCGestureDetectorTester() + : APZCBasicTester(AsyncPanZoomController::USE_GESTURE_DETECTOR) {} + + protected: + FrameMetrics GetPinchableFrameMetrics() { + FrameMetrics fm; + fm.SetCompositionBounds(ParentLayerRect(200, 200, 100, 200)); + fm.SetScrollableRect(CSSRect(0, 0, 980, 1000)); + fm.SetVisualScrollOffset(CSSPoint(300, 300)); + fm.SetZoom(CSSToParentLayerScale(2.0)); + // APZC only allows zooming on the root scrollable frame. + fm.SetIsRootContent(true); + // the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100 + return fm; + } +}; + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCGestureDetectorTester, Pan_After_Pinch) { + SCOPED_GFX_PREF_INT("apz.axis_lock.mode", 2); + SCOPED_GFX_PREF_FLOAT("apz.axis_lock.lock_angle", M_PI / 6.0f); + SCOPED_GFX_PREF_FLOAT("apz.axis_lock.breakout_angle", M_PI / 8.0f); + + FrameMetrics originalMetrics = GetPinchableFrameMetrics(); + apzc->SetFrameMetrics(originalMetrics); + + MakeApzcZoomable(); + + // Test parameters + float zoomAmount = 1.25; + float pinchLength = 100.0; + float pinchLengthScaled = pinchLength * zoomAmount; + int focusX = 250; + int focusY = 300; + int panDistance = 20; + const TimeDuration TIME_BETWEEN_TOUCH_EVENT = + TimeDuration::FromMilliseconds(50); + + int firstFingerId = 0; + int secondFingerId = firstFingerId + 1; + + // Put fingers down + MultiTouchInput mti = + MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, mcc->Time(), 0); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, focusX, focusY)); + mti.mTouches.AppendElement( + CreateSingleTouchData(secondFingerId, focusX, focusY)); + apzc->ReceiveInputEvent(mti, Some(nsTArray{kDefaultTouchBehavior})); + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + // Spread fingers out to enter the pinch state + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, focusX - pinchLength, focusY)); + mti.mTouches.AppendElement( + CreateSingleTouchData(secondFingerId, focusX + pinchLength, focusY)); + apzc->ReceiveInputEvent(mti); + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + // Do the actual pinch of 1.25x + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY)); + mti.mTouches.AppendElement(CreateSingleTouchData( + secondFingerId, focusX + pinchLengthScaled, focusY)); + apzc->ReceiveInputEvent(mti); + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + // Verify that the zoom changed, just to make sure our code above did what it + // was supposed to. + FrameMetrics zoomedMetrics = apzc->GetFrameMetrics(); + float newZoom = zoomedMetrics.GetZoom().scale; + EXPECT_EQ(originalMetrics.GetZoom().scale * zoomAmount, newZoom); + + // Now we lift one finger... + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, mcc->Time(), 0); + mti.mTouches.AppendElement(CreateSingleTouchData( + secondFingerId, focusX + pinchLengthScaled, focusY)); + apzc->ReceiveInputEvent(mti); + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + // ... and pan with the remaining finger. This pan just breaks through the + // distance threshold. + focusY += StaticPrefs::apz_touch_start_tolerance() * tm->GetDPI(); + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY)); + apzc->ReceiveInputEvent(mti); + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + // This one does an actual pan of 20 pixels + focusY += panDistance; + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY)); + apzc->ReceiveInputEvent(mti); + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + // Lift the remaining finger + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, mcc->Time(), 0); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY)); + apzc->ReceiveInputEvent(mti); + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + // Verify that we scrolled + FrameMetrics finalMetrics = apzc->GetFrameMetrics(); + EXPECT_EQ(zoomedMetrics.GetVisualScrollOffset().y - (panDistance / newZoom), + finalMetrics.GetVisualScrollOffset().y); + + // Clear out any remaining fling animation and pending tasks + apzc->AdvanceAnimationsUntilEnd(); + while (mcc->RunThroughDelayedTasks()) + ; + apzc->AssertStateIsReset(); +} +#endif + +TEST_F(APZCGestureDetectorTester, Pan_With_Tap) { + SCOPED_GFX_PREF_FLOAT("apz.touch_start_tolerance", 0.1); + + FrameMetrics originalMetrics = GetPinchableFrameMetrics(); + apzc->SetFrameMetrics(originalMetrics); + + // Making the APZC zoomable isn't really needed for the correct operation of + // this test, but it could help catch regressions where we accidentally enter + // a pinch state. + MakeApzcZoomable(); + + // Test parameters + int touchX = 250; + int touchY = 300; + int panDistance = 20; + + int firstFingerId = 0; + int secondFingerId = firstFingerId + 1; + + const float panThreshold = + StaticPrefs::apz_touch_start_tolerance() * tm->GetDPI(); + + // Put finger down + MultiTouchInput mti = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, touchX, touchY)); + apzc->ReceiveInputEvent(mti, Some(nsTArray{kDefaultTouchBehavior})); + + // Start a pan, break through the threshold + touchY += panThreshold; + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, touchX, touchY)); + apzc->ReceiveInputEvent(mti); + + // Do an actual pan for a bit + touchY += panDistance; + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, touchX, touchY)); + apzc->ReceiveInputEvent(mti); + + // Put a second finger down + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, touchX, touchY)); + mti.mTouches.AppendElement( + CreateSingleTouchData(secondFingerId, touchX + 10, touchY)); + apzc->ReceiveInputEvent(mti, Some(nsTArray{kDefaultTouchBehavior})); + + // Lift the second finger + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time()); + mti.mTouches.AppendElement( + CreateSingleTouchData(secondFingerId, touchX + 10, touchY)); + apzc->ReceiveInputEvent(mti); + + // Bust through the threshold again + touchY += panThreshold; + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, touchX, touchY)); + apzc->ReceiveInputEvent(mti); + + // Do some more actual panning + touchY += panDistance; + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, touchX, touchY)); + apzc->ReceiveInputEvent(mti); + + // Lift the first finger + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time()); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, touchX, touchY)); + apzc->ReceiveInputEvent(mti); + + // Verify that we scrolled + FrameMetrics finalMetrics = apzc->GetFrameMetrics(); + float zoom = finalMetrics.GetZoom().scale; + EXPECT_EQ( + originalMetrics.GetVisualScrollOffset().y - (panDistance * 2 / zoom), + finalMetrics.GetVisualScrollOffset().y); + + // Clear out any remaining fling animation and pending tasks + apzc->AdvanceAnimationsUntilEnd(); + while (mcc->RunThroughDelayedTasks()) + ; + apzc->AssertStateIsReset(); +} + +TEST_F(APZCGestureDetectorTester, SecondTapIsFar_Bug1586496) { + // Test that we receive two single-tap events when two tap gestures are + // close in time but far in distance. + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, 0, apzc->GetGuid(), _)) + .Times(2); + + TimeDuration brief = + TimeDuration::FromMilliseconds(StaticPrefs::apz_max_tap_time() / 10.0); + + ScreenIntPoint point(10, 10); + Tap(apzc, point, brief); + + mcc->AdvanceBy(brief); + + point.x += apzc->GetSecondTapTolerance() * 2; + point.y += apzc->GetSecondTapTolerance() * 2; + + Tap(apzc, point, brief); +} + +class APZCFlingStopTester : public APZCGestureDetectorTester { + protected: + // Start a fling, and then tap while the fling is ongoing. When + // aSlow is false, the tap will happen while the fling is at a + // high velocity, and we check that the tap doesn't trigger sending a tap + // to content. If aSlow is true, the tap will happen while the fling + // is at a slow velocity, and we check that the tap does trigger sending + // a tap to content. See bug 1022956. + void DoFlingStopTest(bool aSlow) { + int touchStart = 50; + int touchEnd = 10; + + // Start the fling down. + Pan(apzc, touchStart, touchEnd); + // The touchstart from the pan will leave some cancelled tasks in the queue, + // clear them out + + // If we want to tap while the fling is fast, let the fling advance for 10ms + // only. If we want the fling to slow down more, advance to 2000ms. These + // numbers may need adjusting if our friction and threshold values change, + // but they should be deterministic at least. + int timeDelta = aSlow ? 2000 : 10; + int tapCallsExpected = aSlow ? 2 : 1; + + // Advance the fling animation by timeDelta milliseconds. + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + apzc->SampleContentTransformForFrame( + &viewTransformOut, pointOut, TimeDuration::FromMilliseconds(timeDelta)); + + // Deliver a tap to abort the fling. Ensure that we get a SingleTap + // call out of it if and only if the fling is slow. + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, 0, apzc->GetGuid(), _)) + .Times(tapCallsExpected); + Tap(apzc, ScreenIntPoint(10, 10), 0); + while (mcc->RunThroughDelayedTasks()) + ; + + // Deliver another tap, to make sure that taps are flowing properly once + // the fling is aborted. + Tap(apzc, ScreenIntPoint(100, 100), 0); + while (mcc->RunThroughDelayedTasks()) + ; + + // Verify that we didn't advance any further after the fling was aborted, in + // either case. + ParentLayerPoint finalPointOut; + apzc->SampleContentTransformForFrame(&viewTransformOut, finalPointOut); + EXPECT_EQ(pointOut.x, finalPointOut.x); + EXPECT_EQ(pointOut.y, finalPointOut.y); + + apzc->AssertStateIsReset(); + } + + void DoFlingStopWithSlowListener(bool aPreventDefault) { + MakeApzcWaitForMainThread(); + + int touchStart = 50; + int touchEnd = 10; + uint64_t blockId = 0; + + // Start the fling down. + Pan(apzc, touchStart, touchEnd, PanOptions::None, nullptr, nullptr, + &blockId); + apzc->ConfirmTarget(blockId); + apzc->ContentReceivedInputBlock(blockId, false); + + // Sample the fling a couple of times to ensure it's going. + ParentLayerPoint point, finalPoint; + AsyncTransform viewTransform; + apzc->SampleContentTransformForFrame(&viewTransform, point, + TimeDuration::FromMilliseconds(10)); + apzc->SampleContentTransformForFrame(&viewTransform, finalPoint, + TimeDuration::FromMilliseconds(10)); + EXPECT_GT(finalPoint.y, point.y); + + // Now we put our finger down to stop the fling + blockId = + TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()).mInputBlockId; + + // Re-sample to make sure it hasn't moved + apzc->SampleContentTransformForFrame(&viewTransform, point, + TimeDuration::FromMilliseconds(10)); + EXPECT_EQ(finalPoint.x, point.x); + EXPECT_EQ(finalPoint.y, point.y); + + // respond to the touchdown that stopped the fling. + // even if we do a prevent-default on it, the animation should remain + // stopped. + apzc->ContentReceivedInputBlock(blockId, aPreventDefault); + + // Verify the page hasn't moved + apzc->SampleContentTransformForFrame(&viewTransform, point, + TimeDuration::FromMilliseconds(70)); + EXPECT_EQ(finalPoint.x, point.x); + EXPECT_EQ(finalPoint.y, point.y); + + // clean up + TouchUp(apzc, ScreenIntPoint(10, 10), mcc->Time()); + + apzc->AssertStateIsReset(); + } +}; + +TEST_F(APZCFlingStopTester, FlingStop) { + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + DoFlingStopTest(false); +} + +TEST_F(APZCFlingStopTester, FlingStopTap) { + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + DoFlingStopTest(true); +} + +TEST_F(APZCFlingStopTester, FlingStopSlowListener) { + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + DoFlingStopWithSlowListener(false); +} + +TEST_F(APZCFlingStopTester, FlingStopPreventDefault) { + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + DoFlingStopWithSlowListener(true); +} + +TEST_F(APZCGestureDetectorTester, ShortPress) { + MakeApzcUnzoomable(); + + MockFunction check; + { + InSequence s; + // This verifies that the single tap notification is sent after the + // touchup is fully processed. The ordering here is important. + EXPECT_CALL(check, Call("pre-tap")); + EXPECT_CALL(check, Call("post-tap")); + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), + 0, apzc->GetGuid(), _)) + .Times(1); + } + + check.Call("pre-tap"); + TapAndCheckStatus(apzc, ScreenIntPoint(10, 10), + TimeDuration::FromMilliseconds(100)); + check.Call("post-tap"); + + apzc->AssertStateIsReset(); +} + +TEST_F(APZCGestureDetectorTester, MediumPress) { + MakeApzcUnzoomable(); + + MockFunction check; + { + InSequence s; + // This verifies that the single tap notification is sent after the + // touchup is fully processed. The ordering here is important. + EXPECT_CALL(check, Call("pre-tap")); + EXPECT_CALL(check, Call("post-tap")); + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), + 0, apzc->GetGuid(), _)) + .Times(1); + } + + check.Call("pre-tap"); + TapAndCheckStatus(apzc, ScreenIntPoint(10, 10), + TimeDuration::FromMilliseconds(400)); + check.Call("post-tap"); + + apzc->AssertStateIsReset(); +} + +class APZCLongPressTester : public APZCGestureDetectorTester { + protected: + void DoLongPressTest(uint32_t aBehavior) { + MakeApzcUnzoomable(); + + APZEventResult result = + TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, result.GetStatus()); + uint64_t blockId = result.mInputBlockId; + + if (result.GetStatus() != nsEventStatus_eConsumeNoDefault) { + // SetAllowedTouchBehavior() must be called after sending touch-start. + nsTArray allowedTouchBehaviors; + allowedTouchBehaviors.AppendElement(aBehavior); + apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors); + } + // Have content "respond" to the touchstart + apzc->ContentReceivedInputBlock(blockId, false); + + MockFunction check; + + { + InSequence s; + + EXPECT_CALL(check, Call("preHandleLongTap")); + blockId++; + EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, LayoutDevicePoint(10, 10), + 0, apzc->GetGuid(), blockId)) + .Times(1); + EXPECT_CALL(check, Call("postHandleLongTap")); + + EXPECT_CALL(check, Call("preHandleLongTapUp")); + EXPECT_CALL(*mcc, + HandleTap(TapType::eLongTapUp, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(1); + EXPECT_CALL(check, Call("postHandleLongTapUp")); + } + + // Manually invoke the longpress while the touch is currently down. + check.Call("preHandleLongTap"); + mcc->RunThroughDelayedTasks(); + check.Call("postHandleLongTap"); + + // Dispatching the longpress event starts a new touch block, which + // needs a new content response and also has a pending timeout task + // in the queue. Deal with those here. We do the content response first + // with preventDefault=false, and then we run the timeout task which + // "loses the race" and does nothing. + apzc->ContentReceivedInputBlock(blockId, false); + mcc->AdvanceByMillis(1000); + + // Finally, simulate lifting the finger. Since the long-press wasn't + // prevent-defaulted, we should get a long-tap-up event. + check.Call("preHandleLongTapUp"); + result = TouchUp(apzc, ScreenIntPoint(10, 10), mcc->Time()); + mcc->RunThroughDelayedTasks(); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, result.GetStatus()); + check.Call("postHandleLongTapUp"); + + apzc->AssertStateIsReset(); + } + + void DoLongPressPreventDefaultTest(uint32_t aBehavior) { + MakeApzcUnzoomable(); + + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0); + + int touchX = 10, touchStartY = 10, touchEndY = 50; + + APZEventResult result = + TouchDown(apzc, ScreenIntPoint(touchX, touchStartY), mcc->Time()); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, result.GetStatus()); + uint64_t blockId = result.mInputBlockId; + + if (result.GetStatus() != nsEventStatus_eConsumeNoDefault) { + // SetAllowedTouchBehavior() must be called after sending touch-start. + nsTArray allowedTouchBehaviors; + allowedTouchBehaviors.AppendElement(aBehavior); + apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors); + } + // Have content "respond" to the touchstart + apzc->ContentReceivedInputBlock(blockId, false); + + MockFunction check; + + { + InSequence s; + + EXPECT_CALL(check, Call("preHandleLongTap")); + blockId++; + EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, + LayoutDevicePoint(touchX, touchStartY), 0, + apzc->GetGuid(), blockId)) + .Times(1); + EXPECT_CALL(check, Call("postHandleLongTap")); + } + + // Manually invoke the longpress while the touch is currently down. + check.Call("preHandleLongTap"); + mcc->RunThroughDelayedTasks(); + check.Call("postHandleLongTap"); + + // There should be a TimeoutContentResponse task in the queue still, + // waiting for the response from the longtap event dispatched above. + // Send the signal that content has handled the long-tap, and then run + // the timeout task (it will be a no-op because the content "wins" the + // race. This takes the place of the "contextmenu" event. + apzc->ContentReceivedInputBlock(blockId, true); + mcc->AdvanceByMillis(1000); + + MultiTouchInput mti = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + mti.mTouches.AppendElement(SingleTouchData( + 0, ParentLayerPoint(touchX, touchEndY), ScreenSize(0, 0), 0, 0)); + result = apzc->ReceiveInputEvent(mti); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, result.GetStatus()); + + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, + LayoutDevicePoint(touchX, touchEndY), 0, + apzc->GetGuid(), _)) + .Times(0); + result = TouchUp(apzc, ScreenIntPoint(touchX, touchEndY), mcc->Time()); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, result.GetStatus()); + + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + + EXPECT_EQ(ParentLayerPoint(), pointOut); + EXPECT_EQ(AsyncTransform(), viewTransformOut); + + apzc->AssertStateIsReset(); + } +}; + +TEST_F(APZCLongPressTester, LongPress) { + DoLongPressTest(kDefaultTouchBehavior); +} + +TEST_F(APZCLongPressTester, LongPressPreventDefault) { + DoLongPressPreventDefaultTest(kDefaultTouchBehavior); +} + +TEST_F(APZCGestureDetectorTester, DoubleTap) { + MakeApzcWaitForMainThread(); + MakeApzcZoomable(); + + apzc->GetFrameMetrics().SetIsRootContent(true); + + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(0); + EXPECT_CALL(*mcc, HandleTap(TapType::eDoubleTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(1); + + uint64_t blockIds[2]; + DoubleTapAndCheckStatus(apzc, ScreenIntPoint(10, 10), &blockIds); + + // responses to the two touchstarts + apzc->ContentReceivedInputBlock(blockIds[0], false); + apzc->ContentReceivedInputBlock(blockIds[1], false); + + apzc->AssertStateIsReset(); +} + +TEST_F(APZCGestureDetectorTester, DoubleTapNotZoomable) { + MakeApzcWaitForMainThread(); + MakeApzcUnzoomable(); + + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(1); + EXPECT_CALL(*mcc, HandleTap(TapType::eSecondTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(1); + EXPECT_CALL(*mcc, HandleTap(TapType::eDoubleTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(0); + + uint64_t blockIds[2]; + DoubleTapAndCheckStatus(apzc, ScreenIntPoint(10, 10), &blockIds); + + // responses to the two touchstarts + apzc->ContentReceivedInputBlock(blockIds[0], false); + apzc->ContentReceivedInputBlock(blockIds[1], false); + + apzc->AssertStateIsReset(); +} + +TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultFirstOnly) { + MakeApzcWaitForMainThread(); + MakeApzcZoomable(); + + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(1); + EXPECT_CALL(*mcc, HandleTap(TapType::eDoubleTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(0); + + uint64_t blockIds[2]; + DoubleTapAndCheckStatus(apzc, ScreenIntPoint(10, 10), &blockIds); + + // responses to the two touchstarts + apzc->ContentReceivedInputBlock(blockIds[0], true); + apzc->ContentReceivedInputBlock(blockIds[1], false); + + apzc->AssertStateIsReset(); +} + +TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultBoth) { + MakeApzcWaitForMainThread(); + MakeApzcZoomable(); + + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(0); + EXPECT_CALL(*mcc, HandleTap(TapType::eDoubleTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(0); + + uint64_t blockIds[2]; + DoubleTapAndCheckStatus(apzc, ScreenIntPoint(10, 10), &blockIds); + + // responses to the two touchstarts + apzc->ContentReceivedInputBlock(blockIds[0], true); + apzc->ContentReceivedInputBlock(blockIds[1], true); + + apzc->AssertStateIsReset(); +} + +// Test for bug 947892 +// We test whether we dispatch tap event when the tap is followed by pinch. +TEST_F(APZCGestureDetectorTester, TapFollowedByPinch) { + MakeApzcZoomable(); + + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(1); + + Tap(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(100)); + + int inputId = 0; + MultiTouchInput mti; + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), + ScreenSize(0, 0), 0, 0)); + mti.mTouches.AppendElement(SingleTouchData( + inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0)); + apzc->ReceiveInputEvent(mti); + + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time()); + mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), + ScreenSize(0, 0), 0, 0)); + mti.mTouches.AppendElement(SingleTouchData( + inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0)); + apzc->ReceiveInputEvent(mti); + + apzc->AssertStateIsReset(); +} + +TEST_F(APZCGestureDetectorTester, TapFollowedByMultipleTouches) { + MakeApzcZoomable(); + + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(1); + + Tap(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(100)); + + int inputId = 0; + MultiTouchInput mti; + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), + ScreenSize(0, 0), 0, 0)); + apzc->ReceiveInputEvent(mti, Some(nsTArray{kDefaultTouchBehavior})); + + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), + ScreenSize(0, 0), 0, 0)); + mti.mTouches.AppendElement(SingleTouchData( + inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0)); + apzc->ReceiveInputEvent(mti, Some(nsTArray{kDefaultTouchBehavior})); + + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time()); + mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), + ScreenSize(0, 0), 0, 0)); + mti.mTouches.AppendElement(SingleTouchData( + inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0)); + apzc->ReceiveInputEvent(mti); + + apzc->AssertStateIsReset(); +} + +TEST_F(APZCGestureDetectorTester, LongPressInterruptedByWheel) { + // Since we try to allow concurrent input blocks of different types to + // co-exist, the wheel block shouldn't interrupt the long-press detection. + // But more importantly, this shouldn't crash, which is what it did at one + // point in time. + EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, _, _, _, _)).Times(1); + + APZEventResult result = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); + uint64_t touchBlockId = result.mInputBlockId; + if (result.GetStatus() != nsEventStatus_eConsumeNoDefault) { + SetDefaultAllowedTouchBehavior(apzc, touchBlockId); + } + mcc->AdvanceByMillis(10); + uint64_t wheelBlockId = + Wheel(apzc, ScreenIntPoint(10, 10), ScreenPoint(0, -10), mcc->Time()) + .mInputBlockId; + EXPECT_NE(touchBlockId, wheelBlockId); + mcc->AdvanceByMillis(1000); +} + +TEST_F(APZCGestureDetectorTester, TapTimeoutInterruptedByWheel) { + // In this test, even though the wheel block comes right after the tap, the + // tap should still be dispatched because it completes fully before the wheel + // block arrived. + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(1); + + // We make the APZC zoomable so the gesture detector needs to wait to + // distinguish between tap and double-tap. During that timeout is when we + // insert the wheel event. + MakeApzcZoomable(); + + uint64_t touchBlockId = 0; + Tap(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(100), + nullptr, &touchBlockId); + mcc->AdvanceByMillis(10); + uint64_t wheelBlockId = + Wheel(apzc, ScreenIntPoint(10, 10), ScreenPoint(0, -10), mcc->Time()) + .mInputBlockId; + EXPECT_NE(touchBlockId, wheelBlockId); + while (mcc->RunThroughDelayedTasks()) + ; +} + +TEST_F(APZCGestureDetectorTester, LongPressWithInputQueueDelay) { + // In this test, we ensure that any time spent waiting in the input queue for + // the content response is subtracted from the long-press timeout in the + // GestureEventListener. In this test the content response timeout is longer + // than the long-press timeout. + SCOPED_GFX_PREF_INT("apz.content_response_timeout", 60); + SCOPED_GFX_PREF_INT("ui.click_hold_context_menus.delay", 30); + + MakeApzcWaitForMainThread(); + + MockFunction check; + + { + InSequence s; + EXPECT_CALL(check, Call("pre long-tap dispatch")); + EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(1); + EXPECT_CALL(check, Call("post long-tap dispatch")); + } + + // Touch down + APZEventResult result = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); + uint64_t touchBlockId = result.mInputBlockId; + // Simulate content response after 10ms + mcc->AdvanceByMillis(10); + apzc->ContentReceivedInputBlock(touchBlockId, false); + apzc->SetAllowedTouchBehavior(touchBlockId, {kDefaultTouchBehavior}); + apzc->ConfirmTarget(touchBlockId); + // Ensure long-tap event happens within 20ms after that + check.Call("pre long-tap dispatch"); + mcc->AdvanceByMillis(20); + check.Call("post long-tap dispatch"); +} + +TEST_F(APZCGestureDetectorTester, LongPressWithInputQueueDelay2) { + // Similar to the previous test, except this time we don't simulate the + // content response at all, and still expect the long-press to happen on + // schedule. + SCOPED_GFX_PREF_INT("apz.content_response_timeout", 60); + SCOPED_GFX_PREF_INT("ui.click_hold_context_menus.delay", 30); + + MakeApzcWaitForMainThread(); + + MockFunction check; + + { + InSequence s; + EXPECT_CALL(check, Call("pre long-tap dispatch")); + EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(1); + EXPECT_CALL(check, Call("post long-tap dispatch")); + } + + // Touch down + TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); + // Ensure the long-tap happens within 30ms even though there's no content + // response. + check.Call("pre long-tap dispatch"); + mcc->AdvanceByMillis(30); + check.Call("post long-tap dispatch"); +} + +TEST_F(APZCGestureDetectorTester, LongPressWithInputQueueDelay3) { + // Similar to the previous test, except now we have the long-press delay + // being longer than the content response timeout. + SCOPED_GFX_PREF_INT("apz.content_response_timeout", 30); + SCOPED_GFX_PREF_INT("ui.click_hold_context_menus.delay", 60); + + MakeApzcWaitForMainThread(); + + MockFunction check; + + { + InSequence s; + EXPECT_CALL(check, Call("pre long-tap dispatch")); + EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(1); + EXPECT_CALL(check, Call("post long-tap dispatch")); + } + + // Touch down + TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); + // Ensure the long-tap happens at the 60ms mark even though the input event + // waits in the input queue for the full content response timeout of 30ms + mcc->AdvanceByMillis(59); + check.Call("pre long-tap dispatch"); + mcc->AdvanceByMillis(1); + check.Call("post long-tap dispatch"); +} diff --git a/gfx/layers/apz/test/gtest/TestHitTesting.cpp b/gfx/layers/apz/test/gtest/TestHitTesting.cpp new file mode 100644 index 0000000000..04f1e40f5d --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestHitTesting.cpp @@ -0,0 +1,352 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCTreeManagerTester.h" +#include "APZTestCommon.h" + +#include "InputUtils.h" + +class APZHitTestingTester : public APZCTreeManagerTester { + protected: + ScreenToParentLayerMatrix4x4 transformToApzc; + ParentLayerToScreenMatrix4x4 transformToGecko; + + already_AddRefed GetTargetAPZC( + const ScreenPoint& aPoint) { + RefPtr hit = + manager->GetTargetAPZC(aPoint).mTargetApzc; + if (hit) { + transformToApzc = manager->GetScreenToApzcTransform(hit.get()); + transformToGecko = + manager->GetApzcToGeckoTransform(hit.get(), LayoutAndVisual); + } + return hit.forget(); + } + + protected: + void DisableApzOn(WebRenderLayerScrollData* aLayer) { + ModifyFrameMetrics(aLayer, [](ScrollMetadata& aSm, FrameMetrics&) { + aSm.SetForceDisableApz(true); + }); + } + + void CreateComplexMultiLayerTree() { + const char* treeShape = "x(xx(x)xx(x(x)xx))"; + // LayerID 0 12 3 45 6 7 89 + LayerIntRegion layerVisibleRegion[] = { + LayerIntRect(0, 0, 300, 400), // root(0) + LayerIntRect(0, 0, 100, 100), // layer(1) in top-left + LayerIntRect(50, 50, 200, 300), // layer(2) centered in root(0) + LayerIntRect(50, 50, 200, + 300), // layer(3) fully occupying parent layer(2) + LayerIntRect(0, 200, 100, 100), // layer(4) in bottom-left + LayerIntRect(200, 0, 100, + 400), // layer(5) along the right 100px of root(0) + LayerIntRect(200, 0, 100, 200), // layer(6) taking up the top + // half of parent layer(5) + LayerIntRect(200, 0, 100, + 200), // layer(7) fully occupying parent layer(6) + LayerIntRect(200, 200, 100, + 100), // layer(8) in bottom-right (below (6)) + LayerIntRect(200, 300, 100, + 100), // layer(9) in bottom-right (below (8)) + }; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID); + SetScrollableFrameMetrics(layers[2], ScrollableLayerGuid::START_SCROLL_ID); + SetScrollableFrameMetrics(layers[4], + ScrollableLayerGuid::START_SCROLL_ID + 1); + SetScrollableFrameMetrics(layers[6], + ScrollableLayerGuid::START_SCROLL_ID + 1); + SetScrollableFrameMetrics(layers[7], + ScrollableLayerGuid::START_SCROLL_ID + 2); + SetScrollableFrameMetrics(layers[8], + ScrollableLayerGuid::START_SCROLL_ID + 1); + SetScrollableFrameMetrics(layers[9], + ScrollableLayerGuid::START_SCROLL_ID + 3); + } + + void CreateBug1148350LayerTree() { + const char* treeShape = "x(x)"; + // LayerID 0 1 + LayerIntRegion layerVisibleRegion[] = { + LayerIntRect(0, 0, 200, 200), + LayerIntRect(0, 0, 200, 200), + }; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID); + } +}; + +TEST_F(APZHitTestingTester, ComplexMultiLayerTree) { + CreateComplexMultiLayerTree(); + ScopedLayerTreeRegistration registration(LayersId{0}, mcc); + UpdateHitTestingTree(); + + /* The layer tree looks like this: + + 0 + |----|--+--|----| + 1 2 4 5 + | /|\ + 3 6 8 9 + | + 7 + + Layers 1,2 have the same APZC + Layers 4,6,8 have the same APZC + Layer 7 has an APZC + Layer 9 has an APZC + */ + + TestAsyncPanZoomController* nullAPZC = nullptr; + // Ensure all the scrollable layers have an APZC + + EXPECT_FALSE(HasScrollableFrameMetrics(layers[0])); + EXPECT_NE(nullAPZC, ApzcOf(layers[1])); + EXPECT_NE(nullAPZC, ApzcOf(layers[2])); + EXPECT_FALSE(HasScrollableFrameMetrics(layers[3])); + EXPECT_NE(nullAPZC, ApzcOf(layers[4])); + EXPECT_FALSE(HasScrollableFrameMetrics(layers[5])); + EXPECT_NE(nullAPZC, ApzcOf(layers[6])); + EXPECT_NE(nullAPZC, ApzcOf(layers[7])); + EXPECT_NE(nullAPZC, ApzcOf(layers[8])); + EXPECT_NE(nullAPZC, ApzcOf(layers[9])); + // Ensure those that scroll together have the same APZCs + EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2])); + EXPECT_EQ(ApzcOf(layers[4]), ApzcOf(layers[6])); + EXPECT_EQ(ApzcOf(layers[8]), ApzcOf(layers[6])); + // Ensure those that don't scroll together have different APZCs + EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[4])); + EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[7])); + EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[9])); + EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[7])); + EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[9])); + EXPECT_NE(ApzcOf(layers[7]), ApzcOf(layers[9])); + // Ensure the APZC parent chains are set up correctly + TestAsyncPanZoomController* layers1_2 = ApzcOf(layers[1]); + TestAsyncPanZoomController* layers4_6_8 = ApzcOf(layers[4]); + TestAsyncPanZoomController* layer7 = ApzcOf(layers[7]); + TestAsyncPanZoomController* layer9 = ApzcOf(layers[9]); + EXPECT_EQ(nullptr, layers1_2->GetParent()); + EXPECT_EQ(nullptr, layers4_6_8->GetParent()); + EXPECT_EQ(layers4_6_8, layer7->GetParent()); + EXPECT_EQ(nullptr, layer9->GetParent()); + // Ensure the hit-testing tree looks like the layer tree + RefPtr root = manager->GetRootNode(); + RefPtr node5 = root->GetLastChild(); + RefPtr node4 = node5->GetPrevSibling(); + RefPtr node2 = node4->GetPrevSibling(); + RefPtr node1 = node2->GetPrevSibling(); + RefPtr node3 = node2->GetLastChild(); + RefPtr node9 = node5->GetLastChild(); + RefPtr node8 = node9->GetPrevSibling(); + RefPtr node6 = node8->GetPrevSibling(); + RefPtr node7 = node6->GetLastChild(); + EXPECT_EQ(nullptr, node1->GetPrevSibling()); + EXPECT_EQ(nullptr, node3->GetPrevSibling()); + EXPECT_EQ(nullptr, node6->GetPrevSibling()); + EXPECT_EQ(nullptr, node7->GetPrevSibling()); + EXPECT_EQ(nullptr, node1->GetLastChild()); + EXPECT_EQ(nullptr, node3->GetLastChild()); + EXPECT_EQ(nullptr, node4->GetLastChild()); + EXPECT_EQ(nullptr, node7->GetLastChild()); + EXPECT_EQ(nullptr, node8->GetLastChild()); + EXPECT_EQ(nullptr, node9->GetLastChild()); + + // Assertions about hit-testing have been ported to mochitest, + // in helper_hittest_bug1730606-4.html. +} + +TEST_F(APZHitTestingTester, TestRepaintFlushOnNewInputBlock) { + // The main purpose of this test is to verify that touch-start events (or + // anything that starts a new input block) don't ever get untransformed. This + // should always hold because the APZ code should flush repaints when we start + // a new input block and the transform to gecko space should be empty. + + CreateSimpleScrollingLayer(); + ScopedLayerTreeRegistration registration(LayersId{0}, mcc); + UpdateHitTestingTree(); + RefPtr apzcroot = ApzcOf(root); + + // At this point, the following holds (all coordinates in screen pixels): + // layers[0] has content from (0,0)-(500,500), clipped by composition bounds + // (0,0)-(200,200) + + MockFunction check; + + { + InSequence s; + + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1)); + EXPECT_CALL(check, Call("post-first-touch-start")); + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1)); + EXPECT_CALL(check, Call("post-second-fling")); + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1)); + EXPECT_CALL(check, Call("post-second-touch-start")); + } + + // This first pan will move the APZC by 50 pixels, and dispatch a paint + // request. + Pan(apzcroot, 100, 50, PanOptions::NoFling); + + // Verify that a touch start doesn't get untransformed + ScreenIntPoint touchPoint(50, 50); + MultiTouchInput mti = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + mti.mTouches.AppendElement( + SingleTouchData(0, touchPoint, ScreenSize(0, 0), 0, 0)); + + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, + manager->ReceiveInputEvent(mti).GetStatus()); + EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint); + check.Call("post-first-touch-start"); + + // Send a touchend to clear state + mti.mType = MultiTouchInput::MULTITOUCH_END; + manager->ReceiveInputEvent(mti); + + mcc->AdvanceByMillis(1000); + + // Now do two pans. The first of these will dispatch a repaint request, as + // above. The second will get stuck in the paint throttler because the first + // one doesn't get marked as "completed", so this will result in a non-empty + // LD transform. (Note that any outstanding repaint requests from the first + // half of this test don't impact this half because we advance the time by 1 + // second, which will trigger the max-wait-exceeded codepath in the paint + // throttler). + Pan(apzcroot, 100, 50, PanOptions::NoFling); + check.Call("post-second-fling"); + Pan(apzcroot, 100, 50, PanOptions::NoFling); + + // Ensure that a touch start again doesn't get untransformed by flushing + // a repaint + mti.mType = MultiTouchInput::MULTITOUCH_START; + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, + manager->ReceiveInputEvent(mti).GetStatus()); + EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint); + check.Call("post-second-touch-start"); + + mti.mType = MultiTouchInput::MULTITOUCH_END; + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, + manager->ReceiveInputEvent(mti).GetStatus()); + EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint); +} + +TEST_F(APZHitTestingTester, TestRepaintFlushOnWheelEvents) { + // The purpose of this test is to ensure that wheel events trigger a repaint + // flush as per bug 1166871, and that the wheel event untransform is a no-op. + + CreateSimpleScrollingLayer(); + ScopedLayerTreeRegistration registration(LayersId{0}, mcc); + UpdateHitTestingTree(); + TestAsyncPanZoomController* apzcroot = ApzcOf(root); + + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(3)); + ScreenPoint origin(100, 50); + for (int i = 0; i < 3; i++) { + ScrollWheelInput swi(mcc->Time(), 0, ScrollWheelInput::SCROLLMODE_INSTANT, + ScrollWheelInput::SCROLLDELTA_PIXEL, origin, 0, 10, + false, WheelDeltaAdjustmentStrategy::eNone); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, + manager->ReceiveInputEvent(swi).GetStatus()); + EXPECT_EQ(origin, swi.mOrigin); + + AsyncTransform viewTransform; + ParentLayerPoint point; + apzcroot->SampleContentTransformForFrame(&viewTransform, point); + EXPECT_EQ(0, point.x); + EXPECT_EQ((i + 1) * 10, point.y); + EXPECT_EQ(0, viewTransform.mTranslation.x); + EXPECT_EQ((i + 1) * -10, viewTransform.mTranslation.y); + + mcc->AdvanceByMillis(5); + } +} + +TEST_F(APZHitTestingTester, TestForceDisableApz) { + CreateSimpleScrollingLayer(); + ScopedLayerTreeRegistration registration(LayersId{0}, mcc); + UpdateHitTestingTree(); + DisableApzOn(root); + TestAsyncPanZoomController* apzcroot = ApzcOf(root); + + ScreenPoint origin(100, 50); + ScrollWheelInput swi(mcc->Time(), 0, ScrollWheelInput::SCROLLMODE_INSTANT, + ScrollWheelInput::SCROLLDELTA_PIXEL, origin, 0, 10, + false, WheelDeltaAdjustmentStrategy::eNone); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, + manager->ReceiveInputEvent(swi).GetStatus()); + EXPECT_EQ(origin, swi.mOrigin); + + AsyncTransform viewTransform; + ParentLayerPoint point; + apzcroot->SampleContentTransformForFrame(&viewTransform, point); + // Since APZ is force-disabled, we expect to see the async transform via + // the NORMAL AsyncMode, but not via the RESPECT_FORCE_DISABLE AsyncMode. + EXPECT_EQ(0, point.x); + EXPECT_EQ(10, point.y); + EXPECT_EQ(0, viewTransform.mTranslation.x); + EXPECT_EQ(-10, viewTransform.mTranslation.y); + viewTransform = apzcroot->GetCurrentAsyncTransform( + AsyncPanZoomController::eForCompositing); + point = apzcroot->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::eForCompositing); + EXPECT_EQ(0, point.x); + EXPECT_EQ(0, point.y); + EXPECT_EQ(0, viewTransform.mTranslation.x); + EXPECT_EQ(0, viewTransform.mTranslation.y); + + mcc->AdvanceByMillis(10); + + // With untransforming events we should get normal behaviour (in this case, + // no noticeable untransform, because the repaint request already got + // flushed). + swi = ScrollWheelInput(mcc->Time(), 0, ScrollWheelInput::SCROLLMODE_INSTANT, + ScrollWheelInput::SCROLLDELTA_PIXEL, origin, 0, 0, + false, WheelDeltaAdjustmentStrategy::eNone); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, + manager->ReceiveInputEvent(swi).GetStatus()); + EXPECT_EQ(origin, swi.mOrigin); +} + +TEST_F(APZHitTestingTester, Bug1148350) { + CreateBug1148350LayerTree(); + ScopedLayerTreeRegistration registration(LayersId{0}, mcc); + UpdateHitTestingTree(); + + MockFunction check; + { + InSequence s; + EXPECT_CALL(*mcc, + HandleTap(TapType::eSingleTap, LayoutDevicePoint(100, 100), 0, + ApzcOf(layers[1])->GetGuid(), _)) + .Times(1); + EXPECT_CALL(check, Call("Tapped without transform")); + EXPECT_CALL(*mcc, + HandleTap(TapType::eSingleTap, LayoutDevicePoint(100, 100), 0, + ApzcOf(layers[1])->GetGuid(), _)) + .Times(1); + EXPECT_CALL(check, Call("Tapped with interleaved transform")); + } + + Tap(manager, ScreenIntPoint(100, 100), TimeDuration::FromMilliseconds(100)); + mcc->RunThroughDelayedTasks(); + check.Call("Tapped without transform"); + + uint64_t blockId = + TouchDown(manager, ScreenIntPoint(100, 100), mcc->Time()).mInputBlockId; + SetDefaultAllowedTouchBehavior(manager, blockId); + mcc->AdvanceByMillis(100); + + layers[0]->SetVisibleRegion(LayerIntRegion(LayerIntRect(0, 50, 200, 150))); + layers[0]->SetTransform(Matrix4x4::Translation(0, 50, 0)); + UpdateHitTestingTree(); + + TouchUp(manager, ScreenIntPoint(100, 100), mcc->Time()); + mcc->RunThroughDelayedTasks(); + check.Call("Tapped with interleaved transform"); +} diff --git a/gfx/layers/apz/test/gtest/TestInputQueue.cpp b/gfx/layers/apz/test/gtest/TestInputQueue.cpp new file mode 100644 index 0000000000..6e47340da5 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestInputQueue.cpp @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCTreeManagerTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" + +// Test of scenario described in bug 1269067 - that a continuing mouse drag +// doesn't interrupt a wheel scrolling animation +TEST_F(APZCTreeManagerTester, WheelInterruptedByMouseDrag) { + // Set up a scrollable layer + CreateSimpleScrollingLayer(); + ScopedLayerTreeRegistration registration(LayersId{0}, mcc); + UpdateHitTestingTree(); + RefPtr apzc = ApzcOf(root); + + // First start the mouse drag + uint64_t dragBlockId = + MouseDown(apzc, ScreenIntPoint(5, 5), mcc->Time()).mInputBlockId; + uint64_t tmpBlockId = + MouseMove(apzc, ScreenIntPoint(6, 6), mcc->Time()).mInputBlockId; + EXPECT_EQ(dragBlockId, tmpBlockId); + + // Insert the wheel event, check that it has a new block id + uint64_t wheelBlockId = + SmoothWheel(apzc, ScreenIntPoint(6, 6), ScreenPoint(0, 1), mcc->Time()) + .mInputBlockId; + EXPECT_NE(dragBlockId, wheelBlockId); + + // Continue the drag, check that the block id is the same as before + tmpBlockId = MouseMove(apzc, ScreenIntPoint(7, 5), mcc->Time()).mInputBlockId; + EXPECT_EQ(dragBlockId, tmpBlockId); + + // Finish the wheel animation + apzc->AdvanceAnimationsUntilEnd(); + + // Check that it scrolled + ParentLayerPoint scroll = + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting); + EXPECT_EQ(scroll.x, 0); + EXPECT_EQ(scroll.y, 10); // We scrolled 1 "line" or 10 pixels +} diff --git a/gfx/layers/apz/test/gtest/TestOverscroll.cpp b/gfx/layers/apz/test/gtest/TestOverscroll.cpp new file mode 100644 index 0000000000..10327871fb --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestOverscroll.cpp @@ -0,0 +1,1991 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCBasicTester.h" +#include "APZCTreeManagerTester.h" +#include "APZTestCommon.h" +#include "mozilla/layers/WebRenderScrollDataWrapper.h" + +#include "InputUtils.h" + +class APZCOverscrollTester : public APZCBasicTester { + public: + explicit APZCOverscrollTester( + AsyncPanZoomController::GestureBehavior aGestureBehavior = + AsyncPanZoomController::DEFAULT_GESTURES) + : APZCBasicTester(aGestureBehavior) {} + + protected: + UniquePtr registration; + + void TestOverscroll() { + // Pan sufficiently to hit overscroll behavior + PanIntoOverscroll(); + + // Check that we recover from overscroll via an animation. + ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost()); + SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset); + } + + void PanIntoOverscroll() { + int touchStart = 500; + int touchEnd = 10; + Pan(apzc, touchStart, touchEnd); + EXPECT_TRUE(apzc->IsOverscrolled()); + } + + /** + * Sample animations until we recover from overscroll. + * @param aExpectedScrollOffset the expected reported scroll offset + * throughout the animation + */ + void SampleAnimationUntilRecoveredFromOverscroll( + const ParentLayerPoint& aExpectedScrollOffset) { + const TimeDuration increment = TimeDuration::FromMilliseconds(1); + bool recoveredFromOverscroll = false; + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + while (apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut)) { + // The reported scroll offset should be the same throughout. + EXPECT_EQ(aExpectedScrollOffset, pointOut); + + // Trigger computation of the overscroll tranform, to make sure + // no assetions fire during the calculation. + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + + if (!apzc->IsOverscrolled()) { + recoveredFromOverscroll = true; + } + + mcc->AdvanceBy(increment); + } + EXPECT_TRUE(recoveredFromOverscroll); + apzc->AssertStateIsReset(); + } + + ScrollableLayerGuid CreateSimpleRootScrollableForWebRender() { + ScrollableLayerGuid guid; + guid.mScrollId = ScrollableLayerGuid::START_SCROLL_ID; + guid.mLayersId = LayersId{0}; + + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + metrics.SetScrollableRect(CSSRect(0, 0, 100, 1000)); + metrics.SetScrollId(guid.mScrollId); + metadata.SetIsLayersIdRoot(true); + + WebRenderLayerScrollData rootLayerScrollData; + rootLayerScrollData.InitializeRoot(0); + WebRenderScrollData scrollData; + rootLayerScrollData.AppendScrollMetadata(scrollData, metadata); + scrollData.AddLayerData(std::move(rootLayerScrollData)); + + registration = MakeUnique(guid.mLayersId, mcc); + tm->UpdateHitTestingTree(WebRenderScrollDataWrapper(*updater, &scrollData), + false, guid.mLayersId, 0); + return guid; + } +}; + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCOverscrollTester, FlingIntoOverscroll) { + // Enable overscrolling. + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + + // Scroll down by 25 px. Don't fling for simplicity. + Pan(apzc, 50, 25, PanOptions::NoFling); + + // Now scroll back up by 20px, this time flinging after. + // The fling should cover the remaining 5 px of room to scroll, then + // go into overscroll, and finally snap-back to recover from overscroll. + Pan(apzc, 25, 45); + const TimeDuration increment = TimeDuration::FromMilliseconds(1); + bool reachedOverscroll = false; + bool recoveredFromOverscroll = false; + while (apzc->AdvanceAnimations(mcc->GetSampleTime())) { + if (!reachedOverscroll && apzc->IsOverscrolled()) { + reachedOverscroll = true; + } + if (reachedOverscroll && !apzc->IsOverscrolled()) { + recoveredFromOverscroll = true; + } + mcc->AdvanceBy(increment); + } + EXPECT_TRUE(reachedOverscroll); + EXPECT_TRUE(recoveredFromOverscroll); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCOverscrollTester, OverScrollPanning) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + TestOverscroll(); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +// Tests that an overscroll animation doesn't trigger an assertion failure +// in the case where a sample has a velocity of zero. +TEST_F(APZCOverscrollTester, OverScroll_Bug1152051a) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + // Doctor the prefs to make the velocity zero at the end of the first sample. + + // This ensures our incoming velocity to the overscroll animation is + // a round(ish) number, 4.9 (that being the distance of the pan before + // overscroll, which is 500 - 10 = 490 pixels, divided by the duration of + // the pan, which is 100 ms). + SCOPED_GFX_PREF_FLOAT("apz.fling_friction", 0); + + TestOverscroll(); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +// Tests that ending an overscroll animation doesn't leave around state that +// confuses the next overscroll animation. +TEST_F(APZCOverscrollTester, OverScroll_Bug1152051b) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + SCOPED_GFX_PREF_FLOAT("apz.overscroll.stop_distance_threshold", 0.1f); + + // Pan sufficiently to hit overscroll behavior + PanIntoOverscroll(); + + // Sample animations once, to give the fling animation started on touch-up + // a chance to realize it's overscrolled, and schedule a call to + // HandleFlingOverscroll(). + SampleAnimationOnce(); + + // This advances the time and runs the HandleFlingOverscroll task scheduled in + // the previous call, which starts an overscroll animation. It then samples + // the overscroll animation once, to get it to initialize the first overscroll + // sample. + SampleAnimationOnce(); + + // Do a touch-down to cancel the overscroll animation, and then a touch-up + // to schedule a new one since we're still overscrolled. We don't pan because + // panning can trigger functions that clear the overscroll animation state + // in other ways. + APZEventResult result = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); + if (result.GetStatus() != nsEventStatus_eConsumeNoDefault) { + SetDefaultAllowedTouchBehavior(apzc, result.mInputBlockId); + } + TouchUp(apzc, ScreenIntPoint(10, 10), mcc->Time()); + + // Sample the second overscroll animation to its end. + // If the ending of the first overscroll animation fails to clear state + // properly, this will assert. + ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost()); + SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +// Tests that the page doesn't get stuck in an +// overscroll animation after a low-velocity pan. +TEST_F(APZCOverscrollTester, OverScrollAfterLowVelocityPan_Bug1343775) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + // Pan into overscroll with a velocity less than the + // apz.fling_min_velocity_threshold preference. + Pan(apzc, 10, 30); + + EXPECT_TRUE(apzc->IsOverscrolled()); + + apzc->AdvanceAnimationsUntilEnd(); + + // Check that we recovered from overscroll. + EXPECT_FALSE(apzc->IsOverscrolled()); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCOverscrollTester, OverScrollAbort) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + // Pan sufficiently to hit overscroll behavior + int touchStart = 500; + int touchEnd = 10; + Pan(apzc, touchStart, touchEnd); + EXPECT_TRUE(apzc->IsOverscrolled()); + + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + + // This sample call will run to the end of the fling animation + // and will schedule the overscroll animation. + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut, + TimeDuration::FromMilliseconds(10000)); + EXPECT_TRUE(apzc->IsOverscrolled()); + + // At this point, we have an active overscroll animation. + // Check that cancelling the animation clears the overscroll. + apzc->CancelAnimation(); + EXPECT_FALSE(apzc->IsOverscrolled()); + apzc->AssertStateIsReset(); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCOverscrollTester, OverScrollPanningAbort) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + // Pan sufficiently to hit overscroll behaviour. Keep the finger down so + // the pan does not end. + int touchStart = 500; + int touchEnd = 10; + Pan(apzc, touchStart, touchEnd, PanOptions::KeepFingerDown); + EXPECT_TRUE(apzc->IsOverscrolled()); + + // Check that calling CancelAnimation() while the user is still panning + // (and thus no fling or snap-back animation has had a chance to start) + // clears the overscroll. + apzc->CancelAnimation(); + EXPECT_FALSE(apzc->IsOverscrolled()); + apzc->AssertStateIsReset(); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Maybe fails on Android +TEST_F(APZCOverscrollTester, OverscrollByVerticalPanGestures) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, -2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, -10), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, -2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 0), mcc->Time()); + + EXPECT_TRUE(apzc->IsOverscrolled()); + + // Check that we recover from overscroll via an animation. + ParentLayerPoint expectedScrollOffset(0, 0); + SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCOverscrollTester, StuckInOverscroll_Bug1767337) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, -2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, -10), mcc->Time()); + mcc->AdvanceByMillis(5); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, -10), mcc->Time()); + mcc->AdvanceByMillis(5); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, -10), mcc->Time()); + mcc->AdvanceByMillis(5); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, -10), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, -2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + // Send two PANGESTURE_END in a row, to see if the second one gets us + // stuck in overscroll. + PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 0), mcc->Time(), MODIFIER_NONE, true); + SampleAnimationOnce(); + PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 0), mcc->Time(), MODIFIER_NONE, true); + + EXPECT_TRUE(apzc->IsOverscrolled()); + + // Check that we recover from overscroll via an animation. + ParentLayerPoint expectedScrollOffset(0, 0); + SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCOverscrollTester, OverscrollByVerticalAndHorizontalPanGestures) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, -2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, -10), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, -2), mcc->Time()); + + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(-10, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(-2, 0), mcc->Time()); + + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 0), mcc->Time()); + + EXPECT_TRUE(apzc->IsOverscrolled()); + + // Check that we recover from overscroll via an animation. + ParentLayerPoint expectedScrollOffset(0, 0); + SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCOverscrollTester, OverscrollByPanMomentumGestures) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 10), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 0), mcc->Time()); + + // Make sure we are not yet in overscrolled region. + EXPECT_TRUE(!apzc->IsOverscrolled()); + + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 200), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 100), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMEND, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time()); + + EXPECT_TRUE(apzc->IsOverscrolled()); + + // Check that we recover from overscroll via an animation. + ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost()); + SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCOverscrollTester, IgnoreMomemtumDuringOverscroll) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + float yMost = GetScrollRange().YMost(); + PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, yMost / 10), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, yMost), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, yMost / 10), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 0), mcc->Time()); + + // Make sure we've started an overscroll animation. + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_TRUE(apzc->IsOverscrollAnimationRunning()); + + // And check the overscrolled transform value before/after calling PanGesture + // to make sure the overscroll amount isn't affected by momentum events. + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + AsyncTransformComponentMatrix overscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time()); + EXPECT_EQ(overscrolledTransform, apzc->GetOverscrollTransform( + AsyncPanZoomController::eForHitTesting)); + + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + overscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 200), mcc->Time()); + EXPECT_EQ(overscrolledTransform, apzc->GetOverscrollTransform( + AsyncPanZoomController::eForHitTesting)); + + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + overscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 100), mcc->Time()); + EXPECT_EQ(overscrolledTransform, apzc->GetOverscrollTransform( + AsyncPanZoomController::eForHitTesting)); + + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + overscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 2), mcc->Time()); + EXPECT_EQ(overscrolledTransform, apzc->GetOverscrollTransform( + AsyncPanZoomController::eForHitTesting)); + + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + overscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMEND, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time()); + EXPECT_EQ(overscrolledTransform, apzc->GetOverscrollTransform( + AsyncPanZoomController::eForHitTesting)); + + // Check that we've recovered from overscroll via an animation. + ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost()); + SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCOverscrollTester, VerticalOnlyOverscroll) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + // Make the content scrollable only vertically. + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + metrics.SetScrollableRect(CSSRect(0, 0, 100, 1000)); + apzc->SetFrameMetrics(metrics); + + // Scroll up into overscroll a bit. + PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80), + ScreenPoint(-2, -2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(-10, -10), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(-2, -2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 0), mcc->Time()); + // Now it's overscrolled. + EXPECT_TRUE(apzc->IsOverscrolled()); + AsyncTransformComponentMatrix overscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + // The overscroll shouldn't happen horizontally. + EXPECT_TRUE(overscrolledTransform._41 == 0); + // Happens only vertically. + EXPECT_TRUE(overscrolledTransform._42 != 0); + + // Send pan momentum events including horizontal bits. + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 80), ScreenPoint(-10, -100), mcc->Time()); + overscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + // The overscroll shouldn't happen horizontally. + EXPECT_TRUE(overscrolledTransform._41 == 0); + EXPECT_TRUE(overscrolledTransform._42 != 0); + + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 80), ScreenPoint(-5, -50), mcc->Time()); + overscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + EXPECT_TRUE(overscrolledTransform._41 == 0); + EXPECT_TRUE(overscrolledTransform._42 != 0); + + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, -2), mcc->Time()); + overscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + EXPECT_TRUE(overscrolledTransform._41 == 0); + EXPECT_TRUE(overscrolledTransform._42 != 0); + + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMEND, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time()); + overscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + EXPECT_TRUE(overscrolledTransform._41 == 0); + EXPECT_TRUE(overscrolledTransform._42 != 0); + + // Check that we recover from overscroll via an animation. + ParentLayerPoint expectedScrollOffset(0, 0); + SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCOverscrollTester, VerticalOnlyOverscrollByPanMomentum) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + // Make the content scrollable only vertically. + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + metrics.SetScrollableRect(CSSRect(0, 0, 100, 1000)); + // Scrolls the content down a bit. + metrics.SetVisualScrollOffset(CSSPoint(0, 50)); + apzc->SetFrameMetrics(metrics); + + // Scroll up a bit where overscroll will not happen. + PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, -2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, -10), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, -2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 0), mcc->Time()); + + // Make sure it's not yet overscrolled. + EXPECT_TRUE(!apzc->IsOverscrolled()); + + // Send pan momentum events including horizontal bits. + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 80), ScreenPoint(-10, -100), mcc->Time()); + // Now it's overscrolled. + EXPECT_TRUE(apzc->IsOverscrolled()); + + AsyncTransformComponentMatrix overscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + // But the overscroll shouldn't happen horizontally. + EXPECT_TRUE(overscrolledTransform._41 == 0); + // Happens only vertically. + EXPECT_TRUE(overscrolledTransform._42 != 0); + + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 80), ScreenPoint(-5, -50), mcc->Time()); + overscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + EXPECT_TRUE(overscrolledTransform._41 == 0); + EXPECT_TRUE(overscrolledTransform._42 != 0); + + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, -2), mcc->Time()); + overscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + EXPECT_TRUE(overscrolledTransform._41 == 0); + EXPECT_TRUE(overscrolledTransform._42 != 0); + + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMEND, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time()); + overscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + EXPECT_TRUE(overscrolledTransform._41 == 0); + EXPECT_TRUE(overscrolledTransform._42 != 0); + + // Check that we recover from overscroll via an animation. + ParentLayerPoint expectedScrollOffset(0, 0); + SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCOverscrollTester, DisallowOverscrollInSingleLineTextControl) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + // Create a horizontal scrollable frame with `vertical disregarded direction`. + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 10)); + metrics.SetScrollableRect(CSSRect(0, 0, 1000, 10)); + apzc->SetFrameMetrics(metrics); + metadata.SetDisregardedDirection(Some(ScrollDirection::eVertical)); + apzc->NotifyLayersUpdated(metadata, /*aIsFirstPaint=*/false, + /*aThisLayerTreeUpdated=*/true); + + // Try to overscroll up and left with pan gestures. + PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 5), + ScreenPoint(-2, -2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 5), + ScreenPoint(-10, -10), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 5), + ScreenPoint(-2, -2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 5), + ScreenPoint(0, 0), mcc->Time()); + + // No overscrolling should happen. + EXPECT_TRUE(!apzc->IsOverscrolled()); + + // Send pan momentum events too. + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, apzc, + ScreenIntPoint(50, 5), ScreenPoint(0, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 5), ScreenPoint(-100, -100), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 5), ScreenPoint(-50, -50), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 5), ScreenPoint(-2, -2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMEND, apzc, + ScreenIntPoint(50, 5), ScreenPoint(0, 0), mcc->Time()); + // No overscrolling should happen either. + EXPECT_TRUE(!apzc->IsOverscrolled()); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Maybe fails on Android +// Tests that horizontal overscroll animation keeps running with vertical +// pan momentum scrolling. +TEST_F(APZCOverscrollTester, + HorizontalOverscrollAnimationWithVerticalPanMomentumScrolling) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + metrics.SetScrollableRect(CSSRect(0, 0, 1000, 5000)); + apzc->SetFrameMetrics(metrics); + + // Try to overscroll left with pan gestures. + PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80), + ScreenPoint(-2, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(-10, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(-2, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 0), mcc->Time()); + + // Make sure we've started an overscroll animation. + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_TRUE(apzc->IsOverscrollAnimationRunning()); + AsyncTransformComponentMatrix initialOverscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + + // Send lengthy downward momentums to make sure the overscroll animation + // doesn't clobber the momentums scrolling. + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + // The overscroll amount on X axis has started being managed by the overscroll + // animation. + AsyncTransformComponentMatrix currentOverscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + EXPECT_NE(initialOverscrolledTransform._41, currentOverscrolledTransform._41); + // There is no overscroll on Y axis. + EXPECT_EQ(currentOverscrolledTransform._42, 0); + ParentLayerPoint scrollOffset = + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting); + // The scroll offset shouldn't be changed by the overscroll animation. + EXPECT_EQ(scrollOffset.y, 0); + + // Simple gesture on the Y axis to ensure that we can send a vertical + // momentum scroll + PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, -2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 0), mcc->Time()); + + ParentLayerPoint offsetAfterPan = + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting); + + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time()); + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_TRUE(apzc->IsOverscrollAnimationRunning()); + // The overscroll amount on both axes shouldn't be changed by this pan + // momentum start event since the displacement is zero. + EXPECT_EQ( + currentOverscrolledTransform._41, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41); + EXPECT_EQ( + currentOverscrolledTransform._42, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42); + + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + // The overscroll amount should be managed by the overscroll animation. + EXPECT_NE( + currentOverscrolledTransform._41, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41); + scrollOffset = + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting); + // Not yet started scrolling. + EXPECT_EQ(scrollOffset.y, offsetAfterPan.y); + EXPECT_EQ(scrollOffset.x, 0); + + currentOverscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + + // Send a long pan momentum. + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 200), mcc->Time()); + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_TRUE(apzc->IsOverscrollAnimationRunning()); + // The overscroll amount on X axis shouldn't be changed by this momentum pan. + EXPECT_EQ( + currentOverscrolledTransform._41, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41); + // Now it started scrolling vertically. + scrollOffset = + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting); + EXPECT_GT(scrollOffset.y, 0); + EXPECT_EQ(scrollOffset.x, 0); + + currentOverscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + // The overscroll on X axis keeps being managed by the overscroll animation. + EXPECT_NE( + currentOverscrolledTransform._41, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41); + // The scroll offset on Y axis shouldn't be changed by the overscroll + // animation. + EXPECT_EQ(scrollOffset.y, apzc->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::eForHitTesting) + .y); + + currentOverscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + scrollOffset = + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 100), mcc->Time()); + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_TRUE(apzc->IsOverscrollAnimationRunning()); + // The overscroll amount on X axis shouldn't be changed by this momentum pan. + EXPECT_EQ( + currentOverscrolledTransform._41, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41); + // Scrolling keeps going by momentum. + EXPECT_GT( + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting) + .y, + scrollOffset.y); + + scrollOffset = + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 10), mcc->Time()); + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_TRUE(apzc->IsOverscrollAnimationRunning()); + // Scrolling keeps going by momentum. + EXPECT_GT( + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting) + .y, + scrollOffset.y); + + currentOverscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + scrollOffset = + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMEND, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time()); + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_TRUE(apzc->IsOverscrollAnimationRunning()); + // This momentum event doesn't change the scroll offset since its + // displacement is zero. + EXPECT_EQ( + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting) + .y, + scrollOffset.y); + + // Check that we recover from the horizontal overscroll via the animation. + ParentLayerPoint expectedScrollOffset(0, scrollOffset.y); + SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Maybe fails on Android +// Similar to above +// HorizontalOverscrollAnimationWithVerticalPanMomentumScrolling, +// but having OverscrollAnimation on both axes initially. +TEST_F(APZCOverscrollTester, + BothAxesOverscrollAnimationWithPanMomentumScrolling) { + // TODO: This test currently requires gestures that cause movement on both + // axis, which excludes DOMINANT_AXIS locking mode. The gestures should be + // broken up into multiple gestures to cause the overscroll. + SCOPED_GFX_PREF_INT("apz.axis_lock.mode", 2); + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + metrics.SetScrollableRect(CSSRect(0, 0, 1000, 5000)); + apzc->SetFrameMetrics(metrics); + + // Try to overscroll up and left with pan gestures. + PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80), + ScreenPoint(-2, -2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(-10, -10), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(-2, -2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 0), mcc->Time()); + + // Make sure we've started an overscroll animation. + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_TRUE(apzc->IsOverscrollAnimationRunning()); + AsyncTransformComponentMatrix initialOverscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + + // Send lengthy downward momentums to make sure the overscroll animation + // doesn't clobber the momentums scrolling. + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + // The overscroll amount has started being managed by the overscroll + // animation. + AsyncTransformComponentMatrix currentOverscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + EXPECT_NE(initialOverscrolledTransform._41, currentOverscrolledTransform._41); + EXPECT_NE(initialOverscrolledTransform._42, currentOverscrolledTransform._42); + + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time()); + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_TRUE(apzc->IsOverscrollAnimationRunning()); + // The overscroll amount on both axes shouldn't be changed by this pan + // momentum start event since the displacement is zero. + EXPECT_EQ( + currentOverscrolledTransform._41, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41); + EXPECT_EQ( + currentOverscrolledTransform._42, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42); + + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + // Still being managed by the overscroll animation. + EXPECT_NE( + currentOverscrolledTransform._41, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41); + EXPECT_NE( + currentOverscrolledTransform._42, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42); + + currentOverscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + // Send a long pan momentum. + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 200), mcc->Time()); + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_TRUE(apzc->IsOverscrollAnimationRunning()); + // The overscroll amount on X axis shouldn't be changed by this momentum pan. + EXPECT_EQ( + currentOverscrolledTransform._41, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41); + // But now the overscroll amount on Y axis should be changed by this momentum + // pan. + EXPECT_NE( + currentOverscrolledTransform._42, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42); + // Actually it's no longer overscrolled. + EXPECT_EQ( + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42, + 0); + + ParentLayerPoint currentScrollOffset = + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting); + // Now it started scrolling. + EXPECT_GT(currentScrollOffset.y, 0); + + currentOverscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + // The overscroll on X axis keeps being managed by the overscroll animation. + EXPECT_NE( + currentOverscrolledTransform._41, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41); + // But the overscroll on Y axis is no longer affected by the overscroll + // animation. + EXPECT_EQ( + currentOverscrolledTransform._42, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42); + // The scroll offset on Y axis shouldn't be changed by the overscroll + // animation. + EXPECT_EQ( + currentScrollOffset.y, + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting) + .y); + + currentOverscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + currentScrollOffset = + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 100), mcc->Time()); + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_TRUE(apzc->IsOverscrollAnimationRunning()); + // The overscroll amount on X axis shouldn't be changed by this momentum pan. + EXPECT_EQ( + currentOverscrolledTransform._41, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41); + // Keeping no overscrolling on Y axis. + EXPECT_EQ( + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42, + 0); + // Scrolling keeps going by momentum. + EXPECT_GT( + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting) + .y, + currentScrollOffset.y); + + currentScrollOffset = + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 10), mcc->Time()); + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_TRUE(apzc->IsOverscrollAnimationRunning()); + // Keeping no overscrolling on Y axis. + EXPECT_EQ( + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42, + 0); + // Scrolling keeps going by momentum. + EXPECT_GT( + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting) + .y, + currentScrollOffset.y); + + currentOverscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + currentScrollOffset = + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMEND, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time()); + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_TRUE(apzc->IsOverscrollAnimationRunning()); + // Keeping no overscrolling on Y axis. + EXPECT_EQ( + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42, + 0); + // This momentum event doesn't change the scroll offset since its + // displacement is zero. + EXPECT_EQ( + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting) + .y, + currentScrollOffset.y); + + // Check that we recover from the horizontal overscroll via the animation. + ParentLayerPoint expectedScrollOffset(0, currentScrollOffset.y); + SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Maybe fails on Android +// This is another variant of +// HorizontalOverscrollAnimationWithVerticalPanMomentumScrolling. In this test, +// after a horizontal overscroll animation started, upwards pan moments happen, +// thus there should be a new vertical overscroll animation in addition to +// the horizontal one. +TEST_F( + APZCOverscrollTester, + VerticalOverscrollAnimationInAdditionToExistingHorizontalOverscrollAnimation) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + metrics.SetScrollableRect(CSSRect(0, 0, 1000, 5000)); + // Scrolls the content 50px down. + metrics.SetVisualScrollOffset(CSSPoint(0, 50)); + apzc->SetFrameMetrics(metrics); + + // Try to overscroll left with pan gestures. + PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80), + ScreenPoint(-2, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(-10, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(-2, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 0), mcc->Time()); + + // Make sure we've started an overscroll animation. + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_TRUE(apzc->IsOverscrollAnimationRunning()); + AsyncTransformComponentMatrix initialOverscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + + // Send lengthy __upward__ momentums to make sure the overscroll animation + // doesn't clobber the momentums scrolling. + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + // The overscroll amount on X axis has started being managed by the overscroll + // animation. + AsyncTransformComponentMatrix currentOverscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + EXPECT_NE(initialOverscrolledTransform._41, currentOverscrolledTransform._41); + // There is no overscroll on Y axis. + EXPECT_EQ( + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42, + 0); + ParentLayerPoint scrollOffset = + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting); + // The scroll offset shouldn't be changed by the overscroll animation. + EXPECT_EQ(scrollOffset.y, 50); + + // Simple gesture on the Y axis to ensure that we can send a vertical + // momentum scroll + PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, -2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 0), mcc->Time()); + + ParentLayerPoint offsetAfterPan = + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting); + + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time()); + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_TRUE(apzc->IsOverscrollAnimationRunning()); + // The overscroll amount on both axes shouldn't be changed by this pan + // momentum start event since the displacement is zero. + EXPECT_EQ( + currentOverscrolledTransform._41, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41); + EXPECT_EQ( + currentOverscrolledTransform._42, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42); + + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + // The overscroll amount should be managed by the overscroll animation. + EXPECT_NE( + currentOverscrolledTransform._41, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41); + scrollOffset = + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting); + // Not yet started scrolling. + EXPECT_EQ(scrollOffset.y, offsetAfterPan.y); + EXPECT_EQ(scrollOffset.x, 0); + + currentOverscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + + // Send a long pan momentum. + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, -200), mcc->Time()); + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_TRUE(apzc->IsOverscrollAnimationRunning()); + // The overscroll amount on X axis shouldn't be changed by this momentum pan. + EXPECT_EQ( + currentOverscrolledTransform._41, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41); + // Now it started scrolling vertically. + scrollOffset = + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting); + EXPECT_EQ(scrollOffset.y, 0); + EXPECT_EQ(scrollOffset.x, 0); + // Actually it's also vertically overscrolled. + EXPECT_GT( + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42, + 0); + + currentOverscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + // The overscroll on X axis keeps being managed by the overscroll animation. + EXPECT_NE( + currentOverscrolledTransform._41, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41); + // The overscroll on Y Axis hasn't been changed by the overscroll animation at + // this moment, sine the last displacement was consumed in the last pan + // momentum. + EXPECT_EQ( + currentOverscrolledTransform._42, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42); + + currentOverscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, -100), mcc->Time()); + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_TRUE(apzc->IsOverscrollAnimationRunning()); + // The overscroll amount on X axis shouldn't be changed by this momentum pan. + EXPECT_EQ( + currentOverscrolledTransform._41, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41); + // Now the overscroll amount on Y axis shouldn't be changed by this momentum + // pan either. + EXPECT_EQ( + currentOverscrolledTransform._42, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42); + + currentOverscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + EXPECT_NE( + currentOverscrolledTransform._41, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41); + // And now the overscroll on Y Axis should be also managed by the overscroll + // animation. + EXPECT_NE( + currentOverscrolledTransform._42, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42); + + currentOverscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, -10), mcc->Time()); + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_TRUE(apzc->IsOverscrollAnimationRunning()); + // The overscroll amount on both axes shouldn't be changed by momentum event. + EXPECT_EQ( + currentOverscrolledTransform._41, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41); + EXPECT_EQ( + currentOverscrolledTransform._42, + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42); + + currentOverscrolledTransform = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMEND, apzc, + ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time()); + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_TRUE(apzc->IsOverscrollAnimationRunning()); + + // Check that we recover from the horizontal overscroll via the animation. + ParentLayerPoint expectedScrollOffset(0, 0); + SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCOverscrollTester, OverscrollByPanGesturesInterruptedByReflowZoom) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + SCOPED_GFX_PREF_INT("mousewheel.with_control.action", 3); // reflow zoom. + + // A sanity check that pan gestures with ctrl modifier will not be handled by + // APZ. + PanGestureInput panInput(PanGestureInput::PANGESTURE_START, mcc->Time(), + ScreenIntPoint(5, 5), ScreenPoint(0, -2), + MODIFIER_CONTROL); + WidgetWheelEvent wheelEvent = panInput.ToWidgetEvent(nullptr); + EXPECT_FALSE(APZInputBridge::ActionForWheelEvent(&wheelEvent).isSome()); + + ScrollableLayerGuid rootGuid = CreateSimpleRootScrollableForWebRender(); + RefPtr apzc = + tm->GetTargetAPZC(rootGuid.mLayersId, rootGuid.mScrollId); + + PanGesture(PanGestureInput::PANGESTURE_START, tm, ScreenIntPoint(50, 80), + ScreenPoint(0, -2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, tm, ScreenIntPoint(50, 80), + ScreenPoint(0, -10), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + // Make sure overscrolling has started. + EXPECT_TRUE(apzc->IsOverscrolled()); + + // Press ctrl until PANGESTURE_END. + PanGestureWithModifiers(PanGestureInput::PANGESTURE_PAN, MODIFIER_CONTROL, tm, + ScreenIntPoint(50, 80), ScreenPoint(0, -2), + mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + // At this moment (i.e. PANGESTURE_PAN), still in overscrolling state. + EXPECT_TRUE(apzc->IsOverscrolled()); + + PanGestureWithModifiers(PanGestureInput::PANGESTURE_END, MODIFIER_CONTROL, tm, + ScreenIntPoint(50, 80), ScreenPoint(0, 0), + mcc->Time()); + // The overscrolling state should have been restored. + EXPECT_TRUE(!apzc->IsOverscrolled()); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Only applies to GenericOverscrollEffect +TEST_F(APZCOverscrollTester, SmoothTransitionFromPanToAnimation) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + metrics.SetScrollableRect(CSSRect(0, 0, 100, 1000)); + // Start scrolled down to y=500px. + metrics.SetVisualScrollOffset(CSSPoint(0, 500)); + apzc->SetFrameMetrics(metrics); + + int frameLength = 10; // milliseconds; 10 to keep the math simple + float panVelocity = 10; // pixels per millisecond + int panPixelsPerFrame = frameLength * panVelocity; // 100 pixels per frame + + ScreenIntPoint panPoint(50, 50); + PanGesture(PanGestureInput::PANGESTURE_START, apzc, panPoint, + ScreenPoint(0, -1), mcc->Time()); + // Pan up for 6 frames at 100 pixels per frame. This should reduce + // the vertical scroll offset from 500 to 0, and get us into overscroll. + for (int i = 0; i < 6; ++i) { + mcc->AdvanceByMillis(frameLength); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, panPoint, + ScreenPoint(0, -panPixelsPerFrame), mcc->Time()); + } + EXPECT_TRUE(apzc->IsOverscrolled()); + + // Pan further into overscroll at the same input velocity, enough + // for the frames while we are in overscroll to dominate the computation + // in the velocity tracker. + // Importantly, while the input velocity is still 100 pixels per frame, + // in the overscrolled state the page only visual moves by at most 8 pixels + // per frame. + int frames = StaticPrefs::apz_velocity_relevance_time_ms() / frameLength; + for (int i = 0; i < frames; ++i) { + mcc->AdvanceByMillis(frameLength); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, panPoint, + ScreenPoint(0, -panPixelsPerFrame), mcc->Time()); + } + EXPECT_TRUE(apzc->IsOverscrolled()); + + // End the pan, allowing an overscroll animation to start. + mcc->AdvanceByMillis(frameLength); + PanGesture(PanGestureInput::PANGESTURE_END, apzc, panPoint, ScreenPoint(0, 0), + mcc->Time()); + EXPECT_TRUE(apzc->IsOverscrollAnimationRunning()); + + // Check that the velocity reflects the actual movement (no more than 8 + // pixels/frame ==> 0.8 pixels per millisecond), not the input velocity + // (100 pixels/frame ==> 10 pixels per millisecond). This ensures that + // the transition from the pan to the animation appears smooth. + // (Note: velocities are negative since they are upwards.) + EXPECT_LT(apzc->GetVelocityVector().y, 0); + EXPECT_GT(apzc->GetVelocityVector().y, -0.8); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Only applies to GenericOverscrollEffect +TEST_F(APZCOverscrollTester, NoOverscrollForMousewheel) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + metrics.SetScrollableRect(CSSRect(0, 0, 100, 1000)); + // Start scrolled down just a few pixels from the top. + metrics.SetVisualScrollOffset(CSSPoint(0, 3)); + // Set line and page scroll amounts. Otherwise, even though Wheel() uses + // SCROLLDELTA_PIXEL, the wheel handling code will get confused by things + // like the "don't scroll more than one page" check. + metadata.SetPageScrollAmount(LayoutDeviceIntSize(50, 100)); + metadata.SetLineScrollAmount(LayoutDeviceIntSize(5, 10)); + apzc->SetScrollMetadata(metadata); + + // Send a wheel with enough delta to scrollto y=0 *and* overscroll. + Wheel(apzc, ScreenIntPoint(10, 10), ScreenPoint(0, -10), mcc->Time()); + + // Check that we did not actually go into overscroll. + EXPECT_FALSE(apzc->IsOverscrolled()); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Only applies to GenericOverscrollEffect +TEST_F(APZCOverscrollTester, ClickWhileOverscrolled) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + metrics.SetScrollableRect(CSSRect(0, 0, 100, 1000)); + metrics.SetVisualScrollOffset(CSSPoint(0, 0)); + apzc->SetFrameMetrics(metrics); + + // Pan into overscroll at the top. + ScreenIntPoint panPoint(50, 50); + PanGesture(PanGestureInput::PANGESTURE_START, apzc, panPoint, + ScreenPoint(0, -1), mcc->Time()); + mcc->AdvanceByMillis(10); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, panPoint, + ScreenPoint(0, -100), mcc->Time()); + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_TRUE(apzc->GetOverscrollAmount().y < 0); // overscrolled at top + + // End the pan. This should start an overscroll animation. + mcc->AdvanceByMillis(10); + PanGesture(PanGestureInput::PANGESTURE_END, apzc, panPoint, ScreenPoint(0, 0), + mcc->Time()); + EXPECT_TRUE(apzc->GetOverscrollAmount().y < 0); // overscrolled at top + EXPECT_TRUE(apzc->IsOverscrollAnimationRunning()); + + // Send a mouse-down. This should interrupt the animation but not relieve + // overscroll yet. + ParentLayerPoint overscrollBefore = apzc->GetOverscrollAmount(); + MouseDown(apzc, panPoint, mcc->Time()); + EXPECT_FALSE(apzc->IsOverscrollAnimationRunning()); + EXPECT_EQ(overscrollBefore, apzc->GetOverscrollAmount()); + + // Send a mouse-up. This should start an overscroll animation again. + MouseUp(apzc, panPoint, mcc->Time()); + EXPECT_TRUE(apzc->IsOverscrollAnimationRunning()); + + SampleAnimationUntilRecoveredFromOverscroll(ParentLayerPoint(0, 0)); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Only applies to GenericOverscrollEffect +TEST_F(APZCOverscrollTester, DynamicallyLoadingContent) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + metrics.SetScrollableRect(CSSRect(0, 0, 100, 1000)); + metrics.SetVisualScrollOffset(CSSPoint(0, 0)); + apzc->SetFrameMetrics(metrics); + + // Pan to the bottom of the page, and further, into overscroll. + ScreenIntPoint panPoint(50, 50); + PanGesture(PanGestureInput::PANGESTURE_START, apzc, panPoint, + ScreenPoint(0, 1), mcc->Time()); + for (int i = 0; i < 12; ++i) { + mcc->AdvanceByMillis(10); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, panPoint, + ScreenPoint(0, 100), mcc->Time()); + } + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_TRUE(apzc->GetOverscrollAmount().y > 0); // overscrolled at bottom + + // Grow the scrollable rect at the bottom, simulating the page loading content + // dynamically. + CSSRect scrollableRect = metrics.GetScrollableRect(); + scrollableRect.height += 500; + metrics.SetScrollableRect(scrollableRect); + apzc->NotifyLayersUpdated(metadata, false, true); + + // Check that the modified scrollable rect cleared the overscroll. + EXPECT_FALSE(apzc->IsOverscrolled()); + + // Pan back up to the top, and further, into overscroll. + PanGesture(PanGestureInput::PANGESTURE_START, apzc, panPoint, + ScreenPoint(0, -1), mcc->Time()); + for (int i = 0; i < 12; ++i) { + mcc->AdvanceByMillis(10); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, panPoint, + ScreenPoint(0, -100), mcc->Time()); + } + EXPECT_TRUE(apzc->IsOverscrolled()); + ParentLayerPoint overscrollAmount = apzc->GetOverscrollAmount(); + EXPECT_TRUE(overscrollAmount.y < 0); // overscrolled at top + + // Grow the scrollable rect at the bottom again. + scrollableRect = metrics.GetScrollableRect(); + scrollableRect.height += 500; + metrics.SetScrollableRect(scrollableRect); + apzc->NotifyLayersUpdated(metadata, false, true); + + // Check that the modified scrollable rect did NOT clear overscroll at the + // top. + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_EQ(overscrollAmount, + apzc->GetOverscrollAmount()); // overscroll did not change at all +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Only applies to GenericOverscrollEffect +TEST_F(APZCOverscrollTester, SmallAmountOfOverscroll) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + metrics.SetScrollableRect(CSSRect(0, 0, 100, 1000)); + + // Do vertical overscroll first. + ScreenIntPoint panPoint(50, 50); + PanGesture(PanGestureInput::PANGESTURE_START, apzc, panPoint, + ScreenPoint(0, -10), mcc->Time()); + mcc->AdvanceByMillis(10); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, panPoint, + ScreenPoint(0, -10), mcc->Time()); + mcc->AdvanceByMillis(10); + PanGesture(PanGestureInput::PANGESTURE_END, apzc, panPoint, ScreenPoint(0, 0), + mcc->Time()); + mcc->AdvanceByMillis(10); + + // Then do small horizontal overscroll which will be considered as "finished" + // by our overscroll animation physics model. + PanGesture(PanGestureInput::PANGESTURE_START, apzc, panPoint, + ScreenPoint(-0.1, 0), mcc->Time()); + mcc->AdvanceByMillis(10); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, panPoint, + ScreenPoint(-0.2, 0), mcc->Time()); + mcc->AdvanceByMillis(10); + PanGesture(PanGestureInput::PANGESTURE_END, apzc, panPoint, ScreenPoint(0, 0), + mcc->Time()); + mcc->AdvanceByMillis(10); + + EXPECT_TRUE(apzc->IsOverscrolled()); + EXPECT_TRUE(apzc->GetOverscrollAmount().y < 0); // overscrolled at top + EXPECT_TRUE(apzc->GetOverscrollAmount().x < 0); // and overscrolled at left + + // Then do vertical scroll. + PanGesture(PanGestureInput::PANGESTURE_START, apzc, panPoint, + ScreenPoint(0, 10), mcc->Time()); + mcc->AdvanceByMillis(10); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, panPoint, + ScreenPoint(0, 100), mcc->Time()); + mcc->AdvanceByMillis(10); + PanGesture(PanGestureInput::PANGESTURE_END, apzc, panPoint, ScreenPoint(0, 0), + mcc->Time()); + + ParentLayerPoint scrollOffset = + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting); + EXPECT_GT(scrollOffset.y, 0); // Make sure the vertical scroll offset is + // greater than zero. + + // The small horizontal overscroll amount should be restored to zero. + ParentLayerPoint expectedScrollOffset(0, scrollOffset.y); + SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset); +} +#endif + +#ifdef MOZ_WIDGET_ANDROID // Only applies to WidgetOverscrollEffect +TEST_F(APZCOverscrollTester, StuckInOverscroll_Bug1786452) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + metrics.SetScrollableRect(CSSRect(0, 0, 100, 1000)); + + // Over the course of the test, expect one or more calls to + // UpdateOverscrollOffset(), followed by a call to UpdateOverscrollVelocity(). + // The latter ensures the widget has a chance to end its overscroll effect. + InSequence s; + EXPECT_CALL(*mcc, UpdateOverscrollOffset(_, _, _, _)).Times(AtLeast(1)); + EXPECT_CALL(*mcc, UpdateOverscrollVelocity(_, _, _, _)).Times(1); + + // Pan into overscroll, keeping the finger down + ScreenIntPoint startPoint(10, 500); + ScreenIntPoint endPoint(10, 10); + Pan(apzc, startPoint, endPoint, PanOptions::KeepFingerDown); + EXPECT_TRUE(apzc->IsOverscrolled()); + + // Linger a while to cause the velocity to drop to very low or zero + mcc->AdvanceByMillis(100); + TouchMove(apzc, endPoint, mcc->Time()); + EXPECT_LT(apzc->GetVelocityVector().Length(), + StaticPrefs::apz_fling_min_velocity_threshold()); + EXPECT_TRUE(apzc->IsOverscrolled()); + + // Lift the finger + mcc->AdvanceByMillis(20); + TouchUp(apzc, endPoint, mcc->Time()); + EXPECT_FALSE(apzc->IsOverscrolled()); +} +#endif + +class APZCOverscrollTesterMock : public APZCTreeManagerTester { + public: + APZCOverscrollTesterMock() { CreateMockHitTester(); } + + UniquePtr registration; + TestAsyncPanZoomController* rootApzc; +}; + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCOverscrollTesterMock, OverscrollHandoff) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + const char* treeShape = "x(x)"; + LayerIntRegion layerVisibleRegion[] = {LayerIntRect(0, 0, 100, 100), + LayerIntRect(0, 0, 100, 50)}; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 200, 200)); + SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1, + // same size as the visible region so that + // the container is not scrollable in any directions + // actually. This is simulating overflow: hidden + // iframe document in Fission, though we don't set + // a different layers id. + CSSRect(0, 0, 100, 50)); + + SetScrollHandoff(layers[1], root); + + registration = MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + rootApzc = ApzcOf(root); + rootApzc->GetFrameMetrics().SetIsRootContent(true); + + // A pan gesture on the child scroller (which is not scrollable though). + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 20), + ScreenPoint(0, -2), mcc->Time()); + EXPECT_TRUE(rootApzc->IsOverscrolled()); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCOverscrollTesterMock, VerticalOverscrollHandoffToScrollableRoot) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + // Create a layer tree having two vertical scrollable layers. + const char* treeShape = "x(x)"; + LayerIntRegion layerVisibleRegion[] = {LayerIntRect(0, 0, 100, 100), + LayerIntRect(0, 0, 100, 50)}; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 100, 200)); + SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 100, 200)); + + SetScrollHandoff(layers[1], root); + + registration = MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + rootApzc = ApzcOf(root); + rootApzc->GetFrameMetrics().SetIsRootContent(true); + + // A vertical pan gesture on the child scroller which will be handed off to + // the root APZC. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 20), + ScreenPoint(0, -2), mcc->Time()); + EXPECT_TRUE(rootApzc->IsOverscrolled()); + EXPECT_FALSE(ApzcOf(layers[1])->IsOverscrolled()); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCOverscrollTesterMock, NoOverscrollHandoffToNonScrollableRoot) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + // Create a layer tree having non-scrollable root and a vertical scrollable + // child. + const char* treeShape = "x(x)"; + LayerIntRegion layerVisibleRegion[] = {LayerIntRect(0, 0, 100, 100), + LayerIntRect(0, 0, 100, 50)}; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 100, 100)); + SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 100, 200)); + + SetScrollHandoff(layers[1], root); + + registration = MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + rootApzc = ApzcOf(root); + rootApzc->GetFrameMetrics().SetIsRootContent(true); + + // A vertical pan gesture on the child scroller which should not be handed + // off the root APZC. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 20), + ScreenPoint(0, -2), mcc->Time()); + EXPECT_FALSE(rootApzc->IsOverscrolled()); + EXPECT_TRUE(ApzcOf(layers[1])->IsOverscrolled()); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCOverscrollTesterMock, NoOverscrollHandoffOrthogonalPanGesture) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + // Create a layer tree having horizontal scrollable root and a vertical + // scrollable child. + const char* treeShape = "x(x)"; + LayerIntRegion layerVisibleRegion[] = {LayerIntRect(0, 0, 100, 100), + LayerIntRect(0, 0, 100, 50)}; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 200, 100)); + SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 100, 200)); + + SetScrollHandoff(layers[1], root); + + registration = MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + rootApzc = ApzcOf(root); + rootApzc->GetFrameMetrics().SetIsRootContent(true); + + // A vertical pan gesture on the child scroller which should not be handed + // off the root APZC because the root APZC is not scrollable vertically. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 20), + ScreenPoint(0, -2), mcc->Time()); + EXPECT_FALSE(rootApzc->IsOverscrolled()); + EXPECT_TRUE(ApzcOf(layers[1])->IsOverscrolled()); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Only applies to GenericOverscrollEffect +TEST_F(APZCOverscrollTesterMock, + RetriggerCancelledOverscrollAnimationByNewPanGesture) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + // Create a layer tree having vertical scrollable root and a horizontal + // scrollable child. + const char* treeShape = "x(x)"; + LayerIntRegion layerVisibleRegion[] = {LayerIntRect(0, 0, 100, 100), + LayerIntRect(0, 0, 100, 50)}; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 100, 200)); + SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 200, 50)); + + SetScrollHandoff(layers[1], root); + + registration = MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + rootApzc = ApzcOf(root); + rootApzc->GetFrameMetrics().SetIsRootContent(true); + + ScreenIntPoint panPoint(50, 20); + // A vertical pan gesture on the child scroller which should be handed off the + // root APZC. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + PanGesture(PanGestureInput::PANGESTURE_START, manager, panPoint, + ScreenPoint(0, -2), mcc->Time()); + mcc->AdvanceByMillis(10); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + PanGesture(PanGestureInput::PANGESTURE_PAN, manager, panPoint, + ScreenPoint(0, -10), mcc->Time()); + mcc->AdvanceByMillis(10); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + PanGesture(PanGestureInput::PANGESTURE_END, manager, panPoint, + ScreenPoint(0, 0), mcc->Time()); + + // The root APZC should be overscrolled and the child APZC should not be. + EXPECT_TRUE(rootApzc->IsOverscrolled()); + EXPECT_FALSE(ApzcOf(layers[1])->IsOverscrolled()); + + mcc->AdvanceByMillis(10); + + // Make sure the root APZC is still overscrolled. + EXPECT_TRUE(rootApzc->IsOverscrolled()); + + // Start a new horizontal pan gesture on the child scroller which should be + // handled by the child APZC now. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + APZEventResult result = PanGesture(PanGestureInput::PANGESTURE_START, manager, + panPoint, ScreenPoint(-2, 0), mcc->Time()); + // The above horizontal pan start event was flagged as "this event may trigger + // swipe" and either the root scrollable frame or the horizontal child + // scrollable frame is not scrollable in the pan start direction, thus the pan + // start event run into the short circuit path for swipe-to-navigation in + // InputQueue::ReceivePanGestureInput, which means it's waiting for the + // content response, so we need to respond explicitly here. + manager->ContentReceivedInputBlock(result.mInputBlockId, false); + mcc->AdvanceByMillis(10); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + PanGesture(PanGestureInput::PANGESTURE_PAN, manager, panPoint, + ScreenPoint(-10, 0), mcc->Time()); + mcc->AdvanceByMillis(10); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + PanGesture(PanGestureInput::PANGESTURE_END, manager, panPoint, + ScreenPoint(0, 0), mcc->Time()); + + // Now both APZCs should be overscrolled. + EXPECT_TRUE(rootApzc->IsOverscrolled()); + EXPECT_TRUE(ApzcOf(layers[1])->IsOverscrolled()); + + // Sample all animations until all of them have been finished. + while (SampleAnimationsOnce()) + ; + + // After the animations finished, all overscrolled states should have been + // restored. + EXPECT_FALSE(rootApzc->IsOverscrolled()); + EXPECT_FALSE(ApzcOf(layers[1])->IsOverscrolled()); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Only applies to GenericOverscrollEffect +TEST_F(APZCOverscrollTesterMock, RetriggeredOverscrollAnimationVelocity) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + // Setup two nested vertical scrollable frames. + const char* treeShape = "x(x)"; + LayerIntRegion layerVisibleRegion[] = {LayerIntRect(0, 0, 100, 100), + LayerIntRect(0, 0, 100, 50)}; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 100, 200)); + SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 100, 200)); + + SetScrollHandoff(layers[1], root); + + registration = MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + rootApzc = ApzcOf(root); + rootApzc->GetFrameMetrics().SetIsRootContent(true); + + ScreenIntPoint panPoint(50, 20); + // A vertical upward pan gesture on the child scroller which should be handed + // off the root APZC. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + PanGesture(PanGestureInput::PANGESTURE_START, manager, panPoint, + ScreenPoint(0, -2), mcc->Time()); + mcc->AdvanceByMillis(10); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + PanGesture(PanGestureInput::PANGESTURE_PAN, manager, panPoint, + ScreenPoint(0, -10), mcc->Time()); + mcc->AdvanceByMillis(10); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + PanGesture(PanGestureInput::PANGESTURE_END, manager, panPoint, + ScreenPoint(0, 0), mcc->Time()); + + // The root APZC should be overscrolled and the child APZC should not be. + EXPECT_TRUE(rootApzc->IsOverscrolled()); + EXPECT_FALSE(ApzcOf(layers[1])->IsOverscrolled()); + + mcc->AdvanceByMillis(10); + + // Make sure the root APZC is still overscrolled and there's an overscroll + // animation. + EXPECT_TRUE(rootApzc->IsOverscrolled()); + EXPECT_TRUE(rootApzc->IsOverscrollAnimationRunning()); + + // And make sure the overscroll animation's velocity is a certain amount in + // the upward direction. + EXPECT_LT(rootApzc->GetVelocityVector().y, 0); + + // Start a new downward pan gesture on the child scroller which + // should be handled by the child APZC now. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + PanGesture(PanGestureInput::PANGESTURE_START, manager, panPoint, + ScreenPoint(0, 2), mcc->Time()); + mcc->AdvanceByMillis(10); + // The new pan-start gesture stops the overscroll animation at this moment. + EXPECT_TRUE(!rootApzc->IsOverscrollAnimationRunning()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + PanGesture(PanGestureInput::PANGESTURE_PAN, manager, panPoint, + ScreenPoint(0, 10), mcc->Time()); + mcc->AdvanceByMillis(10); + // There's no overscroll animation yet even if the root APZC is still + // overscrolled. + EXPECT_TRUE(!rootApzc->IsOverscrollAnimationRunning()); + EXPECT_TRUE(rootApzc->IsOverscrolled()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + PanGesture(PanGestureInput::PANGESTURE_END, manager, panPoint, + ScreenPoint(0, 10), mcc->Time()); + + // Now an overscroll animation should have been triggered by the pan-end + // gesture. + EXPECT_TRUE(rootApzc->IsOverscrollAnimationRunning()); + EXPECT_TRUE(rootApzc->IsOverscrolled()); + // And the newly created overscroll animation's positions should never exceed + // 0. + while (SampleAnimationsOnce()) { + EXPECT_LE(rootApzc->GetOverscrollAmount().y, 0); + } +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Only applies to GenericOverscrollEffect +TEST_F(APZCOverscrollTesterMock, OverscrollIntoPreventDefault) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + const char* treeShape = "x"; + LayerIntRegion layerVisibleRegions[] = {LayerIntRect(0, 0, 100, 100)}; + CreateScrollData(treeShape, layerVisibleRegions); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 100, 200)); + + registration = MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + rootApzc = ApzcOf(root); + + // Start a pan gesture a few pixels below the 20px DTC region. + ScreenIntPoint cursorLocation(10, 25); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + APZEventResult result = + PanGesture(PanGestureInput::PANGESTURE_START, manager, cursorLocation, + ScreenPoint(0, -2), mcc->Time()); + + // At this point, we should be overscrolled. + EXPECT_TRUE(rootApzc->IsOverscrolled()); + + // Pan further, until the DTC region is under the cursor. + // Note that, due to ApplyResistance(), we need a large input delta to cause a + // visual transform enough to bridge the 5px to the DTC region. + mcc->AdvanceByMillis(10); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_PAN, manager, cursorLocation, + ScreenPoint(0, -100), mcc->Time()); + + // At this point, we are still overscrolled. Record the overscroll amount. + EXPECT_TRUE(rootApzc->IsOverscrolled()); + float overscrollY = rootApzc->GetOverscrollAmount().y; + + // Send a content response with preventDefault = true. + manager->SetAllowedTouchBehavior(result.mInputBlockId, + {AllowedTouchBehavior::VERTICAL_PAN}); + manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid}); + manager->ContentReceivedInputBlock(result.mInputBlockId, + /*aPreventDefault=*/true); + + // The content response has the effect of interrupting the input block + // but no processing happens yet (as there are no events in the block). + EXPECT_TRUE(rootApzc->IsOverscrolled()); + EXPECT_EQ(overscrollY, rootApzc->GetOverscrollAmount().y); + + // Send one more pan event. This starts a new, *unconfirmed* input block + // (via the "transmogrify" codepath). + mcc->AdvanceByMillis(10); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + result = PanGesture(PanGestureInput::PANGESTURE_PAN, manager, cursorLocation, + ScreenPoint(0, -10), mcc->Time()); + + // No overscroll occurs (the event is waiting in the queue for confirmation). + EXPECT_TRUE(rootApzc->IsOverscrolled()); + EXPECT_EQ(overscrollY, rootApzc->GetOverscrollAmount().y); + + // preventDefault the new event as well + manager->SetAllowedTouchBehavior(result.mInputBlockId, + {AllowedTouchBehavior::VERTICAL_PAN}); + manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid}); + manager->ContentReceivedInputBlock(result.mInputBlockId, + /*aPreventDefault=*/true); + + // This should trigger clearing the overscrolling and resetting the state. + EXPECT_FALSE(rootApzc->IsOverscrolled()); + rootApzc->AssertStateIsReset(); + + // If there are momentum events after this point, they should not cause + // further scrolling or overscorll. + mcc->AdvanceByMillis(10); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + result = PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, manager, + cursorLocation, ScreenPoint(0, -100), mcc->Time()); + mcc->AdvanceByMillis(10); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + result = PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, manager, + cursorLocation, ScreenPoint(0, -100), mcc->Time()); + EXPECT_FALSE(rootApzc->IsOverscrolled()); + EXPECT_EQ(rootApzc->GetFrameMetrics().GetVisualScrollOffset(), + CSSPoint(0, 0)); +} +#endif diff --git a/gfx/layers/apz/test/gtest/TestPanning.cpp b/gfx/layers/apz/test/gtest/TestPanning.cpp new file mode 100644 index 0000000000..886b0fec99 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestPanning.cpp @@ -0,0 +1,251 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCBasicTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" +#include "gtest/gtest.h" + +class APZCPanningTester : public APZCBasicTester { + protected: + void DoPanTest(bool aShouldTriggerScroll, bool aShouldBeConsumed, + uint32_t aBehavior) { + if (aShouldTriggerScroll) { + // Three repaint request for each pan. + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(6); + } else { + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0); + } + + int touchStart = 50; + int touchEnd = 10; + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + + nsTArray allowedTouchBehaviors; + allowedTouchBehaviors.AppendElement(aBehavior); + + // Pan down + PanAndCheckStatus(apzc, touchStart, touchEnd, aShouldBeConsumed, + &allowedTouchBehaviors); + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + + if (aShouldTriggerScroll) { + EXPECT_EQ(ParentLayerPoint(0, -(touchEnd - touchStart)), pointOut); + EXPECT_NE(AsyncTransform(), viewTransformOut); + } else { + EXPECT_EQ(ParentLayerPoint(), pointOut); + EXPECT_EQ(AsyncTransform(), viewTransformOut); + } + + // Clear the fling from the previous pan, or stopping it will + // consume the next touchstart + apzc->CancelAnimation(); + + // Pan back + PanAndCheckStatus(apzc, touchEnd, touchStart, aShouldBeConsumed, + &allowedTouchBehaviors); + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + + EXPECT_EQ(ParentLayerPoint(), pointOut); + EXPECT_EQ(AsyncTransform(), viewTransformOut); + } + + void DoPanWithPreventDefaultTest() { + MakeApzcWaitForMainThread(); + + int touchStart = 50; + int touchEnd = 10; + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + uint64_t blockId = 0; + + // Pan down + nsTArray allowedTouchBehaviors; + allowedTouchBehaviors.AppendElement( + mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN); + PanAndCheckStatus(apzc, touchStart, touchEnd, true, &allowedTouchBehaviors, + &blockId); + + // Send the signal that content has handled and preventDefaulted the touch + // events. This flushes the event queue. + apzc->ContentReceivedInputBlock(blockId, true); + + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + EXPECT_EQ(ParentLayerPoint(), pointOut); + EXPECT_EQ(AsyncTransform(), viewTransformOut); + + apzc->AssertStateIsReset(); + } +}; + +// In the each of the following 4 pan tests we are performing two pan gestures: +// vertical pan from top to bottom and back - from bottom to top. According to +// the pointer-events/touch-action spec AUTO and PAN_Y touch-action values allow +// vertical scrolling while NONE and PAN_X forbid it. The first parameter of +// DoPanTest method specifies this behavior. However, the events will be marked +// as consumed even if the behavior in PAN_X, because the user could move their +// finger horizontally too - APZ has no way of knowing beforehand and so must +// consume the events. +TEST_F(APZCPanningTester, PanWithTouchActionAuto) { + // Velocity bias can cause extra repaint requests. + SCOPED_GFX_PREF_FLOAT("apz.velocity_bias", 0.0); + DoPanTest(true, true, + mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN | + mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN); +} + +TEST_F(APZCPanningTester, PanWithTouchActionNone) { + // Velocity bias can cause extra repaint requests. + SCOPED_GFX_PREF_FLOAT("apz.velocity_bias", 0.0); + DoPanTest(false, false, 0); +} + +TEST_F(APZCPanningTester, PanWithTouchActionPanX) { + // Velocity bias can cause extra repaint requests. + SCOPED_GFX_PREF_FLOAT("apz.velocity_bias", 0.0); + DoPanTest(false, false, + mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN); +} + +TEST_F(APZCPanningTester, PanWithTouchActionPanY) { + // Velocity bias can cause extra repaint requests. + SCOPED_GFX_PREF_FLOAT("apz.velocity_bias", 0.0); + DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN); +} + +TEST_F(APZCPanningTester, PanWithPreventDefault) { + DoPanWithPreventDefaultTest(); +} + +TEST_F(APZCPanningTester, PanWithHistoricalTouchData) { + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0); + + // Simulate the same pan gesture, in three different ways. + // We start at y=50, with a 50ms resting period at the start of the pan. + // Then we accelerate the finger upwards towards y=10, reaching a 10px/10ms + // velocity towards the end of the panning motion. + // + // The first simulation fires touch move events with 10ms gaps. + // The second simulation skips two of the touch move events, simulating + // "jank". The third simulation also skips those two events, but reports the + // missed positions in the following event's historical coordinates. + // + // Consequently, the first and third simulation should estimate the same + // velocities, whereas the second simulation should estimate a different + // velocity because it is missing data. + + // First simulation: full data + + APZEventResult result = TouchDown(apzc, ScreenIntPoint(0, 50), mcc->Time()); + if (result.GetStatus() != nsEventStatus_eConsumeNoDefault) { + SetDefaultAllowedTouchBehavior(apzc, result.mInputBlockId); + } + + mcc->AdvanceByMillis(50); + result = TouchMove(apzc, ScreenIntPoint(0, 45), mcc->Time()); + mcc->AdvanceByMillis(10); + result = TouchMove(apzc, ScreenIntPoint(0, 40), mcc->Time()); + mcc->AdvanceByMillis(10); + result = TouchMove(apzc, ScreenIntPoint(0, 30), mcc->Time()); + mcc->AdvanceByMillis(10); + result = TouchMove(apzc, ScreenIntPoint(0, 20), mcc->Time()); + result = TouchUp(apzc, ScreenIntPoint(0, 20), mcc->Time()); + auto velocityFromFullDataAsSeparateEvents = apzc->GetVelocityVector(); + apzc->CancelAnimation(); + + mcc->AdvanceByMillis(100); + + // Second simulation: partial data + + result = TouchDown(apzc, ScreenIntPoint(0, 50), mcc->Time()); + if (result.GetStatus() != nsEventStatus_eConsumeNoDefault) { + SetDefaultAllowedTouchBehavior(apzc, result.mInputBlockId); + } + + mcc->AdvanceByMillis(50); + result = TouchMove(apzc, ScreenIntPoint(0, 45), mcc->Time()); + mcc->AdvanceByMillis(30); + result = TouchMove(apzc, ScreenIntPoint(0, 20), mcc->Time()); + result = TouchUp(apzc, ScreenIntPoint(0, 20), mcc->Time()); + auto velocityFromPartialData = apzc->GetVelocityVector(); + apzc->CancelAnimation(); + + mcc->AdvanceByMillis(100); + + // Third simulation: full data via historical data + + result = TouchDown(apzc, ScreenIntPoint(0, 50), mcc->Time()); + if (result.GetStatus() != nsEventStatus_eConsumeNoDefault) { + SetDefaultAllowedTouchBehavior(apzc, result.mInputBlockId); + } + + mcc->AdvanceByMillis(50); + result = TouchMove(apzc, ScreenIntPoint(0, 45), mcc->Time()); + mcc->AdvanceByMillis(30); + + MultiTouchInput mti = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + auto singleTouchData = CreateSingleTouchData(0, ScreenIntPoint(0, 20)); + singleTouchData.mHistoricalData.AppendElement( + SingleTouchData::HistoricalTouchData{ + mcc->Time() - TimeDuration::FromMilliseconds(20), + ScreenIntPoint(0, 40), + {}, + {}, + 0.0f, + 0.0f}); + singleTouchData.mHistoricalData.AppendElement( + SingleTouchData::HistoricalTouchData{ + mcc->Time() - TimeDuration::FromMilliseconds(10), + ScreenIntPoint(0, 30), + {}, + {}, + 0.0f, + 0.0f}); + mti.mTouches.AppendElement(singleTouchData); + result = apzc->ReceiveInputEvent(mti); + + result = TouchUp(apzc, ScreenIntPoint(0, 20), mcc->Time()); + auto velocityFromFullDataViaHistory = apzc->GetVelocityVector(); + apzc->CancelAnimation(); + + EXPECT_EQ(velocityFromFullDataAsSeparateEvents, + velocityFromFullDataViaHistory); + EXPECT_NE(velocityFromPartialData, velocityFromFullDataViaHistory); +} + +TEST_F(APZCPanningTester, DuplicatePanEndEvents_Bug1833950) { + // Send a pan gesture that triggers a fling animation at the end. + // Note that we need at least two _PAN events to have enough samples + // in the velocity tracker to compute a fling velocity. + PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 2), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 10), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 10), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 0), mcc->Time(), MODIFIER_NONE, + /*aSimulateMomentum=*/true); + + // Give the fling animation a chance to start. + SampleAnimationOnce(); + apzc->AssertStateIsFling(); + + // Send a duplicate pan-end event. + // This test is just intended to check that doing this doesn't + // trigger an assertion failure in debug mode. + PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80), + ScreenPoint(0, 0), mcc->Time(), MODIFIER_NONE, + /*aSimulateMomentum=*/true); +} diff --git a/gfx/layers/apz/test/gtest/TestPinching.cpp b/gfx/layers/apz/test/gtest/TestPinching.cpp new file mode 100644 index 0000000000..f6d1280bf3 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestPinching.cpp @@ -0,0 +1,675 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCBasicTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" +#include "mozilla/StaticPrefs_apz.h" + +class APZCPinchTester : public APZCBasicTester { + private: + // This (multiplied by apz.touch_start_tolerance) needs to be the hypotenuse + // in a Pythagorean triple, along with overcomeTouchToleranceX and + // overcomeTouchToleranceY from APZCTesterBase::Pan(). + // This is because APZCTesterBase::Pan(), when run without the + // PanOptions::ExactCoordinates option, will need to first overcome the + // touch start tolerance by performing a move of exactly + // (apz.touch_start_tolerance * DPI) length. + // When moving on both axes at once, we need to use integers for both legs + // (overcomeTouchToleranceX and overcomeTouchToleranceY) while making sure + // that the hypotenuse is also a round integer number (hence Pythagorean + // triples). (The hypotenuse is the length of the movement in this case.) + static const int mDPI = 100; + + public: + explicit APZCPinchTester( + AsyncPanZoomController::GestureBehavior aGestureBehavior = + AsyncPanZoomController::DEFAULT_GESTURES) + : APZCBasicTester(aGestureBehavior) {} + + void SetUp() override { + APZCBasicTester::SetUp(); + tm->SetDPI(mDPI); + } + + protected: + FrameMetrics GetPinchableFrameMetrics() { + FrameMetrics fm; + fm.SetCompositionBounds(ParentLayerRect(0, 0, 100, 200)); + fm.SetScrollableRect(CSSRect(0, 0, 980, 1000)); + fm.SetVisualScrollOffset(CSSPoint(300, 300)); + fm.SetLayoutViewport(CSSRect(300, 300, 100, 200)); + fm.SetZoom(CSSToParentLayerScale(2.0)); + // APZC only allows zooming on the root scrollable frame. + fm.SetIsRootContent(true); + // the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100 + return fm; + } + + void DoPinchTest(bool aShouldTriggerPinch, + nsTArray* aAllowedTouchBehaviors = nullptr) { + apzc->SetFrameMetrics(GetPinchableFrameMetrics()); + MakeApzcZoomable(); + + if (aShouldTriggerPinch) { + // One repaint request for each gesture. + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(2); + } else { + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0); + } + + int touchInputId = 0; + if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) { + PinchWithTouchInputAndCheckStatus(apzc, ScreenIntPoint(250, 300), 1.25, + touchInputId, aShouldTriggerPinch, + aAllowedTouchBehaviors); + } else { + PinchWithPinchInputAndCheckStatus(apzc, ScreenIntPoint(250, 300), 1.25, + aShouldTriggerPinch); + } + + apzc->AssertStateIsReset(); + + FrameMetrics fm = apzc->GetFrameMetrics(); + + if (aShouldTriggerPinch) { + // the visible area of the document in CSS pixels is now x=325 y=330 w=40 + // h=80 + EXPECT_EQ(2.5f, fm.GetZoom().scale); + EXPECT_EQ(325, fm.GetVisualScrollOffset().x); + EXPECT_EQ(330, fm.GetVisualScrollOffset().y); + } else { + // The frame metrics should stay the same since touch-action:none makes + // apzc ignore pinch gestures. + EXPECT_EQ(2.0f, fm.GetZoom().scale); + EXPECT_EQ(300, fm.GetVisualScrollOffset().x); + EXPECT_EQ(300, fm.GetVisualScrollOffset().y); + } + + // part 2 of the test, move to the top-right corner of the page and pinch + // and make sure we stay in the correct spot + fm.SetZoom(CSSToParentLayerScale(2.0)); + fm.SetVisualScrollOffset(CSSPoint(930, 5)); + apzc->SetFrameMetrics(fm); + // the visible area of the document in CSS pixels is x=930 y=5 w=50 h=100 + + if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) { + PinchWithTouchInputAndCheckStatus(apzc, ScreenIntPoint(250, 300), 0.5, + touchInputId, aShouldTriggerPinch, + aAllowedTouchBehaviors); + } else { + PinchWithPinchInputAndCheckStatus(apzc, ScreenIntPoint(250, 300), 0.5, + aShouldTriggerPinch); + } + + apzc->AssertStateIsReset(); + + fm = apzc->GetFrameMetrics(); + + if (aShouldTriggerPinch) { + // the visible area of the document in CSS pixels is now x=805 y=0 w=100 + // h=200 + EXPECT_EQ(1.0f, fm.GetZoom().scale); + EXPECT_EQ(805, fm.GetVisualScrollOffset().x); + EXPECT_EQ(0, fm.GetVisualScrollOffset().y); + } else { + EXPECT_EQ(2.0f, fm.GetZoom().scale); + EXPECT_EQ(930, fm.GetVisualScrollOffset().x); + EXPECT_EQ(5, fm.GetVisualScrollOffset().y); + } + } +}; + +class APZCPinchGestureDetectorTester : public APZCPinchTester { + public: + APZCPinchGestureDetectorTester() + : APZCPinchTester(AsyncPanZoomController::USE_GESTURE_DETECTOR) {} + + void DoPinchWithPreventDefaultTest() { + FrameMetrics originalMetrics = GetPinchableFrameMetrics(); + apzc->SetFrameMetrics(originalMetrics); + + MakeApzcWaitForMainThread(); + MakeApzcZoomable(); + + int touchInputId = 0; + uint64_t blockId = 0; + PinchWithTouchInput(apzc, ScreenIntPoint(250, 300), 1.25, touchInputId, + nullptr, nullptr, &blockId); + + // Send the prevent-default notification for the touch block + apzc->ContentReceivedInputBlock(blockId, true); + + // verify the metrics didn't change (i.e. the pinch was ignored) + FrameMetrics fm = apzc->GetFrameMetrics(); + EXPECT_EQ(originalMetrics.GetZoom(), fm.GetZoom()); + EXPECT_EQ(originalMetrics.GetVisualScrollOffset().x, + fm.GetVisualScrollOffset().x); + EXPECT_EQ(originalMetrics.GetVisualScrollOffset().y, + fm.GetVisualScrollOffset().y); + + apzc->AssertStateIsReset(); + } +}; + +class APZCPinchLockingTester : public APZCPinchTester { + private: + ScreenIntPoint mFocus; + float mSpan; + int mPinchLockBufferMaxAge; + + public: + APZCPinchLockingTester() + : APZCPinchTester(AsyncPanZoomController::USE_GESTURE_DETECTOR), + mFocus(ScreenIntPoint(200, 300)), + mSpan(10.0) {} + + virtual void SetUp() { + mPinchLockBufferMaxAge = + StaticPrefs::apz_pinch_lock_buffer_max_age_AtStartup(); + + APZCPinchTester::SetUp(); + apzc->SetFrameMetrics(GetPinchableFrameMetrics()); + MakeApzcZoomable(); + + auto event = CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_START, + mFocus, mSpan, mSpan, mcc->Time()); + apzc->ReceiveInputEvent(event); + mcc->AdvanceBy(TimeDuration::FromMilliseconds(mPinchLockBufferMaxAge + 1)); + } + + void twoFingerPan() { + ScreenCoord panDistance = + StaticPrefs::apz_pinch_lock_scroll_lock_threshold() * 1.2 * + tm->GetDPI(); + + mFocus = ScreenIntPoint((int)(mFocus.x.value + panDistance), + (int)(mFocus.y.value)); + + auto event = CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE, + mFocus, mSpan, mSpan, mcc->Time()); + apzc->ReceiveInputEvent(event); + mcc->AdvanceBy(TimeDuration::FromMilliseconds(mPinchLockBufferMaxAge + 1)); + } + + void twoFingerZoom() { + float pinchDistance = + StaticPrefs::apz_pinch_lock_span_breakout_threshold() * 1.2 * + tm->GetDPI(); + + float newSpan = mSpan + pinchDistance; + + auto event = CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE, + mFocus, newSpan, mSpan, mcc->Time()); + apzc->ReceiveInputEvent(event); + mcc->AdvanceBy(TimeDuration::FromMilliseconds(mPinchLockBufferMaxAge + 1)); + mSpan = newSpan; + } + + bool isPinchLockActive() { + FrameMetrics originalMetrics = apzc->GetFrameMetrics(); + + // Send a small scale input to the APZC + float pinchDistance = + StaticPrefs::apz_pinch_lock_span_breakout_threshold() * 0.8 * + tm->GetDPI(); + auto event = + CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE, mFocus, + mSpan + pinchDistance, mSpan, mcc->Time()); + apzc->ReceiveInputEvent(event); + + FrameMetrics result = apzc->GetFrameMetrics(); + bool lockActive = originalMetrics.GetZoom() == result.GetZoom() && + originalMetrics.GetVisualScrollOffset().x == + result.GetVisualScrollOffset().x && + originalMetrics.GetVisualScrollOffset().y == + result.GetVisualScrollOffset().y; + + // Avoid side effects, reset to original frame metrics + apzc->SetFrameMetrics(originalMetrics); + return lockActive; + } +}; + +TEST_F(APZCPinchGestureDetectorTester, + Pinch_UseGestureDetector_TouchActionNone) { + nsTArray behaviors = {mozilla::layers::AllowedTouchBehavior::NONE, + mozilla::layers::AllowedTouchBehavior::NONE}; + DoPinchTest(false, &behaviors); +} + +TEST_F(APZCPinchGestureDetectorTester, + Pinch_UseGestureDetector_TouchActionZoom) { + nsTArray behaviors; + behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM); + behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM); + DoPinchTest(true, &behaviors); +} + +TEST_F(APZCPinchGestureDetectorTester, + Pinch_UseGestureDetector_TouchActionNotAllowZoom) { + nsTArray behaviors; + behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::NONE); + behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM); + DoPinchTest(false, &behaviors); +} + +TEST_F(APZCPinchGestureDetectorTester, + Pinch_UseGestureDetector_TouchActionNone_NoAPZZoom) { + SCOPED_GFX_PREF_BOOL("apz.allow_zooming", false); + + // Since we are preventing the pinch action via touch-action we should not be + // sending the pinch gesture notifications that would normally be sent when + // apz_allow_zooming is false. + EXPECT_CALL(*mcc, NotifyPinchGesture(_, _, _, _, _)).Times(0); + nsTArray behaviors = {mozilla::layers::AllowedTouchBehavior::NONE, + mozilla::layers::AllowedTouchBehavior::NONE}; + DoPinchTest(false, &behaviors); +} + +TEST_F(APZCPinchGestureDetectorTester, Pinch_PreventDefault) { + DoPinchWithPreventDefaultTest(); +} + +TEST_F(APZCPinchGestureDetectorTester, Pinch_PreventDefault_NoAPZZoom) { + SCOPED_GFX_PREF_BOOL("apz.allow_zooming", false); + + // Since we are preventing the pinch action we should not be sending the pinch + // gesture notifications that would normally be sent when apz_allow_zooming is + // false. + EXPECT_CALL(*mcc, NotifyPinchGesture(_, _, _, _, _)).Times(0); + + DoPinchWithPreventDefaultTest(); +} + +TEST_F(APZCPinchGestureDetectorTester, Panning_TwoFingerFling_ZoomDisabled) { + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + + apzc->SetFrameMetrics(GetPinchableFrameMetrics()); + MakeApzcUnzoomable(); + + // Perform a two finger pan + int touchInputId = 0; + uint64_t blockId = 0; + PinchWithTouchInput(apzc, ScreenIntPoint(100, 200), ScreenIntPoint(100, 100), + 1, touchInputId, nullptr, nullptr, &blockId); + + // Expect to be in a flinging state + apzc->AssertStateIsFling(); +} + +TEST_F(APZCPinchGestureDetectorTester, Pinch_DoesntFling_ZoomDisabled) { + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + + apzc->SetFrameMetrics(GetPinchableFrameMetrics()); + MakeApzcUnzoomable(); + + // Perform a pinch + int touchInputId = 0; + uint64_t blockId = 0; + + PinchWithTouchInput(apzc, ScreenIntPoint(100, 200), ScreenIntPoint(100, 100), + 2, touchInputId, nullptr, nullptr, &blockId, + PinchOptions::LiftFinger2, true); + + // Lift second finger after a pause + mcc->AdvanceBy(TimeDuration::FromMilliseconds(50)); + TouchUp(apzc, ScreenIntPoint(100, 100), mcc->Time()); + + // Pinch should not trigger a fling + EXPECT_EQ(apzc->GetVelocityVector().y, 0); +} + +TEST_F(APZCPinchGestureDetectorTester, Panning_TwoFingerFling_ZoomEnabled) { + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + + apzc->SetFrameMetrics(GetPinchableFrameMetrics()); + MakeApzcZoomable(); + + // Perform a two finger pan + int touchInputId = 0; + uint64_t blockId = 0; + PinchWithTouchInput(apzc, ScreenIntPoint(100, 200), ScreenIntPoint(100, 100), + 1, touchInputId, nullptr, nullptr, &blockId); + + // Expect to NOT be in flinging state + apzc->AssertStateIsReset(); +} + +TEST_F(APZCPinchGestureDetectorTester, + Panning_TwoThenOneFingerFling_ZoomEnabled) { + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + + apzc->SetFrameMetrics(GetPinchableFrameMetrics()); + MakeApzcZoomable(); + + // Perform a two finger pan lifting only the first finger + int touchInputId = 0; + uint64_t blockId = 0; + PinchWithTouchInput(apzc, ScreenIntPoint(100, 200), ScreenIntPoint(100, 100), + 1, touchInputId, nullptr, nullptr, &blockId, + PinchOptions::LiftFinger2); + + // Lift second finger after a pause + mcc->AdvanceBy(TimeDuration::FromMilliseconds(50)); + TouchUp(apzc, ScreenIntPoint(100, 100), mcc->Time()); + + // This gesture should activate the pinch lock, and result + // in a fling even if the page is zoomable. + apzc->AssertStateIsFling(); +} + +TEST_F(APZCPinchGestureDetectorTester, + Panning_TwoThenOneFingerFling_ZoomDisabled) { + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + + apzc->SetFrameMetrics(GetPinchableFrameMetrics()); + MakeApzcUnzoomable(); + + // Perform a two finger pan lifting only the first finger + int touchInputId = 0; + uint64_t blockId = 0; + PinchWithTouchInput(apzc, ScreenIntPoint(100, 200), ScreenIntPoint(100, 100), + 1, touchInputId, nullptr, nullptr, &blockId, + PinchOptions::LiftFinger2); + + // Lift second finger after a pause + mcc->AdvanceBy(TimeDuration::FromMilliseconds(50)); + TouchUp(apzc, ScreenIntPoint(100, 100), mcc->Time()); + + // This gesture should activate the pinch lock and result in a fling + apzc->AssertStateIsFling(); +} + +TEST_F(APZCPinchTester, Panning_TwoFinger_ZoomDisabled) { + // set up APZ + apzc->SetFrameMetrics(GetPinchableFrameMetrics()); + MakeApzcUnzoomable(); + + nsEventStatus statuses[3]; // scalebegin, scale, scaleend + PinchWithPinchInput(apzc, ScreenIntPoint(250, 350), ScreenIntPoint(200, 300), + 10, &statuses); + + FrameMetrics fm = apzc->GetFrameMetrics(); + + // It starts from (300, 300), then moves the focus point from (250, 350) to + // (200, 300) pans by (50, 50) screen pixels, but there is a 2x zoom, which + // causes the scroll offset to change by half of that (25, 25) pixels. + EXPECT_EQ(325, fm.GetVisualScrollOffset().x); + EXPECT_EQ(325, fm.GetVisualScrollOffset().y); + EXPECT_EQ(2.0, fm.GetZoom().scale); +} + +TEST_F(APZCPinchTester, Panning_Beyond_LayoutViewport) { + SCOPED_GFX_PREF_INT("apz.axis_lock.mode", 0); + + apzc->SetFrameMetrics(GetPinchableFrameMetrics()); + MakeApzcZoomable(); + + // Case 1 - visual viewport is still inside layout viewport. + Pan(apzc, 350, 300, PanOptions::NoFling); + FrameMetrics fm = apzc->GetFrameMetrics(); + // It starts from (300, 300) pans by (0, 50) screen pixels, but there is a + // 2x zoom, which causes the scroll offset to change by half of that (0, 25). + // But the visual viewport is still inside the layout viewport. + EXPECT_EQ(300, fm.GetVisualScrollOffset().x); + EXPECT_EQ(325, fm.GetVisualScrollOffset().y); + EXPECT_EQ(300, fm.GetLayoutViewport().X()); + EXPECT_EQ(300, fm.GetLayoutViewport().Y()); + + // Case 2 - visual viewport crosses the bottom boundary of the layout + // viewport. + Pan(apzc, 525, 325, PanOptions::NoFling); + fm = apzc->GetFrameMetrics(); + // It starts from (300, 325) pans by (0, 200) screen pixels, but there is a + // 2x zoom, which causes the scroll offset to change by half of that + // (0, 100). The visual viewport crossed the bottom boundary of the layout + // viewport by 25px. + EXPECT_EQ(300, fm.GetVisualScrollOffset().x); + EXPECT_EQ(425, fm.GetVisualScrollOffset().y); + EXPECT_EQ(300, fm.GetLayoutViewport().X()); + EXPECT_EQ(325, fm.GetLayoutViewport().Y()); + + // Case 3 - visual viewport crosses the top boundary of the layout viewport. + Pan(apzc, 425, 775, PanOptions::NoFling); + fm = apzc->GetFrameMetrics(); + // It starts from (300, 425) pans by (0, -350) screen pixels, but there is a + // 2x zoom, which causes the scroll offset to change by half of that + // (0, -175). The visual viewport crossed the top of the layout viewport by + // 75px. + EXPECT_EQ(300, fm.GetVisualScrollOffset().x); + EXPECT_EQ(250, fm.GetVisualScrollOffset().y); + EXPECT_EQ(300, fm.GetLayoutViewport().X()); + EXPECT_EQ(250, fm.GetLayoutViewport().Y()); + + // Case 4 - visual viewport crosses the left boundary of the layout viewport. + Pan(apzc, ScreenIntPoint(150, 10), ScreenIntPoint(350, 10), + PanOptions::NoFling); + fm = apzc->GetFrameMetrics(); + // It starts from (300, 250) pans by (-200, 0) screen pixels, but there is a + // 2x zoom, which causes the scroll offset to change by half of that + // (-100, 0). The visual viewport crossed the left boundary of the layout + // viewport by 100px. + EXPECT_EQ(200, fm.GetVisualScrollOffset().x); + EXPECT_EQ(250, fm.GetVisualScrollOffset().y); + EXPECT_EQ(200, fm.GetLayoutViewport().X()); + EXPECT_EQ(250, fm.GetLayoutViewport().Y()); + + // Case 5 - visual viewport crosses the right boundary of the layout viewport. + Pan(apzc, ScreenIntPoint(350, 10), ScreenIntPoint(150, 10), + PanOptions::NoFling); + fm = apzc->GetFrameMetrics(); + // It starts from (200, 250) pans by (200, 0) screen pixels, but there is a + // 2x zoom, which causes the scroll offset to change by half of that + // (100, 0). The visual viewport crossed the right boundary of the layout + // viewport by 50px. + EXPECT_EQ(300, fm.GetVisualScrollOffset().x); + EXPECT_EQ(250, fm.GetVisualScrollOffset().y); + EXPECT_EQ(250, fm.GetLayoutViewport().X()); + EXPECT_EQ(250, fm.GetLayoutViewport().Y()); + + // Case 6 - visual viewport crosses both the vertical and horizontal + // boundaries of the layout viewport by moving diagonally towards the + // top-right corner. + Pan(apzc, ScreenIntPoint(350, 200), ScreenIntPoint(150, 400), + PanOptions::NoFling); + fm = apzc->GetFrameMetrics(); + // It starts from (300, 250) pans by (200, -200) screen pixels, but there is + // a 2x zoom, which causes the scroll offset to change by half of that + // (100, -100). The visual viewport moved by (100, -100) outside the + // boundary of the layout viewport. + EXPECT_EQ(400, fm.GetVisualScrollOffset().x); + EXPECT_EQ(150, fm.GetVisualScrollOffset().y); + EXPECT_EQ(350, fm.GetLayoutViewport().X()); + EXPECT_EQ(150, fm.GetLayoutViewport().Y()); +} + +TEST_F(APZCPinchGestureDetectorTester, Pinch_APZZoom_Disabled) { + SCOPED_GFX_PREF_BOOL("apz.allow_zooming", false); + + FrameMetrics originalMetrics = GetPinchableFrameMetrics(); + apzc->SetFrameMetrics(originalMetrics); + + // When apz_allow_zooming is false, the ZoomConstraintsClient produces + // ZoomConstraints with mAllowZoom set to false. + MakeApzcUnzoomable(); + + // With apz_allow_zooming false, we expect the NotifyPinchGesture function to + // get called as the pinch progresses, but the metrics shouldn't change. + EXPECT_CALL(*mcc, + NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_START, + apzc->GetGuid(), _, LayoutDeviceCoord(0), _)) + .Times(1); + EXPECT_CALL(*mcc, NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_SCALE, + apzc->GetGuid(), _, _, _)) + .Times(AtLeast(1)); + EXPECT_CALL(*mcc, + NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_END, + apzc->GetGuid(), _, LayoutDeviceCoord(0), _)) + .Times(1); + + int touchInputId = 0; + uint64_t blockId = 0; + PinchWithTouchInput(apzc, ScreenIntPoint(250, 300), 1.25, touchInputId, + nullptr, nullptr, &blockId); + + // verify the metrics didn't change (i.e. the pinch was ignored inside APZ) + FrameMetrics fm = apzc->GetFrameMetrics(); + EXPECT_EQ(originalMetrics.GetZoom(), fm.GetZoom()); + EXPECT_EQ(originalMetrics.GetVisualScrollOffset().x, + fm.GetVisualScrollOffset().x); + EXPECT_EQ(originalMetrics.GetVisualScrollOffset().y, + fm.GetVisualScrollOffset().y); + + apzc->AssertStateIsReset(); +} + +TEST_F(APZCPinchGestureDetectorTester, Pinch_NoSpan) { + SCOPED_GFX_PREF_BOOL("apz.allow_zooming", false); + + FrameMetrics originalMetrics = GetPinchableFrameMetrics(); + apzc->SetFrameMetrics(originalMetrics); + + // When apz_allow_zooming is false, the ZoomConstraintsClient produces + // ZoomConstraints with mAllowZoom set to false. + MakeApzcUnzoomable(); + + // With apz_allow_zooming false, we expect the NotifyPinchGesture function to + // get called as the pinch progresses, but the metrics shouldn't change. + EXPECT_CALL(*mcc, + NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_START, + apzc->GetGuid(), _, LayoutDeviceCoord(0), _)) + .Times(1); + EXPECT_CALL(*mcc, NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_SCALE, + apzc->GetGuid(), _, _, _)) + .Times(AtLeast(1)); + EXPECT_CALL(*mcc, + NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_END, + apzc->GetGuid(), _, LayoutDeviceCoord(0), _)) + .Times(1); + + int inputId = 0; + ScreenIntPoint focus(250, 300); + + // Do a pinch holding a zero span and moving the focus by y=100 + + const TimeDuration TIME_BETWEEN_TOUCH_EVENT = + TimeDuration::FromMilliseconds(50); + const auto touchBehaviors = Some(nsTArray{kDefaultTouchBehavior}); + + MultiTouchInput mtiStart = + MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, mcc->Time(), 0); + mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId, focus)); + mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, focus)); + apzc->ReceiveInputEvent(mtiStart, touchBehaviors); + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + focus.y -= 35 + 1; // this is to get over the PINCH_START_THRESHOLD in + // GestureEventListener.cpp + MultiTouchInput mtiMove1 = + MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0); + mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId, focus)); + mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, focus)); + apzc->ReceiveInputEvent(mtiMove1); + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + focus.y -= 100; // do a two-finger scroll of 100 screen pixels + MultiTouchInput mtiMove2 = + MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0); + mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId, focus)); + mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, focus)); + apzc->ReceiveInputEvent(mtiMove2); + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + MultiTouchInput mtiEnd = + MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, mcc->Time(), 0); + mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId, focus)); + mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, focus)); + apzc->ReceiveInputEvent(mtiEnd); + + // Done, check the metrics to make sure we scrolled by 100 screen pixels, + // which is 50 CSS pixels for the pinchable frame metrics. + + FrameMetrics fm = apzc->GetFrameMetrics(); + EXPECT_EQ(originalMetrics.GetZoom(), fm.GetZoom()); + EXPECT_EQ(originalMetrics.GetVisualScrollOffset().x, + fm.GetVisualScrollOffset().x); + EXPECT_EQ(originalMetrics.GetVisualScrollOffset().y + 50, + fm.GetVisualScrollOffset().y); + + apzc->AssertStateIsReset(); +} + +TEST_F(APZCPinchTester, Pinch_TwoFinger_APZZoom_Disabled_Bug1354185) { + // Set up APZ such that mZoomConstraints.mAllowZoom is false. + SCOPED_GFX_PREF_BOOL("apz.allow_zooming", false); + apzc->SetFrameMetrics(GetPinchableFrameMetrics()); + MakeApzcUnzoomable(); + + // We expect a repaint request for scrolling. + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); + + // Send only the PINCHGESTURE_START and PINCHGESTURE_SCALE events, + // in order to trigger a call to AsyncPanZoomController::OnScale + // but not to AsyncPanZoomController::OnScaleEnd. + ScreenIntPoint aFocus(250, 350); + ScreenIntPoint aSecondFocus(200, 300); + float aScale = 10; + auto event = CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_START, + aFocus, 10.0, 10.0, mcc->Time()); + apzc->ReceiveInputEvent(event); + + event = + CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE, + aSecondFocus, 10.0f * aScale, 10.0, mcc->Time()); + apzc->ReceiveInputEvent(event); +} + +TEST_F(APZCPinchLockingTester, Pinch_Locking_Free) { + SCOPED_GFX_PREF_INT("apz.pinch_lock.mode", 0); // PINCH_FREE + + twoFingerPan(); + EXPECT_FALSE(isPinchLockActive()); +} + +TEST_F(APZCPinchLockingTester, Pinch_Locking_Normal_Lock) { + SCOPED_GFX_PREF_INT("apz.pinch_lock.mode", 1); // PINCH_NORMAL + + twoFingerPan(); + EXPECT_TRUE(isPinchLockActive()); +} + +TEST_F(APZCPinchLockingTester, Pinch_Locking_Normal_Lock_Break) { + SCOPED_GFX_PREF_INT("apz.pinch_lock.mode", 1); // PINCH_NORMAL + + twoFingerPan(); + twoFingerZoom(); + EXPECT_TRUE(isPinchLockActive()); +} + +TEST_F(APZCPinchLockingTester, Pinch_Locking_Sticky_Lock) { + SCOPED_GFX_PREF_INT("apz.pinch_lock.mode", 2); // PINCH_STICKY + + twoFingerPan(); + EXPECT_TRUE(isPinchLockActive()); +} + +TEST_F(APZCPinchLockingTester, Pinch_Locking_Sticky_Lock_Break) { + SCOPED_GFX_PREF_INT("apz.pinch_lock.mode", 2); // PINCH_STICKY + + twoFingerPan(); + twoFingerZoom(); + EXPECT_FALSE(isPinchLockActive()); +} + +TEST_F(APZCPinchLockingTester, Pinch_Locking_Sticky_Lock_Break_Lock) { + SCOPED_GFX_PREF_INT("apz.pinch_lock.mode", 2); // PINCH_STICKY + + twoFingerPan(); + twoFingerZoom(); + twoFingerPan(); + EXPECT_TRUE(isPinchLockActive()); +} diff --git a/gfx/layers/apz/test/gtest/TestPointerEventsConsumable.cpp b/gfx/layers/apz/test/gtest/TestPointerEventsConsumable.cpp new file mode 100644 index 0000000000..1946baafe6 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestPointerEventsConsumable.cpp @@ -0,0 +1,500 @@ +/* -*- Mode: C+; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include "APZCTreeManagerTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" +#include "apz/src/AsyncPanZoomController.h" +#include "apz/src/InputBlockState.h" +#include "apz/src/OverscrollHandoffState.h" +#include "mozilla/layers/IAPZCTreeManager.h" + +class APZCArePointerEventsConsumable : public APZCTreeManagerTester { + public: + APZCArePointerEventsConsumable() { CreateMockHitTester(); } + + void CreateSingleElementTree() { + const char* treeShape = "x"; + LayerIntRegion layerVisibleRegion[] = { + LayerIntRect(0, 0, 100, 100), + }; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 500, 500)); + + registration = MakeUnique(LayersId{0}, mcc); + + UpdateHitTestingTree(); + + ApzcOf(root)->GetFrameMetrics().SetIsRootContent(true); + } + + void CreateScrollHandoffTree() { + const char* treeShape = "x(x)"; + LayerIntRegion layerVisibleRegion[] = {LayerIntRect(0, 0, 200, 200), + LayerIntRect(50, 50, 100, 100)}; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 300, 300)); + SetScrollableFrameMetrics(layers[1], + ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 200, 200)); + SetScrollHandoff(layers[1], root); + registration = MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + + ApzcOf(root)->GetFrameMetrics().SetIsRootContent(true); + } + + RefPtr CreateTouchBlockStateForApzc( + const RefPtr& aApzc) { + TouchCounter counter{}; + TargetConfirmationFlags flags{true}; + + return new TouchBlockState(aApzc, flags, counter); + } + + void UpdateOverscrollBehavior(ScrollableLayerGuid::ViewID aScrollId, + OverscrollBehavior aX, OverscrollBehavior aY) { + auto* layer = layers[aScrollId - ScrollableLayerGuid::START_SCROLL_ID]; + ModifyFrameMetrics(layer, [aX, aY](ScrollMetadata& sm, FrameMetrics& _) { + OverscrollBehaviorInfo overscroll; + overscroll.mBehaviorX = aX; + overscroll.mBehaviorY = aY; + sm.SetOverscrollBehavior(overscroll); + }); + UpdateHitTestingTree(); + } + + UniquePtr registration; +}; + +TEST_F(APZCArePointerEventsConsumable, EmptyInput) { + CreateSingleElementTree(); + + RefPtr apzc = ApzcOf(root); + RefPtr blockState = CreateTouchBlockStateForApzc(apzc); + + MultiTouchInput touchInput = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + + const PointerEventsConsumableFlags expected{false, false}; + const PointerEventsConsumableFlags actual = + apzc->ArePointerEventsConsumable(blockState, touchInput); + EXPECT_EQ(expected, actual); +} + +TEST_F(APZCArePointerEventsConsumable, ScrollHorizontally) { + CreateSingleElementTree(); + + RefPtr apzc = ApzcOf(root); + RefPtr blockState = CreateTouchBlockStateForApzc(apzc); + + // Create touch with horizontal 20 unit scroll + MultiTouchInput touchStart = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + touchStart.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(10, 10), ScreenSize(0, 0), 0, 0)); + + MultiTouchInput touchMove = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + touchMove.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(30, 10), ScreenSize(0, 0), 0, 0)); + + blockState->UpdateSlopState(touchStart, false); + + PointerEventsConsumableFlags actual{}; + PointerEventsConsumableFlags expected{}; + + // Scroll area 500x500, room to pan x, room to pan y + expected = {true, true}; + actual = apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); + + // Scroll area 100x100, no room to pan x, no room to pan y + apzc->GetFrameMetrics().SetScrollableRect(CSSRect{0, 0, 100, 100}); + expected = {false, true}; + actual = apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); + + // Scroll area 500x100, room to pan x, no room to pan y + apzc->GetFrameMetrics().SetScrollableRect(CSSRect{0, 0, 500, 100}); + expected = {true, true}; + actual = apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); + + // Scroll area 100x500, no room to pan x, room to pan y + apzc->GetFrameMetrics().SetScrollableRect(CSSRect{0, 0, 100, 500}); + expected = {false, true}; + actual = apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); +} + +TEST_F(APZCArePointerEventsConsumable, ScrollVertically) { + CreateSingleElementTree(); + + RefPtr apzc = ApzcOf(root); + RefPtr blockState = CreateTouchBlockStateForApzc(apzc); + + // Create touch with vertical 20 unit scroll + MultiTouchInput touchStart = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + touchStart.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(10, 10), ScreenSize(0, 0), 0, 0)); + + MultiTouchInput touchMove = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + touchMove.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(10, 30), ScreenSize(0, 0), 0, 0)); + + blockState->UpdateSlopState(touchStart, false); + + PointerEventsConsumableFlags actual{}; + PointerEventsConsumableFlags expected{}; + + // Scroll area 500x500, room to pan x, room to pan y + expected = {true, true}; + actual = apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); + + // Scroll area 100x100, no room to pan x, no room to pan y + apzc->GetFrameMetrics().SetScrollableRect(CSSRect{0, 0, 100, 100}); + expected = {false, true}; + actual = apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); + + // Scroll area 500x100, room to pan x, no room to pan y + apzc->GetFrameMetrics().SetScrollableRect(CSSRect{0, 0, 500, 100}); + expected = {false, true}; + actual = apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); + + // Scroll area 100x500, no room to pan x, room to pan y + apzc->GetFrameMetrics().SetScrollableRect(CSSRect{0, 0, 100, 500}); + expected = {true, true}; + actual = apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); +} + +TEST_F(APZCArePointerEventsConsumable, NestedElementCanScroll) { + CreateScrollHandoffTree(); + + RefPtr apzc = ApzcOf(layers[1]); + RefPtr blockState = CreateTouchBlockStateForApzc(apzc); + + // Create touch with vertical 20 unit scroll + MultiTouchInput touchStart = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + touchStart.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(60, 60), ScreenSize(0, 0), 0, 0)); + + MultiTouchInput touchMove = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + touchMove.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(60, 80), ScreenSize(0, 0), 0, 0)); + + blockState->UpdateSlopState(touchStart, false); + + const PointerEventsConsumableFlags expected{true, true}; + const PointerEventsConsumableFlags actual = + apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); +} + +TEST_F(APZCArePointerEventsConsumable, NestedElementCannotScroll) { + CreateScrollHandoffTree(); + + RefPtr apzc = ApzcOf(layers[1]); + RefPtr blockState = CreateTouchBlockStateForApzc(apzc); + + // Create touch with vertical 20 unit scroll + MultiTouchInput touchStart = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + touchStart.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(60, 60), ScreenSize(0, 0), 0, 0)); + + MultiTouchInput touchMove = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + touchMove.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(60, 80), ScreenSize(0, 0), 0, 0)); + + blockState->UpdateSlopState(touchStart, false); + + PointerEventsConsumableFlags actual{}; + PointerEventsConsumableFlags expected{}; + + // Set the nested element to have no room to scroll. + // Because of the overscroll handoff, we still have room to scroll + // in the parent element. + apzc->GetFrameMetrics().SetScrollableRect(CSSRect{0, 0, 100, 100}); + expected = {true, true}; + actual = apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); + + // Set overscroll handoff for the nested element to none. + // Because no handoff will happen, we are not able to use the parent's + // room to scroll. + // Bug 1814886: Once fixed, change expected value to {false, true}. + UpdateOverscrollBehavior(ScrollableLayerGuid::START_SCROLL_ID + 1, + OverscrollBehavior::None, OverscrollBehavior::None); + expected = {true, true}; + actual = apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); +} + +TEST_F(APZCArePointerEventsConsumable, NotScrollableButZoomable) { + CreateSingleElementTree(); + + RefPtr apzc = ApzcOf(root); + RefPtr blockState = CreateTouchBlockStateForApzc(apzc); + + // Create touch with vertical 20 unit scroll + MultiTouchInput touchStart = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + touchStart.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(60, 60), ScreenSize(0, 0), 0, 0)); + + MultiTouchInput touchMove = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + touchMove.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(60, 80), ScreenSize(0, 0), 0, 0)); + + blockState->UpdateSlopState(touchStart, false); + + // Make the root have no room to scroll + apzc->GetFrameMetrics().SetScrollableRect(CSSRect{0, 0, 100, 100}); + + // Make zoomable + apzc->UpdateZoomConstraints(ZoomConstraints( + true, true, CSSToParentLayerScale(0.25f), CSSToParentLayerScale(4.0f))); + + PointerEventsConsumableFlags actual{}; + PointerEventsConsumableFlags expected{}; + + expected = {false, true}; + actual = apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); + + // Add a second touch point and therefore make the APZC consider + // zoom use cases as well. + touchMove.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(60, 90), ScreenSize(0, 0), 0, 0)); + + expected = {true, true}; + actual = apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); +} + +TEST_F(APZCArePointerEventsConsumable, TouchActionsProhibitAll) { + CreateSingleElementTree(); + + RefPtr apzc = ApzcOf(root); + + // Create touch with vertical 20 unit scroll + MultiTouchInput touchStart = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + touchStart.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(60, 60), ScreenSize(0, 0), 0, 0)); + + MultiTouchInput touchMove = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + touchMove.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(60, 80), ScreenSize(0, 0), 0, 0)); + + PointerEventsConsumableFlags expected{}; + PointerEventsConsumableFlags actual{}; + + { + RefPtr blockState = CreateTouchBlockStateForApzc(apzc); + blockState->UpdateSlopState(touchStart, false); + + blockState->SetAllowedTouchBehaviors({AllowedTouchBehavior::NONE}); + expected = {true, false}; + actual = apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); + } + + // Convert touch input to two-finger pinch + touchStart.mTouches.AppendElement( + SingleTouchData(1, ScreenIntPoint(80, 80), ScreenSize(0, 0), 0, 0)); + touchMove.mTouches.AppendElement( + SingleTouchData(1, ScreenIntPoint(90, 90), ScreenSize(0, 0), 0, 0)); + + { + RefPtr blockState = CreateTouchBlockStateForApzc(apzc); + blockState->UpdateSlopState(touchStart, false); + + blockState->SetAllowedTouchBehaviors({AllowedTouchBehavior::NONE}); + expected = {true, false}; + actual = apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); + } +} + +TEST_F(APZCArePointerEventsConsumable, TouchActionsAllowVerticalScrolling) { + CreateSingleElementTree(); + + RefPtr apzc = ApzcOf(root); + + // Create touch with vertical 20 unit scroll + MultiTouchInput touchStart = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + touchStart.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(60, 60), ScreenSize(0, 0), 0, 0)); + + MultiTouchInput touchMove = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + touchMove.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(60, 80), ScreenSize(0, 0), 0, 0)); + + PointerEventsConsumableFlags expected{}; + PointerEventsConsumableFlags actual{}; + + { + RefPtr blockState = CreateTouchBlockStateForApzc(apzc); + blockState->UpdateSlopState(touchStart, false); + + blockState->SetAllowedTouchBehaviors({AllowedTouchBehavior::NONE}); + expected = {true, false}; + actual = apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); + } + + { + RefPtr blockState = CreateTouchBlockStateForApzc(apzc); + blockState->UpdateSlopState(touchStart, false); + + blockState->SetAllowedTouchBehaviors({AllowedTouchBehavior::VERTICAL_PAN}); + expected = {true, true}; + actual = apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); + } +} + +TEST_F(APZCArePointerEventsConsumable, TouchActionsAllowHorizontalScrolling) { + CreateSingleElementTree(); + + RefPtr apzc = ApzcOf(root); + + // Create touch with horizontal 20 unit scroll + MultiTouchInput touchStart = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + touchStart.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(60, 60), ScreenSize(0, 0), 0, 0)); + + MultiTouchInput touchMove = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + touchMove.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(80, 60), ScreenSize(0, 0), 0, 0)); + + PointerEventsConsumableFlags expected{}; + PointerEventsConsumableFlags actual{}; + + { + RefPtr blockState = CreateTouchBlockStateForApzc(apzc); + blockState->UpdateSlopState(touchStart, false); + + blockState->SetAllowedTouchBehaviors({AllowedTouchBehavior::NONE}); + expected = {true, false}; + actual = apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); + } + + { + RefPtr blockState = CreateTouchBlockStateForApzc(apzc); + blockState->UpdateSlopState(touchStart, false); + + blockState->SetAllowedTouchBehaviors( + {AllowedTouchBehavior::HORIZONTAL_PAN}); + expected = {true, true}; + actual = apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); + } +} + +TEST_F(APZCArePointerEventsConsumable, TouchActionsAllowPinchZoom) { + CreateSingleElementTree(); + + RefPtr apzc = ApzcOf(root); + + // Create two-finger pinch + MultiTouchInput touchStart = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + touchStart.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(60, 60), ScreenSize(0, 0), 0, 0)); + touchStart.mTouches.AppendElement( + SingleTouchData(1, ScreenIntPoint(80, 80), ScreenSize(0, 0), 0, 0)); + + MultiTouchInput touchMove = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + touchMove.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(50, 50), ScreenSize(0, 0), 0, 0)); + touchMove.mTouches.AppendElement( + SingleTouchData(1, ScreenIntPoint(90, 90), ScreenSize(0, 0), 0, 0)); + + PointerEventsConsumableFlags expected{}; + PointerEventsConsumableFlags actual{}; + + { + RefPtr blockState = CreateTouchBlockStateForApzc(apzc); + blockState->UpdateSlopState(touchStart, false); + + blockState->SetAllowedTouchBehaviors({AllowedTouchBehavior::NONE}); + expected = {true, false}; + actual = apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); + } + + { + RefPtr blockState = CreateTouchBlockStateForApzc(apzc); + blockState->UpdateSlopState(touchStart, false); + + blockState->SetAllowedTouchBehaviors({AllowedTouchBehavior::PINCH_ZOOM}); + expected = {true, true}; + actual = apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); + } +} + +TEST_F(APZCArePointerEventsConsumable, DynamicToolbar) { + CreateSingleElementTree(); + + RefPtr apzc = ApzcOf(root); + RefPtr blockState = CreateTouchBlockStateForApzc(apzc); + + // Create touch with vertical 20 unit scroll + MultiTouchInput touchStart = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + touchStart.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(60, 30), ScreenSize(0, 0), 0, 0)); + + MultiTouchInput touchMove = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + touchMove.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(60, 40), ScreenSize(0, 0), 0, 0)); + + blockState->UpdateSlopState(touchStart, false); + + // Restrict size of scrollable area: No room to pan X, no room to pan Y + apzc->GetFrameMetrics().SetScrollableRect(CSSRect{0, 0, 100, 100}); + + PointerEventsConsumableFlags actual{}; + PointerEventsConsumableFlags expected{}; + + expected = {false, true}; + actual = apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); + + apzc->GetFrameMetrics().SetCompositionSizeWithoutDynamicToolbar( + ParentLayerSize{100, 90}); + UpdateHitTestingTree(); + + expected = {true, true}; + actual = apzc->ArePointerEventsConsumable(blockState, touchMove); + EXPECT_EQ(expected, actual); +} diff --git a/gfx/layers/apz/test/gtest/TestScrollHandoff.cpp b/gfx/layers/apz/test/gtest/TestScrollHandoff.cpp new file mode 100644 index 0000000000..8f497dabe6 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestScrollHandoff.cpp @@ -0,0 +1,809 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCTreeManagerTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" + +class APZScrollHandoffTester : public APZCTreeManagerTester { + protected: + UniquePtr registration; + TestAsyncPanZoomController* rootApzc; + + void CreateScrollHandoffLayerTree1() { + const char* treeShape = "x(x)"; + LayerIntRegion layerVisibleRegion[] = {LayerIntRect(0, 0, 100, 100), + LayerIntRect(0, 50, 100, 50)}; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 200, 200)); + SetScrollableFrameMetrics(layers[1], + ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 100, 100)); + SetScrollHandoff(layers[1], root); + registration = MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + rootApzc = ApzcOf(root); + rootApzc->GetFrameMetrics().SetIsRootContent( + true); // make root APZC zoomable + } + + void CreateScrollHandoffLayerTree2() { + const char* treeShape = "x(x(x))"; + LayerIntRegion layerVisibleRegion[] = {LayerIntRect(0, 0, 100, 100), + LayerIntRect(0, 0, 100, 100), + LayerIntRect(0, 50, 100, 50)}; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 200, 200)); + SetScrollableFrameMetrics(layers[1], + ScrollableLayerGuid::START_SCROLL_ID + 2, + CSSRect(-100, -100, 200, 200)); + SetScrollableFrameMetrics(layers[2], + ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 100, 100)); + SetScrollHandoff(layers[1], root); + SetScrollHandoff(layers[2], layers[1]); + // No ScopedLayerTreeRegistration as that just needs to be done once per + // test and this is the second layer tree for a particular test. + MOZ_ASSERT(registration); + UpdateHitTestingTree(); + rootApzc = ApzcOf(root); + } + + void CreateScrollHandoffLayerTree3() { + const char* treeShape = "x(x(x)x(x))"; + LayerIntRegion layerVisibleRegion[] = { + LayerIntRect(0, 0, 100, 100), // root + LayerIntRect(0, 0, 100, 50), // scrolling parent 1 + LayerIntRect(0, 0, 100, 50), // scrolling child 1 + LayerIntRect(0, 50, 100, 50), // scrolling parent 2 + LayerIntRect(0, 50, 100, 50) // scrolling child 2 + }; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(layers[0], ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 100, 100)); + SetScrollableFrameMetrics(layers[1], + ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 100, 100)); + SetScrollableFrameMetrics(layers[2], + ScrollableLayerGuid::START_SCROLL_ID + 2, + CSSRect(0, 0, 100, 100)); + SetScrollableFrameMetrics(layers[3], + ScrollableLayerGuid::START_SCROLL_ID + 3, + CSSRect(0, 50, 100, 100)); + SetScrollableFrameMetrics(layers[4], + ScrollableLayerGuid::START_SCROLL_ID + 4, + CSSRect(0, 50, 100, 100)); + SetScrollHandoff(layers[1], layers[0]); + SetScrollHandoff(layers[3], layers[0]); + SetScrollHandoff(layers[2], layers[1]); + SetScrollHandoff(layers[4], layers[3]); + registration = MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + } + + // Creates a layer tree with a parent layer that is only scrollable + // horizontally, and a child layer that is only scrollable vertically. + void CreateScrollHandoffLayerTree4() { + const char* treeShape = "x(x)"; + LayerIntRegion layerVisibleRegion[] = {LayerIntRect(0, 0, 100, 100), + LayerIntRect(0, 0, 100, 100)}; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 200, 100)); + SetScrollableFrameMetrics(layers[1], + ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 100, 200)); + SetScrollHandoff(layers[1], root); + registration = MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + rootApzc = ApzcOf(root); + } + + // Creates a layer tree with a parent layer that is not scrollable, and a + // child layer that is only scrollable vertically. + void CreateScrollHandoffLayerTree5() { + const char* treeShape = "x(x)"; + LayerIntRegion layerVisibleRegion[] = { + LayerIntRect(0, 0, 100, 100), // scrolling parent + LayerIntRect(0, 50, 100, 50) // scrolling child + }; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 100, 100)); + SetScrollableFrameMetrics(layers[1], + ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 100, 200)); + SetScrollHandoff(layers[1], root); + registration = MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + rootApzc = ApzcOf(root); + } + + void CreateScrollgrabLayerTree(bool makeParentScrollable = true) { + const char* treeShape = "x(x)"; + LayerIntRegion layerVisibleRegion[] = { + LayerIntRect(0, 0, 100, 100), // scroll-grabbing parent + LayerIntRect(0, 20, 100, 80) // child + }; + CreateScrollData(treeShape, layerVisibleRegion); + float parentHeight = makeParentScrollable ? 120 : 100; + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 100, parentHeight)); + SetScrollableFrameMetrics(layers[1], + ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 100, 800)); + SetScrollHandoff(layers[1], root); + registration = MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + rootApzc = ApzcOf(root); + rootApzc->GetScrollMetadata().SetHasScrollgrab(true); + } + + void TestFlingAcceleration() { + // Jack up the fling acceleration multiplier so we can easily determine + // whether acceleration occured. + const float kAcceleration = 100.0f; + SCOPED_GFX_PREF_FLOAT("apz.fling_accel_base_mult", kAcceleration); + SCOPED_GFX_PREF_FLOAT("apz.fling_accel_min_fling_velocity", 0.0); + SCOPED_GFX_PREF_FLOAT("apz.fling_accel_min_pan_velocity", 0.0); + + RefPtr childApzc = ApzcOf(layers[1]); + + // Pan once, enough to fully scroll the scrollgrab parent and then scroll + // and fling the child. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + Pan(manager, 70, 40); + + // Give the fling animation a chance to start. + SampleAnimationsOnce(); + + float childVelocityAfterFling1 = childApzc->GetVelocityVector().y; + + // Pan again. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + Pan(manager, 70, 40); + + // Give the fling animation a chance to start. + // This time it should be accelerated. + SampleAnimationsOnce(); + + float childVelocityAfterFling2 = childApzc->GetVelocityVector().y; + + // We should have accelerated once. + // The division by 2 is to account for friction. + EXPECT_GT(childVelocityAfterFling2, + childVelocityAfterFling1 * kAcceleration / 2); + + // We should not have accelerated twice. + // The division by 4 is to account for friction. + EXPECT_LE(childVelocityAfterFling2, + childVelocityAfterFling1 * kAcceleration * kAcceleration / 4); + } + + void TestCrossApzcAxisLock() { + SCOPED_GFX_PREF_INT("apz.axis_lock.mode", 1); + + CreateScrollHandoffLayerTree1(); + + RefPtr childApzc = ApzcOf(layers[1]); + Pan(childApzc, ScreenIntPoint(10, 60), ScreenIntPoint(15, 90), + PanOptions::KeepFingerDown | PanOptions::ExactCoordinates); + + childApzc->AssertAxisLocked(ScrollDirection::eVertical); + childApzc->AssertStateIsPanningLockedY(); + } +}; + +class APZScrollHandoffTesterMock : public APZScrollHandoffTester { + public: + APZScrollHandoffTesterMock() { CreateMockHitTester(); } +}; + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +// Here we test that if the processing of a touch block is deferred while we +// wait for content to send a prevent-default message, overscroll is still +// handed off correctly when the block is processed. +TEST_F(APZScrollHandoffTester, DeferredInputEventProcessing) { + SCOPED_GFX_PREF_BOOL("apz.allow_immediate_handoff", true); + + // Set up the APZC tree. + CreateScrollHandoffLayerTree1(); + + RefPtr childApzc = ApzcOf(layers[1]); + + // Enable touch-listeners so that we can separate the queueing of input + // events from them being processed. + childApzc->SetWaitForMainThread(); + + // Queue input events for a pan. + uint64_t blockId = 0; + Pan(childApzc, 90, 30, PanOptions::NoFling, nullptr, nullptr, &blockId); + + // Allow the pan to be processed. + childApzc->ContentReceivedInputBlock(blockId, false); + childApzc->ConfirmTarget(blockId); + + // Make sure overscroll was handed off correctly. + EXPECT_EQ(50, childApzc->GetFrameMetrics().GetVisualScrollOffset().y); + EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetVisualScrollOffset().y); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +// Here we test that if the layer structure changes in between two input +// blocks being queued, and the first block is only processed after the second +// one has been queued, overscroll handoff for the first block follows +// the original layer structure while overscroll handoff for the second block +// follows the new layer structure. +TEST_F(APZScrollHandoffTester, LayerStructureChangesWhileEventsArePending) { + SCOPED_GFX_PREF_BOOL("apz.allow_immediate_handoff", true); + + // Set up an initial APZC tree. + CreateScrollHandoffLayerTree1(); + + RefPtr childApzc = ApzcOf(layers[1]); + + // Enable touch-listeners so that we can separate the queueing of input + // events from them being processed. + childApzc->SetWaitForMainThread(); + + // Queue input events for a pan. + uint64_t blockId = 0; + Pan(childApzc, 90, 30, PanOptions::NoFling, nullptr, nullptr, &blockId); + + // Modify the APZC tree to insert a new APZC 'middle' into the handoff chain + // between the child and the root. + CreateScrollHandoffLayerTree2(); + WebRenderLayerScrollData* middle = layers[1]; + childApzc->SetWaitForMainThread(); + TestAsyncPanZoomController* middleApzc = ApzcOf(middle); + + // Queue input events for another pan. + uint64_t secondBlockId = 0; + Pan(childApzc, 30, 90, PanOptions::NoFling, nullptr, nullptr, &secondBlockId); + + // Allow the first pan to be processed. + childApzc->ContentReceivedInputBlock(blockId, false); + childApzc->ConfirmTarget(blockId); + + // Make sure things have scrolled according to the handoff chain in + // place at the time the touch-start of the first pan was queued. + EXPECT_EQ(50, childApzc->GetFrameMetrics().GetVisualScrollOffset().y); + EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetVisualScrollOffset().y); + EXPECT_EQ(0, middleApzc->GetFrameMetrics().GetVisualScrollOffset().y); + + // Allow the second pan to be processed. + childApzc->ContentReceivedInputBlock(secondBlockId, false); + childApzc->ConfirmTarget(secondBlockId); + + // Make sure things have scrolled according to the handoff chain in + // place at the time the touch-start of the second pan was queued. + EXPECT_EQ(0, childApzc->GetFrameMetrics().GetVisualScrollOffset().y); + EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetVisualScrollOffset().y); + EXPECT_EQ(-10, middleApzc->GetFrameMetrics().GetVisualScrollOffset().y); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +// Test that putting a second finger down on an APZC while a down-chain APZC +// is overscrolled doesn't result in being stuck in overscroll. +TEST_F(APZScrollHandoffTesterMock, StuckInOverscroll_Bug1073250) { + // Enable overscrolling. + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + + CreateScrollHandoffLayerTree1(); + + TestAsyncPanZoomController* child = ApzcOf(layers[1]); + + // Pan, causing the parent APZC to overscroll. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + Pan(manager, 10, 40, PanOptions::KeepFingerDown); + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_TRUE(rootApzc->IsOverscrolled()); + + // Put a second finger down. + MultiTouchInput secondFingerDown = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + // Use the same touch identifier for the first touch (0) as Pan(). (A bit + // hacky.) + secondFingerDown.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(10, 40), ScreenSize(0, 0), 0, 0)); + secondFingerDown.mTouches.AppendElement( + SingleTouchData(1, ScreenIntPoint(30, 20), ScreenSize(0, 0), 0, 0)); + manager->ReceiveInputEvent(secondFingerDown); + + // Release the fingers. + MultiTouchInput fingersUp = secondFingerDown; + fingersUp.mType = MultiTouchInput::MULTITOUCH_END; + manager->ReceiveInputEvent(fingersUp); + + // Allow any animations to run their course. + child->AdvanceAnimationsUntilEnd(); + rootApzc->AdvanceAnimationsUntilEnd(); + + // Make sure nothing is overscrolled. + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_FALSE(rootApzc->IsOverscrolled()); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +// This is almost exactly like StuckInOverscroll_Bug1073250, except the +// APZC receiving the input events for the first touch block is the child +// (and thus not the same APZC that overscrolls, which is the parent). +TEST_F(APZScrollHandoffTesterMock, StuckInOverscroll_Bug1231228) { + // Enable overscrolling. + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + + CreateScrollHandoffLayerTree1(); + + TestAsyncPanZoomController* child = ApzcOf(layers[1]); + + // Pan, causing the parent APZC to overscroll. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + Pan(manager, 60, 90, PanOptions::KeepFingerDown); + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_TRUE(rootApzc->IsOverscrolled()); + + // Put a second finger down. + MultiTouchInput secondFingerDown = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + // Use the same touch identifier for the first touch (0) as Pan(). (A bit + // hacky.) + secondFingerDown.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(10, 40), ScreenSize(0, 0), 0, 0)); + secondFingerDown.mTouches.AppendElement( + SingleTouchData(1, ScreenIntPoint(30, 20), ScreenSize(0, 0), 0, 0)); + manager->ReceiveInputEvent(secondFingerDown); + + // Release the fingers. + MultiTouchInput fingersUp = secondFingerDown; + fingersUp.mType = MultiTouchInput::MULTITOUCH_END; + manager->ReceiveInputEvent(fingersUp); + + // Allow any animations to run their course. + child->AdvanceAnimationsUntilEnd(); + rootApzc->AdvanceAnimationsUntilEnd(); + + // Make sure nothing is overscrolled. + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_FALSE(rootApzc->IsOverscrolled()); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZScrollHandoffTester, StuckInOverscroll_Bug1240202a) { + // Enable overscrolling. + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + CreateScrollHandoffLayerTree1(); + + TestAsyncPanZoomController* child = ApzcOf(layers[1]); + + // Pan, causing the parent APZC to overscroll. + Pan(manager, 60, 90, PanOptions::KeepFingerDown); + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_TRUE(rootApzc->IsOverscrolled()); + + // Lift the finger, triggering an overscroll animation + // (but don't allow it to run). + TouchUp(manager, ScreenIntPoint(10, 90), mcc->Time()); + + // Put the finger down again, interrupting the animation + // and entering the TOUCHING state. + TouchDown(manager, ScreenIntPoint(10, 90), mcc->Time()); + + // Lift the finger once again. + TouchUp(manager, ScreenIntPoint(10, 90), mcc->Time()); + + // Allow any animations to run their course. + child->AdvanceAnimationsUntilEnd(); + rootApzc->AdvanceAnimationsUntilEnd(); + + // Make sure nothing is overscrolled. + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_FALSE(rootApzc->IsOverscrolled()); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZScrollHandoffTesterMock, StuckInOverscroll_Bug1240202b) { + // Enable overscrolling. + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + CreateScrollHandoffLayerTree1(); + + TestAsyncPanZoomController* child = ApzcOf(layers[1]); + + // Pan, causing the parent APZC to overscroll. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + Pan(manager, 60, 90, PanOptions::KeepFingerDown); + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_TRUE(rootApzc->IsOverscrolled()); + + // Lift the finger, triggering an overscroll animation + // (but don't allow it to run). + TouchUp(manager, ScreenIntPoint(10, 90), mcc->Time()); + + // Put the finger down again, interrupting the animation + // and entering the TOUCHING state. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + TouchDown(manager, ScreenIntPoint(10, 90), mcc->Time()); + + // Put a second finger down. Since we're in the TOUCHING state, + // the "are we panned into overscroll" check will fail and we + // will not ignore the second finger, instead entering the + // PINCHING state. + MultiTouchInput secondFingerDown(MultiTouchInput::MULTITOUCH_START, 0, + TimeStamp(), 0); + // Use the same touch identifier for the first touch (0) as TouchDown(). (A + // bit hacky.) + secondFingerDown.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(10, 90), ScreenSize(0, 0), 0, 0)); + secondFingerDown.mTouches.AppendElement( + SingleTouchData(1, ScreenIntPoint(10, 80), ScreenSize(0, 0), 0, 0)); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + manager->ReceiveInputEvent(secondFingerDown); + + // Release the fingers. + MultiTouchInput fingersUp = secondFingerDown; + fingersUp.mType = MultiTouchInput::MULTITOUCH_END; + manager->ReceiveInputEvent(fingersUp); + + // Allow any animations to run their course. + child->AdvanceAnimationsUntilEnd(); + rootApzc->AdvanceAnimationsUntilEnd(); + + // Make sure nothing is overscrolled. + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_FALSE(rootApzc->IsOverscrolled()); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZScrollHandoffTester, OpposingConstrainedAxes_Bug1201098) { + // Enable overscrolling. + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + CreateScrollHandoffLayerTree4(); + + RefPtr childApzc = ApzcOf(layers[1]); + + // Pan, causing the child APZC to overscroll. + Pan(childApzc, 50, 60); + + // Make sure only the child is overscrolled. + EXPECT_TRUE(childApzc->IsOverscrolled()); + EXPECT_FALSE(rootApzc->IsOverscrolled()); +} +#endif + +// Test that flinging in a direction where one component of the fling goes into +// overscroll but the other doesn't, results in just the one component being +// handed off to the parent, while the original APZC continues flinging in the +// other direction. +TEST_F(APZScrollHandoffTesterMock, PartialFlingHandoff) { + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + + CreateScrollHandoffLayerTree1(); + + // Fling up and to the left. The child APZC has room to scroll up, but not + // to the left, so the horizontal component of the fling should be handed + // off to the parent APZC. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + Pan(manager, ScreenIntPoint(90, 90), ScreenIntPoint(55, 55)); + + RefPtr parent = ApzcOf(layers[0]); + RefPtr child = ApzcOf(layers[1]); + + // Advance the child's fling animation once to give the partial handoff + // a chance to occur. + mcc->AdvanceByMillis(10); + child->AdvanceAnimations(mcc->GetSampleTime()); + + // Assert that partial handoff has occurred. + child->AssertStateIsFling(); + parent->AssertStateIsFling(); +} + +// Here we test that if two flings are happening simultaneously, overscroll +// is handed off correctly for each. +TEST_F(APZScrollHandoffTester, SimultaneousFlings) { + SCOPED_GFX_PREF_BOOL("apz.allow_immediate_handoff", true); + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + + // Set up an initial APZC tree. + CreateScrollHandoffLayerTree3(); + + RefPtr parent1 = ApzcOf(layers[1]); + RefPtr child1 = ApzcOf(layers[2]); + RefPtr parent2 = ApzcOf(layers[3]); + RefPtr child2 = ApzcOf(layers[4]); + + // Pan on the lower child. + Pan(child2, 45, 5); + + // Pan on the upper child. + Pan(child1, 95, 55); + + // Check that child1 and child2 are in a FLING state. + child1->AssertStateIsFling(); + child2->AssertStateIsFling(); + + // Advance the animations on child1 and child2 until their end. + child1->AdvanceAnimationsUntilEnd(); + child2->AdvanceAnimationsUntilEnd(); + + // Check that the flings have been handed off to the parents. + child1->AssertStateIsReset(); + parent1->AssertStateIsFling(); + child2->AssertStateIsReset(); + parent2->AssertStateIsFling(); +} + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZScrollHandoffTester, Scrollgrab) { + SCOPED_GFX_PREF_BOOL("apz.allow_immediate_handoff", true); + + // Set up the layer tree + CreateScrollgrabLayerTree(); + + RefPtr childApzc = ApzcOf(layers[1]); + + // Pan on the child, enough to fully scroll the scrollgrab parent (20 px) + // and leave some more (another 15 px) for the child. + Pan(childApzc, 80, 45); + + // Check that the parent and child have scrolled as much as we expect. + EXPECT_EQ(20, rootApzc->GetFrameMetrics().GetVisualScrollOffset().y); + EXPECT_EQ(15, childApzc->GetFrameMetrics().GetVisualScrollOffset().y); +} +#endif + +TEST_F(APZScrollHandoffTester, ScrollgrabFling) { + SCOPED_GFX_PREF_BOOL("apz.allow_immediate_handoff", true); + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + + // Set up the layer tree + CreateScrollgrabLayerTree(); + + RefPtr childApzc = ApzcOf(layers[1]); + + // Pan on the child, not enough to fully scroll the scrollgrab parent. + Pan(childApzc, 80, 70); + + // Check that it is the scrollgrab parent that's in a fling, not the child. + rootApzc->AssertStateIsFling(); + childApzc->AssertStateIsReset(); +} + +TEST_F(APZScrollHandoffTesterMock, ScrollgrabFlingAcceleration1) { + SCOPED_GFX_PREF_BOOL("apz.allow_immediate_handoff", true); + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + CreateScrollgrabLayerTree(true /* make parent scrollable */); + + // Note: Usually, fling acceleration does not work across handoff, because our + // fling acceleration code does not propagate the "fling cancel velocity" + // across handoff. However, this test sets apz.fling_min_velocity_threshold to + // zero, so the "fling cancel velocity" is allowed to be zero, and fling + // acceleration succeeds, almost by accident. + TestFlingAcceleration(); +} + +TEST_F(APZScrollHandoffTesterMock, ScrollgrabFlingAcceleration2) { + SCOPED_GFX_PREF_BOOL("apz.allow_immediate_handoff", true); + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + CreateScrollgrabLayerTree(false /* do not make parent scrollable */); + TestFlingAcceleration(); +} + +TEST_F(APZScrollHandoffTester, ImmediateHandoffDisallowed_Pan) { + SCOPED_GFX_PREF_BOOL("apz.allow_immediate_handoff", false); + + CreateScrollHandoffLayerTree1(); + + RefPtr parentApzc = ApzcOf(layers[0]); + RefPtr childApzc = ApzcOf(layers[1]); + + // Pan on the child, enough to scroll it to its end and have scroll + // left to hand off. Since immediate handoff is disallowed, we expect + // the leftover scroll not to be handed off. + Pan(childApzc, 60, 5); + + // Verify that the parent has not scrolled. + EXPECT_EQ(50, childApzc->GetFrameMetrics().GetVisualScrollOffset().y); + EXPECT_EQ(0, parentApzc->GetFrameMetrics().GetVisualScrollOffset().y); + + // Pan again on the child. This time, since the child was scrolled to + // its end when the gesture began, we expect the scroll to be handed off. + Pan(childApzc, 60, 50); + + // Verify that the parent scrolled. + EXPECT_EQ(10, parentApzc->GetFrameMetrics().GetVisualScrollOffset().y); +} + +TEST_F(APZScrollHandoffTester, ImmediateHandoffDisallowed_Fling) { + SCOPED_GFX_PREF_BOOL("apz.allow_immediate_handoff", false); + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + + CreateScrollHandoffLayerTree1(); + + RefPtr parentApzc = ApzcOf(layers[0]); + RefPtr childApzc = ApzcOf(layers[1]); + + // Pan on the child, enough to get very close to the end, so that the + // subsequent fling reaches the end and has leftover velocity to hand off. + Pan(childApzc, 60, 2); + + // Allow the fling to run its course. + childApzc->AdvanceAnimationsUntilEnd(); + parentApzc->AdvanceAnimationsUntilEnd(); + + // Verify that the parent has not scrolled. + // The first comparison needs to be an ASSERT_NEAR because the fling + // computations are such that the final scroll position can be within + // COORDINATE_EPSILON of the end rather than right at the end. + ASSERT_NEAR(50, childApzc->GetFrameMetrics().GetVisualScrollOffset().y, + COORDINATE_EPSILON); + EXPECT_EQ(0, parentApzc->GetFrameMetrics().GetVisualScrollOffset().y); + + // Pan again on the child. This time, since the child was scrolled to + // its end when the gesture began, we expect the scroll to be handed off. + Pan(childApzc, 60, 40); + + // Allow the fling to run its course. The fling should also be handed off. + childApzc->AdvanceAnimationsUntilEnd(); + parentApzc->AdvanceAnimationsUntilEnd(); + + // Verify that the parent scrolled from the fling. + EXPECT_GT(parentApzc->GetFrameMetrics().GetVisualScrollOffset().y, 10); +} + +TEST_F(APZScrollHandoffTester, CrossApzcAxisLock_TouchAction) { + TestCrossApzcAxisLock(); +} + +TEST_F(APZScrollHandoffTesterMock, WheelHandoffAfterDirectionReversal) { + // Explicitly set the wheel transaction timeout pref because the test relies + // on its value. + SCOPED_GFX_PREF_INT("mousewheel.transaction.timeout", 1500); + + // Set up a basic scroll handoff layer tree. + CreateScrollHandoffLayerTree1(); + + rootApzc = ApzcOf(layers[0]); + RefPtr childApzc = ApzcOf(layers[1]); + FrameMetrics& rootMetrics = rootApzc->GetFrameMetrics(); + FrameMetrics& childMetrics = childApzc->GetFrameMetrics(); + CSSRect childScrollRange = childMetrics.CalculateScrollRange(); + + EXPECT_EQ(0, rootMetrics.GetVisualScrollOffset().y); + EXPECT_EQ(0, childMetrics.GetVisualScrollOffset().y); + + ScreenIntPoint cursorLocation(10, 60); // positioned to hit the subframe + ScreenPoint upwardDelta(0, -10); + ScreenPoint downwardDelta(0, 10); + + // First wheel upwards. This will have no effect because we're already + // scrolled to the top. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + Wheel(manager, cursorLocation, upwardDelta, mcc->Time()); + EXPECT_EQ(0, rootMetrics.GetVisualScrollOffset().y); + EXPECT_EQ(0, childMetrics.GetVisualScrollOffset().y); + + // Now wheel downwards 6 times. This should scroll the child, and get it + // to the bottom of its 50px scroll range. + for (size_t i = 0; i < 6; ++i) { + mcc->AdvanceByMillis(100); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + Wheel(manager, cursorLocation, downwardDelta, mcc->Time()); + } + EXPECT_EQ(0, rootMetrics.GetVisualScrollOffset().y); + EXPECT_EQ(childScrollRange.YMost(), childMetrics.GetVisualScrollOffset().y); + + // Wheel downwards an additional 16 times, with 100ms increments. + // This should be enough to overcome the 1500ms wheel transaction timeout + // and start scrolling the root. + for (size_t i = 0; i < 16; ++i) { + mcc->AdvanceByMillis(100); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + Wheel(manager, cursorLocation, downwardDelta, mcc->Time()); + } + EXPECT_EQ(childScrollRange.YMost(), childMetrics.GetVisualScrollOffset().y); + EXPECT_GT(rootMetrics.GetVisualScrollOffset().y, 0); +} + +TEST_F(APZScrollHandoffTesterMock, WheelHandoffNonscrollable) { + // Set up a basic scroll layer tree. + CreateScrollHandoffLayerTree5(); + + RefPtr childApzc = ApzcOf(layers[1]); + FrameMetrics& childMetrics = childApzc->GetFrameMetrics(); + + EXPECT_EQ(0, childMetrics.GetVisualScrollOffset().y); + + ScreenPoint downwardDelta(0, 10); + // Positioned to hit the nonscrollable parent frame + ScreenIntPoint nonscrollableLocation(40, 10); + // Positioned to hit the scrollable subframe + ScreenIntPoint scrollableLocation(40, 60); + + // Start the wheel transaction on a nonscrollable parent frame. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + Wheel(manager, nonscrollableLocation, downwardDelta, mcc->Time()); + EXPECT_EQ(0, childMetrics.GetVisualScrollOffset().y); + + // Mouse moves to a scrollable subframe. This should end the transaction. + mcc->AdvanceByMillis(100); + MouseInput mouseInput(MouseInput::MOUSE_MOVE, + MouseInput::ButtonType::PRIMARY_BUTTON, 0, 0, + scrollableLocation, mcc->Time(), 0); + WidgetMouseEvent mouseEvent = mouseInput.ToWidgetEvent(nullptr); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + ((APZInputBridge*)manager.get())->ReceiveInputEvent(mouseEvent); + + // Wheel downward should scroll the subframe. + mcc->AdvanceByMillis(100); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + Wheel(manager, scrollableLocation, downwardDelta, mcc->Time()); + EXPECT_GT(childMetrics.GetVisualScrollOffset().y, 0); +} + +TEST_F(APZScrollHandoffTesterMock, ChildCloseToEndOfScrollRange) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + CreateScrollHandoffLayerTree1(); + + RefPtr childApzc = ApzcOf(layers[1]); + + FrameMetrics& rootMetrics = rootApzc->GetFrameMetrics(); + FrameMetrics& childMetrics = childApzc->GetFrameMetrics(); + + // Zoom the page in by 3x. This needs to be reflected in the zoom level + // and composition bounds of both APZCs. + rootMetrics.SetZoom(CSSToParentLayerScale(3.0)); + rootMetrics.SetCompositionBounds(ParentLayerRect(0, 0, 300, 300)); + childMetrics.SetZoom(CSSToParentLayerScale(3.0)); + childMetrics.SetCompositionBounds(ParentLayerRect(0, 150, 300, 150)); + + // Scroll the child APZC very close to the end of the scroll range. + // The scroll offset is chosen such that in CSS pixels it has 0.01 pixels + // room to scroll (less than COORDINATE_EPSILON = 0.02), but in ParentLayer + // pixels it has 0.03 pixels room (greater than COORDINATE_EPSILON). + childMetrics.SetVisualScrollOffset(CSSPoint(0, 49.99)); + + EXPECT_FALSE(childApzc->IsOverscrolled()); + + CSSPoint childBefore = childApzc->GetFrameMetrics().GetVisualScrollOffset(); + CSSPoint parentBefore = rootApzc->GetFrameMetrics().GetVisualScrollOffset(); + + // Synthesize a pan gesture that tries to scroll the child further down. + PanGesture(PanGestureInput::PANGESTURE_START, childApzc, + ScreenIntPoint(10, 20), ScreenPoint(0, 40), mcc->Time()); + mcc->AdvanceByMillis(5); + childApzc->AdvanceAnimations(mcc->GetSampleTime()); + + PanGesture(PanGestureInput::PANGESTURE_END, childApzc, ScreenIntPoint(10, 21), + ScreenPoint(0, 0), mcc->Time()); + + CSSPoint childAfter = childApzc->GetFrameMetrics().GetVisualScrollOffset(); + CSSPoint parentAfter = rootApzc->GetFrameMetrics().GetVisualScrollOffset(); + + bool childScrolled = (childBefore != childAfter); + bool parentScrolled = (parentBefore != parentAfter); + + // Check that either the child or the parent scrolled. + // (With the current implementation of comparing quantities to + // COORDINATE_EPSILON in CSS units, it will be the parent, but the important + // thing is that at least one of the child or parent scroll, i.e. we're not + // stuck in a situation where no scroll offset is changing). + EXPECT_TRUE(childScrolled || parentScrolled); +} diff --git a/gfx/layers/apz/test/gtest/TestSnapping.cpp b/gfx/layers/apz/test/gtest/TestSnapping.cpp new file mode 100644 index 0000000000..13bb1e5591 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestSnapping.cpp @@ -0,0 +1,305 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCTreeManagerTester.h" +#include "APZTestCommon.h" + +#include "InputUtils.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/StaticPrefs_mousewheel.h" + +class APZCSnappingTesterMock : public APZCTreeManagerTester { + public: + APZCSnappingTesterMock() { CreateMockHitTester(); } +}; + +TEST_F(APZCSnappingTesterMock, Bug1265510) { + const char* treeShape = "x(x)"; + LayerIntRegion layerVisibleRegion[] = {LayerIntRect(0, 0, 100, 100), + LayerIntRect(0, 100, 100, 100)}; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 100, 200)); + SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 100, 200)); + SetScrollHandoff(layers[1], root); + + ScrollSnapInfo snap; + snap.mScrollSnapStrictnessY = StyleScrollSnapStrictness::Mandatory; + snap.mSnapportSize = CSSSize::ToAppUnits( + layerVisibleRegion[0].GetBounds().Size() * LayerToCSSScale(1.0)); + + snap.mSnapTargets.AppendElement(ScrollSnapInfo::SnapTarget( + Nothing(), Some(0 * AppUnitsPerCSSPixel()), + CSSRect::ToAppUnits(CSSRect(0, 0, 10, 10)), StyleScrollSnapStop::Normal, + ScrollSnapTargetId{1})); + snap.mSnapTargets.AppendElement(ScrollSnapInfo::SnapTarget( + Nothing(), Some(100 * AppUnitsPerCSSPixel()), + CSSRect::ToAppUnits(CSSRect(0, 100, 10, 10)), StyleScrollSnapStop::Normal, + ScrollSnapTargetId{2})); + + ModifyFrameMetrics(root, [&](ScrollMetadata& aSm, FrameMetrics&) { + aSm.SetSnapInfo(ScrollSnapInfo(snap)); + }); + + UniquePtr registration = + MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + + TestAsyncPanZoomController* outer = ApzcOf(layers[0]); + TestAsyncPanZoomController* inner = ApzcOf(layers[1]); + + // Position the mouse near the bottom of the outer frame and scroll by 60px. + // (6 lines of 10px each). APZC will actually scroll to y=100 because of the + // mandatory snap coordinate there. + TimeStamp now = mcc->Time(); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + SmoothWheel(manager, ScreenIntPoint(50, 80), ScreenPoint(0, 6), now); + // Advance in 5ms increments until we've scrolled by 70px. At this point, the + // closest snap point is y=100, and the inner frame should be under the mouse + // cursor. + while (outer + ->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::AsyncTransformConsumer::eForHitTesting) + .y < 70) { + mcc->AdvanceByMillis(5); + outer->AdvanceAnimations(mcc->GetSampleTime()); + } + // Now do another wheel in a new transaction. This should start scrolling the + // inner frame; we verify that it does by checking the inner scroll position. + TimeStamp newTransactionTime = + now + TimeDuration::FromMilliseconds( + StaticPrefs::mousewheel_transaction_timeout() + 100); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1); + SmoothWheel(manager, ScreenIntPoint(50, 80), ScreenPoint(0, 6), + newTransactionTime); + inner->AdvanceAnimationsUntilEnd(); + EXPECT_LT( + 0.0f, + inner + ->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::AsyncTransformConsumer::eForHitTesting) + .y); + + // However, the outer frame should also continue to the snap point, otherwise + // it is demonstrating incorrect behaviour by violating the mandatory + // snapping. + outer->AdvanceAnimationsUntilEnd(); + EXPECT_EQ( + 100.0f, + outer + ->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::AsyncTransformConsumer::eForHitTesting) + .y); +} + +TEST_F(APZCSnappingTesterMock, Snap_After_Pinch) { + const char* treeShape = "x"; + LayerIntRegion layerVisibleRegion[] = { + LayerIntRect(0, 0, 100, 100), + }; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 100, 200)); + + // Set up some basic scroll snapping + ScrollSnapInfo snap; + snap.mScrollSnapStrictnessY = StyleScrollSnapStrictness::Mandatory; + snap.mSnapportSize = CSSSize::ToAppUnits( + layerVisibleRegion[0].GetBounds().Size() * LayerToCSSScale(1.0)); + + snap.mSnapTargets.AppendElement(ScrollSnapInfo::SnapTarget( + Nothing(), Some(0 * AppUnitsPerCSSPixel()), + CSSRect::ToAppUnits(CSSRect(0, 0, 10, 10)), StyleScrollSnapStop::Normal, + ScrollSnapTargetId{1})); + snap.mSnapTargets.AppendElement(ScrollSnapInfo::SnapTarget( + Nothing(), Some(100 * AppUnitsPerCSSPixel()), + CSSRect::ToAppUnits(CSSRect(0, 100, 10, 10)), StyleScrollSnapStop::Normal, + ScrollSnapTargetId{2})); + + // Save the scroll snap info on the root APZC. + // Also mark the root APZC as "root content", since APZC only allows + // zooming on the root content APZC. + ModifyFrameMetrics(root, [&](ScrollMetadata& aSm, FrameMetrics& aMetrics) { + aSm.SetSnapInfo(ScrollSnapInfo(snap)); + aMetrics.SetIsRootContent(true); + }); + + UniquePtr registration = + MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + + RefPtr apzc = ApzcOf(root); + + // Allow zooming + apzc->UpdateZoomConstraints(ZoomConstraints( + true, true, CSSToParentLayerScale(0.25f), CSSToParentLayerScale(4.0f))); + + PinchWithPinchInput(apzc, ScreenIntPoint(50, 50), ScreenIntPoint(50, 50), + 1.2f); + + apzc->AssertStateIsSmoothMsdScroll(); +} + +// Currently fails on Android because on the platform we have a different +// VelocityTracker. +#ifndef MOZ_WIDGET_ANDROID +TEST_F(APZCSnappingTesterMock, SnapOnPanEndWithZeroVelocity) { + // Use pref values for desktop everywhere. + SCOPED_GFX_PREF_FLOAT("apz.fling_friction", 0.002); + SCOPED_GFX_PREF_FLOAT("apz.fling_stopped_threshold", 0.01); + SCOPED_GFX_PREF_FLOAT("apz.fling_curve_function_x1", 0.0); + SCOPED_GFX_PREF_FLOAT("apz.fling_curve_function_x2", 1.0); + SCOPED_GFX_PREF_FLOAT("apz.fling_curve_function_y1", 0.0); + SCOPED_GFX_PREF_FLOAT("apz.fling_curve_function_y2", 1.0); + SCOPED_GFX_PREF_INT("apz.velocity_relevance_time_ms", 100); + + const char* treeShape = "x"; + LayerIntRegion layerVisibleRegion[] = { + LayerIntRect(0, 0, 100, 100), + }; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 100, 400)); + + // Set up two snap points, 30 and 100. + ScrollSnapInfo snap; + snap.mScrollSnapStrictnessY = StyleScrollSnapStrictness::Mandatory; + snap.mSnapportSize = CSSSize::ToAppUnits( + layerVisibleRegion[0].GetBounds().Size() * LayerToCSSScale(1.0)); + snap.mSnapTargets.AppendElement(ScrollSnapInfo::SnapTarget( + Nothing(), Some(30 * AppUnitsPerCSSPixel()), + CSSRect::ToAppUnits(CSSRect(0, 30, 10, 10)), StyleScrollSnapStop::Normal, + ScrollSnapTargetId{1})); + snap.mSnapTargets.AppendElement(ScrollSnapInfo::SnapTarget( + Nothing(), Some(100 * AppUnitsPerCSSPixel()), + CSSRect::ToAppUnits(CSSRect(0, 100, 10, 10)), StyleScrollSnapStop::Normal, + ScrollSnapTargetId{2})); + + // Save the scroll snap info on the root APZC. + ModifyFrameMetrics(root, [&](ScrollMetadata& aSm, FrameMetrics& aMetrics) { + aSm.SetSnapInfo(ScrollSnapInfo(snap)); + }); + + UniquePtr registration = + MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + + RefPtr apzc = ApzcOf(root); + + // Send a series of pan gestures to scroll to position at 50. + const ScreenIntPoint position = ScreenIntPoint(50, 30); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_START, manager, position, + ScreenPoint(0, 10), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_PAN, manager, position, + ScreenPoint(0, 40), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + // Make sure the velocity just before sending a pan-end is zero. + EXPECT_EQ(apzc->GetVelocityVector().y, 0); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_END, manager, position, + ScreenPoint(0, 0), mcc->Time()); + + // Now a smooth animation has been triggered for snapping to 30. + apzc->AssertStateIsSmoothMsdScroll(); + + apzc->AdvanceAnimationsUntilEnd(); + // The snapped position should be 30 rather than 100 because it's the nearest + // snap point. + EXPECT_EQ( + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting) + .y, + 30); +} + +// Smililar to above SnapOnPanEndWithZeroVelocity but with positive velocity so +// that the snap position would be the one in the scrolling direction. +TEST_F(APZCSnappingTesterMock, SnapOnPanEndWithPositiveVelocity) { + // Use pref values for desktop everywhere. + SCOPED_GFX_PREF_FLOAT("apz.fling_friction", 0.002); + SCOPED_GFX_PREF_FLOAT("apz.fling_stopped_threshold", 0.01); + SCOPED_GFX_PREF_FLOAT("apz.fling_curve_function_x1", 0.0); + SCOPED_GFX_PREF_FLOAT("apz.fling_curve_function_x2", 1.0); + SCOPED_GFX_PREF_FLOAT("apz.fling_curve_function_y1", 0.0); + SCOPED_GFX_PREF_FLOAT("apz.fling_curve_function_y2", 1.0); + SCOPED_GFX_PREF_INT("apz.velocity_relevance_time_ms", 100); + + const char* treeShape = "x"; + LayerIntRegion layerVisibleRegion[] = { + LayerIntRect(0, 0, 100, 100), + }; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 100, 400)); + + // Set up two snap points, 30 and 100. + ScrollSnapInfo snap; + snap.mScrollSnapStrictnessY = StyleScrollSnapStrictness::Mandatory; + snap.mSnapportSize = CSSSize::ToAppUnits( + layerVisibleRegion[0].GetBounds().Size() * LayerToCSSScale(1.0)); + snap.mSnapTargets.AppendElement(ScrollSnapInfo::SnapTarget( + Nothing(), Some(30 * AppUnitsPerCSSPixel()), + CSSRect::ToAppUnits(CSSRect(0, 30, 10, 10)), StyleScrollSnapStop::Normal, + ScrollSnapTargetId{1})); + snap.mSnapTargets.AppendElement(ScrollSnapInfo::SnapTarget( + Nothing(), Some(100 * AppUnitsPerCSSPixel()), + CSSRect::ToAppUnits(CSSRect(0, 100, 10, 10)), StyleScrollSnapStop::Normal, + ScrollSnapTargetId{2})); + + // Save the scroll snap info on the root APZC. + ModifyFrameMetrics(root, [&](ScrollMetadata& aSm, FrameMetrics& aMetrics) { + aSm.SetSnapInfo(ScrollSnapInfo(snap)); + }); + + UniquePtr registration = + MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + + RefPtr apzc = ApzcOf(root); + + // Send a series of pan gestures that a pan-end event happens at 65 + const ScreenIntPoint position = ScreenIntPoint(50, 30); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_START, manager, position, + ScreenPoint(0, 10), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_PAN, manager, position, + ScreenPoint(0, 35), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_PAN, manager, position, + ScreenPoint(0, 20), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + + // There should be positive velocity in this case. + EXPECT_GT(apzc->GetVelocityVector().y, 0); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_END, manager, position, + ScreenPoint(0, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + + // A smooth animation has been triggered by the pan-end event above. + apzc->AssertStateIsSmoothMsdScroll(); + + apzc->AdvanceAnimationsUntilEnd(); + EXPECT_EQ( + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting) + .y, + 100); +} +#endif diff --git a/gfx/layers/apz/test/gtest/TestSnappingOnMomentum.cpp b/gfx/layers/apz/test/gtest/TestSnappingOnMomentum.cpp new file mode 100644 index 0000000000..02b1d6798c --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestSnappingOnMomentum.cpp @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCTreeManagerTester.h" +#include "APZTestCommon.h" + +#include "InputUtils.h" +#include "mozilla/StaticPrefs_layout.h" + +class APZCSnappingOnMomentumTesterMock : public APZCTreeManagerTester { + public: + APZCSnappingOnMomentumTesterMock() { CreateMockHitTester(); } +}; + +TEST_F(APZCSnappingOnMomentumTesterMock, Snap_On_Momentum) { + const char* treeShape = "x"; + LayerIntRegion layerVisibleRegion[] = { + LayerIntRect(0, 0, 100, 100), + }; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 100, 500)); + + // Set up some basic scroll snapping + ScrollSnapInfo snap; + snap.mScrollSnapStrictnessY = StyleScrollSnapStrictness::Mandatory; + snap.mSnapportSize = CSSSize::ToAppUnits( + layerVisibleRegion[0].GetBounds().Size() * LayerToCSSScale(1.0)); + snap.mSnapTargets.AppendElement(ScrollSnapInfo::SnapTarget( + Nothing(), Some(0 * AppUnitsPerCSSPixel()), + CSSRect::ToAppUnits(CSSRect(0, 0, 10, 10)), StyleScrollSnapStop::Normal, + ScrollSnapTargetId{1})); + snap.mSnapTargets.AppendElement(ScrollSnapInfo::SnapTarget( + Nothing(), Some(100 * AppUnitsPerCSSPixel()), + CSSRect::ToAppUnits(CSSRect(0, 100, 10, 10)), StyleScrollSnapStop::Normal, + ScrollSnapTargetId{2})); + + ModifyFrameMetrics(root, [&](ScrollMetadata& aSm, FrameMetrics&) { + aSm.SetSnapInfo(ScrollSnapInfo(snap)); + }); + + UniquePtr registration = + MakeUnique(LayersId{0}, mcc); + UpdateHitTestingTree(); + + RefPtr apzc = ApzcOf(root); + + TimeStamp now = mcc->Time(); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 80), + ScreenPoint(0, 2), now); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_PAN, manager, ScreenIntPoint(50, 80), + ScreenPoint(0, 25), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_PAN, manager, ScreenIntPoint(50, 80), + ScreenPoint(0, 25), mcc->Time()); + + // The velocity should be positive when panning with positive displacement. + EXPECT_GT(apzc->GetVelocityVector().y, 3.0); + + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_END, manager, ScreenIntPoint(50, 80), + ScreenPoint(0, 0), mcc->Time()); + + // After lifting the fingers, the velocity should be zero and a smooth + // animation should have been triggered for scroll snap. + EXPECT_EQ(apzc->GetVelocityVector().y, 0); + apzc->AssertStateIsSmoothMsdScroll(); + + mcc->AdvanceByMillis(5); + + apzc->AdvanceAnimations(mcc->GetSampleTime()); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, manager, + ScreenIntPoint(50, 80), ScreenPoint(0, 200), mcc->Time()); + mcc->AdvanceByMillis(10); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, manager, + ScreenIntPoint(50, 80), ScreenPoint(0, 50), mcc->Time()); + mcc->AdvanceByMillis(10); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMEND, manager, + ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time()); + + apzc->AdvanceAnimationsUntilEnd(); + EXPECT_EQ( + 100.0f, + apzc->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::AsyncTransformConsumer::eForHitTesting) + .y); +} diff --git a/gfx/layers/apz/test/gtest/TestTransformNotifications.cpp b/gfx/layers/apz/test/gtest/TestTransformNotifications.cpp new file mode 100644 index 0000000000..53b7aa297f --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestTransformNotifications.cpp @@ -0,0 +1,567 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCBasicTester.h" +#include "APZCTreeManagerTester.h" +#include "APZTestCommon.h" +#include "mozilla/layers/WebRenderScrollDataWrapper.h" +#include "apz/util/APZEventState.h" + +#include "InputUtils.h" + +class APZCTransformNotificationTester : public APZCTreeManagerTester { + public: + explicit APZCTransformNotificationTester() { CreateMockHitTester(); } + + UniquePtr mRegistration; + + RefPtr mRootApzc; + + void SetupBasicTest() { + const char* treeShape = "x"; + LayerIntRegion layerVisibleRegion[] = { + LayerIntRect(0, 0, 100, 100), + }; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 500, 500)); + + mRegistration = MakeUnique(LayersId{0}, mcc); + + UpdateHitTestingTree(); + + mRootApzc = ApzcOf(root); + } + + void SetupNonScrollableTest() { + const char* treeShape = "x"; + LayerIntRegion layerVisibleRegion[] = { + LayerIntRect(0, 0, 100, 100), + }; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 100, 100)); + + mRegistration = MakeUnique(LayersId{0}, mcc); + + UpdateHitTestingTree(); + + mRootApzc = ApzcOf(root); + + mRootApzc->GetFrameMetrics().SetIsRootContent(true); + } +}; + +TEST_F(APZCTransformNotificationTester, PanningTransformNotifications) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + SetupBasicTest(); + + // Scroll down by 25 px. Ensure we only get one set of + // state change notifications. + // + // Then, scroll back up by 20px, this time flinging after. + // The fling should cover the remaining 5 px of room to scroll, then + // go into overscroll, and finally snap-back to recover from overscroll. + // Again, ensure we only get one set of state change notifications for + // this entire procedure. + + MockFunction check; + { + InSequence s; + EXPECT_CALL(check, Call("Simple pan")); + EXPECT_CALL( + *mcc, NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eStartTouch, _, _)) + .Times(1); + EXPECT_CALL( + *mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eTransformBegin, _, _)) + .Times(1); + EXPECT_CALL( + *mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eStartPanning, _, _)) + .Times(1); + EXPECT_CALL(*mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eEndTouch, _, _)) + .Times(1); + EXPECT_CALL( + *mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eTransformEnd, _, _)) + .Times(1); + EXPECT_CALL(check, Call("Complex pan")); + EXPECT_CALL( + *mcc, NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eStartTouch, _, _)) + .Times(1); + EXPECT_CALL( + *mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eTransformBegin, _, _)) + .Times(1); + EXPECT_CALL( + *mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eStartPanning, _, _)) + .Times(1); + EXPECT_CALL(*mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eEndTouch, _, _)) + .Times(1); + EXPECT_CALL( + *mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eTransformEnd, _, _)) + .Times(1); + EXPECT_CALL(check, Call("Done")); + } + + check.Call("Simple pan"); + Pan(mRootApzc, 50, 25, PanOptions::NoFling); + check.Call("Complex pan"); + Pan(mRootApzc, 25, 45); + mRootApzc->AdvanceAnimationsUntilEnd(); + check.Call("Done"); +} + +TEST_F(APZCTransformNotificationTester, PanWithMomentumTransformNotifications) { + SetupBasicTest(); + + MockFunction check; + { + InSequence s; + EXPECT_CALL(check, Call("Pan Start")); + EXPECT_CALL( + *mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eTransformBegin, _, _)) + .Times(1); + + EXPECT_CALL(check, Call("Panning")); + EXPECT_CALL(check, Call("Pan End")); + EXPECT_CALL(check, Call("Momentum Start")); + + EXPECT_CALL(check, Call("Momentum Pan")); + EXPECT_CALL(check, Call("Momentum End")); + // The TransformEnd should only be sent after the momentum pan. + EXPECT_CALL( + *mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eTransformEnd, _, _)) + .Times(1); + + EXPECT_CALL(check, Call("Done")); + } + + check.Call("Pan Start"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 50), + ScreenIntPoint(1, 2), mcc->Time()); + mcc->AdvanceByMillis(5); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + check.Call("Panning"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_PAN, mRootApzc, ScreenIntPoint(50, 50), + ScreenPoint(15, 30), mcc->Time()); + mcc->AdvanceByMillis(5); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + check.Call("Pan End"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_END, manager, ScreenIntPoint(50, 50), + ScreenPoint(0, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + check.Call("Momentum Start"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, manager, + ScreenIntPoint(50, 50), ScreenPoint(30, 90), mcc->Time()); + mcc->AdvanceByMillis(10); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + check.Call("Momentum Pan"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, manager, + ScreenIntPoint(50, 50), ScreenPoint(10, 30), mcc->Time()); + mcc->AdvanceByMillis(10); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + check.Call("Momentum End"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMEND, manager, + ScreenIntPoint(50, 50), ScreenPoint(0, 0), mcc->Time()); + mcc->AdvanceByMillis(10); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + check.Call("Done"); +} + +TEST_F(APZCTransformNotificationTester, + PanWithoutMomentumTransformNotifications) { + // Ensure that the TransformEnd delay is 100ms. + SCOPED_GFX_PREF_INT("apz.scrollend-event.content.delay_ms", 100); + + SetupBasicTest(); + + MockFunction check; + { + InSequence s; + EXPECT_CALL(check, Call("Pan Start")); + EXPECT_CALL( + *mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eTransformBegin, _, _)) + .Times(1); + + EXPECT_CALL(check, Call("Panning")); + EXPECT_CALL(check, Call("Pan End")); + EXPECT_CALL(check, Call("TransformEnd delay")); + // The TransformEnd should only be sent after the pan gesture and 100ms + // timer fire. + EXPECT_CALL( + *mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eTransformEnd, _, _)) + .Times(1); + + EXPECT_CALL(check, Call("Done")); + } + + check.Call("Pan Start"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 50), + ScreenIntPoint(1, 2), mcc->Time()); + mcc->AdvanceByMillis(5); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + check.Call("Panning"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_PAN, mRootApzc, ScreenIntPoint(50, 50), + ScreenPoint(15, 30), mcc->Time()); + mcc->AdvanceByMillis(5); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + check.Call("Pan End"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_END, manager, ScreenIntPoint(50, 50), + ScreenPoint(0, 0), mcc->Time()); + mcc->AdvanceByMillis(55); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + check.Call("TransformEnd delay"); + mcc->AdvanceByMillis(55); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + check.Call("Done"); +} + +TEST_F(APZCTransformNotificationTester, + PanFollowedByNewPanTransformNotifications) { + // Ensure that the TransformEnd delay is 100ms. + SCOPED_GFX_PREF_INT("apz.scrollend-event.content.delay_ms", 100); + + SetupBasicTest(); + + MockFunction check; + { + InSequence s; + EXPECT_CALL(check, Call("Pan Start")); + EXPECT_CALL( + *mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eTransformBegin, _, _)) + .Times(1); + + EXPECT_CALL(check, Call("Panning")); + EXPECT_CALL(check, Call("Pan End")); + // The TransformEnd delay should be cut short and delivered before the + // new pan gesture begins. + EXPECT_CALL(check, Call("New Pan Start")); + EXPECT_CALL( + *mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eTransformEnd, _, _)) + .Times(1); + EXPECT_CALL( + *mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eTransformBegin, _, _)) + .Times(1); + EXPECT_CALL(check, Call("New Pan End")); + EXPECT_CALL( + *mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eTransformEnd, _, _)) + .Times(1); + + EXPECT_CALL(check, Call("Done")); + } + + check.Call("Pan Start"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 50), + ScreenIntPoint(1, 2), mcc->Time()); + mcc->AdvanceByMillis(5); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + check.Call("Panning"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_PAN, mRootApzc, ScreenIntPoint(50, 50), + ScreenPoint(15, 30), mcc->Time()); + mcc->AdvanceByMillis(5); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + check.Call("Pan End"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_END, manager, ScreenIntPoint(50, 50), + ScreenPoint(0, 0), mcc->Time()); + mcc->AdvanceByMillis(55); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + check.Call("New Pan Start"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 50), + ScreenIntPoint(1, 2), mcc->Time()); + mcc->AdvanceByMillis(5); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_PAN, mRootApzc, ScreenIntPoint(50, 50), + ScreenPoint(15, 30), mcc->Time()); + mcc->AdvanceByMillis(5); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + check.Call("New Pan End"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_END, manager, ScreenIntPoint(50, 50), + ScreenPoint(0, 0), mcc->Time()); + mcc->AdvanceByMillis(105); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + check.Call("Done"); +} + +TEST_F(APZCTransformNotificationTester, + PanFollowedByWheelTransformNotifications) { + // Ensure that the TransformEnd delay is 100ms. + SCOPED_GFX_PREF_INT("apz.scrollend-event.content.delay_ms", 100); + + SetupBasicTest(); + + MockFunction check; + { + InSequence s; + EXPECT_CALL(check, Call("Pan Start")); + EXPECT_CALL( + *mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eTransformBegin, _, _)) + .Times(1); + + EXPECT_CALL(check, Call("Panning")); + EXPECT_CALL(check, Call("Pan End")); + // The TransformEnd delay should be cut short and delivered before the + // new wheel event begins. + EXPECT_CALL(check, Call("Wheel Start")); + EXPECT_CALL( + *mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eTransformEnd, _, _)) + .Times(1); + EXPECT_CALL( + *mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eTransformBegin, _, _)) + .Times(1); + EXPECT_CALL(check, Call("Wheel End")); + EXPECT_CALL( + *mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eTransformEnd, _, _)) + .Times(1); + EXPECT_CALL(check, Call("Done")); + } + + check.Call("Pan Start"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 50), + ScreenIntPoint(1, 2), mcc->Time()); + mcc->AdvanceByMillis(5); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + check.Call("Panning"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_PAN, mRootApzc, ScreenIntPoint(50, 50), + ScreenPoint(15, 30), mcc->Time()); + mcc->AdvanceByMillis(5); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + check.Call("Pan End"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_END, manager, ScreenIntPoint(50, 50), + ScreenPoint(0, 0), mcc->Time()); + mcc->AdvanceByMillis(55); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + check.Call("Wheel Start"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + SmoothWheel(manager, ScreenIntPoint(50, 50), ScreenPoint(10, 10), + mcc->Time()); + mcc->AdvanceByMillis(10); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + check.Call("Wheel End"); + + mRootApzc->AdvanceAnimationsUntilEnd(); + + check.Call("Done"); +} + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCTransformNotificationTester, PanOverscrollTransformNotifications) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + SetupBasicTest(); + + MockFunction check; + { + InSequence s; + EXPECT_CALL(check, Call("Pan Start")); + EXPECT_CALL( + *mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eTransformBegin, _, _)) + .Times(1); + + EXPECT_CALL(check, Call("Panning Into Overscroll")); + EXPECT_CALL(check, Call("Pan End")); + EXPECT_CALL(check, Call("Overscroll Animation End")); + // The TransformEnd should only be sent after the overscroll animation + // completes. + EXPECT_CALL( + *mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eTransformEnd, _, _)) + .Times(1); + EXPECT_CALL(check, Call("Done")); + } + + check.Call("Pan Start"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 50), + ScreenIntPoint(1, 2), mcc->Time()); + mcc->AdvanceByMillis(5); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + check.Call("Panning Into Overscroll"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_PAN, mRootApzc, ScreenIntPoint(50, 50), + ScreenPoint(15, -30), mcc->Time()); + mcc->AdvanceByMillis(5); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + // Ensure that we have overscrolled. + EXPECT_TRUE(mRootApzc->IsOverscrolled()); + + check.Call("Pan End"); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + PanGesture(PanGestureInput::PANGESTURE_END, manager, ScreenIntPoint(50, 50), + ScreenPoint(0, 0), mcc->Time()); + mcc->AdvanceByMillis(5); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + // Wait for the overscroll animation to complete and the TransformEnd + // notification to be sent. + check.Call("Overscroll Animation End"); + mcc->AdvanceByMillis(5); + mRootApzc->AdvanceAnimationsUntilEnd(); + EXPECT_FALSE(mRootApzc->IsOverscrolled()); + + check.Call("Done"); +} +#endif + +TEST_F(APZCTransformNotificationTester, ScrollableTouchStateChange) { + // Create a scroll frame with available space for a scroll. + SetupBasicTest(); + + MockFunction check; + { + EXPECT_CALL(check, Call("Start")); + // We receive a touch-start with the flag indicating that the + // touch-start occurred over a scrollable element. + EXPECT_CALL( + *mcc, NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eStartTouch, 1, _)) + .Times(1); + + EXPECT_CALL(*mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eEndTouch, 1, _)) + .Times(1); + EXPECT_CALL(check, Call("Done")); + } + + check.Call("Start"); + + // Conduct a touch down and touch up in the scrollable element, + // and ensure the correct state change notifications are sent. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + TouchDown(mRootApzc, ScreenIntPoint(10, 10), mcc->Time()); + mcc->AdvanceByMillis(5); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + TouchUp(mRootApzc, ScreenIntPoint(10, 10), mcc->Time()); + mcc->AdvanceByMillis(5); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + check.Call("Done"); +} + +TEST_F(APZCTransformNotificationTester, NonScrollableTouchStateChange) { + // Create a non-scrollable frame with no space to scroll. + SetupNonScrollableTest(); + + MockFunction check; + { + EXPECT_CALL(check, Call("Start")); + // We receive a touch-start with the flag indicating that the + // touch-start occurred over a non-scrollable element. + EXPECT_CALL( + *mcc, NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eStartTouch, 0, _)) + .Times(1); + + EXPECT_CALL(*mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eEndTouch, 1, _)) + .Times(1); + EXPECT_CALL(check, Call("Done")); + } + + check.Call("Start"); + + // Conduct a touch down and touch up in the non-scrollable element, + // and ensure the correct state change notifications are sent. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + TouchDown(mRootApzc, ScreenIntPoint(10, 10), mcc->Time()); + mcc->AdvanceByMillis(5); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); + TouchUp(mRootApzc, ScreenIntPoint(10, 10), mcc->Time()); + mcc->AdvanceByMillis(5); + mRootApzc->AdvanceAnimations(mcc->GetSampleTime()); + + check.Call("Done"); +} diff --git a/gfx/layers/apz/test/gtest/TestTreeManager.cpp b/gfx/layers/apz/test/gtest/TestTreeManager.cpp new file mode 100644 index 0000000000..963a400cb8 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestTreeManager.cpp @@ -0,0 +1,347 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCTreeManagerTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" +#include "Units.h" + +class APZCTreeManagerGenericTester : public APZCTreeManagerTester { + protected: + void CreateSimpleScrollingLayer() { + const char* treeShape = "x"; + LayerIntRegion layerVisibleRegion[] = { + LayerIntRect(0, 0, 200, 200), + }; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(layers[0], ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 500, 500)); + } + + void CreateSimpleMultiLayerTree() { + const char* treeShape = "x(xx)"; + // LayerID 0 12 + LayerIntRegion layerVisibleRegion[] = { + LayerIntRect(0, 0, 100, 100), + LayerIntRect(0, 0, 100, 50), + LayerIntRect(0, 50, 100, 50), + }; + CreateScrollData(treeShape, layerVisibleRegion); + } + + void CreatePotentiallyLeakingTree() { + const char* treeShape = "x(x(x(x))x(x(x)))"; + // LayerID 0 1 2 3 4 5 6 + CreateScrollData(treeShape); + SetScrollableFrameMetrics(layers[0], ScrollableLayerGuid::START_SCROLL_ID); + SetScrollableFrameMetrics(layers[2], + ScrollableLayerGuid::START_SCROLL_ID + 1); + SetScrollableFrameMetrics(layers[5], + ScrollableLayerGuid::START_SCROLL_ID + 1); + SetScrollableFrameMetrics(layers[3], + ScrollableLayerGuid::START_SCROLL_ID + 2); + SetScrollableFrameMetrics(layers[6], + ScrollableLayerGuid::START_SCROLL_ID + 3); + } + + void CreateTwoLayerTree(int32_t aRootContentLayerIndex) { + const char* treeShape = "x(x)"; + // LayerID 0 1 + LayerIntRegion layerVisibleRegion[] = { + LayerIntRect(0, 0, 100, 100), + LayerIntRect(0, 0, 100, 100), + }; + CreateScrollData(treeShape, layerVisibleRegion); + SetScrollableFrameMetrics(layers[0], ScrollableLayerGuid::START_SCROLL_ID); + SetScrollableFrameMetrics(layers[1], + ScrollableLayerGuid::START_SCROLL_ID + 1); + SetScrollHandoff(layers[1], layers[0]); + + // Make layers[aRootContentLayerIndex] the root content + ModifyFrameMetrics(layers[aRootContentLayerIndex], + [](ScrollMetadata& sm, FrameMetrics& fm) { + fm.SetIsRootContent(true); + }); + } +}; + +TEST_F(APZCTreeManagerGenericTester, ScrollablePaintedLayers) { + CreateSimpleMultiLayerTree(); + ScopedLayerTreeRegistration registration(LayersId{0}, mcc); + + // both layers have the same scrollId + SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID); + SetScrollableFrameMetrics(layers[2], ScrollableLayerGuid::START_SCROLL_ID); + UpdateHitTestingTree(); + + TestAsyncPanZoomController* nullAPZC = nullptr; + // so they should have the same APZC + EXPECT_FALSE(HasScrollableFrameMetrics(layers[0])); + EXPECT_NE(nullAPZC, ApzcOf(layers[1])); + EXPECT_NE(nullAPZC, ApzcOf(layers[2])); + EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2])); +} + +TEST_F(APZCTreeManagerGenericTester, Bug1068268) { + CreatePotentiallyLeakingTree(); + ScopedLayerTreeRegistration registration(LayersId{0}, mcc); + + UpdateHitTestingTree(); + RefPtr root = manager->GetRootNode(); + RefPtr node2 = root->GetFirstChild()->GetFirstChild(); + RefPtr node5 = root->GetLastChild()->GetLastChild(); + + EXPECT_EQ(ApzcOf(layers[2]), node5->GetApzc()); + EXPECT_EQ(ApzcOf(layers[2]), node2->GetApzc()); + EXPECT_EQ(ApzcOf(layers[0]), ApzcOf(layers[2])->GetParent()); + EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[5])); + + EXPECT_EQ(node2->GetFirstChild(), node2->GetLastChild()); + EXPECT_EQ(ApzcOf(layers[3]), node2->GetLastChild()->GetApzc()); + EXPECT_EQ(node5->GetFirstChild(), node5->GetLastChild()); + EXPECT_EQ(ApzcOf(layers[6]), node5->GetLastChild()->GetApzc()); + EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[3])->GetParent()); + EXPECT_EQ(ApzcOf(layers[5]), ApzcOf(layers[6])->GetParent()); +} + +class APZCTreeManagerGenericTesterMock : public APZCTreeManagerGenericTester { + public: + APZCTreeManagerGenericTesterMock() { CreateMockHitTester(); } +}; + +TEST_F(APZCTreeManagerGenericTesterMock, Bug1194876) { + // Create a layer tree with parent and child scrollable layers, with the + // child being the root content. + CreateTwoLayerTree(1); + ScopedLayerTreeRegistration registration(LayersId{0}, mcc); + UpdateHitTestingTree(); + + uint64_t blockId; + nsTArray targets; + + // First touch goes down, APZCTM will hit layers[1] because it is on top of + // layers[0], but we tell it the real target APZC is layers[0]. + MultiTouchInput mti; + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + mti.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(25, 50), ScreenSize(0, 0), 0, 0)); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + blockId = manager->ReceiveInputEvent(mti).mInputBlockId; + manager->ContentReceivedInputBlock(blockId, false); + targets.AppendElement(ApzcOf(layers[0])->GetGuid()); + manager->SetTargetAPZC(blockId, targets); + + // Around here, the above touch will get processed by ApzcOf(layers[0]) + + // Second touch goes down (first touch remains down), APZCTM will again hit + // layers[1]. Again we tell it both touches landed on layers[0], but because + // layers[1] is the RCD layer, it will end up being the multitouch target. + mti.mTouches.AppendElement( + SingleTouchData(1, ScreenIntPoint(75, 50), ScreenSize(0, 0), 0, 0)); + // Each touch will get hit-tested, so queue two hit-test results. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + blockId = manager->ReceiveInputEvent(mti).mInputBlockId; + manager->ContentReceivedInputBlock(blockId, false); + targets.AppendElement(ApzcOf(layers[0])->GetGuid()); + manager->SetTargetAPZC(blockId, targets); + + // Around here, the above multi-touch will get processed by ApzcOf(layers[1]). + // We want to ensure that ApzcOf(layers[0]) has had its state cleared, because + // otherwise it will do things like dispatch spurious long-tap events. + + EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, _, _, _, _)).Times(0); +} + +TEST_F(APZCTreeManagerGenericTesterMock, TargetChangesMidGesture_Bug1570559) { + // Create a layer tree with parent and child scrollable layers, with the + // parent being the root content. + CreateTwoLayerTree(0); + ScopedLayerTreeRegistration registration(LayersId{0}, mcc); + UpdateHitTestingTree(); + + uint64_t blockId; + nsTArray targets; + + // First touch goes down. APZCTM hits the child layer because it is on top + // (and we confirm this target), but do not prevent-default the event, causing + // the child APZC's gesture detector to start a long-tap timeout task. + MultiTouchInput mti = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + mti.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(25, 50), ScreenSize(0, 0), 0, 0)); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + blockId = manager->ReceiveInputEvent(mti).mInputBlockId; + manager->ContentReceivedInputBlock(blockId, /* default prevented = */ false); + targets.AppendElement(ApzcOf(layers[1])->GetGuid()); + manager->SetTargetAPZC(blockId, targets); + + // Second touch goes down (first touch remains down). APZCTM again hits the + // child and we confirm this, but multi-touch events are routed to the root + // content APZC which is the parent. This event is prevent-defaulted, so we + // clear the parent's gesture state. The bug is that we fail to clear the + // child's gesture state. + mti.mTouches.AppendElement( + SingleTouchData(1, ScreenIntPoint(75, 50), ScreenSize(0, 0), 0, 0)); + // Each touch will get hit-tested, so queue two hit-test results. + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + blockId = manager->ReceiveInputEvent(mti).mInputBlockId; + manager->ContentReceivedInputBlock(blockId, /* default prevented = */ true); + targets.AppendElement(ApzcOf(layers[1])->GetGuid()); + manager->SetTargetAPZC(blockId, targets); + + // If we've failed to clear the child's gesture state, then the long tap + // timeout task will fire in TearDown() and a long-tap will be dispatched. + EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, _, _, _, _)).Times(0); +} + +TEST_F(APZCTreeManagerGenericTesterMock, Bug1198900) { + // This is just a test that cancels a wheel event to make sure it doesn't + // crash. + CreateSimpleScrollingLayer(); + ScopedLayerTreeRegistration registration(LayersId{0}, mcc); + UpdateHitTestingTree(); + + ScreenPoint origin(100, 50); + ScrollWheelInput swi(mcc->Time(), 0, ScrollWheelInput::SCROLLMODE_INSTANT, + ScrollWheelInput::SCROLLDELTA_PIXEL, origin, 0, 10, + false, WheelDeltaAdjustmentStrategy::eNone); + uint64_t blockId; + QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, + {CompositorHitTestFlags::eVisibleToHitTest, + CompositorHitTestFlags::eIrregularArea}); + blockId = manager->ReceiveInputEvent(swi).mInputBlockId; + manager->ContentReceivedInputBlock(blockId, /* preventDefault= */ true); +} + +// The next two tests check that APZ clamps the scroll offset it composites even +// if the main thread fails to do so. (The main thread will always clamp its +// scroll offset internally, but it may not send APZ the clamped version for +// scroll offset synchronization reasons.) +TEST_F(APZCTreeManagerTester, Bug1551582) { + // The simple layer tree has a scrollable rect of 500x500 and a composition + // bounds of 200x200, leading to a scroll range of (0,0,300,300). + CreateSimpleScrollingLayer(); + ScopedLayerTreeRegistration registration(LayersId{0}, mcc); + UpdateHitTestingTree(); + + // Simulate the main thread scrolling to the end of the scroll range. + ModifyFrameMetrics(root, [](ScrollMetadata& aSm, FrameMetrics& aMetrics) { + aMetrics.SetLayoutScrollOffset(CSSPoint(300, 300)); + nsTArray scrollUpdates; + scrollUpdates.AppendElement(ScrollPositionUpdate::NewScroll( + ScrollOrigin::Other, CSSPoint::ToAppUnits(CSSPoint(300, 300)))); + aSm.SetScrollUpdates(scrollUpdates); + aMetrics.SetScrollGeneration(scrollUpdates.LastElement().GetGeneration()); + }); + UpdateHitTestingTree(); + + // Sanity check. + RefPtr apzc = ApzcOf(root); + CSSPoint compositedScrollOffset = apzc->GetCompositedScrollOffset(); + EXPECT_EQ(CSSPoint(300, 300), compositedScrollOffset); + + // Simulate the main thread shrinking the scrollable rect to 400x400 (and + // thereby the scroll range to (0,0,200,200) without sending a new scroll + // offset update for the clamped scroll position (200,200). + ModifyFrameMetrics(root, [](ScrollMetadata& aSm, FrameMetrics& aMetrics) { + aMetrics.SetScrollableRect(CSSRect(0, 0, 400, 400)); + }); + UpdateHitTestingTree(); + + // Check that APZ has clamped the scroll offset to (200,200) for us. + compositedScrollOffset = apzc->GetCompositedScrollOffset(); + EXPECT_EQ(CSSPoint(200, 200), compositedScrollOffset); +} +TEST_F(APZCTreeManagerTester, Bug1557424) { + // The simple layer tree has a scrollable rect of 500x500 and a composition + // bounds of 200x200, leading to a scroll range of (0,0,300,300). + CreateSimpleScrollingLayer(); + ScopedLayerTreeRegistration registration(LayersId{0}, mcc); + UpdateHitTestingTree(); + + // Simulate the main thread scrolling to the end of the scroll range. + ModifyFrameMetrics(root, [](ScrollMetadata& aSm, FrameMetrics& aMetrics) { + aMetrics.SetLayoutScrollOffset(CSSPoint(300, 300)); + nsTArray scrollUpdates; + scrollUpdates.AppendElement(ScrollPositionUpdate::NewScroll( + ScrollOrigin::Other, CSSPoint::ToAppUnits(CSSPoint(300, 300)))); + aSm.SetScrollUpdates(scrollUpdates); + aMetrics.SetScrollGeneration(scrollUpdates.LastElement().GetGeneration()); + }); + UpdateHitTestingTree(); + + // Sanity check. + RefPtr apzc = ApzcOf(root); + CSSPoint compositedScrollOffset = apzc->GetCompositedScrollOffset(); + EXPECT_EQ(CSSPoint(300, 300), compositedScrollOffset); + + // Simulate the main thread expanding the composition bounds to 300x300 (and + // thereby shrinking the scroll range to (0,0,200,200) without sending a new + // scroll offset update for the clamped scroll position (200,200). + ModifyFrameMetrics(root, [](ScrollMetadata& aSm, FrameMetrics& aMetrics) { + aMetrics.SetCompositionBounds(ParentLayerRect(0, 0, 300, 300)); + }); + UpdateHitTestingTree(); + + // Check that APZ has clamped the scroll offset to (200,200) for us. + compositedScrollOffset = apzc->GetCompositedScrollOffset(); + EXPECT_EQ(CSSPoint(200, 200), compositedScrollOffset); +} + +TEST_F(APZCTreeManagerTester, Bug1805601) { + // The simple layer tree has a scrollable rect of 500x500 and a composition + // bounds of 200x200, leading to a scroll range of (0,0,300,300) at unit zoom. + CreateSimpleScrollingLayer(); + ScopedLayerTreeRegistration registration(LayersId{0}, mcc); + UpdateHitTestingTree(); + RefPtr apzc = ApzcOf(root); + FrameMetrics& compositorMetrics = apzc->GetFrameMetrics(); + EXPECT_EQ(CSSRect(0, 0, 300, 300), compositorMetrics.CalculateScrollRange()); + + // Zoom the page in by 2x. This needs to be reflected in each of the pres + // shell resolution, cumulative resolution, and zoom. This makes the scroll + // range (0,0,400,400). + compositorMetrics.SetZoom(CSSToParentLayerScale(2.0)); + EXPECT_EQ(CSSRect(0, 0, 400, 400), compositorMetrics.CalculateScrollRange()); + + // Scroll to an area inside the 2x scroll range but outside the original one. + compositorMetrics.ClampAndSetVisualScrollOffset(CSSPoint(350, 350)); + EXPECT_EQ(CSSPoint(350, 350), compositorMetrics.GetVisualScrollOffset()); + + // Simulate a main-thread update where the zoom is reset to 1x but the visual + // scroll offset is unmodified. + ModifyFrameMetrics(root, [](ScrollMetadata& aSm, FrameMetrics& aMetrics) { + // Changes to |compositorMetrics| are not reflected in |aMetrics|, which + // is the "layer tree" copy, so we don't need to explicitly set the zoom to + // 1.0 (it still has that as the initial value), but we do need to set + // the visual scroll offset to the same value the APZ copy has. + aMetrics.SetVisualScrollOffset(CSSPoint(350, 350)); + + // Needed to get APZ to accept the 1.0 zoom in |aMetrics|, otherwise + // it will act as though its zoom is newer (e.g. an async zoom that hasn't + // been repainted yet) and ignore ours. + aSm.SetResolutionUpdated(true); + }); + UpdateHitTestingTree(); + + // Check that APZ clamped the scroll offset. + EXPECT_EQ(CSSRect(0, 0, 300, 300), compositorMetrics.CalculateScrollRange()); + EXPECT_EQ(CSSPoint(300, 300), compositorMetrics.GetVisualScrollOffset()); +} diff --git a/gfx/layers/apz/test/gtest/TestWRScrollData.cpp b/gfx/layers/apz/test/gtest/TestWRScrollData.cpp new file mode 100644 index 0000000000..e267e58e90 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestWRScrollData.cpp @@ -0,0 +1,273 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TestWRScrollData.h" +#include "APZTestAccess.h" +#include "gtest/gtest.h" +#include "FrameMetrics.h" +#include "gfxPlatform.h" +#include "mozilla/layers/APZUpdater.h" +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/layers/ScrollableLayerGuid.h" +#include "mozilla/layers/WebRenderScrollDataWrapper.h" +#include "mozilla/UniquePtr.h" +#include "apz/src/APZCTreeManager.h" + +using mozilla::layers::APZCTreeManager; +using mozilla::layers::APZUpdater; +using mozilla::layers::LayersId; +using mozilla::layers::ScrollableLayerGuid; +using mozilla::layers::ScrollMetadata; +using mozilla::layers::TestWRScrollData; +using mozilla::layers::WebRenderLayerScrollData; +using mozilla::layers::WebRenderScrollDataWrapper; + +/* static */ +TestWRScrollData TestWRScrollData::Create(const char* aTreeShape, + const APZUpdater& aUpdater, + const LayerIntRegion* aVisibleRegions, + const gfx::Matrix4x4* aTransforms) { + // The WebRenderLayerScrollData tree needs to be created in a fairly + // particular way (for example, each node needs to know the number of + // descendants it has), so this function takes care to create the nodes + // in the same order as WebRenderCommandBuilder would. + TestWRScrollData result; + const size_t len = strlen(aTreeShape); + // "Layer index" in this function refers to the index by which a layer will + // be accessible via TestWRScrollData::GetLayer(), and matches the order + // in which the layer appears in |aTreeShape|. + size_t currentLayerIndex = 0; + struct LayerEntry { + size_t mLayerIndex; + int32_t mDescendantCount = 0; + }; + // Layers we have encountered in |aTreeShape|, but have not built a + // WebRenderLayerScrollData for. (It can only be built after its + // descendants have been encountered and counted.) + std::stack pendingLayers; + std::vector finishedLayers; + // Tracks the level of nesting of '(' characters. Starts at 1 to account + // for the root layer. + size_t depth = 1; + // Helper function for finishing a layer once all its descendants have been + // encountered. + auto finishLayer = [&] { + MOZ_ASSERT(!pendingLayers.empty()); + LayerEntry entry = pendingLayers.top(); + + WebRenderLayerScrollData layer; + APZTestAccess::InitializeForTest(layer, entry.mDescendantCount); + if (aVisibleRegions) { + layer.SetVisibleRegion(aVisibleRegions[entry.mLayerIndex]); + } + if (aTransforms) { + layer.SetTransform(aTransforms[entry.mLayerIndex]); + } + finishedLayers.push_back(std::move(layer)); + + // |finishedLayers| stores the layers in a different order than they + // appeared in |aTreeShape|. To be able to access layers by their layer + // index, keep a mapping from layer index to index in |finishedLayers|. + result.mIndexMap.emplace(entry.mLayerIndex, finishedLayers.size() - 1); + + pendingLayers.pop(); + + // Keep track of descendant counts. The +1 is for the layer just finished. + if (!pendingLayers.empty()) { + pendingLayers.top().mDescendantCount += (entry.mDescendantCount + 1); + } + }; + for (size_t i = 0; i < len; ++i) { + if (aTreeShape[i] == '(') { + ++depth; + } else if (aTreeShape[i] == ')') { + if (pendingLayers.size() <= 1) { + printf("Invalid tree shape: too many ')'\n"); + MOZ_CRASH(); + } + finishLayer(); // finish last layer at current depth + --depth; + } else { + if (aTreeShape[i] != 'x') { + printf("The only allowed character to represent a layer is 'x'\n"); + MOZ_CRASH(); + } + if (depth == pendingLayers.size()) { + // We have a previous layer at this same depth to finish. + if (depth <= 1) { + printf("The tree is only allowed to have one root\n"); + MOZ_CRASH(); + } + finishLayer(); + } + MOZ_ASSERT(depth == pendingLayers.size() + 1); + pendingLayers.push({currentLayerIndex}); + ++currentLayerIndex; + } + } + if (pendingLayers.size() != 1) { + printf("Invalid tree shape: '(' and ')' not balanced\n"); + MOZ_CRASH(); + } + finishLayer(); // finish root layer + + // As in WebRenderCommandBuilder, the layers need to be added to the + // WebRenderScrollData in reverse of the order in which they were built. + for (auto it = finishedLayers.rbegin(); it != finishedLayers.rend(); ++it) { + result.AddLayerData(std::move(*it)); + } + // mIndexMap also needs to be adjusted to accout for the reversal above. + for (auto& [layerIndex, storedIndex] : result.mIndexMap) { + (void)layerIndex; // suppress -Werror=unused-variable + storedIndex = result.GetLayerCount() - storedIndex - 1; + } + + return result; +} + +const WebRenderLayerScrollData* TestWRScrollData::operator[]( + size_t aLayerIndex) const { + auto it = mIndexMap.find(aLayerIndex); + if (it == mIndexMap.end()) { + return nullptr; + } + return GetLayerData(it->second); +} + +WebRenderLayerScrollData* TestWRScrollData::operator[](size_t aLayerIndex) { + auto it = mIndexMap.find(aLayerIndex); + if (it == mIndexMap.end()) { + return nullptr; + } + return GetLayerData(it->second); +} + +void TestWRScrollData::SetScrollMetadata( + size_t aLayerIndex, const nsTArray& aMetadata) { + WebRenderLayerScrollData* layer = operator[](aLayerIndex); + MOZ_ASSERT(layer); + for (const ScrollMetadata& metadata : aMetadata) { + layer->AppendScrollMetadata(*this, metadata); + } +} + +class WebRenderScrollDataWrapperTester : public ::testing::Test { + protected: + virtual void SetUp() { + // This ensures ScrollMetadata::sNullMetadata is initialized. + gfxPlatform::GetPlatform(); + + mManager = new APZCTreeManager(LayersId{0}); + mUpdater = new APZUpdater(mManager, false); + } + + RefPtr mManager; + RefPtr mUpdater; +}; + +TEST_F(WebRenderScrollDataWrapperTester, SimpleTree) { + auto layers = TestWRScrollData::Create("x(x(x(xx)x(x)))", *mUpdater); + WebRenderScrollDataWrapper w0(*mUpdater, &layers); + + ASSERT_EQ(layers[0], w0.GetLayer()); + WebRenderScrollDataWrapper w1 = w0.GetLastChild(); + ASSERT_EQ(layers[1], w1.GetLayer()); + ASSERT_FALSE(w1.GetPrevSibling().IsValid()); + WebRenderScrollDataWrapper w5 = w1.GetLastChild(); + ASSERT_EQ(layers[5], w5.GetLayer()); + WebRenderScrollDataWrapper w6 = w5.GetLastChild(); + ASSERT_EQ(layers[6], w6.GetLayer()); + ASSERT_FALSE(w6.GetLastChild().IsValid()); + WebRenderScrollDataWrapper w2 = w5.GetPrevSibling(); + ASSERT_EQ(layers[2], w2.GetLayer()); + ASSERT_FALSE(w2.GetPrevSibling().IsValid()); + WebRenderScrollDataWrapper w4 = w2.GetLastChild(); + ASSERT_EQ(layers[4], w4.GetLayer()); + ASSERT_FALSE(w4.GetLastChild().IsValid()); + WebRenderScrollDataWrapper w3 = w4.GetPrevSibling(); + ASSERT_EQ(layers[3], w3.GetLayer()); + ASSERT_FALSE(w3.GetLastChild().IsValid()); + ASSERT_FALSE(w3.GetPrevSibling().IsValid()); +} + +static ScrollMetadata MakeMetadata(ScrollableLayerGuid::ViewID aId) { + ScrollMetadata metadata; + metadata.GetMetrics().SetScrollId(aId); + return metadata; +} + +TEST_F(WebRenderScrollDataWrapperTester, MultiFramemetricsTree) { + auto layers = TestWRScrollData::Create("x(x(x(xx)x(x)))", *mUpdater); + + nsTArray metadata; + metadata.InsertElementAt(0, + MakeMetadata(ScrollableLayerGuid::START_SCROLL_ID + + 0)); // topmost of root layer + metadata.InsertElementAt(0, + MakeMetadata(ScrollableLayerGuid::NULL_SCROLL_ID)); + metadata.InsertElementAt( + 0, MakeMetadata(ScrollableLayerGuid::START_SCROLL_ID + 1)); + metadata.InsertElementAt( + 0, MakeMetadata(ScrollableLayerGuid::START_SCROLL_ID + 2)); + metadata.InsertElementAt(0, + MakeMetadata(ScrollableLayerGuid::NULL_SCROLL_ID)); + metadata.InsertElementAt( + 0, MakeMetadata( + ScrollableLayerGuid::NULL_SCROLL_ID)); // bottom of root layer + layers.SetScrollMetadata(0, metadata); + + metadata.Clear(); + metadata.InsertElementAt( + 0, MakeMetadata(ScrollableLayerGuid::START_SCROLL_ID + 3)); + layers.SetScrollMetadata(1, metadata); + + metadata.Clear(); + metadata.InsertElementAt( + 0, MakeMetadata(ScrollableLayerGuid::START_SCROLL_ID + 4)); + layers.SetScrollMetadata(2, metadata); + + metadata.Clear(); + metadata.InsertElementAt( + 0, MakeMetadata(ScrollableLayerGuid::START_SCROLL_ID + 5)); + layers.SetScrollMetadata(4, metadata); + + metadata.Clear(); + metadata.InsertElementAt(0, + MakeMetadata(ScrollableLayerGuid::NULL_SCROLL_ID)); + metadata.InsertElementAt( + 0, MakeMetadata(ScrollableLayerGuid::START_SCROLL_ID + 6)); + layers.SetScrollMetadata(5, metadata); + + WebRenderScrollDataWrapper wrapper(*mUpdater, &layers); + nsTArray expectedLayers; + expectedLayers.AppendElement(layers[0]); + expectedLayers.AppendElement(layers[0]); + expectedLayers.AppendElement(layers[0]); + expectedLayers.AppendElement(layers[0]); + expectedLayers.AppendElement(layers[0]); + expectedLayers.AppendElement(layers[0]); + expectedLayers.AppendElement(layers[1]); + expectedLayers.AppendElement(layers[5]); + expectedLayers.AppendElement(layers[5]); + expectedLayers.AppendElement(layers[6]); + nsTArray expectedIds; + expectedIds.AppendElement(ScrollableLayerGuid::START_SCROLL_ID + 0); + expectedIds.AppendElement(ScrollableLayerGuid::NULL_SCROLL_ID); + expectedIds.AppendElement(ScrollableLayerGuid::START_SCROLL_ID + 1); + expectedIds.AppendElement(ScrollableLayerGuid::START_SCROLL_ID + 2); + expectedIds.AppendElement(ScrollableLayerGuid::NULL_SCROLL_ID); + expectedIds.AppendElement(ScrollableLayerGuid::NULL_SCROLL_ID); + expectedIds.AppendElement(ScrollableLayerGuid::START_SCROLL_ID + 3); + expectedIds.AppendElement(ScrollableLayerGuid::NULL_SCROLL_ID); + expectedIds.AppendElement(ScrollableLayerGuid::START_SCROLL_ID + 6); + expectedIds.AppendElement(ScrollableLayerGuid::NULL_SCROLL_ID); + for (int i = 0; i < 10; i++) { + ASSERT_EQ(expectedLayers[i], wrapper.GetLayer()); + ASSERT_EQ(expectedIds[i], wrapper.Metrics().GetScrollId()); + wrapper = wrapper.GetLastChild(); + } + ASSERT_FALSE(wrapper.IsValid()); +} diff --git a/gfx/layers/apz/test/gtest/TestWRScrollData.h b/gfx/layers/apz/test/gtest/TestWRScrollData.h new file mode 100644 index 0000000000..92e79aaee0 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestWRScrollData.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_TestWRScrollData_h +#define mozilla_layers_TestWRScrollData_h + +#include "mozilla/gfx/MatrixFwd.h" +#include "mozilla/layers/WebRenderScrollData.h" + +namespace mozilla { +namespace layers { + +class APZUpdater; + +// Extends WebRenderScrollData with some methods useful for gtests. +class TestWRScrollData : public WebRenderScrollData { + public: + TestWRScrollData() = default; + TestWRScrollData(TestWRScrollData&& aOther) = default; + TestWRScrollData& operator=(TestWRScrollData&& aOther) = default; + + /* + * Create a WebRenderLayerScrollData tree described by |aTreeShape|. + * |aTreeShape| is expected to be a string where each character is + * either 'x' to indicate a node in the tree, or a '(' or ')' to indicate + * the start/end of a subtree. + * + * Example "x(x(x(xx)x))" would yield: + * x + * | + * x + * / \ + * x x + * / \ + * x x + * + * The caller may optionally provide visible regions and/or transforms + * for the nodes. If provided, the array should contain one element + * for each node, in the same order as in |aTreeShape|. + */ + static TestWRScrollData Create( + const char* aTreeShape, const APZUpdater& aUpdater, + const LayerIntRegion* aVisibleRegions = nullptr, + const gfx::Matrix4x4* aTransforms = nullptr); + + // These methods allow accessing and manipulating layers based on an index + // representing the order in which they appear in |aTreeShape|. + WebRenderLayerScrollData* operator[](size_t aLayerIndex); + const WebRenderLayerScrollData* operator[](size_t aLayerIndex) const; + void SetScrollMetadata(size_t aLayerIndex, + const nsTArray& aMetadata); + + private: + std::map mIndexMap; // Used to implement GetLayer() +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/apz/test/gtest/moz.build b/gfx/layers/apz/test/gtest/moz.build new file mode 100644 index 0000000000..e6fb799008 --- /dev/null +++ b/gfx/layers/apz/test/gtest/moz.build @@ -0,0 +1,39 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES += [ + "APZTestAccess.cpp", + "APZTestCommon.cpp", + "MockHitTester.cpp", + "TestAxisLock.cpp", + "TestBasic.cpp", + "TestEventRegions.cpp", + "TestEventResult.cpp", + "TestFlingAcceleration.cpp", + "TestGestureDetector.cpp", + "TestHitTesting.cpp", + "TestInputQueue.cpp", + "TestOverscroll.cpp", + "TestPanning.cpp", + "TestPinching.cpp", + "TestPointerEventsConsumable.cpp", + "TestScrollHandoff.cpp", + "TestSnapping.cpp", + "TestSnappingOnMomentum.cpp", + "TestTransformNotifications.cpp", + "TestTreeManager.cpp", + "TestWRScrollData.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +LOCAL_INCLUDES += [ + "/gfx/2d", + "/gfx/cairo/cairo/src", + "/gfx/layers", +] + +FINAL_LIBRARY = "xul-gtest" diff --git a/gfx/layers/apz/test/gtest/mvm/TestMobileViewportManager.cpp b/gfx/layers/apz/test/gtest/mvm/TestMobileViewportManager.cpp new file mode 100644 index 0000000000..cd7e0a7d0d --- /dev/null +++ b/gfx/layers/apz/test/gtest/mvm/TestMobileViewportManager.cpp @@ -0,0 +1,220 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +#include + +#include "MobileViewportManager.h" +#include "mozilla/MVMContext.h" +#include "mozilla/dom/Event.h" + +using namespace mozilla; + +class MockMVMContext : public MVMContext { + using AutoSizeFlag = nsViewportInfo::AutoSizeFlag; + using AutoScaleFlag = nsViewportInfo::AutoScaleFlag; + using ZoomFlag = nsViewportInfo::ZoomFlag; + + // A "layout function" is a function that computes the content size + // as a function of the ICB size. + using LayoutFunction = std::function; + + public: + // MVMContext methods we don't care to implement. + MOCK_METHOD3(AddEventListener, + void(const nsAString& aType, nsIDOMEventListener* aListener, + bool aUseCapture)); + MOCK_METHOD3(RemoveEventListener, + void(const nsAString& aType, nsIDOMEventListener* aListener, + bool aUseCapture)); + MOCK_METHOD3(AddObserver, void(nsIObserver* aObserver, const char* aTopic, + bool aOwnsWeak)); + MOCK_METHOD2(RemoveObserver, + void(nsIObserver* aObserver, const char* aTopic)); + MOCK_METHOD0(Destroy, void()); + + MOCK_METHOD1(SetVisualViewportSize, void(const CSSSize& aSize)); + MOCK_METHOD0(PostVisualViewportResizeEventByDynamicToolbar, void()); + MOCK_METHOD0(UpdateDisplayPortMargins, void()); + + void SetMVM(MobileViewportManager* aMVM) { mMVM = aMVM; } + + // MVMContext method implementations. + nsViewportInfo GetViewportInfo(const ScreenIntSize& aDisplaySize) const { + // This is a very basic approximation of what Document::GetViewportInfo() + // does in the most common cases. + // Ideally, we would invoke the algorithm in Document::GetViewportInfo() + // itself, but that would require refactoring it a bit to remove + // dependencies on the actual Document which we don't have available in + // this test harness. + CSSSize viewportSize = mDisplaySize / mDeviceScale; + if (mAutoSizeFlag == AutoSizeFlag::FixedSize) { + viewportSize = CSSSize(mFixedViewportWidth, + mFixedViewportWidth * (float(mDisplaySize.height) / + mDisplaySize.width)); + } + return nsViewportInfo(mDefaultScale, mMinScale, mMaxScale, viewportSize, + mAutoSizeFlag, mAutoScaleFlag, mZoomFlag, + dom::ViewportFitType::Auto); + } + CSSToLayoutDeviceScale CSSToDevPixelScale() const { return mDeviceScale; } + float GetResolution() const { return mResolution; } + bool SubjectMatchesDocument(nsISupports* aSubject) const { return true; } + Maybe CalculateScrollableRectForRSF() const { + return Some(CSSRect(CSSPoint(), mContentSize)); + } + bool IsResolutionUpdatedByApz() const { return false; } + LayoutDeviceMargin ScrollbarAreaToExcludeFromCompositionBounds() const { + return LayoutDeviceMargin(); + } + Maybe GetContentViewerSize() const { + return Some(mDisplaySize); + } + bool AllowZoomingForDocument() const { return true; } + bool IsInReaderMode() const { return false; } + bool IsDocumentLoading() const { return false; } + + void SetResolutionAndScaleTo(float aResolution, + ResolutionChangeOrigin aOrigin) { + mResolution = aResolution; + mMVM->ResolutionUpdated(aOrigin); + } + void Reflow(const CSSSize& aNewSize) { + mICBSize = aNewSize; + mContentSize = mLayoutFunction(mICBSize); + } + + // Allow test code to modify the input metrics. + void SetMinScale(CSSToScreenScale aMinScale) { mMinScale = aMinScale; } + void SetMaxScale(CSSToScreenScale aMaxScale) { mMaxScale = aMaxScale; } + void SetInitialScale(CSSToScreenScale aInitialScale) { + mDefaultScale = aInitialScale; + mAutoScaleFlag = AutoScaleFlag::FixedScale; + } + void SetFixedViewportWidth(CSSCoord aWidth) { + mFixedViewportWidth = aWidth; + mAutoSizeFlag = AutoSizeFlag::FixedSize; + } + void SetDisplaySize(const LayoutDeviceIntSize& aNewDisplaySize) { + mDisplaySize = aNewDisplaySize; + } + void SetLayoutFunction(const LayoutFunction& aLayoutFunction) { + mLayoutFunction = aLayoutFunction; + } + + // Allow test code to query the output metrics. + CSSSize GetICBSize() const { return mICBSize; } + CSSSize GetContentSize() const { return mContentSize; } + + private: + // Input metrics, with some sensible defaults. + LayoutDeviceIntSize mDisplaySize{300, 600}; + CSSToScreenScale mDefaultScale{1.0f}; + CSSToScreenScale mMinScale{0.25f}; + CSSToScreenScale mMaxScale{10.0f}; + CSSToLayoutDeviceScale mDeviceScale{1.0f}; + CSSCoord mFixedViewportWidth; + AutoSizeFlag mAutoSizeFlag = AutoSizeFlag::AutoSize; + AutoScaleFlag mAutoScaleFlag = AutoScaleFlag::AutoScale; + ZoomFlag mZoomFlag = ZoomFlag::AllowZoom; + // As a default layout function, just set the content size to the ICB size. + LayoutFunction mLayoutFunction = [](CSSSize aICBSize) { return aICBSize; }; + + // Output metrics. + float mResolution = 1.0f; + CSSSize mICBSize; + CSSSize mContentSize; + + MobileViewportManager* mMVM = nullptr; +}; + +class MVMTester : public ::testing::Test { + public: + MVMTester() + : mMVMContext(new MockMVMContext()), + mMVM(new MobileViewportManager( + mMVMContext, + MobileViewportManager::ManagerType::VisualAndMetaViewport)) { + mMVMContext->SetMVM(mMVM.get()); + } + + void Resize(const LayoutDeviceIntSize& aNewDisplaySize) { + mMVMContext->SetDisplaySize(aNewDisplaySize); + mMVM->RequestReflow(false); + } + + protected: + RefPtr mMVMContext; + RefPtr mMVM; +}; + +TEST_F(MVMTester, ZoomBoundsRespectedAfterRotation_Bug1536755) { + // Set up initial conditions. + mMVMContext->SetDisplaySize(LayoutDeviceIntSize(600, 300)); + mMVMContext->SetInitialScale(CSSToScreenScale(1.0f)); + mMVMContext->SetMinScale(CSSToScreenScale(1.0f)); + mMVMContext->SetMaxScale(CSSToScreenScale(1.0f)); + // Set a layout function that simulates a page which is twice + // as tall as it is wide. + mMVMContext->SetLayoutFunction([](CSSSize aICBSize) { + return CSSSize(aICBSize.width, aICBSize.width * 2); + }); + + // Perform an initial viewport computation and reflow, and + // sanity-check the results. + mMVM->SetInitialViewport(); + EXPECT_EQ(CSSSize(600, 300), mMVMContext->GetICBSize()); + EXPECT_EQ(CSSSize(600, 1200), mMVMContext->GetContentSize()); + EXPECT_EQ(1.0f, mMVMContext->GetResolution()); + + // Now rotate the screen, and check that the minimum and maximum + // scales are still respected after the rotation. + Resize(LayoutDeviceIntSize(300, 600)); + EXPECT_EQ(CSSSize(300, 600), mMVMContext->GetICBSize()); + EXPECT_EQ(CSSSize(300, 600), mMVMContext->GetContentSize()); + EXPECT_EQ(1.0f, mMVMContext->GetResolution()); +} + +TEST_F(MVMTester, LandscapeToPortraitRotation_Bug1523844) { + // Set up initial conditions. + mMVMContext->SetDisplaySize(LayoutDeviceIntSize(300, 600)); + // Set a layout function that simulates a page with a fixed + // content size that's as wide as the screen in one orientation + // (and wider in the other). + mMVMContext->SetLayoutFunction( + [](CSSSize aICBSize) { return CSSSize(600, 1200); }); + + // Simulate a "DOMMetaAdded" event being fired before calling + // SetInitialViewport(). This matches what typically happens + // during real usage (the MVM receives the "DOMMetaAdded" + // before the "load", and it's the "load" that calls + // SetInitialViewport()), and is important to trigger this + // bug, because it causes the MVM to be stuck with an + // "mRestoreResolution" (prior to the fix). + mMVM->HandleDOMMetaAdded(); + + // Perform an initial viewport computation and reflow, and + // sanity-check the results. + mMVM->SetInitialViewport(); + EXPECT_EQ(CSSSize(300, 600), mMVMContext->GetICBSize()); + EXPECT_EQ(CSSSize(600, 1200), mMVMContext->GetContentSize()); + EXPECT_EQ(0.5f, mMVMContext->GetResolution()); + + // Rotate to landscape. + Resize(LayoutDeviceIntSize(600, 300)); + EXPECT_EQ(CSSSize(600, 300), mMVMContext->GetICBSize()); + EXPECT_EQ(CSSSize(600, 1200), mMVMContext->GetContentSize()); + EXPECT_EQ(1.0f, mMVMContext->GetResolution()); + + // Rotate back to portrait and check that we have returned + // to the portrait resolution. + Resize(LayoutDeviceIntSize(300, 600)); + EXPECT_EQ(CSSSize(300, 600), mMVMContext->GetICBSize()); + EXPECT_EQ(CSSSize(600, 1200), mMVMContext->GetContentSize()); + EXPECT_EQ(0.5f, mMVMContext->GetResolution()); +} diff --git a/gfx/layers/apz/test/gtest/mvm/moz.build b/gfx/layers/apz/test/gtest/mvm/moz.build new file mode 100644 index 0000000000..0fa985307b --- /dev/null +++ b/gfx/layers/apz/test/gtest/mvm/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES += [ + "TestMobileViewportManager.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" diff --git a/gfx/layers/apz/test/mochitest/FissionTestHelperChild.sys.mjs b/gfx/layers/apz/test/mochitest/FissionTestHelperChild.sys.mjs new file mode 100644 index 0000000000..c0695b7abb --- /dev/null +++ b/gfx/layers/apz/test/mochitest/FissionTestHelperChild.sys.mjs @@ -0,0 +1,157 @@ +// This code runs in the content process that holds the window to which +// this actor is attached. There is one instance of this class for each +// "inner window" (i.e. one per content document, including iframes/nested +// iframes). +// There is a 1:1 relationship between instances of this class and +// FissionTestHelperParent instances, and the pair are entangled such +// that they can communicate with each other regardless of which process +// they live in. + +export class FissionTestHelperChild extends JSWindowActorChild { + constructor() { + super(); + this._msgCounter = 0; + this._oopifResponsePromiseResolvers = []; + } + + cw() { + return this.contentWindow.wrappedJSObject; + } + + initialize() { + // This exports a bunch of things into the content window so that + // the test can access them. Most things are scoped inside the + // FissionTestHelper object on the window to avoid polluting the global + // namespace. + + let cw = this.cw(); + Cu.exportFunction( + (cond, msg) => this.sendAsyncMessage("ok", { cond, msg }), + cw, + { defineAs: "ok" } + ); + Cu.exportFunction( + (a, b, msg) => this.sendAsyncMessage("is", { a, b, msg }), + cw, + { defineAs: "is" } + ); + + let FissionTestHelper = Cu.createObjectIn(cw, { + defineAs: "FissionTestHelper", + }); + FissionTestHelper.startTestPromise = new cw.Promise( + Cu.exportFunction(resolve => { + this._startTestPromiseResolver = resolve; + }, cw) + ); + + Cu.exportFunction(this.subtestDone.bind(this), FissionTestHelper, { + defineAs: "subtestDone", + }); + + Cu.exportFunction(this.subtestFailed.bind(this), FissionTestHelper, { + defineAs: "subtestFailed", + }); + + Cu.exportFunction(this.sendToOopif.bind(this), FissionTestHelper, { + defineAs: "sendToOopif", + }); + Cu.exportFunction(this.fireEventInEmbedder.bind(this), FissionTestHelper, { + defineAs: "fireEventInEmbedder", + }); + } + + // Called by the subtest to indicate completion to the top-level browser-chrome + // mochitest. + subtestDone() { + let cw = this.cw(); + if (cw.ApzCleanup) { + cw.ApzCleanup.execute(); + } + this.sendAsyncMessage("Test:Complete", {}); + } + + // Called by the subtest to indicate subtest failure. Only one of subtestDone + // or subtestFailed should be called. + subtestFailed(msg) { + this.sendAsyncMessage("ok", { cond: false, msg }); + this.subtestDone(); + } + + // Called by the subtest to eval some code in the OOP iframe. This returns + // a promise that resolves to the return value from the eval. + sendToOopif(iframeElement, stringToEval) { + let browsingContextId = iframeElement.browsingContext.id; + let msgId = ++this._msgCounter; + let cw = this.cw(); + let responsePromise = new cw.Promise( + Cu.exportFunction(resolve => { + this._oopifResponsePromiseResolvers[msgId] = resolve; + }, cw) + ); + this.sendAsyncMessage("EmbedderToOopif", { + browsingContextId, + msgId, + stringToEval, + }); + return responsePromise; + } + + // Called by OOP iframes to dispatch an event in the embedder window. This + // can be used by the OOP iframe to asynchronously notify the embedder of + // things that happen. The embedder can use promiseOneEvent from + // helper_fission_utils.js to listen for these events. + fireEventInEmbedder(eventType, data) { + this.sendAsyncMessage("OopifToEmbedder", { eventType, data }); + } + + handleEvent(evt) { + switch (evt.type) { + case "FissionTestHelper:Init": + this.initialize(); + break; + } + } + + receiveMessage(msg) { + switch (msg.name) { + case "Test:Start": + this._startTestPromiseResolver(); + delete this._startTestPromiseResolver; + break; + case "FromEmbedder": + let evalResult = this.contentWindow.eval(msg.data.stringToEval); + this.sendAsyncMessage("OopifToEmbedder", { + msgId: msg.data.msgId, + evalResult, + }); + break; + case "FromOopif": + if (typeof msg.data.msgId == "number") { + if (!(msg.data.msgId in this._oopifResponsePromiseResolvers)) { + dump( + "Error: FromOopif got a message with unknown numeric msgId in " + + this.contentWindow.location.href + + "\n" + ); + } + this._oopifResponsePromiseResolvers[msg.data.msgId]( + msg.data.evalResult + ); + delete this._oopifResponsePromiseResolvers[msg.data.msgId]; + } else if (typeof msg.data.eventType == "string") { + let cw = this.cw(); + let event = new cw.Event(msg.data.eventType); + event.data = Cu.cloneInto(msg.data.data, cw); + this.contentWindow.dispatchEvent(event); + } else { + dump( + "Warning: Unrecognized FromOopif message received in " + + this.contentWindow.location.href + + "\n" + ); + } + break; + } + } +} diff --git a/gfx/layers/apz/test/mochitest/FissionTestHelperParent.sys.mjs b/gfx/layers/apz/test/mochitest/FissionTestHelperParent.sys.mjs new file mode 100644 index 0000000000..d71de3b2ad --- /dev/null +++ b/gfx/layers/apz/test/mochitest/FissionTestHelperParent.sys.mjs @@ -0,0 +1,103 @@ +// This code always runs in the parent process. There is one instance of +// this class for each "inner window" (should be one per content document, +// including iframes/nested iframes). +// There is a 1:1 relationship between instances of this class and +// FissionTestHelperChild instances, and the pair are entangled such +// that they can communicate with each other regardless of which process +// they live in. + +export class FissionTestHelperParent extends JSWindowActorParent { + constructor() { + super(); + this._testCompletePromise = new Promise(resolve => { + this._testCompletePromiseResolver = resolve; + }); + } + + embedderWindow() { + let embedder = this.manager.browsingContext.embedderWindowGlobal; + // embedder is of type WindowGlobalParent, defined in WindowGlobalActors.webidl + if (!embedder) { + dump("ERROR: no embedder found in FissionTestHelperParent\n"); + } + return embedder; + } + + docURI() { + return this.manager.documentURI.spec; + } + + // Returns a promise that is resolved when this parent actor receives a + // "Test:Complete" message from the child. + getTestCompletePromise() { + return this._testCompletePromise; + } + + startTest() { + this.sendAsyncMessage("Test:Start", {}); + } + + receiveMessage(msg) { + switch (msg.name) { + case "ok": + FissionTestHelperParent.SimpleTest.ok( + msg.data.cond, + this.docURI() + " | " + msg.data.msg + ); + break; + + case "is": + FissionTestHelperParent.SimpleTest.is( + msg.data.a, + msg.data.b, + this.docURI() + " | " + msg.data.msg + ); + break; + + case "Test:Complete": + this._testCompletePromiseResolver(); + break; + + case "EmbedderToOopif": + // This relays messages from the embedder to an OOP-iframe. The browsing + // context id in the message data identifies the OOP-iframe. + let oopifBrowsingContext = BrowsingContext.get( + msg.data.browsingContextId + ); + if (oopifBrowsingContext == null) { + FissionTestHelperParent.SimpleTest.ok( + false, + "EmbedderToOopif couldn't find oopif" + ); + break; + } + let oopifActor = + oopifBrowsingContext.currentWindowGlobal.getActor( + "FissionTestHelper" + ); + if (!oopifActor) { + FissionTestHelperParent.SimpleTest.ok( + false, + "EmbedderToOopif couldn't find oopif actor" + ); + break; + } + oopifActor.sendAsyncMessage("FromEmbedder", msg.data); + break; + + case "OopifToEmbedder": + // This relays messages from the OOP-iframe to the top-level content + // window which is embedding it. + let embedderActor = this.embedderWindow().getActor("FissionTestHelper"); + if (!embedderActor) { + FissionTestHelperParent.SimpleTest.ok( + false, + "OopifToEmbedder couldn't find embedder" + ); + break; + } + embedderActor.sendAsyncMessage("FromOopif", msg.data); + break; + } + } +} diff --git a/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js b/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js new file mode 100644 index 0000000000..1b1ae8db26 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js @@ -0,0 +1,1881 @@ +// ownerGlobal isn't defined in content privileged windows. +/* eslint-disable mozilla/use-ownerGlobal */ + +// Utilities for synthesizing of native events. + +async function getResolution() { + let resolution = -1; // bogus value in case DWU fails us + // Use window.top to get the root content window which is what has + // the resolution. + resolution = await SpecialPowers.spawn(window.top, [], () => { + return SpecialPowers.getDOMWindowUtils(content.window).getResolution(); + }); + return resolution; +} + +function getPlatform() { + if (navigator.platform.indexOf("Win") == 0) { + return "windows"; + } + if (navigator.platform.indexOf("Mac") == 0) { + return "mac"; + } + // Check for Android before Linux + if (navigator.appVersion.includes("Android")) { + return "android"; + } + if (navigator.platform.indexOf("Linux") == 0) { + return "linux"; + } + return "unknown"; +} + +function nativeVerticalWheelEventMsg() { + switch (getPlatform()) { + case "windows": + return 0x020a; // WM_MOUSEWHEEL + case "mac": + var useWheelCodepath = SpecialPowers.getBoolPref( + "apz.test.mac.synth_wheel_input", + false + ); + // Default to 1 (kCGScrollPhaseBegan) to trigger PanGestureInput events + // from widget code. Allow setting a pref to override this behaviour and + // trigger ScrollWheelInput events instead. + return useWheelCodepath ? 0 : 1; + case "linux": + return 4; // value is unused, pass GDK_SCROLL_SMOOTH anyway + } + throw new Error( + "Native wheel events not supported on platform " + getPlatform() + ); +} + +function nativeHorizontalWheelEventMsg() { + switch (getPlatform()) { + case "windows": + return 0x020e; // WM_MOUSEHWHEEL + case "mac": + return 0; // value is unused, can be anything + case "linux": + return 4; // value is unused, pass GDK_SCROLL_SMOOTH anyway + } + throw new Error( + "Native wheel events not supported on platform " + getPlatform() + ); +} + +function nativeArrowDownKey() { + switch (getPlatform()) { + case "windows": + return WIN_VK_DOWN; + case "mac": + return MAC_VK_DownArrow; + } + throw new Error( + "Native key events not supported on platform " + getPlatform() + ); +} + +function nativeArrowUpKey() { + switch (getPlatform()) { + case "windows": + return WIN_VK_UP; + case "mac": + return MAC_VK_UpArrow; + } + throw new Error( + "Native key events not supported on platform " + getPlatform() + ); +} + +function targetIsWindow(aTarget) { + return aTarget.Window && aTarget instanceof aTarget.Window; +} + +function targetIsTopWindow(aTarget) { + if (!targetIsWindow(aTarget)) { + return false; + } + return aTarget == aTarget.top; +} + +// Given an event target which may be a window or an element, get the associated window. +function windowForTarget(aTarget) { + if (targetIsWindow(aTarget)) { + return aTarget; + } + return aTarget.ownerDocument.defaultView; +} + +// Given an event target which may be a window or an element, get the associated element. +function elementForTarget(aTarget) { + if (targetIsWindow(aTarget)) { + return aTarget.document.documentElement; + } + return aTarget; +} + +// Given an event target which may be a window or an element, get the associatd nsIDOMWindowUtils. +function utilsForTarget(aTarget) { + return SpecialPowers.getDOMWindowUtils(windowForTarget(aTarget)); +} + +// Given a pixel scrolling delta, converts it to the platform's native units. +function nativeScrollUnits(aTarget, aDimen) { + switch (getPlatform()) { + case "linux": { + // GTK deltas are treated as line height divided by 3 by gecko. + var targetWindow = windowForTarget(aTarget); + var targetElement = elementForTarget(aTarget); + var lineHeight = + targetWindow.getComputedStyle(targetElement)["font-size"]; + return aDimen / (parseInt(lineHeight) * 3); + } + } + return aDimen; +} + +function parseNativeModifiers(aModifiers, aWindow = window) { + let modifiers = 0; + if (aModifiers.capsLockKey) { + modifiers |= SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_CAPS_LOCK; + } + if (aModifiers.numLockKey) { + modifiers |= SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_NUM_LOCK; + } + if (aModifiers.shiftKey) { + modifiers |= SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_SHIFT_LEFT; + } + if (aModifiers.shiftRightKey) { + modifiers |= SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_SHIFT_RIGHT; + } + if (aModifiers.ctrlKey) { + modifiers |= + SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_CONTROL_LEFT; + } + if (aModifiers.ctrlRightKey) { + modifiers |= + SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_CONTROL_RIGHT; + } + if (aModifiers.altKey) { + modifiers |= SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_ALT_LEFT; + } + if (aModifiers.altRightKey) { + modifiers |= SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_ALT_RIGHT; + } + if (aModifiers.metaKey) { + modifiers |= + SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_COMMAND_LEFT; + } + if (aModifiers.metaRightKey) { + modifiers |= + SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_COMMAND_RIGHT; + } + if (aModifiers.helpKey) { + modifiers |= SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_HELP; + } + if (aModifiers.fnKey) { + modifiers |= SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_FUNCTION; + } + if (aModifiers.numericKeyPadKey) { + modifiers |= + SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_NUMERIC_KEY_PAD; + } + + if (aModifiers.accelKey) { + modifiers |= _EU_isMac(aWindow) + ? SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_COMMAND_LEFT + : SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_CONTROL_LEFT; + } + if (aModifiers.accelRightKey) { + modifiers |= _EU_isMac(aWindow) + ? SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_COMMAND_RIGHT + : SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_CONTROL_RIGHT; + } + if (aModifiers.altGrKey) { + modifiers |= _EU_isMac(aWindow) + ? SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_ALT_LEFT + : SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_ALT_GRAPH; + } + return modifiers; +} + +// Several event sythesization functions below (and their helpers) take a "target" +// parameter which may be either an element or a window. For such functions, +// the target's "bounding rect" refers to the bounding client rect for an element, +// and the window's origin for a window. +// Not all functions have been "upgraded" to allow a window argument yet; feel +// free to upgrade others as necessary. + +// Get the origin of |aTarget| relative to the root content document's +// visual viewport in CSS coordinates. +// |aTarget| may be an element (contained in the root content document or +// a subdocument) or, as a special case, the root content window. +// FIXME: Support iframe windows as targets. +function _getTargetRect(aTarget) { + let rect = { left: 0, top: 0, width: 0, height: 0 }; + + // If the target is the root content window, its origin relative + // to the visual viewport is (0, 0). + if (aTarget instanceof Window) { + return rect; + } + if (aTarget.Window && aTarget instanceof aTarget.Window) { + // iframe window + // FIXME: Compute proper rect against the root content window + return rect; + } + + // Otherwise, we have an element. Start with the origin of + // its bounding client rect which is relative to the enclosing + // document's layout viewport. Note that for iframes, the + // layout viewport is also the visual viewport. + const boundingClientRect = aTarget.getBoundingClientRect(); + rect.left = boundingClientRect.left; + rect.top = boundingClientRect.top; + rect.width = boundingClientRect.width; + rect.height = boundingClientRect.height; + + // Iterate up the window hierarchy until we reach the root + // content window, adding the offsets of any iframe windows + // relative to their parent window. + while (aTarget.ownerDocument.defaultView.frameElement) { + const iframe = aTarget.ownerDocument.defaultView.frameElement; + // The offset of the iframe window relative to the parent window + // includes the iframe's border, and the iframe's origin in its + // containing document. + const style = iframe.ownerDocument.defaultView.getComputedStyle(iframe); + const borderLeft = parseFloat(style.borderLeftWidth) || 0; + const borderTop = parseFloat(style.borderTopWidth) || 0; + const borderRight = parseFloat(style.borderRightWidth) || 0; + const borderBottom = parseFloat(style.borderBottomWidth) || 0; + const paddingLeft = parseFloat(style.paddingLeft) || 0; + const paddingTop = parseFloat(style.paddingTop) || 0; + const paddingRight = parseFloat(style.paddingRight) || 0; + const paddingBottom = parseFloat(style.paddingBottom) || 0; + const iframeRect = iframe.getBoundingClientRect(); + rect.left += iframeRect.left + borderLeft + paddingLeft; + rect.top += iframeRect.top + borderTop + paddingTop; + if ( + rect.left + rect.width > + iframeRect.right - borderRight - paddingRight + ) { + rect.width = Math.max( + iframeRect.right - borderRight - paddingRight - rect.left, + 0 + ); + } + if ( + rect.top + rect.height > + iframeRect.bottom - borderBottom - paddingBottom + ) { + rect.height = Math.max( + iframeRect.bottom - borderBottom - paddingBottom - rect.top, + 0 + ); + } + aTarget = iframe; + } + + return rect; +} + +// Returns the in-process root window for the given |aWindow|. +function getInProcessRootWindow(aWindow) { + let window = aWindow; + while (window.frameElement) { + window = window.frameElement.ownerDocument.defaultView; + } + return window; +} + +// Convert (offsetX, offsetY) of target or center of it, in CSS pixels to device +// pixels relative to the screen. +// TODO: this function currently does not incorporate some CSS transforms on +// elements enclosing target, e.g. scale transforms. +async function coordinatesRelativeToScreen(aParams) { + const { + target, // The target element or window + offsetX, // X offset relative to `target` + offsetY, // Y offset relative to `target` + atCenter, // Instead of offsetX/offsetY, return center of `target` + } = aParams; + // Note that |window| might not be the root content window, for two + // possible reasons: + // 1. The mochitest that's calling into this function is not using a mechanism + // like runSubtestsSeriallyInFreshWindows() to load the test page in + // a top-level context, so it's loaded into an iframe by the mochitest + // harness. + // 2. The mochitest itself creates an iframe and calls this function from + // script running in the context of the iframe. + // Since the resolution applies to the top level content document, below we + // use the mozInnerScreen{X,Y} of the top level content window (window.top) + // only for the case where this function gets called in the top level content + // document. In other cases we use nsIDOMWindowUtils.toScreenRect(). + + // We do often specify `window` as the target, if it's the top level window, + // `nsIDOMWindowUtils.toScreenRect` isn't suitable because the function is + // supposed to be called with values in the document coords, so for example + // if desktop zoom is being applied, (0, 0) in the document coords might be + // outside of the visual viewport, i.e. it's going to be negative with the + // `toScreenRect` conversion, whereas the call sites with `window` of this + // function expect (0, 0) position should be the visual viport's offset. So + // in such cases we simply use mozInnerScreen{X,Y} to convert the given value + // to the screen coords. + if (target instanceof Window && window.parent == window) { + const resolution = await getResolution(); + const deviceScale = window.devicePixelRatio; + return { + x: + window.mozInnerScreenX * deviceScale + + (atCenter ? 0 : offsetX) * resolution * deviceScale, + y: + window.mozInnerScreenY * deviceScale + + (atCenter ? 0 : offsetY) * resolution * deviceScale, + }; + } + + const rect = _getTargetRect(target); + + const utils = SpecialPowers.getDOMWindowUtils(getInProcessRootWindow(window)); + const positionInScreenCoords = utils.toScreenRect( + rect.left + (atCenter ? rect.width / 2 : offsetX), + rect.top + (atCenter ? rect.height / 2 : offsetY), + 0, + 0 + ); + + return { + x: positionInScreenCoords.x, + y: positionInScreenCoords.y, + }; +} + +// Get the bounding box of aElement, and return it in device pixels +// relative to the screen. +// TODO: This function should probably take into account the resolution and +// the relative viewport rect like coordinatesRelativeToScreen() does. +function rectRelativeToScreen(aElement) { + var targetWindow = aElement.ownerDocument.defaultView; + var scale = targetWindow.devicePixelRatio; + var rect = aElement.getBoundingClientRect(); + return { + x: (targetWindow.mozInnerScreenX + rect.left) * scale, + y: (targetWindow.mozInnerScreenY + rect.top) * scale, + width: rect.width * scale, + height: rect.height * scale, + }; +} + +// Synthesizes a native mousewheel event and returns immediately. This does not +// guarantee anything; you probably want to use one of the other functions below +// which actually wait for results. +// aX and aY are relative to the top-left of |aTarget|'s bounding rect. +// aDeltaX and aDeltaY are pixel deltas, and aObserver can be left undefined +// if not needed. +async function synthesizeNativeWheel( + aTarget, + aX, + aY, + aDeltaX, + aDeltaY, + aObserver +) { + var pt = await coordinatesRelativeToScreen({ + offsetX: aX, + offsetY: aY, + target: aTarget, + }); + if (aDeltaX && aDeltaY) { + throw new Error( + "Simultaneous wheeling of horizontal and vertical is not supported on all platforms." + ); + } + aDeltaX = nativeScrollUnits(aTarget, aDeltaX); + aDeltaY = nativeScrollUnits(aTarget, aDeltaY); + var msg = aDeltaX + ? nativeHorizontalWheelEventMsg() + : nativeVerticalWheelEventMsg(); + var utils = utilsForTarget(aTarget); + var element = elementForTarget(aTarget); + utils.sendNativeMouseScrollEvent( + pt.x, + pt.y, + msg, + aDeltaX, + aDeltaY, + 0, + 0, + // Specify MOUSESCROLL_SCROLL_LINES if the test wants to run through wheel + // input code path on Mac since it's normal mouse wheel inputs. + SpecialPowers.getBoolPref("apz.test.mac.synth_wheel_input", false) + ? SpecialPowers.DOMWindowUtils.MOUSESCROLL_SCROLL_LINES + : 0, + element, + aObserver + ); + return true; +} + +// Synthesizes a native pan gesture event and returns immediately. +// NOTE: This works only on Mac. +// You can specify kCGScrollPhaseBegan = 1, kCGScrollPhaseChanged = 2 and +// kCGScrollPhaseEnded = 4 for |aPhase|. +async function synthesizeNativePanGestureEvent( + aTarget, + aX, + aY, + aDeltaX, + aDeltaY, + aPhase, + aObserver +) { + if (getPlatform() != "mac") { + throw new Error( + `synthesizeNativePanGestureEvent doesn't work on ${getPlatform()}` + ); + } + + var pt = await coordinatesRelativeToScreen({ + offsetX: aX, + offsetY: aY, + target: aTarget, + }); + if (aDeltaX && aDeltaY) { + throw new Error( + "Simultaneous panning of horizontal and vertical is not supported." + ); + } + + aDeltaX = nativeScrollUnits(aTarget, aDeltaX); + aDeltaY = nativeScrollUnits(aTarget, aDeltaY); + + var element = elementForTarget(aTarget); + var utils = utilsForTarget(aTarget); + utils.sendNativeMouseScrollEvent( + pt.x, + pt.y, + aPhase, + aDeltaX, + aDeltaY, + 0 /* deltaZ */, + 0 /* modifiers */, + 0 /* scroll event unit pixel */, + element, + aObserver + ); + + return true; +} + +// Sends a native touchpad pan event and resolve the returned promise once the +// request has been successfully made to the OS. +// NOTE: This works only on Windows and Linux. +// You can specify nsIDOMWindowUtils.PHASE_BEGIN, PHASE_UPDATE and PHASE_END +// for |aPhase|. +async function promiseNativeTouchpadPanEventAndWaitForObserver( + aTarget, + aX, + aY, + aDeltaX, + aDeltaY, + aPhase +) { + if (getPlatform() != "windows" && getPlatform() != "linux") { + throw new Error( + `promiseNativeTouchpadPanEventAndWaitForObserver doesn't work on ${getPlatform()}` + ); + } + + let pt = await coordinatesRelativeToScreen({ + offsetX: aX, + offsetY: aY, + target: aTarget, + }); + + const utils = utilsForTarget(aTarget); + + return new Promise(resolve => { + var observer = { + observe(aSubject, aTopic, aData) { + if (aTopic == "touchpadpanevent") { + resolve(); + } + }, + }; + + utils.sendNativeTouchpadPan( + aPhase, + pt.x, + pt.y, + aDeltaX, + aDeltaY, + 0, + observer + ); + }); +} + +async function synthesizeSimpleGestureEvent( + aElement, + aType, + aX, + aY, + aDirection, + aDelta, + aModifiers, + aClickCount +) { + let pt = await coordinatesRelativeToScreen({ + offsetX: aX, + offsetY: aY, + target: aElement, + }); + + let utils = utilsForTarget(aElement); + utils.sendSimpleGestureEvent( + aType, + pt.x, + pt.y, + aDirection, + aDelta, + aModifiers, + aClickCount + ); +} + +// Synthesizes a native pan gesture event and resolve the returned promise once the +// request has been successfully made to the OS. +function promiseNativePanGestureEventAndWaitForObserver( + aElement, + aX, + aY, + aDeltaX, + aDeltaY, + aPhase +) { + return new Promise(resolve => { + var observer = { + observe(aSubject, aTopic, aData) { + if (aTopic == "mousescrollevent") { + resolve(); + } + }, + }; + synthesizeNativePanGestureEvent( + aElement, + aX, + aY, + aDeltaX, + aDeltaY, + aPhase, + observer + ); + }); +} + +// Synthesizes a native mousewheel event and resolve the returned promise once the +// request has been successfully made to the OS. This does not necessarily +// guarantee that the OS generates the event we requested. See +// synthesizeNativeWheel for details on the parameters. +function promiseNativeWheelAndWaitForObserver( + aElement, + aX, + aY, + aDeltaX, + aDeltaY +) { + return new Promise(resolve => { + var observer = { + observe(aSubject, aTopic, aData) { + if (aTopic == "mousescrollevent") { + resolve(); + } + }, + }; + synthesizeNativeWheel(aElement, aX, aY, aDeltaX, aDeltaY, observer); + }); +} + +// Synthesizes a native mousewheel event and resolve the returned promise once the +// wheel event is dispatched to |aTarget|'s containing window. If the event +// targets content in a subdocument, |aTarget| should be inside the +// subdocument (or the subdocument's window). See synthesizeNativeWheel for +// details on the other parameters. +function promiseNativeWheelAndWaitForWheelEvent( + aTarget, + aX, + aY, + aDeltaX, + aDeltaY +) { + return new Promise((resolve, reject) => { + var targetWindow = windowForTarget(aTarget); + targetWindow.addEventListener( + "wheel", + function (e) { + setTimeout(resolve, 0); + }, + { once: true } + ); + try { + synthesizeNativeWheel(aTarget, aX, aY, aDeltaX, aDeltaY); + } catch (e) { + reject(e); + } + }); +} + +// Synthesizes a native mousewheel event and resolves the returned promise once the +// first resulting scroll event is dispatched to |aTarget|'s containing window. +// If the event targets content in a subdocument, |aTarget| should be inside +// the subdocument (or the subdocument's window). See synthesizeNativeWheel +// for details on the other parameters. +function promiseNativeWheelAndWaitForScrollEvent( + aTarget, + aX, + aY, + aDeltaX, + aDeltaY +) { + return new Promise((resolve, reject) => { + var targetWindow = windowForTarget(aTarget); + targetWindow.addEventListener( + "scroll", + function () { + setTimeout(resolve, 0); + }, + { capture: true, once: true } + ); // scroll events don't always bubble + try { + synthesizeNativeWheel(aTarget, aX, aY, aDeltaX, aDeltaY); + } catch (e) { + reject(e); + } + }); +} + +async function synthesizeTouchpadPinch(scales, focusX, focusY, options) { + var scalesAndFoci = []; + + for (let i = 0; i < scales.length; i++) { + scalesAndFoci.push([scales[i], focusX, focusY]); + } + + await synthesizeTouchpadGesture(scalesAndFoci, options); +} + +// scalesAndFoci is an array of [scale, focusX, focuxY] tuples. +async function synthesizeTouchpadGesture(scalesAndFoci, options) { + // Check for options, fill in defaults if appropriate. + let waitForTransformEnd = + options.waitForTransformEnd !== undefined + ? options.waitForTransformEnd + : true; + let waitForFrames = + options.waitForFrames !== undefined ? options.waitForFrames : false; + + // Register the listener for the TransformEnd observer topic + let transformEndPromise = promiseTransformEnd(); + + var modifierFlags = 0; + var utils = utilsForTarget(document.body); + for (let i = 0; i < scalesAndFoci.length; i++) { + var pt = await coordinatesRelativeToScreen({ + offsetX: scalesAndFoci[i][1], + offsetY: scalesAndFoci[i][2], + target: document.body, + }); + var phase; + if (i === 0) { + phase = SpecialPowers.DOMWindowUtils.PHASE_BEGIN; + } else if (i === scalesAndFoci.length - 1) { + phase = SpecialPowers.DOMWindowUtils.PHASE_END; + } else { + phase = SpecialPowers.DOMWindowUtils.PHASE_UPDATE; + } + utils.sendNativeTouchpadPinch( + phase, + scalesAndFoci[i][0], + pt.x, + pt.y, + modifierFlags + ); + if (waitForFrames) { + await promiseFrame(); + } + } + + // Wait for TransformEnd to fire. + if (waitForTransformEnd) { + await transformEndPromise; + } +} + +async function synthesizeTouchpadPan( + focusX, + focusY, + deltaXs, + deltaYs, + options +) { + // Check for options, fill in defaults if appropriate. + let waitForTransformEnd = + options.waitForTransformEnd !== undefined + ? options.waitForTransformEnd + : true; + let waitForFrames = + options.waitForFrames !== undefined ? options.waitForFrames : false; + + // Register the listener for the TransformEnd observer topic + let transformEndPromise = promiseTransformEnd(); + + var modifierFlags = 0; + var pt = await coordinatesRelativeToScreen({ + offsetX: focusX, + offsetY: focusY, + target: document.body, + }); + var utils = utilsForTarget(document.body); + for (let i = 0; i < deltaXs.length; i++) { + var phase; + if (i === 0) { + phase = SpecialPowers.DOMWindowUtils.PHASE_BEGIN; + } else if (i === deltaXs.length - 1) { + phase = SpecialPowers.DOMWindowUtils.PHASE_END; + } else { + phase = SpecialPowers.DOMWindowUtils.PHASE_UPDATE; + } + utils.sendNativeTouchpadPan( + phase, + pt.x, + pt.y, + deltaXs[i], + deltaYs[i], + modifierFlags + ); + if (waitForFrames) { + await promiseFrame(); + } + } + + // Wait for TransformEnd to fire. + if (waitForTransformEnd) { + await transformEndPromise; + } +} + +// Synthesizes a native touch event and dispatches it. aX and aY in CSS pixels +// relative to the top-left of |aTarget|'s bounding rect. +async function synthesizeNativeTouch( + aTarget, + aX, + aY, + aType, + aObserver = null, + aTouchId = 0 +) { + var pt = await coordinatesRelativeToScreen({ + offsetX: aX, + offsetY: aY, + target: aTarget, + }); + var utils = utilsForTarget(aTarget); + utils.sendNativeTouchPoint(aTouchId, aType, pt.x, pt.y, 1, 90, aObserver); + return true; +} + +function sendBasicNativePointerInput( + utils, + aId, + aPointerType, + aState, + aX, + aY, + aObserver, + { pressure = 1, twist = 0, tiltX = 0, tiltY = 0, button = 0 } = {} +) { + switch (aPointerType) { + case "touch": + utils.sendNativeTouchPoint(aId, aState, aX, aY, pressure, 90, aObserver); + break; + case "pen": + utils.sendNativePenInput( + aId, + aState, + aX, + aY, + pressure, + twist, + tiltX, + tiltY, + button, + aObserver + ); + break; + default: + throw new Error(`Not supported: ${aPointerType}`); + } +} + +async function promiseNativePointerInput( + aTarget, + aPointerType, + aState, + aX, + aY, + options +) { + const pt = await coordinatesRelativeToScreen({ + offsetX: aX, + offsetY: aY, + target: aTarget, + }); + const utils = utilsForTarget(aTarget); + return new Promise(resolve => { + sendBasicNativePointerInput( + utils, + options?.pointerId ?? 0, + aPointerType, + aState, + pt.x, + pt.y, + resolve, + options + ); + }); +} + +/** + * Function to generate native pointer events as a sequence. + * @param aTarget is the element or window whose bounding rect the coordinates are + * relative to. + * @param aPointerType "touch" or "pen". + * @param aPositions is a 2D array of position data. It is indexed as [row][column], + * where advancing the row counter moves forward in time, and each column + * represents a single pointer. Each row must have exactly + * the same number of columns, and the number of columns must match the length + * of the aPointerIds parameter. + * For each row, each entry is either an object with x and y fields, + * or a null. A null value indicates that the pointer should be "lifted" + * (i.e. send a touchend for that touch input). A non-null value therefore + * indicates the position of the pointer input. + * This function takes care of the state tracking necessary to send + * pointerup/pointerdown inputs as necessary as the pointers go up and down. + * @param aObserver is the observer that will get registered on the very last + * native pointer synthesis call this function makes. + * @param aPointerIds is an array holding the pointer ID values. + */ +async function synthesizeNativePointerSequences( + aTarget, + aPointerType, + aPositions, + aObserver = null, + aPointerIds = [0], + options +) { + // We use lastNonNullValue to figure out which synthesizeNativeTouch call + // will be the last one we make, so that we can register aObserver on it. + var lastNonNullValue = -1; + for (let i = 0; i < aPositions.length; i++) { + if (aPositions[i] == null) { + throw new Error(`aPositions[${i}] was unexpectedly null`); + } + if (aPositions[i].length != aPointerIds.length) { + throw new Error( + `aPositions[${i}] did not have the expected number of positions; ` + + `expected ${aPointerIds.length} pointers but found ${aPositions[i].length}` + ); + } + for (let j = 0; j < aPointerIds.length; j++) { + if (aPositions[i][j] != null) { + lastNonNullValue = i * aPointerIds.length + j; + // Do the conversion to screen space before actually synthesizing + // the events, otherwise the screen space may change as a result of + // the touch inputs and the conversion may not work as intended. + aPositions[i][j] = await coordinatesRelativeToScreen({ + offsetX: aPositions[i][j].x, + offsetY: aPositions[i][j].y, + target: aTarget, + }); + } + } + } + if (lastNonNullValue < 0) { + throw new Error("All values in positions array were null!"); + } + + // Insert a row of nulls at the end of aPositions, to ensure that all + // touches get removed. If the touches have already been removed this will + // just add an extra no-op iteration in the aPositions loop below. + var allNullRow = new Array(aPointerIds.length); + allNullRow.fill(null); + aPositions.push(allNullRow); + + // The last sendNativeTouchPoint call will be the TOUCH_REMOVE which happens + // one iteration of aPosition after the last non-null value. + var lastSynthesizeCall = lastNonNullValue + aPointerIds.length; + + // track which touches are down and which are up. start with all up + var currentPositions = new Array(aPointerIds.length); + currentPositions.fill(null); + + var utils = utilsForTarget(aTarget); + // Iterate over the position data now, and generate the touches requested + for (let i = 0; i < aPositions.length; i++) { + for (let j = 0; j < aPointerIds.length; j++) { + if (aPositions[i][j] == null) { + // null means lift the finger + if (currentPositions[j] == null) { + // it's already lifted, do nothing + } else { + // synthesize the touch-up. If this is the last call we're going to + // make, pass the observer as well + var thisIndex = i * aPointerIds.length + j; + var observer = lastSynthesizeCall == thisIndex ? aObserver : null; + sendBasicNativePointerInput( + utils, + aPointerIds[j], + aPointerType, + SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, + currentPositions[j].x, + currentPositions[j].y, + observer, + options + ); + currentPositions[j] = null; + } + } else { + sendBasicNativePointerInput( + utils, + aPointerIds[j], + aPointerType, + SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, + aPositions[i][j].x, + aPositions[i][j].y, + null, + options + ); + currentPositions[j] = aPositions[i][j]; + } + } + } + return true; +} + +async function synthesizeNativeTouchSequences( + aTarget, + aPositions, + aObserver = null, + aTouchIds = [0] +) { + await synthesizeNativePointerSequences( + aTarget, + "touch", + aPositions, + aObserver, + aTouchIds + ); +} + +async function synthesizeNativePointerDrag( + aTarget, + aPointerType, + aX, + aY, + aDeltaX, + aDeltaY, + aObserver = null, + aPointerId = 0, + options +) { + var steps = Math.max(Math.abs(aDeltaX), Math.abs(aDeltaY)); + var positions = [[{ x: aX, y: aY }]]; + for (var i = 1; i < steps; i++) { + var dx = i * (aDeltaX / steps); + var dy = i * (aDeltaY / steps); + var pos = { x: aX + dx, y: aY + dy }; + positions.push([pos]); + } + positions.push([{ x: aX + aDeltaX, y: aY + aDeltaY }]); + return synthesizeNativePointerSequences( + aTarget, + aPointerType, + positions, + aObserver, + [aPointerId], + options + ); +} + +// Note that when calling this function you'll want to make sure that the pref +// "apz.touch_start_tolerance" is set to 0, or some of the touchmove will get +// consumed to overcome the panning threshold. +async function synthesizeNativeTouchDrag( + aTarget, + aX, + aY, + aDeltaX, + aDeltaY, + aObserver = null, + aTouchId = 0 +) { + return synthesizeNativePointerDrag( + aTarget, + "touch", + aX, + aY, + aDeltaX, + aDeltaY, + aObserver, + aTouchId + ); +} + +function promiseNativePointerDrag( + aTarget, + aPointerType, + aX, + aY, + aDeltaX, + aDeltaY, + aPointerId = 0, + options +) { + return new Promise(resolve => { + synthesizeNativePointerDrag( + aTarget, + aPointerType, + aX, + aY, + aDeltaX, + aDeltaY, + resolve, + aPointerId, + options + ); + }); +} + +// Promise-returning variant of synthesizeNativeTouchDrag +function promiseNativeTouchDrag( + aTarget, + aX, + aY, + aDeltaX, + aDeltaY, + aTouchId = 0 +) { + return new Promise(resolve => { + synthesizeNativeTouchDrag( + aTarget, + aX, + aY, + aDeltaX, + aDeltaY, + resolve, + aTouchId + ); + }); +} + +// Tapping is essentially a dragging with no move +function promiseNativePointerTap(aTarget, aPointerType, aX, aY, options) { + return promiseNativePointerDrag( + aTarget, + aPointerType, + aX, + aY, + 0, + 0, + options?.pointerId ?? 0, + options + ); +} + +async function synthesizeNativeTap(aTarget, aX, aY, aObserver = null) { + var pt = await coordinatesRelativeToScreen({ + offsetX: aX, + offsetY: aY, + target: aTarget, + }); + let utils = utilsForTarget(aTarget); + utils.sendNativeTouchTap(pt.x, pt.y, false, aObserver); + return true; +} + +// only currently implemented on macOS +async function synthesizeNativeTouchpadDoubleTap(aTarget, aX, aY) { + ok( + getPlatform() == "mac", + "only implemented on mac. implement sendNativeTouchpadDoubleTap for this platform," + + " see bug 1696802 for how it was done on macOS" + ); + let pt = await coordinatesRelativeToScreen({ + offsetX: aX, + offsetY: aY, + target: aTarget, + }); + let utils = utilsForTarget(aTarget); + utils.sendNativeTouchpadDoubleTap(pt.x, pt.y, 0); + return true; +} + +// If the event targets content in a subdocument, |aTarget| should be inside the +// subdocument (or the subdocument window). +async function synthesizeNativeMouseEventWithAPZ(aParams, aObserver = null) { + if (aParams.win !== undefined) { + throw Error( + "Are you trying to use EventUtils' API? `win` won't be used with synthesizeNativeMouseClickWithAPZ." + ); + } + if (aParams.scale !== undefined) { + throw Error( + "Are you trying to use EventUtils' API? `scale` won't be used with synthesizeNativeMouseClickWithAPZ." + ); + } + if (aParams.elementOnWidget !== undefined) { + throw Error( + "Are you trying to use EventUtils' API? `elementOnWidget` won't be used with synthesizeNativeMouseClickWithAPZ." + ); + } + const { + type, // "click", "mousedown", "mouseup" or "mousemove" + target, // Origin of offsetX and offsetY, must be an element + offsetX, // X offset in `target` in CSS Pixels + offsetY, // Y offset in `target` in CSS pixels + atCenter, // Instead of offsetX/Y, synthesize the event at center of `target` + screenX, // X offset in screen in device pixels, offsetX/Y nor atCenter must not be set if this is set + screenY, // Y offset in screen in device pixels, offsetX/Y nor atCenter must not be set if this is set + button = 0, // if "click", "mousedown", "mouseup", set same value as DOM MouseEvent.button + modifiers = {}, // Active modifiers, see `parseNativeModifiers` + } = aParams; + if (atCenter) { + if (offsetX != undefined || offsetY != undefined) { + throw Error( + `atCenter is specified, but offsetX (${offsetX}) and/or offsetY (${offsetY}) are also specified` + ); + } + if (screenX != undefined || screenY != undefined) { + throw Error( + `atCenter is specified, but screenX (${screenX}) and/or screenY (${screenY}) are also specified` + ); + } + } else if (offsetX != undefined && offsetY != undefined) { + if (screenX != undefined || screenY != undefined) { + throw Error( + `offsetX/Y are specified, but screenX (${screenX}) and/or screenY (${screenY}) are also specified` + ); + } + } else if (screenX != undefined && screenY != undefined) { + if (offsetX != undefined || offsetY != undefined) { + throw Error( + `screenX/Y are specified, but offsetX (${offsetX}) and/or offsetY (${offsetY}) are also specified` + ); + } + } + const pt = await (async () => { + if (screenX != undefined) { + return { x: screenX, y: screenY }; + } + return coordinatesRelativeToScreen({ + offsetX, + offsetY, + atCenter, + target, + }); + })(); + const utils = utilsForTarget(target); + const element = elementForTarget(target); + const modifierFlags = parseNativeModifiers(modifiers); + if (type === "click") { + utils.sendNativeMouseEvent( + pt.x, + pt.y, + utils.NATIVE_MOUSE_MESSAGE_BUTTON_DOWN, + button, + modifierFlags, + element, + function () { + utils.sendNativeMouseEvent( + pt.x, + pt.y, + utils.NATIVE_MOUSE_MESSAGE_BUTTON_UP, + button, + modifierFlags, + element, + aObserver + ); + } + ); + return; + } + + utils.sendNativeMouseEvent( + pt.x, + pt.y, + (() => { + switch (type) { + case "mousedown": + return utils.NATIVE_MOUSE_MESSAGE_BUTTON_DOWN; + case "mouseup": + return utils.NATIVE_MOUSE_MESSAGE_BUTTON_UP; + case "mousemove": + return utils.NATIVE_MOUSE_MESSAGE_MOVE; + default: + throw Error(`Invalid type is specified: ${type}`); + } + })(), + button, + modifierFlags, + element, + aObserver + ); +} + +function promiseNativeMouseEventWithAPZ(aParams) { + return new Promise(resolve => + synthesizeNativeMouseEventWithAPZ(aParams, resolve) + ); +} + +// See synthesizeNativeMouseEventWithAPZ for the detail of aParams. +function promiseNativeMouseEventWithAPZAndWaitForEvent(aParams) { + return new Promise(resolve => { + const targetWindow = windowForTarget(aParams.target); + const eventType = aParams.eventTypeToWait || aParams.type; + targetWindow.addEventListener(eventType, resolve, { + once: true, + }); + synthesizeNativeMouseEventWithAPZ(aParams); + }); +} + +// Move the mouse to (dx, dy) relative to |target|, and scroll the wheel +// at that location. +// Moving the mouse is necessary to avoid wheel events from two consecutive +// promiseMoveMouseAndScrollWheelOver() calls on different elements being incorrectly +// considered as part of the same wheel transaction. +// We also wait for the mouse move event to be processed before sending the +// wheel event, otherwise there is a chance they might get reordered, and +// we have the transaction problem again. +// This function returns a promise that is resolved when the resulting wheel +// (if waitForScroll = false) or scroll (if waitForScroll = true) event is +// received. +function promiseMoveMouseAndScrollWheelOver( + target, + dx, + dy, + waitForScroll = true, + scrollDelta = 10 +) { + let p = promiseNativeMouseEventWithAPZAndWaitForEvent({ + type: "mousemove", + target, + offsetX: dx, + offsetY: dy, + }); + if (waitForScroll) { + p = p.then(() => { + return promiseNativeWheelAndWaitForScrollEvent( + target, + dx, + dy, + 0, + -scrollDelta + ); + }); + } else { + p = p.then(() => { + return promiseNativeWheelAndWaitForWheelEvent( + target, + dx, + dy, + 0, + -scrollDelta + ); + }); + } + return p; +} + +async function scrollbarDragStart(aTarget, aScaleFactor) { + var targetElement = elementForTarget(aTarget); + var w = {}, + h = {}; + utilsForTarget(aTarget).getScrollbarSizes(targetElement, w, h); + var verticalScrollbarWidth = w.value; + if (verticalScrollbarWidth == 0) { + return null; + } + + var upArrowHeight = verticalScrollbarWidth; // assume square scrollbar buttons + var startX = targetElement.clientWidth + verticalScrollbarWidth / 2; + var startY = upArrowHeight + 5; // start dragging somewhere in the thumb + startX *= aScaleFactor; + startY *= aScaleFactor; + + // targetElement.clientWidth is unaffected by the zoom, but if the target + // is the root content window, the distance from the window origin to the + // scrollbar in CSS pixels does decrease proportionally to the zoom, + // so the CSS coordinates we return need to be scaled accordingly. + if (targetIsTopWindow(aTarget)) { + var resolution = await getResolution(); + startX /= resolution; + startY /= resolution; + } + + return { x: startX, y: startY }; +} + +// Synthesizes events to drag |target|'s vertical scrollbar by the distance +// specified, synthesizing a mousemove for each increment as specified. +// Returns null if the element doesn't have a vertical scrollbar. Otherwise, +// returns an async function that should be invoked after the mousemoves have been +// processed by the widget code, to end the scrollbar drag. Mousemoves being +// processed by the widget code can be detected by listening for the mousemove +// events in the caller, or for some other event that is triggered by the +// mousemove, such as the scroll event resulting from the scrollbar drag. +// The aScaleFactor argument should be provided if the scrollframe has been +// scaled by an enclosing CSS transform. (TODO: this is a workaround for the +// fact that coordinatesRelativeToScreen is supposed to do this automatically +// but it currently does not). +// Note: helper_scrollbar_snap_bug1501062.html contains a copy of this code +// with modifications. Fixes here should be copied there if appropriate. +// |target| can be an element (for subframes) or a window (for root frames). +async function promiseVerticalScrollbarDrag( + aTarget, + aDistance = 20, + aIncrement = 5, + aScaleFactor = 1 +) { + var startPoint = await scrollbarDragStart(aTarget, aScaleFactor); + var targetElement = elementForTarget(aTarget); + if (startPoint == null) { + return null; + } + + dump( + "Starting drag at " + + startPoint.x + + ", " + + startPoint.y + + " from top-left of #" + + targetElement.id + + "\n" + ); + + // Move the mouse to the scrollbar thumb and drag it down + await promiseNativeMouseEventWithAPZ({ + target: aTarget, + offsetX: startPoint.x, + offsetY: startPoint.y, + type: "mousemove", + }); + // mouse down + await promiseNativeMouseEventWithAPZ({ + target: aTarget, + offsetX: startPoint.x, + offsetY: startPoint.y, + type: "mousedown", + }); + // drag vertically by |aIncrement| until we reach the specified distance + for (var y = aIncrement; y < aDistance; y += aIncrement) { + await promiseNativeMouseEventWithAPZ({ + target: aTarget, + offsetX: startPoint.x, + offsetY: startPoint.y + y, + type: "mousemove", + }); + } + await promiseNativeMouseEventWithAPZ({ + target: aTarget, + offsetX: startPoint.x, + offsetY: startPoint.y + aDistance, + type: "mousemove", + }); + + // and return an async function to call afterwards to finish up the drag + return async function () { + dump("Finishing drag of #" + targetElement.id + "\n"); + await promiseNativeMouseEventWithAPZ({ + target: aTarget, + offsetX: startPoint.x, + offsetY: startPoint.y + aDistance, + type: "mouseup", + }); + }; +} + +// This is similar to promiseVerticalScrollbarDrag except this triggers +// the vertical scrollbar drag with a touch drag input. This function +// returns true if a scrollbar was present and false if no scrollbar +// was found for the given element. +async function promiseVerticalScrollbarTouchDrag( + aTarget, + aDistance = 20, + aScaleFactor = 1 +) { + var startPoint = await scrollbarDragStart(aTarget, aScaleFactor); + var targetElement = elementForTarget(aTarget); + if (startPoint == null) { + return false; + } + + dump( + "Starting touch drag at " + + startPoint.x + + ", " + + startPoint.y + + " from top-left of #" + + targetElement.id + + "\n" + ); + + await promiseNativeTouchDrag( + aTarget, + startPoint.x, + startPoint.y, + 0, + aDistance + ); + + return true; +} + +// Synthesizes a native mouse drag, starting at offset (mouseX, mouseY) from +// the given target. The drag occurs in the given number of steps, to a final +// destination of (mouseX + distanceX, mouseY + distanceY) from the target. +// Returns a promise (wrapped in a function, so it doesn't execute immediately) +// that should be awaited after the mousemoves have been processed by the widget +// code, to end the drag. This is important otherwise the OS can sometimes +// reorder the events and the drag doesn't have the intended effect (see +// bug 1368603). +// Example usage: +// let dragFinisher = await promiseNativeMouseDrag(myElement, 0, 0); +// await myIndicationThatDragHadAnEffect; +// await dragFinisher(); +async function promiseNativeMouseDrag( + target, + mouseX, + mouseY, + distanceX = 20, + distanceY = 20, + steps = 20 +) { + var targetElement = elementForTarget(target); + dump( + "Starting drag at " + + mouseX + + ", " + + mouseY + + " from top-left of #" + + targetElement.id + + "\n" + ); + + // Move the mouse to the target position + await promiseNativeMouseEventWithAPZ({ + target, + offsetX: mouseX, + offsetY: mouseY, + type: "mousemove", + }); + // mouse down + await promiseNativeMouseEventWithAPZ({ + target, + offsetX: mouseX, + offsetY: mouseY, + type: "mousedown", + }); + // drag vertically by |increment| until we reach the specified distance + for (var s = 1; s <= steps; s++) { + let dx = distanceX * (s / steps); + let dy = distanceY * (s / steps); + dump(`Dragging to ${mouseX + dx}, ${mouseY + dy} from target\n`); + await promiseNativeMouseEventWithAPZ({ + target, + offsetX: mouseX + dx, + offsetY: mouseY + dy, + type: "mousemove", + }); + } + + // and return a function-wrapped promise to call afterwards to finish the drag + return function () { + return promiseNativeMouseEventWithAPZ({ + target, + offsetX: mouseX + distanceX, + offsetY: mouseY + distanceY, + type: "mouseup", + }); + }; +} + +// Synthesizes a native touch sequence of events corresponding to a pinch-zoom-in +// at the given focus point. The focus point must be specified in CSS coordinates +// relative to the document body. +async function pinchZoomInTouchSequence(focusX, focusY) { + // prettier-ignore + var zoom_in = [ + [ { x: focusX - 25, y: focusY - 50 }, { x: focusX + 25, y: focusY + 50 } ], + [ { x: focusX - 30, y: focusY - 80 }, { x: focusX + 30, y: focusY + 80 } ], + [ { x: focusX - 35, y: focusY - 110 }, { x: focusX + 40, y: focusY + 110 } ], + [ { x: focusX - 40, y: focusY - 140 }, { x: focusX + 45, y: focusY + 140 } ], + [ { x: focusX - 45, y: focusY - 170 }, { x: focusX + 50, y: focusY + 170 } ], + [ { x: focusX - 50, y: focusY - 200 }, { x: focusX + 55, y: focusY + 200 } ], + ]; + + var touchIds = [0, 1]; + return synthesizeNativeTouchSequences(document.body, zoom_in, null, touchIds); +} + +// Returns a promise that is resolved when the observer service dispatches a +// message with the given topic. +function promiseTopic(aTopic) { + return new Promise((resolve, reject) => { + SpecialPowers.Services.obs.addObserver(function observer( + subject, + topic, + data + ) { + try { + SpecialPowers.Services.obs.removeObserver(observer, topic); + resolve([subject, data]); + } catch (ex) { + SpecialPowers.Services.obs.removeObserver(observer, topic); + reject(ex); + } + }, + aTopic); + }); +} + +// Returns a promise that is resolved when a APZ transform ends. +function promiseTransformEnd() { + return promiseTopic("APZ:TransformEnd"); +} + +function promiseScrollend(aTarget = window) { + return promiseOneEvent(aTarget, "scrollend"); +} + +// Returns a promise that resolves after the indicated number +// of touchend events have fired on the given target element. +function promiseTouchEnd(element, count = 1) { + return new Promise(resolve => { + var eventCount = 0; + var counterFunction = function (e) { + eventCount++; + if (eventCount == count) { + element.removeEventListener("touchend", counterFunction, { + passive: true, + }); + resolve(); + } + }; + element.addEventListener("touchend", counterFunction, { passive: true }); + }); +} + +// This generates a touch-based pinch zoom-in gesture that is expected +// to succeed. It returns after APZ has completed the zoom and reaches the end +// of the transform. The focus point is expected to be in CSS coordinates +// relative to the document body. +async function pinchZoomInWithTouch(focusX, focusY) { + // Register the listener for the TransformEnd observer topic + let transformEndPromise = promiseTopic("APZ:TransformEnd"); + + // Dispatch all the touch events + await pinchZoomInTouchSequence(focusX, focusY); + + // Wait for TransformEnd to fire. + await transformEndPromise; +} +// This generates a touchpad pinch zoom-in gesture that is expected +// to succeed. It returns after APZ has completed the zoom and reaches the end +// of the transform. The focus point is expected to be in CSS coordinates +// relative to the document body. +async function pinchZoomInWithTouchpad(focusX, focusY, options = {}) { + var zoomIn = [ + 1.0, 1.019531, 1.035156, 1.037156, 1.039156, 1.054688, 1.056688, 1.070312, + 1.072312, 1.089844, 1.091844, 1.109375, 1.128906, 1.144531, 1.160156, + 1.175781, 1.191406, 1.207031, 1.222656, 1.234375, 1.246094, 1.261719, + 1.273438, 1.285156, 1.296875, 1.3125, 1.328125, 1.347656, 1.363281, + 1.382812, 1.402344, 1.421875, 1.0, + ]; + await synthesizeTouchpadPinch(zoomIn, focusX, focusY, options); +} + +async function pinchZoomInAndPanWithTouchpad(options = {}) { + var x = 584; + var y = 347; + var scalesAndFoci = []; + // Zoom + for (var scale = 1.0; scale <= 2.0; scale += 0.2) { + scalesAndFoci.push([scale, x, y]); + } + // Pan (due to a limitation of the current implementation, events + // for which the scale doesn't change are dropped, so vary the + // scale slightly as well). + for (var i = 1; i <= 20; i++) { + x -= 4; + y -= 5; + scalesAndFoci.push([scale + 0.01 * i, x, y]); + } + await synthesizeTouchpadGesture(scalesAndFoci, options); +} + +async function pinchZoomOutWithTouchpad(focusX, focusY, options = {}) { + // The last item equal one to indicate scale end + var zoomOut = [ + 1.0, 1.375, 1.359375, 1.339844, 1.316406, 1.296875, 1.277344, 1.257812, + 1.238281, 1.21875, 1.199219, 1.175781, 1.15625, 1.132812, 1.101562, + 1.078125, 1.054688, 1.03125, 1.011719, 0.992188, 0.972656, 0.953125, + 0.933594, 1.0, + ]; + await synthesizeTouchpadPinch(zoomOut, focusX, focusY, options); +} + +async function pinchZoomInOutWithTouchpad(focusX, focusY, options = {}) { + // Use the same scale for two events in a row to make sure the code handles this properly. + var zoomInOut = [ + 1.0, 1.082031, 1.089844, 1.097656, 1.101562, 1.109375, 1.121094, 1.128906, + 1.128906, 1.125, 1.097656, 1.074219, 1.054688, 1.035156, 1.015625, 1.0, 1.0, + ]; + await synthesizeTouchpadPinch(zoomInOut, focusX, focusY, options); +} +// This generates a touch-based pinch gesture that is expected to succeed +// and trigger an APZ:TransformEnd observer notification. +// It returns after that notification has been dispatched. +// The coordinates of touch events in `touchSequence` are expected to be +// in CSS coordinates relative to the document body. +async function synthesizeNativeTouchAndWaitForTransformEnd( + touchSequence, + touchIds +) { + // Register the listener for the TransformEnd observer topic + let transformEndPromise = promiseTopic("APZ:TransformEnd"); + + // Dispatch all the touch events + await synthesizeNativeTouchSequences( + document.body, + touchSequence, + null, + touchIds + ); + + // Wait for TransformEnd to fire. + await transformEndPromise; +} + +// Returns a touch sequence for a pinch-zoom-out operation in the center +// of the visual viewport. The touch sequence returned is in CSS coordinates +// relative to the document body. +function pinchZoomOutTouchSequenceAtCenter() { + // Divide the half of visual viewport size by 8, then cause touch events + // starting from the 7th furthest away from the center towards the center. + const deltaX = window.visualViewport.width / 16; + const deltaY = window.visualViewport.height / 16; + const centerX = + window.visualViewport.pageLeft + window.visualViewport.width / 2; + const centerY = + window.visualViewport.pageTop + window.visualViewport.height / 2; + // prettier-ignore + var zoom_out = [ + [ { x: centerX - (deltaX * 6), y: centerY - (deltaY * 6) }, + { x: centerX + (deltaX * 6), y: centerY + (deltaY * 6) } ], + [ { x: centerX - (deltaX * 5), y: centerY - (deltaY * 5) }, + { x: centerX + (deltaX * 5), y: centerY + (deltaY * 5) } ], + [ { x: centerX - (deltaX * 4), y: centerY - (deltaY * 4) }, + { x: centerX + (deltaX * 4), y: centerY + (deltaY * 4) } ], + [ { x: centerX - (deltaX * 3), y: centerY - (deltaY * 3) }, + { x: centerX + (deltaX * 3), y: centerY + (deltaY * 3) } ], + [ { x: centerX - (deltaX * 2), y: centerY - (deltaY * 2) }, + { x: centerX + (deltaX * 2), y: centerY + (deltaY * 2) } ], + [ { x: centerX - (deltaX * 1), y: centerY - (deltaY * 1) }, + { x: centerX + (deltaX * 1), y: centerY + (deltaY * 1) } ], + ]; + return zoom_out; +} + +// This generates a touch-based pinch zoom-out gesture that is expected +// to succeed. It returns after APZ has completed the zoom and reaches the end +// of the transform. The touch inputs are directed to the center of the +// current visual viewport. +async function pinchZoomOutWithTouchAtCenter() { + var zoom_out = pinchZoomOutTouchSequenceAtCenter(); + var touchIds = [0, 1]; + await synthesizeNativeTouchAndWaitForTransformEnd(zoom_out, touchIds); +} + +// useTouchpad is only currently implemented on macOS +async function synthesizeDoubleTap(element, x, y, useTouchpad) { + if (useTouchpad) { + await synthesizeNativeTouchpadDoubleTap(element, x, y); + } else { + await synthesizeNativeTap(element, x, y); + await synthesizeNativeTap(element, x, y); + } +} +// useTouchpad is only currently implemented on macOS +async function doubleTapOn(element, x, y, useTouchpad) { + let transformEndPromise = promiseTransformEnd(); + + await synthesizeDoubleTap(element, x, y, useTouchpad); + + // Wait for the APZ:TransformEnd to fire + await transformEndPromise; + + // Flush state so we can query an accurate resolution + await promiseApzFlushedRepaints(); +} + +const NativePanHandlerForLinux = { + beginPhase: SpecialPowers.DOMWindowUtils.PHASE_BEGIN, + updatePhase: SpecialPowers.DOMWindowUtils.PHASE_UPDATE, + endPhase: SpecialPowers.DOMWindowUtils.PHASE_END, + promiseNativePanEvent: promiseNativeTouchpadPanEventAndWaitForObserver, + delta: -50, +}; + +const NativePanHandlerForWindows = { + beginPhase: SpecialPowers.DOMWindowUtils.PHASE_BEGIN, + updatePhase: SpecialPowers.DOMWindowUtils.PHASE_UPDATE, + endPhase: SpecialPowers.DOMWindowUtils.PHASE_END, + promiseNativePanEvent: promiseNativeTouchpadPanEventAndWaitForObserver, + delta: 50, +}; + +const NativePanHandlerForMac = { + // From https://developer.apple.com/documentation/coregraphics/cgscrollphase/kcgscrollphasebegan?language=occ , etc. + beginPhase: 1, // kCGScrollPhaseBegan + updatePhase: 2, // kCGScrollPhaseChanged + endPhase: 4, // kCGScrollPhaseEnded + promiseNativePanEvent: promiseNativePanGestureEventAndWaitForObserver, + delta: -50, +}; + +const NativePanHandlerForHeadless = { + beginPhase: SpecialPowers.DOMWindowUtils.PHASE_BEGIN, + updatePhase: SpecialPowers.DOMWindowUtils.PHASE_UPDATE, + endPhase: SpecialPowers.DOMWindowUtils.PHASE_END, + promiseNativePanEvent: promiseNativeTouchpadPanEventAndWaitForObserver, + delta: 50, +}; + +function getPanHandler() { + if (SpecialPowers.isHeadless) { + return NativePanHandlerForHeadless; + } + + switch (getPlatform()) { + case "linux": + return NativePanHandlerForLinux; + case "windows": + return NativePanHandlerForWindows; + case "mac": + return NativePanHandlerForMac; + default: + throw new Error( + "There's no native pan handler on platform " + getPlatform() + ); + } +} + +// Lazily get `NativePanHandler` to avoid an exception where we don't support +// native pan events (e.g. Android). +if (!window.hasOwnProperty("NativePanHandler")) { + Object.defineProperty(window, "NativePanHandler", { + get() { + return getPanHandler(); + }, + }); +} + +async function panRightToLeftBegin(aElement, aX, aY, aMultiplier) { + await NativePanHandler.promiseNativePanEvent( + aElement, + aX, + aY, + NativePanHandler.delta * aMultiplier, + 0, + NativePanHandler.beginPhase + ); +} + +async function panRightToLeftUpdate(aElement, aX, aY, aMultiplier) { + await NativePanHandler.promiseNativePanEvent( + aElement, + aX, + aY, + NativePanHandler.delta * aMultiplier, + 0, + NativePanHandler.updatePhase + ); +} + +async function panRightToLeftEnd(aElement, aX, aY, aMultiplier) { + await NativePanHandler.promiseNativePanEvent( + aElement, + aX, + aY, + 0, + 0, + NativePanHandler.endPhase + ); +} + +async function panRightToLeft(aElement, aX, aY, aMultiplier) { + await panRightToLeftBegin(aElement, aX, aY, aMultiplier); + await panRightToLeftUpdate(aElement, aX, aY, aMultiplier); + await panRightToLeftEnd(aElement, aX, aY, aMultiplier); +} + +async function panLeftToRight(aElement, aX, aY, aMultiplier) { + await panLeftToRightBegin(aElement, aX, aY, aMultiplier); + await panLeftToRightUpdate(aElement, aX, aY, aMultiplier); + await panLeftToRightEnd(aElement, aX, aY, aMultiplier); +} + +async function panLeftToRightBegin(aElement, aX, aY, aMultiplier) { + await NativePanHandler.promiseNativePanEvent( + aElement, + aX, + aY, + -NativePanHandler.delta * aMultiplier, + 0, + NativePanHandler.beginPhase + ); +} + +async function panLeftToRightUpdate(aElement, aX, aY, aMultiplier) { + await NativePanHandler.promiseNativePanEvent( + aElement, + aX, + aY, + -NativePanHandler.delta * aMultiplier, + 0, + NativePanHandler.updatePhase + ); + await NativePanHandler.promiseNativePanEvent( + aElement, + aX, + aY, + -NativePanHandler.delta * aMultiplier, + 0, + NativePanHandler.updatePhase + ); +} + +async function panLeftToRightEnd(aElement, aX, aY, aMultiplier) { + await NativePanHandler.promiseNativePanEvent( + aElement, + aX, + aY, + 0, + 0, + NativePanHandler.endPhase + ); +} diff --git a/gfx/layers/apz/test/mochitest/apz_test_utils.js b/gfx/layers/apz/test/mochitest/apz_test_utils.js new file mode 100644 index 0000000000..1004f8a3d5 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/apz_test_utils.js @@ -0,0 +1,1287 @@ +// Utilities for writing APZ tests using the framework added in bug 961289 + +// ---------------------------------------------------------------------- +// Functions that convert the APZ test data into a more usable form. +// Every place we have a WebIDL sequence whose elements are dictionaries +// with two elements, a key, and a value, we convert this into a JS +// object with a property for each key/value pair. (This is the structure +// we really want, but we can't express in directly in WebIDL.) +// ---------------------------------------------------------------------- + +// getHitTestConfig() expects apz_test_native_event_utils.js to be loaded as well. +/* import-globals-from apz_test_native_event_utils.js */ + +function convertEntries(entries) { + var result = {}; + for (var i = 0; i < entries.length; ++i) { + result[entries[i].key] = entries[i].value; + } + return result; +} + +function parsePoint(str) { + var pieces = str.replace(/[()\s]+/g, "").split(","); + SimpleTest.is(pieces.length, 2, "expected string of form (x,y)"); + for (var i = 0; i < 2; i++) { + var eq = pieces[i].indexOf("="); + if (eq >= 0) { + pieces[i] = pieces[i].substring(eq + 1); + } + } + return { + x: parseInt(pieces[0]), + y: parseInt(pieces[1]), + }; +} + +// Given a VisualViewport object, return the visual viewport +// rect relative to the page. +function getVisualViewportRect(vv) { + return { + x: vv.pageLeft, + y: vv.pageTop, + width: vv.width, + height: vv.height, + }; +} + +// Return the offset of the visual viewport relative to the layout viewport. +function getRelativeViewportOffset(window) { + const offsetX = {}; + const offsetY = {}; + const utils = SpecialPowers.getDOMWindowUtils(window); + utils.getVisualViewportOffsetRelativeToLayoutViewport(offsetX, offsetY); + return { + x: offsetX.value, + y: offsetY.value, + }; +} + +function parseRect(str) { + var pieces = str.replace(/[()\s]+/g, "").split(","); + SimpleTest.is(pieces.length, 4, "expected string of form (x,y,w,h)"); + for (var i = 0; i < 4; i++) { + var eq = pieces[i].indexOf("="); + if (eq >= 0) { + pieces[i] = pieces[i].substring(eq + 1); + } + } + return { + x: parseInt(pieces[0]), + y: parseInt(pieces[1]), + width: parseInt(pieces[2]), + height: parseInt(pieces[3]), + }; +} + +// These functions expect rects with fields named x/y/width/height, such as +// that returned by parseRect(). +function rectContains(haystack, needle) { + return ( + haystack.x <= needle.x && + haystack.y <= needle.y && + haystack.x + haystack.width >= needle.x + needle.width && + haystack.y + haystack.height >= needle.y + needle.height + ); +} +function rectToString(rect) { + return ( + "(" + rect.x + "," + rect.y + "," + rect.width + "," + rect.height + ")" + ); +} +function assertRectContainment( + haystackRect, + haystackDesc, + needleRect, + needleDesc +) { + SimpleTest.ok( + rectContains(haystackRect, needleRect), + haystackDesc + + " " + + rectToString(haystackRect) + + " should contain " + + needleDesc + + " " + + rectToString(needleRect) + ); +} + +function getPropertyAsRect(scrollFrames, scrollId, prop) { + SimpleTest.ok( + scrollId in scrollFrames, + "expected scroll frame data for scroll id " + scrollId + ); + var scrollFrameData = scrollFrames[scrollId]; + SimpleTest.ok( + "displayport" in scrollFrameData, + "expected a " + prop + " for scroll id " + scrollId + ); + var value = scrollFrameData[prop]; + return parseRect(value); +} + +function convertScrollFrameData(scrollFrames) { + var result = {}; + for (var i = 0; i < scrollFrames.length; ++i) { + result[scrollFrames[i].scrollId] = convertEntries(scrollFrames[i].entries); + } + return result; +} + +function convertBuckets(buckets) { + var result = {}; + for (var i = 0; i < buckets.length; ++i) { + result[buckets[i].sequenceNumber] = convertScrollFrameData( + buckets[i].scrollFrames + ); + } + return result; +} + +function convertTestData(testData) { + var result = {}; + result.paints = convertBuckets(testData.paints); + result.repaintRequests = convertBuckets(testData.repaintRequests); + return result; +} + +// Returns the last bucket that has at least one scrollframe. This +// is useful for skipping over buckets that are from empty transactions, +// because those don't contain any useful data. +function getLastNonemptyBucket(buckets) { + for (var i = buckets.length - 1; i >= 0; --i) { + if (buckets[i].scrollFrames.length) { + return buckets[i]; + } + } + return null; +} + +// Takes something like "matrix(1, 0, 0, 1, 234.024, 528.29023)"" and returns a number array +function parseTransform(transform) { + return /matrix\((.*),(.*),(.*),(.*),(.*),(.*)\)/ + .exec(transform) + .slice(1) + .map(parseFloat); +} + +function isTransformClose(a, b, name) { + is( + a.length, + b.length, + `expected transforms ${a} and ${b} to be the same length` + ); + for (let i = 0; i < a.length; i++) { + ok(Math.abs(a[i] - b[i]) < 0.01, name); + } +} + +// Given APZ test data for a single paint on the compositor side, +// reconstruct the APZC tree structure from the 'parentScrollId' +// entries that were logged. More specifically, the subset of the +// APZC tree structure corresponding to the layer subtree for the +// content process that triggered the paint, is reconstructed (as +// the APZ test data only contains information abot this subtree). +function buildApzcTree(paint) { + // The APZC tree can potentially have multiple root nodes, + // so we invent a node that is the parent of all roots. + // This 'root' does not correspond to an APZC. + var root = { scrollId: -1, children: [] }; + for (let scrollId in paint) { + paint[scrollId].children = []; + paint[scrollId].scrollId = scrollId; + } + for (let scrollId in paint) { + var parentNode = null; + if ("hasNoParentWithSameLayersId" in paint[scrollId]) { + parentNode = root; + } else if ("parentScrollId" in paint[scrollId]) { + parentNode = paint[paint[scrollId].parentScrollId]; + } + parentNode.children.push(paint[scrollId]); + } + return root; +} + +// Given an APZC tree produced by buildApzcTree, return the RCD node in +// the tree, or null if there was none. +function findRcdNode(apzcTree) { + // isRootContent will be undefined or "1" + if (apzcTree.isRootContent) { + return apzcTree; + } + for (var i = 0; i < apzcTree.children.length; i++) { + var rcd = findRcdNode(apzcTree.children[i]); + if (rcd != null) { + return rcd; + } + } + return null; +} + +// Return whether an element whose id includes |elementId| has been layerized. +// Assumes |elementId| will be present in the content description for the +// element, and not in the content descriptions of other elements. +function isLayerized(elementId) { + var contentTestData = + SpecialPowers.getDOMWindowUtils(window).getContentAPZTestData(); + var nonEmptyBucket = getLastNonemptyBucket(contentTestData.paints); + ok(nonEmptyBucket != null, "expected at least one nonempty paint"); + var seqno = nonEmptyBucket.sequenceNumber; + contentTestData = convertTestData(contentTestData); + var paint = contentTestData.paints[seqno]; + for (var scrollId in paint) { + if ("contentDescription" in paint[scrollId]) { + if (paint[scrollId].contentDescription.includes(elementId)) { + return true; + } + } + } + return false; +} + +// Return a rect (or null) that holds the last known content-side displayport +// for a given element. (The element selection works the same way, and with +// the same assumptions as the isLayerized function above). +function getLastContentDisplayportFor(elementId, expectPainted = true) { + var contentTestData = + SpecialPowers.getDOMWindowUtils(window).getContentAPZTestData(); + if (contentTestData == undefined) { + ok(!expectPainted, "expected to have apz test data (1)"); + return null; + } + var nonEmptyBucket = getLastNonemptyBucket(contentTestData.paints); + if (nonEmptyBucket == null) { + ok(!expectPainted, "expected to have apz test data (2)"); + return null; + } + var seqno = nonEmptyBucket.sequenceNumber; + contentTestData = convertTestData(contentTestData); + var paint = contentTestData.paints[seqno]; + for (var scrollId in paint) { + if ("contentDescription" in paint[scrollId]) { + if (paint[scrollId].contentDescription.includes(elementId)) { + if ("displayport" in paint[scrollId]) { + return parseRect(paint[scrollId].displayport); + } + } + } + } + return null; +} + +// Return the APZC tree (as produced by buildApzcTree) for the last +// non-empty paint received by the compositor. +function getLastApzcTree() { + let data = SpecialPowers.getDOMWindowUtils(window).getCompositorAPZTestData(); + if (data == undefined) { + ok(false, "expected to have compositor apz test data"); + return null; + } + if (!data.paints.length) { + ok(false, "expected to have at least one compositor paint bucket"); + return null; + } + var seqno = data.paints[data.paints.length - 1].sequenceNumber; + data = convertTestData(data); + return buildApzcTree(data.paints[seqno]); +} + +// Return a promise that is resolved on the next rAF callback +function promiseFrame(aWindow = window) { + return new Promise(resolve => { + aWindow.requestAnimationFrame(resolve); + }); +} + +// Return a promise that is resolved on the next MozAfterPaint event +function promiseAfterPaint() { + return new Promise(resolve => { + window.addEventListener("MozAfterPaint", resolve, { once: true }); + }); +} + +// This waits until any pending events on the APZ controller thread are +// processed, and any resulting repaint requests are received by the main +// thread. Note that while the repaint requests do get processed by the +// APZ handler on the main thread, the repaints themselves may not have +// occurred by the the returned promise resolves. If you want to wait +// for those repaints, consider using promiseApzFlushedRepaints instead. +function promiseOnlyApzControllerFlushedWithoutSetTimeout(aWindow = window) { + return new Promise(function (resolve, reject) { + var repaintDone = function () { + dump("PromiseApzRepaintsFlushed: APZ flush done\n"); + SpecialPowers.Services.obs.removeObserver( + repaintDone, + "apz-repaints-flushed" + ); + resolve(); + }; + SpecialPowers.Services.obs.addObserver(repaintDone, "apz-repaints-flushed"); + if (SpecialPowers.getDOMWindowUtils(aWindow).flushApzRepaints()) { + dump( + "PromiseApzRepaintsFlushed: Flushed APZ repaints, waiting for callback...\n" + ); + } else { + dump( + "PromiseApzRepaintsFlushed: Flushing APZ repaints was a no-op, triggering callback directly...\n" + ); + repaintDone(); + } + }); +} + +// Another variant of the above promiseOnlyApzControllerFlushedWithoutSetTimeout +// but with a setTimeout(0) callback. +function promiseOnlyApzControllerFlushed(aWindow = window) { + return new Promise(resolve => { + promiseOnlyApzControllerFlushedWithoutSetTimeout(aWindow).then(() => { + setTimeout(resolve, 0); + }); + }); +} + +// Flush repaints, APZ pending repaints, and any repaints resulting from that +// flush. This is particularly useful if the test needs to reach some sort of +// "idle" state in terms of repaints. Usually just waiting for all paints +// followed by flushApzRepaints is sufficient to flush all APZ state back to +// the main thread, but it can leave a paint scheduled which will get triggered +// at some later time. For tests that specifically test for painting at +// specific times, this method is the way to go. Even if in doubt, this is the +// preferred method as the extra step is "safe" and shouldn't interfere with +// most tests. +async function promiseApzFlushedRepaints() { + await promiseAllPaintsDone(); + await promiseOnlyApzControllerFlushed(); + await promiseAllPaintsDone(); +} + +// This function takes a set of subtests to run one at a time in new top-level +// windows, and returns a Promise that is resolved once all the subtests are +// done running. +// +// The aSubtests array is an array of objects with the following keys: +// file: required, the filename of the subtest. +// prefs: optional, an array of arrays containing key-value prefs to set. +// dp_suppression: optional, a boolean on whether or not to respect displayport +// suppression during the test. +// onload: optional, a function that will be registered as a load event listener +// for the child window that will hold the subtest. the function will be +// passed exactly one argument, which will be the child window. +// An example of an array is: +// aSubtests = [ +// { 'file': 'test_file_name.html' }, +// { 'file': 'test_file_2.html', 'prefs': [['pref.name', true], ['other.pref', 1000]], 'dp_suppression': false } +// { 'file': 'file_3.html', 'onload': function(w) { w.subtestDone(); } } +// ]; +// +// Each subtest should call one of the subtestDone() or subtestFailed() +// functions when it is done, to indicate that the window should be torn +// down and the next test should run. +// These functions are injected into the subtest's window by this +// function prior to loading the subtest. For convenience, the |is| and |ok| +// functions provided by SimpleTest are also mapped into the subtest's window. +// For other things from the parent, the subtest can use window.opener. +// to access objects. +function runSubtestsSeriallyInFreshWindows(aSubtests) { + return new Promise(function (resolve, reject) { + var testIndex = -1; + var w = null; + + // If the "apz.subtest" pref has been set, only a single subtest whose name matches + // the pref's value (if any) will be run. + var onlyOneSubtest = SpecialPowers.getCharPref( + "apz.subtest", + /* default = */ "" + ); + + function advanceSubtestExecutionWithFailure(msg) { + SimpleTest.ok(false, msg); + advanceSubtestExecution(); + } + + async function advanceSubtestExecution() { + var test = aSubtests[testIndex]; + if (w) { + // Run any cleanup functions registered in the subtest + // Guard against the subtest not loading apz_test_utils.js + if (w.ApzCleanup) { + w.ApzCleanup.execute(); + } + if (typeof test.dp_suppression != "undefined") { + // We modified the suppression when starting the test, so now undo that. + SpecialPowers.getDOMWindowUtils(window).respectDisplayPortSuppression( + !test.dp_suppression + ); + } + + if (test.prefs) { + // We pushed some prefs for this test, pop them, and re-invoke + // advanceSubtestExecution() after that's been processed + SpecialPowers.popPrefEnv(function () { + w.close(); + w = null; + advanceSubtestExecution(); + }); + return; + } + + w.close(); + } + + testIndex++; + if (testIndex >= aSubtests.length) { + resolve(); + return; + } + + await SimpleTest.promiseFocus(window); + + test = aSubtests[testIndex]; + + let recognizedProps = ["file", "prefs", "dp_suppression", "onload"]; + for (let prop in test) { + if (!recognizedProps.includes(prop)) { + SimpleTest.ok( + false, + "Subtest " + test.file + " has unrecognized property '" + prop + "'" + ); + setTimeout(function () { + advanceSubtestExecution(); + }, 0); + return; + } + } + + if (onlyOneSubtest && onlyOneSubtest != test.file) { + SimpleTest.ok( + true, + "Skipping " + + test.file + + " because only " + + onlyOneSubtest + + " is being run" + ); + setTimeout(function () { + advanceSubtestExecution(); + }, 0); + return; + } + + SimpleTest.ok(true, "Starting subtest " + test.file); + + if (typeof test.dp_suppression != "undefined") { + // Normally during a test, the displayport will get suppressed during page + // load, and unsuppressed at a non-deterministic time during the test. The + // unsuppression can trigger a repaint which interferes with the test, so + // to avoid that we can force the displayport to be unsuppressed for the + // entire test which is more deterministic. + SpecialPowers.getDOMWindowUtils(window).respectDisplayPortSuppression( + test.dp_suppression + ); + } + + function spawnTest(aFile) { + w = window.open("", "_blank"); + w.subtestDone = advanceSubtestExecution; + w.subtestFailed = advanceSubtestExecutionWithFailure; + w.isApzSubtest = true; + w.SimpleTest = SimpleTest; + w.dump = function (msg) { + return dump(aFile + " | " + msg); + }; + w.info = function (msg) { + return info(aFile + " | " + msg); + }; + w.is = function (a, b, msg) { + return is(a, b, aFile + " | " + msg); + }; + w.isnot = function (a, b, msg) { + return isnot(a, b, aFile + " | " + msg); + }; + w.isfuzzy = function (a, b, eps, msg) { + return isfuzzy(a, b, eps, aFile + " | " + msg); + }; + w.ok = function (cond, msg) { + arguments[1] = aFile + " | " + msg; + // Forward all arguments to SimpleTest.ok where we will check that ok() was + // called with at most 2 arguments. + return SimpleTest.ok.apply(SimpleTest, arguments); + }; + w.todo_is = function (a, b, msg) { + return todo_is(a, b, aFile + " | " + msg); + }; + w.todo = function (cond, msg) { + return todo(cond, aFile + " | " + msg); + }; + if (test.onload) { + w.addEventListener( + "load", + function (e) { + test.onload(w); + }, + { once: true } + ); + } + var subtestUrl = + location.href.substring(0, location.href.lastIndexOf("/") + 1) + + aFile; + function urlResolves(url) { + var request = new XMLHttpRequest(); + request.open("GET", url, false); + request.send(); + return request.status !== 404; + } + if (!urlResolves(subtestUrl)) { + SimpleTest.ok( + false, + "Subtest URL " + + subtestUrl + + " does not resolve. " + + "Be sure it's present in the support-files section of mochitest.ini." + ); + reject(); + return undefined; + } + w.location = subtestUrl; + return w; + } + + if (test.prefs) { + // Got some prefs for this subtest, push them + await SpecialPowers.pushPrefEnv({ set: test.prefs }); + } + w = spawnTest(test.file); + } + + advanceSubtestExecution(); + }).catch(function (e) { + SimpleTest.ok(false, "Error occurred while running subtests: " + e); + }); +} + +function pushPrefs(prefs) { + return SpecialPowers.pushPrefEnv({ set: prefs }); +} + +async function waitUntilApzStable() { + if (!SpecialPowers.isMainProcess()) { + // We use this waitUntilApzStable function during test initialization + // and for those scenarios we want to flush the parent-process layer + // tree to the compositor and wait for that as well. That way we know + // that not only is the content-process layer tree ready in the compositor, + // the parent-process layer tree in the compositor has the appropriate + // RefLayer pointing to the content-process layer tree. + + // Sadly this helper function cannot reuse any code from other places because + // it must be totally self-contained to be shipped over to the parent process. + function parentProcessFlush() { + /* eslint-env mozilla/chrome-script */ + function apzFlush() { + var topWin = Services.wm.getMostRecentWindow("navigator:browser"); + if (!topWin) { + topWin = Services.wm.getMostRecentWindow("navigator:geckoview"); + } + var topUtils = topWin.windowUtils; + + var repaintDone = function () { + dump("WaitUntilApzStable: APZ flush done in parent proc\n"); + Services.obs.removeObserver(repaintDone, "apz-repaints-flushed"); + // send message back to content process + sendAsyncMessage("apz-flush-done", null); + }; + var flushRepaint = function () { + if (topUtils.isMozAfterPaintPending) { + topWin.addEventListener("MozAfterPaint", flushRepaint, { + once: true, + }); + return; + } + + Services.obs.addObserver(repaintDone, "apz-repaints-flushed"); + if (topUtils.flushApzRepaints()) { + dump( + "WaitUntilApzStable: flushed APZ repaints in parent proc, waiting for callback...\n" + ); + } else { + dump( + "WaitUntilApzStable: flushing APZ repaints in parent proc was a no-op, triggering callback directly...\n" + ); + repaintDone(); + } + }; + + // Flush APZ repaints, but wait until all the pending paints have been + // sent. + flushRepaint(); + } + function cleanup() { + removeMessageListener("apz-flush", apzFlush); + removeMessageListener("cleanup", cleanup); + } + addMessageListener("apz-flush", apzFlush); + addMessageListener("cleanup", cleanup); + } + + // This is the first time waitUntilApzStable is being called, do initialization + if (typeof waitUntilApzStable.chromeHelper == "undefined") { + waitUntilApzStable.chromeHelper = + SpecialPowers.loadChromeScript(parentProcessFlush); + ApzCleanup.register(() => { + waitUntilApzStable.chromeHelper.sendAsyncMessage("cleanup", null); + waitUntilApzStable.chromeHelper.destroy(); + delete waitUntilApzStable.chromeHelper; + }); + } + + // Actually trigger the parent-process flush and wait for it to finish + waitUntilApzStable.chromeHelper.sendAsyncMessage("apz-flush", null); + await waitUntilApzStable.chromeHelper.promiseOneMessage("apz-flush-done"); + dump("WaitUntilApzStable: got apz-flush-done in child proc\n"); + } + + await SimpleTest.promiseFocus(window); + dump("WaitUntilApzStable: done promiseFocus\n"); + await promiseAllPaintsDone(); + dump("WaitUntilApzStable: done promiseAllPaintsDone\n"); + await promiseOnlyApzControllerFlushed(); + dump("WaitUntilApzStable: all done\n"); +} + +// This function returns a promise that is resolved after at least one paint +// has been sent and processed by the compositor. This function can force +// such a paint to happen if none are pending. This is useful to run after +// the waitUntilApzStable() but before reading the compositor-side APZ test +// data, because the test data for the content layers id only gets populated +// on content layer tree updates *after* the root layer tree has a RefLayer +// pointing to the contnet layer tree. waitUntilApzStable itself guarantees +// that the root layer tree is pointing to the content layer tree, but does +// not guarantee the subsequent paint; this function does that job. +async function forceLayerTreeToCompositor() { + // Modify a style property to force a layout flush + document.body.style.boxSizing = "border-box"; + var utils = SpecialPowers.getDOMWindowUtils(window); + if (!utils.isMozAfterPaintPending) { + dump("Forcing a paint since none was pending already...\n"); + var testMode = utils.isTestControllingRefreshes; + utils.advanceTimeAndRefresh(0); + if (!testMode) { + utils.restoreNormalRefresh(); + } + } + await promiseAllPaintsDone(null, true); + await promiseOnlyApzControllerFlushed(); +} + +function isApzEnabled() { + var enabled = SpecialPowers.getDOMWindowUtils(window).asyncPanZoomEnabled; + if (!enabled) { + // All tests are required to have at least one assertion. Since APZ is + // disabled, and the main test is presumably not going to run, we stick in + // a dummy assertion here to keep the test passing. + SimpleTest.ok(true, "APZ is not enabled; this test will be skipped"); + } + return enabled; +} + +function isKeyApzEnabled() { + return isApzEnabled() && SpecialPowers.getBoolPref("apz.keyboard.enabled"); +} + +// Take a snapshot of the given rect, *including compositor transforms* (i.e. +// includes async scroll transforms applied by APZ). If you don't need the +// compositor transforms, you can probably get away with using +// SpecialPowers.snapshotWindowWithOptions or one of the friendlier wrappers. +// The rect provided is expected to be relative to the screen, for example as +// returned by rectRelativeToScreen in apz_test_native_event_utils.js. +// Example usage: +// var snapshot = getSnapshot(rectRelativeToScreen(myDiv)); +// which will take a snapshot of the 'myDiv' element. Note that if part of the +// element is obscured by other things on top, the snapshot will include those +// things. If it is clipped by a scroll container, the snapshot will include +// that area anyway, so you will probably get parts of the scroll container in +// the snapshot. If the rect extends outside the browser window then the +// results are undefined. +// The snapshot is returned in the form of a data URL. +function getSnapshot(rect) { + function parentProcessSnapshot() { + /* eslint-env mozilla/chrome-script */ + addMessageListener("snapshot", function (parentRect) { + var topWin = Services.wm.getMostRecentWindow("navigator:browser"); + if (!topWin) { + topWin = Services.wm.getMostRecentWindow("navigator:geckoview"); + } + + // reposition the rect relative to the top-level browser window + parentRect = JSON.parse(parentRect); + parentRect.x -= topWin.mozInnerScreenX; + parentRect.y -= topWin.mozInnerScreenY; + + // take the snapshot + var canvas = topWin.document.createElementNS( + "http://www.w3.org/1999/xhtml", + "canvas" + ); + canvas.width = parentRect.width; + canvas.height = parentRect.height; + var ctx = canvas.getContext("2d"); + ctx.drawWindow( + topWin, + parentRect.x, + parentRect.y, + parentRect.width, + parentRect.height, + "rgb(255,255,255)", + ctx.DRAWWINDOW_DRAW_VIEW | + ctx.DRAWWINDOW_USE_WIDGET_LAYERS | + ctx.DRAWWINDOW_DRAW_CARET + ); + return canvas.toDataURL(); + }); + } + + if (typeof getSnapshot.chromeHelper == "undefined") { + // This is the first time getSnapshot is being called; do initialization + getSnapshot.chromeHelper = SpecialPowers.loadChromeScript( + parentProcessSnapshot + ); + ApzCleanup.register(function () { + getSnapshot.chromeHelper.destroy(); + }); + } + + return getSnapshot.chromeHelper.sendQuery("snapshot", JSON.stringify(rect)); +} + +// Takes the document's query string and parses it, assuming the query string +// is composed of key-value pairs where the value is in JSON format. The object +// returned contains the various values indexed by their respective keys. In +// case of duplicate keys, the last value be used. +// Examples: +// ?key="value"&key2=false&key3=500 +// produces { "key": "value", "key2": false, "key3": 500 } +// ?key={"x":0,"y":50}&key2=[1,2,true] +// produces { "key": { "x": 0, "y": 0 }, "key2": [1, 2, true] } +function getQueryArgs() { + var args = {}; + if (location.search.length) { + var params = location.search.substr(1).split("&"); + for (var p of params) { + var [k, v] = p.split("="); + args[k] = JSON.parse(v); + } + } + return args; +} + +// An async function that inserts a script element with the given URI into +// the head of the document of the given window. This function returns when +// the load or error event fires on the script element, indicating completion. +async function injectScript(aScript, aWindow = window) { + var e = aWindow.document.createElement("script"); + e.type = "text/javascript"; + let loadPromise = new Promise((resolve, reject) => { + e.onload = function () { + resolve(); + }; + e.onerror = function () { + dump("Script [" + aScript + "] errored out\n"); + reject(); + }; + }); + e.src = aScript; + aWindow.document.getElementsByTagName("head")[0].appendChild(e); + await loadPromise; +} + +// Compute some configuration information used for hit testing. +// The computed information is cached to avoid recomputing it +// each time this function is called. +// The computed information is an object with three fields: +// utils: the nsIDOMWindowUtils instance for this window +// isWindow: true if the platform is Windows +// activateAllScrollFrames: true if prefs indicate all scroll frames are +// activated with at least a minimal display port +function getHitTestConfig() { + if (!("hitTestConfig" in window)) { + var utils = SpecialPowers.getDOMWindowUtils(window); + var isWindows = getPlatform() == "windows"; + let activateAllScrollFrames = + SpecialPowers.getBoolPref("apz.wr.activate_all_scroll_frames") || + (SpecialPowers.getBoolPref( + "apz.wr.activate_all_scroll_frames_when_fission" + ) && + SpecialPowers.Services.appinfo.fissionAutostart); + + window.hitTestConfig = { + utils, + isWindows, + activateAllScrollFrames, + }; + } + return window.hitTestConfig; +} + +// Compute the coordinates of the center of the given element. The argument +// can either be a string (the id of the element desired) or the element +// itself. +function centerOf(element) { + if (typeof element === "string") { + element = document.getElementById(element); + } + var bounds = element.getBoundingClientRect(); + return { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height / 2 }; +} + +// Peform a compositor hit test at the given point and return the result. +// |point| is expected to be in CSS coordinates relative to the layout +// viewport, since this is what sendMouseEvent() expects. (Note that this +// is different from sendNativeMouseEvent() which expects screen coordinates +// relative to the screen.) +// The returned object has two fields: +// hitInfo: a combination of APZHitResultFlags +// scrollId: the view-id of the scroll frame that was hit +function hitTest(point) { + var utils = getHitTestConfig().utils; + dump("Hit-testing point (" + point.x + ", " + point.y + ")\n"); + utils.sendMouseEvent( + "MozMouseHittest", + point.x, + point.y, + 0, + 0, + 0, + true, + 0, + 0, + true, + true + ); + var data = utils.getCompositorAPZTestData(); + ok( + data.hitResults.length >= 1, + "Expected at least one hit result in the APZTestData" + ); + var result = data.hitResults[data.hitResults.length - 1]; + return { + hitInfo: result.hitResult, + scrollId: result.scrollId, + layersId: result.layersId, + }; +} + +// Returns a canonical stringification of the hitInfo bitfield. +function hitInfoToString(hitInfo) { + var strs = []; + for (var flag in APZHitResultFlags) { + if ((hitInfo & APZHitResultFlags[flag]) != 0) { + strs.push(flag); + } + } + if (!strs.length) { + return "INVISIBLE"; + } + strs.sort(function (a, b) { + return APZHitResultFlags[a] - APZHitResultFlags[b]; + }); + return strs.join(" | "); +} + +// Takes an object returned by hitTest, along with the expected values, and +// asserts that they match. Notably, it uses hitInfoToString to provide a +// more useful message for the case that the hit info doesn't match +function checkHitResult( + hitResult, + expectedHitInfo, + expectedScrollId, + expectedLayersId, + desc +) { + is( + hitInfoToString(hitResult.hitInfo), + hitInfoToString(expectedHitInfo), + desc + " hit info" + ); + is(hitResult.scrollId, expectedScrollId, desc + " scrollid"); + is(hitResult.layersId, expectedLayersId, desc + " layersid"); +} + +// Symbolic constants used by hitTestScrollbar(). +var ScrollbarTrackLocation = { + START: 1, + END: 2, +}; +var LayerState = { + ACTIVE: 1, + INACTIVE: 2, +}; + +// Perform a hit test on the scrollbar(s) of a scroll frame. +// This function takes a single argument which is expected to be +// an object with the following fields: +// element: The scroll frame to perform the hit test on. +// directions: The direction(s) of scrollbars to test. +// If directions.vertical is true, the vertical scrollbar will be tested. +// If directions.horizontal is true, the horizontal scrollbar will be tested. +// Both may be true in a single call (in which case two tests are performed). +// expectedScrollId: The scroll id that is expected to be hit, if activateAllScrollFrames is false. +// expectedLayersId: The layers id that is expected to be hit. +// trackLocation: One of ScrollbarTrackLocation.{START, END}. +// Determines which end of the scrollbar track is targeted. +// expectThumb: Whether the scrollbar thumb is expected to be present +// at the targeted end of the scrollbar track. +// layerState: Whether the scroll frame is active or inactive. +// The function performs the hit tests and asserts that the returned +// hit test information is consistent with the passed parameters. +// There is no return value. +// Tests that use this function must set the pref +// "layout.scrollbars.always-layerize-track". +function hitTestScrollbar(params) { + var config = getHitTestConfig(); + + var elem = params.element; + + var boundingClientRect = elem.getBoundingClientRect(); + + var verticalScrollbarWidth = boundingClientRect.width - elem.clientWidth; + var horizontalScrollbarHeight = boundingClientRect.height - elem.clientHeight; + + // On windows, the scrollbar tracks have buttons on the end. When computing + // coordinates for hit-testing we need to account for this. We assume the + // buttons are square, and so can use the scrollbar width/height to estimate + // the size of the buttons + var scrollbarArrowButtonHeight = config.isWindows + ? verticalScrollbarWidth + : 0; + var scrollbarArrowButtonWidth = config.isWindows + ? horizontalScrollbarHeight + : 0; + + // Compute the expected hit result flags. + // The direction flag (APZHitResultFlags.SCROLLBAR_VERTICAL) is added in + // later, for the vertical test only. + // The APZHitResultFlags.SCROLLBAR flag will be present regardless of whether + // the layer is active or inactive because we force layerization of scrollbar + // tracks. Unfortunately not forcing the layerization results in different + // behaviour on different platforms which makes testing harder. + var expectedHitInfo = APZHitResultFlags.VISIBLE | APZHitResultFlags.SCROLLBAR; + if (params.expectThumb) { + // The thumb has listeners which are APZ-aware. + expectedHitInfo |= APZHitResultFlags.APZ_AWARE_LISTENERS; + var expectActive = + config.activateAllScrollFrames || params.layerState == LayerState.ACTIVE; + if (!expectActive) { + expectedHitInfo |= APZHitResultFlags.INACTIVE_SCROLLFRAME; + } + // We do not generate the layers for thumbs on inactive scrollframes. + if (expectActive) { + expectedHitInfo |= APZHitResultFlags.SCROLLBAR_THUMB; + } + } + + var expectedScrollId = params.expectedScrollId; + if (config.activateAllScrollFrames) { + expectedScrollId = config.utils.getViewId(params.element); + if (params.layerState == LayerState.ACTIVE) { + is( + expectedScrollId, + params.expectedScrollId, + "Expected scrollId for active scrollframe should match" + ); + } + } + + var scrollframeMsg = + params.layerState == LayerState.ACTIVE + ? "active scrollframe" + : "inactive scrollframe"; + + // Hit-test the targeted areas, assuming we don't have overlay scrollbars + // with zero dimensions. + if (params.directions.vertical && verticalScrollbarWidth > 0) { + var verticalScrollbarPoint = { + x: boundingClientRect.right - verticalScrollbarWidth / 2, + y: + params.trackLocation == ScrollbarTrackLocation.START + ? boundingClientRect.y + scrollbarArrowButtonHeight + 5 + : boundingClientRect.bottom - + horizontalScrollbarHeight - + scrollbarArrowButtonHeight - + 5, + }; + checkHitResult( + hitTest(verticalScrollbarPoint), + expectedHitInfo | APZHitResultFlags.SCROLLBAR_VERTICAL, + expectedScrollId, + params.expectedLayersId, + scrollframeMsg + " - vertical scrollbar" + ); + } + + if (params.directions.horizontal && horizontalScrollbarHeight > 0) { + var horizontalScrollbarPoint = { + x: + params.trackLocation == ScrollbarTrackLocation.START + ? boundingClientRect.x + scrollbarArrowButtonWidth + 5 + : boundingClientRect.right - + verticalScrollbarWidth - + scrollbarArrowButtonWidth - + 5, + y: boundingClientRect.bottom - horizontalScrollbarHeight / 2, + }; + checkHitResult( + hitTest(horizontalScrollbarPoint), + expectedHitInfo, + expectedScrollId, + params.expectedLayersId, + scrollframeMsg + " - horizontal scrollbar" + ); + } +} + +// Return a list of prefs for the given test identifier. +function getPrefs(ident) { + switch (ident) { + case "TOUCH_EVENTS:PAN": + return [ + // Dropping the touch slop to 0 makes the tests easier to write because + // we can just do a one-pixel drag to get over the pan threshold rather + // than having to hard-code some larger value. + ["apz.touch_start_tolerance", "0.0"], + // The touchstart from the drag can turn into a long-tap if the touch-move + // events get held up. Try to prevent that by making long-taps require + // a 10 second hold. Note that we also cannot enable chaos mode on this + // test for this reason, since chaos mode can cause the long-press timer + // to fire sooner than the pref dictates. + ["ui.click_hold_context_menus.delay", 10000], + // The subtests in this test do touch-drags to pan the page, but we don't + // want those pans to turn into fling animations, so we increase the + // fling min velocity requirement absurdly high. + ["apz.fling_min_velocity_threshold", "10000"], + // The helper_div_pan's div gets a displayport on scroll, but if the + // test takes too long the displayport can expire before the new scroll + // position is synced back to the main thread. So we disable displayport + // expiry for these tests. + ["apz.displayport_expiry_ms", 0], + // We need to disable touch resampling during these tests because we + // rely on touch move events being processed without delay. Touch + // resampling only processes them once vsync fires. + ["android.touch_resampling.enabled", false], + ]; + case "TOUCH_ACTION": + return [ + ...getPrefs("TOUCH_EVENTS:PAN"), + ["apz.test.fails_with_native_injection", getPlatform() == "windows"], + ]; + default: + return []; + } +} + +var ApzCleanup = { + _cleanups: [], + + register(func) { + if (!this._cleanups.length) { + if (!window.isApzSubtest) { + SimpleTest.registerCleanupFunction(this.execute.bind(this)); + } // else ApzCleanup.execute is called from runSubtestsSeriallyInFreshWindows + } + this._cleanups.push(func); + }, + + execute() { + while (this._cleanups.length) { + var func = this._cleanups.pop(); + try { + func(); + } catch (ex) { + SimpleTest.ok( + false, + "Subtest cleanup function [" + + func.toString() + + "] threw exception [" + + ex + + "] on page [" + + location.href + + "]" + ); + } + } + }, +}; + +/** + * Returns a promise that will resolve if `eventTarget` receives an event of the + * given type that passes the given filter. Only the first matching message is + * used. The filter must be a function (or null); it is called with the event + * object and the call must return true to resolve the promise. + */ +function promiseOneEvent(eventTarget, eventType, filter) { + return new Promise((resolve, reject) => { + eventTarget.addEventListener(eventType, function listener(e) { + let success = false; + if (filter == null) { + success = true; + } else if (typeof filter == "function") { + try { + success = filter(e); + } catch (ex) { + dump( + `ERROR: Filter passed to promiseOneEvent threw exception: ${ex}\n` + ); + reject(); + return; + } + } else { + dump( + "ERROR: Filter passed to promiseOneEvent was neither null nor a function\n" + ); + reject(); + return; + } + if (success) { + eventTarget.removeEventListener(eventType, listener); + resolve(e); + } + }); + }); +} + +function visualViewportAsZoomedRect() { + let vv = window.visualViewport; + return { + x: vv.pageLeft, + y: vv.pageTop, + w: vv.width, + h: vv.height, + z: vv.scale, + }; +} + +// Pulls the latest compositor APZ test data and checks to see if the +// scroller with id `scrollerId` was checkerboarding. It also ensures that +// a scroller with id `scrollerId` was actually found in the test data. +// This function requires that "apz.test.logging_enabled" be set to true, +// in order for the test data to be logged. +function assertNotCheckerboarded(utils, scrollerId, msgPrefix) { + utils.advanceTimeAndRefresh(0); + var data = utils.getCompositorAPZTestData(); + //dump(JSON.stringify(data, null, 4)); + var found = false; + for (apzcData of data.additionalData) { + if (apzcData.key == scrollerId) { + var checkerboarding = apzcData.value + .split(",") + .includes("checkerboarding"); + ok(!checkerboarding, `${msgPrefix}: scroller is not checkerboarding`); + found = true; + } + } + ok(found, `${msgPrefix}: Found the scroller in the APZ data`); + utils.restoreNormalRefresh(); +} + +async function waitToClearOutAnyPotentialScrolls(aWindow) { + await promiseFrame(aWindow); + await promiseFrame(aWindow); + await promiseOnlyApzControllerFlushed(aWindow); + await promiseFrame(aWindow); + await promiseFrame(aWindow); +} + +function waitForScrollEvent(target) { + return new Promise(resolve => { + target.addEventListener("scroll", resolve, { once: true }); + }); +} + +// This is another variant of promiseApzFlushedRepaints. +// We need this function because, unfortunately, there is no easy way to use +// paint_listeners.js' functions and apz_test_utils.js' functions in popup +// contents opened by extensions either as scripts in the popup contents or +// scripts inside SpecialPowers.spawn because we can't use privileged functions +// in the popup contents' script, we can't use functions basically as it as in +// the sandboxed context either. +async function promiseApzFlushedRepaintsInPopup(popup) { + // Flush APZ repaints and waits for MozAfterPaint. + await SpecialPowers.spawn(popup, [], async () => { + const utils = SpecialPowers.getDOMWindowUtils(content.window); + + async function promiseAllPaintsDone() { + return new Promise(resolve => { + function waitForPaints() { + if (utils.isMozAfterPaintPending) { + dump("Waits for a MozAfterPaint event\n"); + content.window.addEventListener( + "MozAfterPaint", + () => { + dump("Got a MozAfterPaint event\n"); + waitForPaints(); + }, + { once: true } + ); + } else { + dump("No more pending MozAfterPaint\n"); + content.window.setTimeout(resolve, 0); + } + } + waitForPaints(); + }); + } + await promiseAllPaintsDone(); + + await new Promise(resolve => { + var repaintDone = function () { + dump("APZ flush done\n"); + SpecialPowers.Services.obs.removeObserver( + repaintDone, + "apz-repaints-flushed" + ); + content.window.setTimeout(resolve, 0); + }; + SpecialPowers.Services.obs.addObserver( + repaintDone, + "apz-repaints-flushed" + ); + if (utils.flushApzRepaints()) { + dump("Flushed APZ repaints, waiting for callback...\n"); + } else { + dump( + "Flushing APZ repaints was a no-op, triggering callback directly...\n" + ); + repaintDone(); + } + }); + + await promiseAllPaintsDone(); + }); +} + +// A utility function to make sure there's no scroll animation on the given +// |aElement|. +async function cancelScrollAnimation(aElement, aWindow = window) { + // In fact there's no good way to directly cancel the active animation on the + // element, so we destroy the corresponding scrollable frame then reconstruct + // a new scrollable frame so that it clobbers the animation. + const originalStyle = aElement.style.display; + aElement.style.display = "none"; + await aWindow.promiseApzFlushedRepaints(); + aElement.style.display = originalStyle; + await aWindow.promiseApzFlushedRepaints(); +} + +function collectSampledScrollOffsets(aElement) { + let data = SpecialPowers.DOMWindowUtils.getCompositorAPZTestData(); + let sampledResults = data.sampledResults; + + const layersId = SpecialPowers.DOMWindowUtils.getLayersId(); + const scrollId = SpecialPowers.DOMWindowUtils.getViewId(aElement); + + return sampledResults.filter( + result => + SpecialPowers.wrap(result).layersId == layersId && + SpecialPowers.wrap(result).scrollId == scrollId + ); +} diff --git a/gfx/layers/apz/test/mochitest/browser.ini b/gfx/layers/apz/test/mochitest/browser.ini new file mode 100644 index 0000000000..577c663fab --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser.ini @@ -0,0 +1,64 @@ +[DEFAULT] +support-files = + apz_test_native_event_utils.js + apz_test_utils.js + helper_browser_test_utils.js + !/browser/base/content/test/forms/head.js + !/browser/components/extensions/test/browser/head.js + !/browser/components/extensions/test/browser/head_browserAction.js + +[browser_test_group_fission.js] +skip-if = + (os == 'win' && bits == 32) # Some subtests fail intermittently on Win7. + (os == 'linux' && bits == 64) # Bug 1773830 +support-files = + FissionTestHelperParent.sys.mjs + FissionTestHelperChild.sys.mjs + helper_fission_*.* + !/dom/animation/test/testcommon.js +[browser_test_select_zoom.js] +skip-if = (os == 'win') # bug 1495580 +support-files = + helper_test_select_zoom.html +[browser_test_select_popup_position.js] +support-files = + helper_test_select_popup_position.html + helper_test_select_popup_position_transformed_in_parent.html + helper_test_select_popup_position_zoomed.html +[browser_test_background_tab_load_scroll.js] +support-files = + helper_background_tab_load_scroll.html +[browser_test_background_tab_scroll.js] +skip-if = (toolkit == 'android') # wheel events not supported on mobile +support-files = + helper_background_tab_scroll.html +[browser_test_reset_scaling_zoom.js] +support-files = + helper_test_reset_scaling_zoom.html +[browser_test_scrollbar_in_extension_popup_window.js] +skip-if = + verify || os == 'linux' # Bug 1713052 +[browser_test_scrolling_in_extension_popup_window.js] +skip-if = + os == "mac" # Bug 1784759 +[browser_test_scrolling_on_inactive_scroller_in_extension_popup_window.js] +run-if = (os == 'mac') # bug 1700805 +[browser_test_scroll_thumb_dragging.js] +support-files = + helper_scroll_thumb_dragging.html +[browser_test_autoscrolling_in_extension_popup_window.js] +[browser_test_autoscrolling_in_oop_frame.js] +skip-if = !fission +support-files = + helper_test_autoscrolling_in_oop_frame.html +[browser_test_animations_without_apz_sampler.js] +[browser_test_position_sticky.js] +support-files = + helper_position_sticky_flicker.html +[browser_test_tab_drag_zoom.js] +skip-if = (os == 'win') # Our Windows touch injection test code doesn't support pinch gestures (bug 1495580) +support-files = + helper_test_tab_drag_zoom.html +[browser_test_content_response_timeout.js] +support-files = + helper_content_response_timeout.html diff --git a/gfx/layers/apz/test/mochitest/browser_test_animations_without_apz_sampler.js b/gfx/layers/apz/test/mochitest/browser_test_animations_without_apz_sampler.js new file mode 100644 index 0000000000..8fdd20887a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser_test_animations_without_apz_sampler.js @@ -0,0 +1,134 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/extensions/test/browser/head.js", + this +); +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/extensions/test/browser/head_browserAction.js", + this +); + +add_task(async () => { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + browser_action: { + default_popup: "popup.html", + browser_style: true, + }, + }, + + files: { + "popup.html": ` + + + + + + +
+
+ + `, + }, + }); + + await extension.startup(); + + async function takeSnapshot(browserWin, callback) { + let browser = await openBrowserActionPanel(extension, browserWin, true); + + if (callback) { + await SpecialPowers.spawn(browser, [], callback); + } + + // Ensure there's no pending paint requests. + // The below code is a simplified version of promiseAllPaintsDone in + // paint_listener.js. + await SpecialPowers.spawn(browser, [], async () => { + return new Promise(resolve => { + function waitForPaints() { + // Wait until paint suppression has ended + if (SpecialPowers.DOMWindowUtils.paintingSuppressed) { + dump`waiting for paint suppression to end...`; + content.window.setTimeout(waitForPaints, 0); + return; + } + + if (SpecialPowers.DOMWindowUtils.isMozAfterPaintPending) { + dump`waiting for paint...`; + content.window.addEventListener("MozAfterPaint", waitForPaints, { + once: true, + }); + return; + } + resolve(); + } + waitForPaints(); + }); + }); + + const snapshot = await SpecialPowers.spawn(browser, [], async () => { + return SpecialPowers.snapshotWindowWithOptions( + content.window, + undefined /* use the default rect */, + undefined /* use the default bgcolor */, + { DRAWWINDOW_DRAW_VIEW: true } /* to capture scrollbars */ + ) + .toDataURL() + .toString(); + }); + + const popup = getBrowserActionPopup(extension, browserWin); + await closeBrowserAction(extension, browserWin); + is(popup.state, "closed", "browserAction popup has been closed"); + + return snapshot; + } + + // Test without apz sampler. + await SpecialPowers.pushPrefEnv({ set: [["apz.popups.enabled", false]] }); + + // Reference + const newWin = await BrowserTestUtils.openNewBrowserWindow(); + const reference = await takeSnapshot(newWin); + await BrowserTestUtils.closeWindow(newWin); + + // Test target + const testWin = await BrowserTestUtils.openNewBrowserWindow(); + const result = await takeSnapshot(testWin, async () => { + let div = content.window.document.getElementById("target"); + const anim = div.animate({ opacity: [1, 0.5] }, 10); + await anim.finished; + const anim2 = div.animate( + { transform: ["translateX(10px)", "translateX(20px)"] }, + 10 + ); + await anim2.finished; + + let div2 = content.window.document.getElementById("target2"); + const anim3 = div2.animate( + { transform: ["translateX(10px)", "translateX(20px)"] }, + 10 + ); + await anim3.finished; + }); + await BrowserTestUtils.closeWindow(testWin); + + is(result, reference, "The omta property value should be reset"); + + await extension.unload(); +}); diff --git a/gfx/layers/apz/test/mochitest/browser_test_autoscrolling_in_extension_popup_window.js b/gfx/layers/apz/test/mochitest/browser_test_autoscrolling_in_extension_popup_window.js new file mode 100644 index 0000000000..911af7548d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser_test_autoscrolling_in_extension_popup_window.js @@ -0,0 +1,189 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/extensions/test/browser/head.js", + this +); +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/extensions/test/browser/head_browserAction.js", + this +); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_utils.js", + this +); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js", + this +); + +add_task(async () => { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + browser_action: { + default_popup: "popup.html", + browser_style: true, + }, + }, + + files: { + "popup.html": ` + + + + + + + +
    +
  • 1
  • +
  • 2
  • +
  • 3
  • +
  • 4
  • +
  • 5
  • +
  • 6
  • +
  • 7
  • +
  • 8
  • +
  • 9
  • +
  • 10
  • +
+ + `, + "popup.js": function () { + window.addEventListener( + "mousemove", + () => { + dump("Got a mousemove event in the popup content document\n"); + browser.test.sendMessage("received-mousemove"); + }, + { once: true } + ); + window.addEventListener( + "scroll", + () => { + dump("Got a scroll event in the popup content document\n"); + browser.test.sendMessage("received-scroll"); + }, + { once: true } + ); + }, + }, + }); + + await extension.startup(); + + await SpecialPowers.pushPrefEnv({ set: [["apz.popups.enabled", true]] }); + + // Open the popup window of the extension. + const browserForPopup = await openBrowserActionPanel( + extension, + undefined, + true + ); + + if (!browserForPopup.isRemoteBrowser) { + await closeBrowserAction(extension); + await extension.unload(); + ok( + true, + "Skipping this test since the popup window doesn't have remote contents" + ); + return; + } + + // Flush APZ repaints and waits for MozAfterPaint to make sure APZ state is + // stable. + await promiseApzFlushedRepaintsInPopup(browserForPopup); + + const { screenX, screenY, viewId, presShellId } = await SpecialPowers.spawn( + browserForPopup, + [], + () => { + const winUtils = SpecialPowers.getDOMWindowUtils(content.window); + return { + screenX: content.window.mozInnerScreenX * content.devicePixelRatio, + screenY: content.window.mozInnerScreenY * content.devicePixelRatio, + viewId: winUtils.getViewId(content.document.documentElement), + presShellId: winUtils.getPresShellId(), + }; + } + ); + + // Before starting autoscroll we need to make sure a mousemove event has been + // processed in the popup content so that subsequent mousemoves for autoscroll + // will be properly processed in autoscroll animation. + const mousemoveEventPromise = extension.awaitMessage("received-mousemove"); + + const nativeMouseEventPromise = promiseNativeMouseEventWithAPZ({ + type: "mousemove", + target: browserForPopup, + offsetX: 100, + offsetY: 50, + }); + + await Promise.all([nativeMouseEventPromise, mousemoveEventPromise]); + + const scrollEventPromise = extension.awaitMessage("received-scroll"); + + // Start autoscrolling. + ok( + browserForPopup.browsingContext.startApzAutoscroll( + screenX + 100, + screenY + 50, + viewId, + presShellId + ) + ); + + // Send sequential mousemove events to cause autoscrolling. + for (let i = 0; i < 10; i++) { + await promiseNativeMouseEventWithAPZ({ + type: "mousemove", + target: browserForPopup, + offsetX: 100, + offsetY: 50 + i * 10, + }); + } + + // Flush APZ repaints and waits for MozAfterPaint to make sure the scroll has + // been reflected on the main thread. + const apzPromise = promiseApzFlushedRepaintsInPopup(browserForPopup); + + await Promise.all([apzPromise, scrollEventPromise]); + + const scrollY = await SpecialPowers.spawn(browserForPopup, [], () => { + return content.window.scrollY; + }); + ok(scrollY > 0, "Autoscrolling works in the popup window"); + + browserForPopup.browsingContext.stopApzAutoscroll(viewId, presShellId); + + await closeBrowserAction(extension); + + await extension.unload(); +}); diff --git a/gfx/layers/apz/test/mochitest/browser_test_autoscrolling_in_oop_frame.js b/gfx/layers/apz/test/mochitest/browser_test_autoscrolling_in_oop_frame.js new file mode 100644 index 0000000000..26d0ff6109 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser_test_autoscrolling_in_oop_frame.js @@ -0,0 +1,120 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_utils.js", + this +); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js", + this +); + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + ["general.autoScroll", true], + ["middlemouse.contentLoadURL", false], + ["test.events.async.enabled", true], + ], + }); +}); + +async function doTest() { + function httpURL(filename) { + const chromeURL = getRootDirectory(gTestPath) + filename; + return chromeURL.replace( + "chrome://mochitests/content/", + "http://mochi.test:8888/" + ); + } + + function getScrollY(context) { + return SpecialPowers.spawn(context, [], () => content.scrollY); + } + + const pageUrl = httpURL("helper_test_autoscrolling_in_oop_frame.html"); + + await BrowserTestUtils.withNewTab(pageUrl, async function (browser) { + await promiseApzFlushedRepaintsInPopup(browser); + + const iframeContext = browser.browsingContext.children[0]; + await promiseApzFlushedRepaintsInPopup(iframeContext); + + const { screenX, screenY, viewId, presShellId } = await SpecialPowers.spawn( + iframeContext, + [], + () => { + const winUtils = SpecialPowers.getDOMWindowUtils(content); + return { + screenX: content.mozInnerScreenX * content.devicePixelRatio, + screenY: content.mozInnerScreenY * content.devicePixelRatio, + viewId: winUtils.getViewId(content.document.documentElement), + presShellId: winUtils.getPresShellId(), + }; + } + ); + + ok( + iframeContext.startApzAutoscroll( + screenX + 100, + screenY + 50, + viewId, + presShellId + ), + "Started autscroll" + ); + + const scrollEventPromise = SpecialPowers.spawn( + iframeContext, + [], + async () => { + return new Promise(resolve => { + content.addEventListener( + "scroll", + event => { + dump("Got a scroll event in the iframe\n"); + resolve(); + }, + { once: true } + ); + }); + } + ); + + // Send sequential mousemove events to cause autoscrolling. + for (let i = 0; i < 10; i++) { + await promiseNativeMouseEventWithAPZ({ + type: "mousemove", + target: browser, + offsetX: 100, + offsetY: 50 + i * 10, + }); + } + + // Flush APZ repaints and waits for MozAfterPaint to make sure the scroll has + // been reflected on the main thread. + const apzPromise = promiseApzFlushedRepaintsInPopup(browser); + + await Promise.all([apzPromise, scrollEventPromise]); + + const frameScrollY = await getScrollY(iframeContext); + ok(frameScrollY > 0, "Autoscrolled the iframe"); + + const rootScrollY = await getScrollY(browser); + ok(rootScrollY == 0, "Didn't scroll the root document"); + + iframeContext.stopApzAutoscroll(viewId, presShellId); + }); +} + +add_task(async function test_autoscroll_in_oop_iframe() { + await doTest(); +}); + +add_task(async function test_autoscroll_in_oop_iframe_with_os_zoom() { + await SpecialPowers.pushPrefEnv({ set: [["ui.textScaleFactor", 200]] }); + await doTest(); +}); diff --git a/gfx/layers/apz/test/mochitest/browser_test_background_tab_load_scroll.js b/gfx/layers/apz/test/mochitest/browser_test_background_tab_load_scroll.js new file mode 100644 index 0000000000..9878907603 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser_test_background_tab_load_scroll.js @@ -0,0 +1,117 @@ +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_utils.js", + this +); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js", + this +); + +add_task(async function test_main() { + // Open a specific page in a background tab, then switch to the tab, check if + // the visual and layout scroll offsets have diverged. + // Then change to another tab so it's background again. Then reload it. Then + // change back to it and check again if the visual and layout scroll offsets + // have diverged. + // The page has a couple important properties to trigger the bug. We need to + // be restoring a non-zero scroll position so that we call ScrollToImpl with + // origin restore so that we do not set the visual viewport offset. We then + // need to call ScrollToImpl with a origin that does not get clobber by apz + // so that we (wrongly) set the visual viewport offset. + + requestLongerTimeout(2); + + async function twoRafsInContent(browser) { + await SpecialPowers.spawn(browser, [], async function () { + await new Promise(r => + content.requestAnimationFrame(() => content.requestAnimationFrame(r)) + ); + }); + } + + async function waitForApzInContent(browser) { + await SpecialPowers.spawn(browser, [], async () => { + await content.wrappedJSObject.waitUntilApzStable(); + await content.wrappedJSObject.promiseApzFlushedRepaints(); + }); + } + + async function checkScrollPosInContent(browser, iter, num) { + let visualScrollPos = await SpecialPowers.spawn(browser, [], function () { + const offsetX = {}; + const offsetY = {}; + SpecialPowers.getDOMWindowUtils(content).getVisualViewportOffset( + offsetX, + offsetY + ); + return offsetY.value; + }); + + let scrollPos = await SpecialPowers.spawn(browser, [], function () { + return content.window.scrollY; + }); + + // When this fails the difference is at least 10000. + ok( + Math.abs(scrollPos - visualScrollPos) < 2, + "expect scroll position and visual scroll position to be the same: visual " + + visualScrollPos + + " scroll " + + scrollPos + + " (" + + iter + + "," + + num + + ")" + ); + } + + for (let i = 0; i < 5; i++) { + let blankurl = "about:blank"; + let blankTab = BrowserTestUtils.addTab(gBrowser, blankurl); + let blankbrowser = blankTab.linkedBrowser; + await BrowserTestUtils.browserLoaded(blankbrowser, false, blankurl); + + let url = + "http://mochi.test:8888/browser/gfx/layers/apz/test/mochitest/helper_background_tab_load_scroll.html"; + let backgroundTab = BrowserTestUtils.addTab(gBrowser, url); + let browser = backgroundTab.linkedBrowser; + await BrowserTestUtils.browserLoaded(browser, false, url); + dump("Done loading background tab\n"); + + await twoRafsInContent(browser); + + // Switch to the foreground. + await BrowserTestUtils.switchTab(gBrowser, backgroundTab); + dump("Switched background tab to foreground\n"); + + await waitForApzInContent(browser); + + await checkScrollPosInContent(browser, i, 1); + + await BrowserTestUtils.switchTab(gBrowser, blankTab); + + browser.reload(); + await BrowserTestUtils.browserLoaded(browser, false, url); + + await twoRafsInContent(browser); + + // Switch to the foreground. + await BrowserTestUtils.switchTab(gBrowser, backgroundTab); + dump("Switched background tab to foreground\n"); + + await waitForApzInContent(browser); + + await checkScrollPosInContent(browser, i, 2); + + // Cleanup + let tabClosed = BrowserTestUtils.waitForTabClosing(backgroundTab); + BrowserTestUtils.removeTab(backgroundTab); + await tabClosed; + + let blanktabClosed = BrowserTestUtils.waitForTabClosing(blankTab); + BrowserTestUtils.removeTab(blankTab); + await blanktabClosed; + } +}); diff --git a/gfx/layers/apz/test/mochitest/browser_test_background_tab_scroll.js b/gfx/layers/apz/test/mochitest/browser_test_background_tab_scroll.js new file mode 100644 index 0000000000..4ce8200199 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser_test_background_tab_scroll.js @@ -0,0 +1,66 @@ +add_task(async function test_main() { + // Load page in the background. This will cause the first-paint of the + // tab (which has ScrollPositionUpdate instances) to get sent to the + // compositor, but the parent process RefLayer won't be pointing to this + // tab so APZ never sees the ScrollPositionUpdate instances. + + let url = + "http://mochi.test:8888/browser/gfx/layers/apz/test/mochitest/helper_background_tab_scroll.html#scrolltarget"; + let backgroundTab = BrowserTestUtils.addTab(gBrowser, url); + let browser = backgroundTab.linkedBrowser; + await BrowserTestUtils.browserLoaded(browser, false, url); + dump("Done loading background tab\n"); + + // Switch to the foreground, to let the APZ tree get built. + await BrowserTestUtils.switchTab(gBrowser, backgroundTab); + dump("Switched background tab to foreground\n"); + + // Verify main-thread scroll position is where we expect + let scrollPos = await ContentTask.spawn(browser, null, function () { + return content.window.scrollY; + }); + is(scrollPos, 5000, "Expected background tab to be at scroll pos 5000"); + + // Trigger an APZ-side scroll via native wheel event, followed by some code + // to ensure APZ's repaint requests to arrive at the main-thread. If + // things are working properly, the main thread will accept the repaint + // requests and update the main-thread scroll position. If the APZ side + // is sending incorrect scroll generations in the repaint request, then + // the main thread will fail to clear the main-thread scroll origin (which + // was set by the scroll to the #scrolltarget anchor), and so will not + // accept APZ's scroll position updates. + let contentScrollFunction = async function () { + await content.window.wrappedJSObject.promiseNativeWheelAndWaitForWheelEvent( + content.window, + 100, + 100, + 0, + 200 + ); + + // Advance some/all frames of the APZ wheel animation + let utils = content.window.SpecialPowers.getDOMWindowUtils(content.window); + for (var i = 0; i < 10; i++) { + utils.advanceTimeAndRefresh(16); + } + utils.restoreNormalRefresh(); + // Flush pending APZ repaints, then read the main-thread scroll + // position + await content.window.wrappedJSObject.promiseOnlyApzControllerFlushed( + content.window + ); + return content.window.scrollY; + }; + scrollPos = await ContentTask.spawn(browser, null, contentScrollFunction); + + // Verify main-thread scroll position has changed + ok( + scrollPos < 5000, + `Expected background tab to have scrolled up, is at ${scrollPos}` + ); + + // Cleanup + let tabClosed = BrowserTestUtils.waitForTabClosing(backgroundTab); + BrowserTestUtils.removeTab(backgroundTab); + await tabClosed; +}); diff --git a/gfx/layers/apz/test/mochitest/browser_test_content_response_timeout.js b/gfx/layers/apz/test/mochitest/browser_test_content_response_timeout.js new file mode 100644 index 0000000000..a80fd77c17 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser_test_content_response_timeout.js @@ -0,0 +1,88 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ + +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_utils.js", + this +); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js", + this +); + +add_task(async () => { + // Use pan gesture events for Mac. + await SpecialPowers.pushPrefEnv({ + set: [ + // Set a relatively shorter timeout value. + ["apz.content_response_timeout", 100], + ], + }); + + const URL_ROOT = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + "http://mochi.test:8888/" + ); + // Load a content having an APZ-aware listener causing 500ms busy state and + // a scroll event listener changing the background color of an element. + // The reason why we change the background color in a scroll listener rather + // than setting up a Promise resolved in a scroll event handler and waiting + // for the Promise is SpecialPowers.spawn doesn't allow it (bug 1743857). + const tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + URL_ROOT + "helper_content_response_timeout.html" + ); + + let scrollPromise = BrowserTestUtils.waitForContentEvent( + tab.linkedBrowser, + "scroll" + ); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + await content.wrappedJSObject.promiseApzFlushedRepaints(); + await content.wrappedJSObject.waitUntilApzStable(); + }); + + // Note that below function uses `WaitForObserver` version of sending a + // pan-start event function so that the notification can be sent in the parent + // process, thus we can get the notification even if the content process is + // busy. + await NativePanHandler.promiseNativePanEvent( + tab.linkedBrowser, + 100, + 100, + 0, + NativePanHandler.delta, + NativePanHandler.beginPhase + ); + + await new Promise(resolve => { + setTimeout(resolve, 200); + }); + + await NativePanHandler.promiseNativePanEvent( + tab.linkedBrowser, + 100, + 100, + 0, + NativePanHandler.delta, + NativePanHandler.updatePhase + ); + await NativePanHandler.promiseNativePanEvent( + tab.linkedBrowser, + 100, + 100, + 0, + 0, + NativePanHandler.endPhase + ); + + await scrollPromise; + ok(true, "We got at least one scroll event"); + + BrowserTestUtils.removeTab(tab); + await SpecialPowers.popPrefEnv(); +}); diff --git a/gfx/layers/apz/test/mochitest/browser_test_group_fission.js b/gfx/layers/apz/test/mochitest/browser_test_group_fission.js new file mode 100644 index 0000000000..43bbcbe444 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser_test_group_fission.js @@ -0,0 +1,150 @@ +add_task(async function setup_pref() { + await SpecialPowers.pushPrefEnv({ + set: [ + // To avoid throttling requestAnimationFrame callbacks in invisible + // iframes + ["layout.throttled_frame_rate", 60], + ["dom.animations-api.getAnimations.enabled", true], + ["dom.animations-api.timelines.enabled", true], + // Next two prefs are needed for hit-testing to work + ["test.events.async.enabled", true], + ["apz.test.logging_enabled", true], + ], + }); +}); + +add_task(async function test_main() { + function httpURL(filename) { + let chromeURL = getRootDirectory(gTestPath) + filename; + return chromeURL.replace( + "chrome://mochitests/content/", + "http://mochi.test:8888/" + ); + } + + // Each of these subtests is a dictionary that contains: + // file (required): filename of the subtest that will get opened in a new tab + // in the top-level fission-enabled browser window. + // setup (optional): function that takes the top-level fission window and is + // run once after the subtest is loaded but before it is started. + var subtests = [ + { file: "helper_fission_basic.html" }, + { file: "helper_fission_transforms.html" }, + { file: "helper_fission_scroll_oopif.html" }, + { + file: "helper_fission_event_region_override.html", + setup(win) { + win.document.addEventListener("wheel", e => e.preventDefault(), { + once: true, + passive: false, + }); + }, + }, + { file: "helper_fission_animation_styling_in_oopif.html" }, + { file: "helper_fission_force_empty_hit_region.html" }, + { file: "helper_fission_touch.html" }, + { + file: "helper_fission_tap.html", + prefs: [["apz.max_tap_time", 10000]], + }, + { file: "helper_fission_inactivescroller_under_oopif.html" }, + { + file: "helper_fission_tap_on_zoomed.html", + prefs: [["apz.max_tap_time", 10000]], + }, + { + file: "helper_fission_tap_in_nested_iframe_on_zoomed.html", + prefs: [["apz.max_tap_time", 10000]], + }, + { file: "helper_fission_scroll_handoff.html" }, + { file: "helper_fission_large_subframe.html" }, + { file: "helper_fission_initial_displayport.html" }, + { file: "helper_fission_checkerboard_severity.html" }, + { file: "helper_fission_setResolution.html" }, + { file: "helper_fission_inactivescroller_positionedcontent.html" }, + { file: "helper_fission_irregular_areas.html" }, + { file: "helper_fission_animation_styling_in_transformed_oopif.html" }, + // add additional tests here + ]; + + // ccov builds run slower and need longer, so let's scale up the timeout + // by the number of tests we're running. + requestLongerTimeout(subtests.length); + + let fissionWindow = await BrowserTestUtils.openNewBrowserWindow({ + fission: true, + }); + + // We import the ESM here so that we can install functions on the class + // below. + const { FissionTestHelperParent } = ChromeUtils.importESModule( + getRootDirectory(gTestPath) + "FissionTestHelperParent.sys.mjs" + ); + FissionTestHelperParent.SimpleTest = SimpleTest; + + ChromeUtils.registerWindowActor("FissionTestHelper", { + parent: { + esModuleURI: + getRootDirectory(gTestPath) + "FissionTestHelperParent.sys.mjs", + }, + child: { + esModuleURI: + getRootDirectory(gTestPath) + "FissionTestHelperChild.sys.mjs", + events: { + "FissionTestHelper:Init": { capture: true, wantUntrusted: true }, + }, + }, + allFrames: true, + }); + + try { + var onlyOneSubtest = SpecialPowers.getCharPref( + "apz.subtest", + /*default = */ "" + ); + + for (var subtest of subtests) { + if (onlyOneSubtest && onlyOneSubtest != subtest.file) { + SimpleTest.ok( + true, + "Skipping " + + subtest.file + + " because only " + + onlyOneSubtest + + " is being run" + ); + continue; + } + let url = httpURL(subtest.file); + dump(`Starting test ${url}\n`); + + // Load the test URL and tell it to get started, and wait until it reports + // completion. + await BrowserTestUtils.withNewTab( + { gBrowser: fissionWindow.gBrowser, url }, + async browser => { + let tabActor = + browser.browsingContext.currentWindowGlobal.getActor( + "FissionTestHelper" + ); + let donePromise = tabActor.getTestCompletePromise(); + if (subtest.setup) { + subtest.setup(fissionWindow); + } + tabActor.startTest(); + await donePromise; + } + ); + + dump(`Finished test ${url}\n`); + } + } finally { + // Delete stuff we added to FissionTestHelperParent, beacuse the object will + // outlive this test, and leaving stuff on it may leak the things reachable + // from it. + delete FissionTestHelperParent.SimpleTest; + // Teardown + ChromeUtils.unregisterWindowActor("FissionTestHelper"); + await BrowserTestUtils.closeWindow(fissionWindow); + } +}); diff --git a/gfx/layers/apz/test/mochitest/browser_test_position_sticky.js b/gfx/layers/apz/test/mochitest/browser_test_position_sticky.js new file mode 100644 index 0000000000..ce6b093a80 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser_test_position_sticky.js @@ -0,0 +1,105 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_utils.js", + this +); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js", + this +); + +add_task(async () => { + function httpURL(filename) { + let chromeURL = getRootDirectory(gTestPath) + filename; + return chromeURL.replace( + "chrome://mochitests/content/", + "http://mochi.test:8888/" + ); + } + + const url = httpURL("helper_position_sticky_flicker.html"); + const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + + const { rect, scrollbarWidth } = await SpecialPowers.spawn( + tab.linkedBrowser, + [], + async () => { + const sticky = content.document.getElementById("sticky"); + + // Get the area in the screen coords where the position:sticky element is. + let stickyRect = sticky.getBoundingClientRect(); + stickyRect.x += content.window.mozInnerScreenX; + stickyRect.y += content.window.mozInnerScreenY; + + // generate some DIVs to make the page complex enough. + for (let i = 1; i <= 120000; i++) { + const div = content.document.createElement("div"); + div.innerText = `${i}`; + content.document.body.appendChild(div); + } + + await content.wrappedJSObject.promiseApzFlushedRepaints(); + await content.wrappedJSObject.waitUntilApzStable(); + + let w = {}, + h = {}; + SpecialPowers.DOMWindowUtils.getScrollbarSizes( + content.document.documentElement, + w, + h + ); + + // Reduce the scrollbar width from the sticky area. + stickyRect.width -= w.value; + return { + rect: stickyRect, + scrollbarWidth: w.value, + }; + } + ); + + // Take a snapshot where the position:sticky element is initially painted. + const reference = await getSnapshot(rect); + + let mouseX = window.innerWidth - scrollbarWidth / 2; + let mouseY = tab.linkedBrowser.getBoundingClientRect().y + 5; + + // Scroll fast to cause checkerboarding multiple times. + const dragFinisher = await promiseNativeMouseDrag( + window, + mouseX, + mouseY, + 0, + window.innerHeight, + 100 + ); + + // On debug builds there seems to be no chance that the content process gets + // painted during above promiseNativeMouseDrag call, wait two frames to make + // sure it happens so that this test is likely able to fail without proper + // fix. + if (AppConstants.DEBUG) { + await SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + await content.wrappedJSObject.promiseFrame(content.window); + await content.wrappedJSObject.promiseFrame(content.window); + }); + } + + // Take a snapshot again where the position:sticky element should be painted. + const snapshot = await getSnapshot(rect); + + await dragFinisher(); + + is( + snapshot, + reference, + "The position:sticky element should stay at the " + + "same place after scrolling on heavy load" + ); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/gfx/layers/apz/test/mochitest/browser_test_reset_scaling_zoom.js b/gfx/layers/apz/test/mochitest/browser_test_reset_scaling_zoom.js new file mode 100644 index 0000000000..168d358fcb --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser_test_reset_scaling_zoom.js @@ -0,0 +1,44 @@ +add_task(async function setup_pref() { + let isWindows = navigator.platform.indexOf("Win") == 0; + await SpecialPowers.pushPrefEnv({ + set: [["apz.test.fails_with_native_injection", isWindows]], + }); +}); + +add_task(async function test_main() { + function httpURL(filename) { + let chromeURL = getRootDirectory(gTestPath) + filename; + return chromeURL.replace( + "chrome://mochitests/content/", + "http://mochi.test:8888/" + ); + } + + const pageUrl = httpURL("helper_test_reset_scaling_zoom.html"); + + await BrowserTestUtils.withNewTab(pageUrl, async function (browser) { + let getResolution = function () { + return content.window.SpecialPowers.getDOMWindowUtils( + content.window + ).getResolution(); + }; + + let doZoomIn = async function () { + await content.window.wrappedJSObject.doZoomIn(); + }; + + let resolution = await ContentTask.spawn(browser, null, getResolution); + is(resolution, 1.0, "Initial page resolution should be 1.0"); + + await ContentTask.spawn(browser, null, doZoomIn); + resolution = await ContentTask.spawn(browser, null, getResolution); + isnot(resolution, 1.0, "Expected resolution to be bigger than 1.0"); + + document.getElementById("cmd_fullZoomReset").doCommand(); + // Spin the event loop once just to make sure the message gets through + await new Promise(resolve => setTimeout(resolve, 0)); + + resolution = await ContentTask.spawn(browser, null, getResolution); + is(resolution, 1.0, "Expected resolution to be reset to 1.0"); + }); +}); diff --git a/gfx/layers/apz/test/mochitest/browser_test_scroll_thumb_dragging.js b/gfx/layers/apz/test/mochitest/browser_test_scroll_thumb_dragging.js new file mode 100644 index 0000000000..bd2d733a32 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser_test_scroll_thumb_dragging.js @@ -0,0 +1,77 @@ +add_task(async function () { + function httpURL(filename) { + let chromeURL = getRootDirectory(gTestPath) + filename; + return chromeURL.replace( + "chrome://mochitests/content/", + "http://mochi.test:8888/" + ); + } + + const newWin = await BrowserTestUtils.openNewBrowserWindow(); + + const pageUrl = httpURL("helper_scroll_thumb_dragging.html"); + const tab = await BrowserTestUtils.openNewForegroundTab( + newWin.gBrowser, + pageUrl + ); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + await content.wrappedJSObject.promiseApzFlushedRepaints(); + await content.wrappedJSObject.waitUntilApzStable(); + }); + + // Send an explicit click event to make sure the new window accidentally + // doesn't get an "enter-notify-event" on Linux during dragging, the event + // forcibly cancels the dragging state. + await SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + // Creating an object in this content privilege so that the object + // properties can be accessed in below + // promiseNativeMouseEventWithAPZAndWaitForEvent function. + const moveParams = content.window.eval(`({ + target: window, + type: "mousemove", + offsetX: 10, + offsetY: 10 + })`); + const clickParams = content.window.eval(`({ + target: window, + type: "click", + offsetX: 10, + offsetY: 10 + })`); + // Send a mouse move event first to make sure the "enter-notify-event" + // happens. + await content.wrappedJSObject.promiseNativeMouseEventWithAPZAndWaitForEvent( + moveParams + ); + await content.wrappedJSObject.promiseNativeMouseEventWithAPZAndWaitForEvent( + clickParams + ); + }); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + const scrollPromise = new Promise(resolve => { + content.window.addEventListener("scroll", resolve, { once: true }); + }); + const dragFinisher = + await content.wrappedJSObject.promiseVerticalScrollbarDrag( + content.window, + 10, + 10 + ); + + await scrollPromise; + await dragFinisher(); + + await content.wrappedJSObject.promiseApzFlushedRepaints(); + }); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + ok( + content.window.scrollY < 100, + "The root scrollable content shouldn't be scrolled too much" + ); + }); + + await BrowserTestUtils.closeWindow(newWin); +}); diff --git a/gfx/layers/apz/test/mochitest/browser_test_scrollbar_in_extension_popup_window.js b/gfx/layers/apz/test/mochitest/browser_test_scrollbar_in_extension_popup_window.js new file mode 100644 index 0000000000..6e18129845 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser_test_scrollbar_in_extension_popup_window.js @@ -0,0 +1,138 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/extensions/test/browser/head.js", + this +); +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/extensions/test/browser/head_browserAction.js", + this +); + +add_task(async () => { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + browser_action: { + default_popup: "popup.html", + browser_style: true, + }, + }, + + files: { + "popup.html": ` + + + + + + +
    +
  • 1
  • +
  • 2
  • +
  • 3
  • +
  • 4
  • +
  • 5
  • +
  • 6
  • +
  • 7
  • +
  • 8
  • +
  • 9
  • +
  • 10
  • +
+ + `, + }, + }); + + await extension.startup(); + + async function takeSnapshot(browserWin) { + let browser = await openBrowserActionPanel(extension, browserWin, true); + + // Ensure there's no pending paint requests. + // The below code is a simplified version of promiseAllPaintsDone in + // paint_listener.js. + await SpecialPowers.spawn(browser, [], async () => { + return new Promise(resolve => { + function waitForPaints() { + // Wait until paint suppression has ended + if (SpecialPowers.DOMWindowUtils.paintingSuppressed) { + dump`waiting for paint suppression to end...`; + content.window.setTimeout(waitForPaints, 0); + return; + } + + if (SpecialPowers.DOMWindowUtils.isMozAfterPaintPending) { + dump`waiting for paint...`; + content.window.addEventListener("MozAfterPaint", waitForPaints, { + once: true, + }); + return; + } + resolve(); + } + waitForPaints(); + }); + }); + + const snapshot = await SpecialPowers.spawn(browser, [], async () => { + return SpecialPowers.snapshotWindowWithOptions( + content.window, + undefined /* use the default rect */, + undefined /* use the default bgcolor */, + { DRAWWINDOW_DRAW_VIEW: true } /* to capture scrollbars */ + ) + .toDataURL() + .toString(); + }); + + const popup = getBrowserActionPopup(extension, browserWin); + await closeBrowserAction(extension, browserWin); + is(popup.state, "closed", "browserAction popup has been closed"); + + return snapshot; + } + + // First, take a snapshot with disabling APZ in the popup window, we assume + // scrollbars are rendered properly there. + await SpecialPowers.pushPrefEnv({ set: [["apz.popups.enabled", false]] }); + const newWin = await BrowserTestUtils.openNewBrowserWindow(); + const reference = await takeSnapshot(newWin); + await BrowserTestUtils.closeWindow(newWin); + + // Then take a snapshot with enabling APZ. + await SpecialPowers.pushPrefEnv({ set: [["apz.popups.enabled", true]] }); + const anotherWin = await BrowserTestUtils.openNewBrowserWindow(); + const test = await takeSnapshot(anotherWin); + await BrowserTestUtils.closeWindow(anotherWin); + + is( + test, + reference, + "Contents in popup window opened by extension should be same regardless of the APZ state in the window" + ); + + await extension.unload(); +}); diff --git a/gfx/layers/apz/test/mochitest/browser_test_scrolling_in_extension_popup_window.js b/gfx/layers/apz/test/mochitest/browser_test_scrolling_in_extension_popup_window.js new file mode 100644 index 0000000000..6da3f3311b --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser_test_scrolling_in_extension_popup_window.js @@ -0,0 +1,128 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/extensions/test/browser/head.js", + this +); +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/extensions/test/browser/head_browserAction.js", + this +); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_utils.js", + this +); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js", + this +); + +add_task(async () => { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + browser_action: { + default_popup: "popup.html", + browser_style: true, + }, + }, + + files: { + "popup.html": ` + + + + + + +
    +
  • 1
  • +
  • 2
  • +
  • 3
  • +
  • 4
  • +
  • 5
  • +
  • 6
  • +
  • 7
  • +
  • 8
  • +
  • 9
  • +
  • 10
  • +
+ + `, + }, + }); + + await extension.startup(); + + await SpecialPowers.pushPrefEnv({ set: [["apz.popups.enabled", true]] }); + + // Open the popup window of the extension. + const browserForPopup = await openBrowserActionPanel( + extension, + undefined, + true + ); + + // Flush APZ repaints and waits for MozAfterPaint to make sure APZ state is + // stable. + await promiseApzFlushedRepaintsInPopup(browserForPopup); + + const scrollEventPromise = SpecialPowers.spawn( + browserForPopup, + [], + async () => { + return new Promise(resolve => { + content.window.addEventListener( + "scroll", + event => { + dump("Got a scroll event in the popup content document\n"); + resolve(); + }, + { once: true } + ); + }); + } + ); + + // Send native mouse wheel to scroll the content in the popup. + await promiseNativeWheelAndWaitForObserver(browserForPopup, 50, 50, 0, -100); + + // Flush APZ repaints and waits for MozAfterPaint to make sure the scroll has + // been reflected on the main thread. + const apzPromise = promiseApzFlushedRepaintsInPopup(browserForPopup); + + await Promise.all([apzPromise, scrollEventPromise]); + + const scrollY = await SpecialPowers.spawn(browserForPopup, [], () => { + return content.window.scrollY; + }); + ok(scrollY > 0, "Mouse wheel scrolling works in the popup window"); + + await closeBrowserAction(extension); + + await extension.unload(); +}); diff --git a/gfx/layers/apz/test/mochitest/browser_test_scrolling_on_inactive_scroller_in_extension_popup_window.js b/gfx/layers/apz/test/mochitest/browser_test_scrolling_on_inactive_scroller_in_extension_popup_window.js new file mode 100644 index 0000000000..99dccd458c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser_test_scrolling_on_inactive_scroller_in_extension_popup_window.js @@ -0,0 +1,137 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/extensions/test/browser/head.js", + this +); +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/extensions/test/browser/head_browserAction.js", + this +); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_utils.js", + this +); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js", + this +); + +add_task(async () => { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + browser_action: { + default_popup: "popup.html", + browser_style: true, + }, + }, + + files: { + "popup.html": ` + + + + + + +
+
123
+
123
+
+ + `, + }, + }); + + await extension.startup(); + + await SpecialPowers.pushPrefEnv({ + set: [ + ["apz.popups.enabled", true], + ["apz.wr.activate_all_scroll_frames", false], + ["apz.wr.activate_all_scroll_frames_when_fission", false], + ], + }); + + // Open the popup window of the extension. + const browserForPopup = await openBrowserActionPanel( + extension, + undefined, + true + ); + + // Flush APZ repaints and waits for MozAfterPaint to make sure APZ state is + // stable. + await promiseApzFlushedRepaintsInPopup(browserForPopup); + + // A Promise to wait for one scroll event for each scrollable element. + const scrollEventsPromise = SpecialPowers.spawn(browserForPopup, [], () => { + let promises = []; + content.document.querySelectorAll(".overflow").forEach(element => { + let promise = new Promise(resolve => { + element.addEventListener( + "scroll", + () => { + resolve(); + }, + { once: true } + ); + }); + promises.push(promise); + }); + return Promise.all(promises); + }); + + // Send two native mouse wheel events to scroll each scrollable element in the + // popup. + await promiseNativeWheelAndWaitForObserver(browserForPopup, 50, 50, 0, -100); + await promiseNativeWheelAndWaitForObserver(browserForPopup, 150, 50, 0, -100); + + // Flush APZ repaints and waits for MozAfterPaint to make sure the scroll has + // been reflected on the main thread. + const apzPromise = promiseApzFlushedRepaintsInPopup(browserForPopup); + + await Promise.all([apzPromise, scrollEventsPromise]); + + const scrollTops = await SpecialPowers.spawn(browserForPopup, [], () => { + let result = []; + content.document.querySelectorAll(".overflow").forEach(element => { + result.push(element.scrollTop); + }); + return result; + }); + + ok(scrollTops[0] > 0, "Mouse wheel scrolling works in the popup window"); + ok(scrollTops[1] > 0, "Mouse wheel scrolling works in the popup window"); + + await closeBrowserAction(extension); + + await extension.unload(); +}); diff --git a/gfx/layers/apz/test/mochitest/browser_test_select_popup_position.js b/gfx/layers/apz/test/mochitest/browser_test_select_popup_position.js new file mode 100644 index 0000000000..08a6ec9b93 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser_test_select_popup_position.js @@ -0,0 +1,130 @@ +/* This test is a a mash up of + https://searchfox.org/mozilla-central/rev/559b25eb41c1cbffcb90a34e008b8288312fcd25/gfx/layers/apz/test/mochitest/browser_test_group_fission.js + https://searchfox.org/mozilla-central/rev/559b25eb41c1cbffcb90a34e008b8288312fcd25/gfx/layers/apz/test/mochitest/helper_basic_zoom.html + https://searchfox.org/mozilla-central/rev/559b25eb41c1cbffcb90a34e008b8288312fcd25/browser/base/content/test/forms/browser_selectpopup.js +*/ + +/* import-globals-from helper_browser_test_utils.js */ +Services.scriptloader.loadSubScript( + new URL("helper_browser_test_utils.js", gTestPath).href, + this +); + +async function runPopupPositionTest(parentDocumentFileName) { + function httpURL(filename) { + let chromeURL = getRootDirectory(gTestPath) + filename; + return chromeURL.replace( + "chrome://mochitests/content/", + "http://mochi.test:8888/" + ); + } + + function httpCrossOriginURL(filename) { + let chromeURL = getRootDirectory(gTestPath) + filename; + return chromeURL.replace( + "chrome://mochitests/content/", + "http://example.com/" + ); + } + + const pageUrl = httpURL(parentDocumentFileName); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl); + + // Load the OOP iframe. + const iframeUrl = httpCrossOriginURL( + "helper_test_select_popup_position.html" + ); + const iframe = await SpecialPowers.spawn( + tab.linkedBrowser, + [iframeUrl], + async url => { + const target = content.document.querySelector("iframe"); + target.src = url; + await new Promise(resolve => { + target.addEventListener("load", resolve, { once: true }); + }); + return target.browsingContext; + } + ); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + await content.wrappedJSObject.promiseApzFlushedRepaints(); + await content.wrappedJSObject.waitUntilApzStable(); + }); + + const selectRect = await SpecialPowers.spawn(iframe, [], () => { + return content.document.querySelector("select").getBoundingClientRect(); + }); + + // Get focus on the select element. + await SpecialPowers.spawn(iframe, [], async () => { + const select = content.document.querySelector("select"); + const focusPromise = new Promise(resolve => { + select.addEventListener("focus", resolve, { once: true }); + }); + select.focus(); + await focusPromise; + }); + + const selectPopup = await openSelectPopup(); + + const popupRect = selectPopup.getBoundingClientRect(); + const popupMarginTop = parseFloat(getComputedStyle(selectPopup).marginTop); + const popupMarginLeft = parseFloat(getComputedStyle(selectPopup).marginLeft); + + info( + `popup rect: (${popupRect.x}, ${popupRect.y}) ${popupRect.width}x${popupRect.height}` + ); + info(`popup margins: ${popupMarginTop} / ${popupMarginLeft}`); + info( + `select rect: (${selectRect.x}, ${selectRect.y}) ${selectRect.width}x${selectRect.height}` + ); + + is( + popupRect.left - popupMarginLeft, + selectRect.x * 2.0, + "select popup position x should be scaled by the desktop zoom" + ); + + // On platforms other than MaxOSX the popup menu is positioned below the + // option element. + if (!navigator.platform.includes("Mac")) { + is( + popupRect.top - popupMarginTop, + tab.linkedBrowser.getBoundingClientRect().top + + (selectRect.y + selectRect.height) * 2.0, + "select popup position y should be scaled by the desktop zoom" + ); + } else { + // On mac it's aligned to the selected menulist option. + const offsetToSelectedItem = + selectPopup.querySelector("menuitem[selected]").getBoundingClientRect() + .top - popupRect.top; + is( + popupRect.top - popupMarginTop + offsetToSelectedItem, + tab.linkedBrowser.getBoundingClientRect().top + selectRect.y * 2.0, + "select popup position y should be scaled by the desktop zoom" + ); + } + + await hideSelectPopup(); + + BrowserTestUtils.removeTab(tab); +} + +add_task(async function () { + if (!SpecialPowers.useRemoteSubframes) { + ok( + true, + "popup window position in non OOP iframe will be fixed by bug 1691346" + ); + return; + } + await runPopupPositionTest( + "helper_test_select_popup_position_transformed_in_parent.html" + ); +}); + +add_task(async function () { + await runPopupPositionTest("helper_test_select_popup_position_zoomed.html"); +}); diff --git a/gfx/layers/apz/test/mochitest/browser_test_select_zoom.js b/gfx/layers/apz/test/mochitest/browser_test_select_zoom.js new file mode 100644 index 0000000000..84baf8e5ac --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser_test_select_zoom.js @@ -0,0 +1,195 @@ +/* This test is a a mash up of + https://searchfox.org/mozilla-central/rev/559b25eb41c1cbffcb90a34e008b8288312fcd25/gfx/layers/apz/test/mochitest/browser_test_group_fission.js + https://searchfox.org/mozilla-central/rev/559b25eb41c1cbffcb90a34e008b8288312fcd25/gfx/layers/apz/test/mochitest/helper_basic_zoom.html + https://searchfox.org/mozilla-central/rev/559b25eb41c1cbffcb90a34e008b8288312fcd25/browser/base/content/test/forms/browser_selectpopup.js +*/ + +/* import-globals-from helper_browser_test_utils.js */ +Services.scriptloader.loadSubScript( + new URL("helper_browser_test_utils.js", gTestPath).href, + this +); + +add_task(async function setup_pref() { + await SpecialPowers.pushPrefEnv({ + set: [ + // Dropping the touch slop to 0 makes the tests easier to write because + // we can just do a one-pixel drag to get over the pan threshold rather + // than having to hard-code some larger value. + ["apz.touch_start_tolerance", "0.0"], + // The subtests in this test do touch-drags to pan the page, but we don't + // want those pans to turn into fling animations, so we increase the + // fling-min threshold velocity to an arbitrarily large value. + ["apz.fling_min_velocity_threshold", "10000"], + ], + }); +}); + +// This test opens a select popup after pinch (apz) zooming has happened. +add_task(async function () { + function httpURL(filename) { + let chromeURL = getRootDirectory(gTestPath) + filename; + //return chromeURL; + return chromeURL.replace( + "chrome://mochitests/content/", + "http://mochi.test:8888/" + ); + } + + const pageUrl = httpURL("helper_test_select_zoom.html"); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + const input = content.document.getElementById("select"); + const focusPromise = new Promise(resolve => { + input.addEventListener("focus", resolve, { once: true }); + }); + input.focus(); + await focusPromise; + }); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + await content.wrappedJSObject.waitUntilApzStable(); + }); + + const initial_resolution = await SpecialPowers.spawn( + tab.linkedBrowser, + [], + () => { + return content.window.windowUtils.getResolution(); + } + ); + + const initial_rect = await SpecialPowers.spawn(tab.linkedBrowser, [], () => { + return content.wrappedJSObject.getSelectRect(); + }); + + ok( + initial_resolution > 0, + "The initial_resolution is " + + initial_resolution + + ", which is some sane value" + ); + + // First, get the position of the select popup when no translations have been applied. + const selectPopup = await openSelectPopup(); + + let popup_initial_rect = selectPopup.getBoundingClientRect(); + let popupInitialX = popup_initial_rect.left; + let popupInitialY = popup_initial_rect.top; + + await hideSelectPopup(); + + ok(popupInitialX > 0, "select position before zooming (x) " + popupInitialX); + ok(popupInitialY > 0, "select position before zooming (y) " + popupInitialY); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + await content.wrappedJSObject.pinchZoomInWithTouch(150, 300); + }); + + // Flush state and get the resolution we're at now + await SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + await content.wrappedJSObject.promiseApzFlushedRepaints(); + }); + + const final_resolution = await SpecialPowers.spawn( + tab.linkedBrowser, + [], + () => { + return content.window.windowUtils.getResolution(); + } + ); + + ok( + final_resolution > initial_resolution, + "The final resolution (" + + final_resolution + + ") is greater after zooming in" + ); + + const final_rect = await SpecialPowers.spawn(tab.linkedBrowser, [], () => { + return content.wrappedJSObject.getSelectRect(); + }); + + await openSelectPopup(); + + let popupRect = selectPopup.getBoundingClientRect(); + ok( + Math.abs(popupRect.left - popupInitialX) > 1, + "popup should have moved by more than one pixel (x) " + + popupRect.left + + " " + + popupInitialX + ); + ok( + Math.abs(popupRect.top - popupInitialY) > 1, + "popup should have moved by more than one pixel (y) " + + popupRect.top + + " " + + popupInitialY + ); + + ok( + Math.abs( + final_rect.left - initial_rect.left - (popupRect.left - popupInitialX) + ) < 1, + "popup should have moved approximately the same as the element (x)" + ); + let tolerance = navigator.platform.includes("Linux") ? final_rect.height : 1; + ok( + Math.abs( + final_rect.top - initial_rect.top - (popupRect.top - popupInitialY) + ) < tolerance, + "popup should have moved approximately the same as the element (y)" + ); + + ok( + true, + "initial " + + initial_rect.left + + " " + + initial_rect.top + + " " + + initial_rect.width + + " " + + initial_rect.height + ); + ok( + true, + "final " + + final_rect.left + + " " + + final_rect.top + + " " + + final_rect.width + + " " + + final_rect.height + ); + + ok( + true, + "initial popup " + + popup_initial_rect.left + + " " + + popup_initial_rect.top + + " " + + popup_initial_rect.width + + " " + + popup_initial_rect.height + ); + ok( + true, + "final popup " + + popupRect.left + + " " + + popupRect.top + + " " + + popupRect.width + + " " + + popupRect.height + ); + + await hideSelectPopup(); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/gfx/layers/apz/test/mochitest/browser_test_tab_drag_zoom.js b/gfx/layers/apz/test/mochitest/browser_test_tab_drag_zoom.js new file mode 100644 index 0000000000..e421e7bd3c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser_test_tab_drag_zoom.js @@ -0,0 +1,103 @@ +/* This test is a a mash up of + https://searchfox.org/mozilla-central/rev/016925857e2f81a9425de9e03021dcf4251cafcc/gfx/layers/apz/test/mochitest/browser_test_select_zoom.js + https://searchfox.org/mozilla-central/rev/016925857e2f81a9425de9e03021dcf4251cafcc/browser/base/content/test/general/browser_tab_drag_drop_perwindow.js +*/ + +const EVENTUTILS_URL = + "chrome://mochikit/content/tests/SimpleTest/EventUtils.js"; +var EventUtils = {}; + +Services.scriptloader.loadSubScript(EVENTUTILS_URL, EventUtils); + +add_task(async function test_dragging_zoom_handling() { + function httpURL(filename) { + let chromeURL = getRootDirectory(gTestPath) + filename; + //return chromeURL; + return chromeURL.replace( + "chrome://mochitests/content/", + "http://mochi.test:8888/" + ); + } + + const pageUrl = httpURL("helper_test_tab_drag_zoom.html"); + + let win1 = await BrowserTestUtils.openNewBrowserWindow(); + let win2 = await BrowserTestUtils.openNewBrowserWindow(); + + let tab1 = await BrowserTestUtils.openNewForegroundTab(win1.gBrowser); + let tab2 = await BrowserTestUtils.openNewForegroundTab( + win2.gBrowser, + pageUrl + ); + + await SpecialPowers.spawn(tab2.linkedBrowser, [], async () => { + await content.wrappedJSObject.waitUntilApzStable(); + }); + + const initial_resolution = await SpecialPowers.spawn( + tab2.linkedBrowser, + [], + () => { + return content.window.windowUtils.getResolution(); + } + ); + + ok( + initial_resolution > 0, + "The initial_resolution is " + + initial_resolution + + ", which is some sane value" + ); + + let effect = EventUtils.synthesizeDrop( + tab2, + tab1, + [[{ type: TAB_DROP_TYPE, data: tab2 }]], + null, + win2, + win1 + ); + is(effect, "move", "Tab should be moved from win2 to win1."); + + await SpecialPowers.spawn(win1.gBrowser.selectedBrowser, [], async () => { + await content.wrappedJSObject.waitUntilApzStable(); + }); + + let resolution = await SpecialPowers.spawn( + win1.gBrowser.selectedBrowser, + [], + () => { + return content.window.windowUtils.getResolution(); + } + ); + + ok( + resolution == initial_resolution, + "The resolution (" + resolution + ") is the same after tab dragging" + ); + + await SpecialPowers.spawn(win1.gBrowser.selectedBrowser, [], async () => { + await content.wrappedJSObject.pinchZoomInWithTouch(150, 300); + }); + + // Flush state and get the resolution we're at now + await SpecialPowers.spawn(win1.gBrowser.selectedBrowser, [], async () => { + await content.wrappedJSObject.promiseApzFlushedRepaints(); + }); + + resolution = await SpecialPowers.spawn( + win1.gBrowser.selectedBrowser, + [], + () => { + return content.window.windowUtils.getResolution(); + } + ); + + ok( + resolution > initial_resolution, + "The resolution (" + resolution + ") is greater after zooming in" + ); + + await BrowserTestUtils.closeWindow(win1); + await BrowserTestUtils.closeWindow(win2); +}); diff --git a/gfx/layers/apz/test/mochitest/green100x100.png b/gfx/layers/apz/test/mochitest/green100x100.png new file mode 100644 index 0000000000..7df25f33bd Binary files /dev/null and b/gfx/layers/apz/test/mochitest/green100x100.png differ diff --git a/gfx/layers/apz/test/mochitest/helper_background_tab_load_scroll.html b/gfx/layers/apz/test/mochitest/helper_background_tab_load_scroll.html new file mode 100644 index 0000000000..4769861b2a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_background_tab_load_scroll.html @@ -0,0 +1,147 @@ + + + + + + + + + +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_background_tab_scroll.html b/gfx/layers/apz/test/mochitest/helper_background_tab_scroll.html new file mode 100644 index 0000000000..f55a55f0fc --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_background_tab_scroll.html @@ -0,0 +1,9 @@ + + + + +
#scrolltarget
diff --git a/gfx/layers/apz/test/mochitest/helper_basic_onetouchpinch.html b/gfx/layers/apz/test/mochitest/helper_basic_onetouchpinch.html new file mode 100644 index 0000000000..1eb1d3dd03 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_basic_onetouchpinch.html @@ -0,0 +1,90 @@ + + + + + + Sanity check for one-touch pinch zooming + + + + + + + + Here is some text to stare at as the test runs. It serves no functional + purpose, but gives you an idea of the zoom level. It's harder to tell what + the zoom level is when the page is just solid white. + + diff --git a/gfx/layers/apz/test/mochitest/helper_basic_pan.html b/gfx/layers/apz/test/mochitest/helper_basic_pan.html new file mode 100644 index 0000000000..db96e34a70 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_basic_pan.html @@ -0,0 +1,73 @@ + + + + + + Sanity panning test + + + + + + + +
+ This div makes the page scrollable. +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_basic_scrollend.html b/gfx/layers/apz/test/mochitest/helper_basic_scrollend.html new file mode 100644 index 0000000000..9d71fe6251 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_basic_scrollend.html @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_basic_zoom.html b/gfx/layers/apz/test/mochitest/helper_basic_zoom.html new file mode 100644 index 0000000000..55717b31a4 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_basic_zoom.html @@ -0,0 +1,71 @@ + + + + + + Sanity check for zooming + + + + + + + + Here is some text to stare at as the test runs. It serves no functional + purpose, but gives you an idea of the zoom level. It's harder to tell what + the zoom level is when the page is just solid white. + + diff --git a/gfx/layers/apz/test/mochitest/helper_browser_test_utils.js b/gfx/layers/apz/test/mochitest/helper_browser_test_utils.js new file mode 100644 index 0000000000..ac68c9b1d4 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_browser_test_utils.js @@ -0,0 +1,11 @@ +// For hideSelectPopup. +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/base/content/test/forms/head.js", + this +); + +function openSelectPopup(selector = "select", win = window) { + let popupShownPromise = BrowserTestUtils.waitForSelectPopupShown(win); + EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true }, win); + return popupShownPromise; +} diff --git a/gfx/layers/apz/test/mochitest/helper_bug1162771.html b/gfx/layers/apz/test/mochitest/helper_bug1162771.html new file mode 100644 index 0000000000..3503341d41 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1162771.html @@ -0,0 +1,107 @@ + + + + + + Test for touchend on media elements + + + + + + + +

Tap on the colored boxes to hide them.

+ +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1271432.html b/gfx/layers/apz/test/mochitest/helper_bug1271432.html new file mode 100644 index 0000000000..1e40421a8f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1271432.html @@ -0,0 +1,573 @@ + + Ensure that the hit region doesn't get unexpectedly expanded + + + + + + + +Some text + +
+Scrolling on the very left edge of this div will work. +Scrolling on the right side of this div (starting with the left edge of the orange box above) should work, but doesn't.
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+
+
this div makes the page scrollable
+ diff --git a/gfx/layers/apz/test/mochitest/helper_bug1280013.html b/gfx/layers/apz/test/mochitest/helper_bug1280013.html new file mode 100644 index 0000000000..6b7d5cf4c3 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1280013.html @@ -0,0 +1,73 @@ + + + + + + Test for bug 1280013 + + + + + + + The iframe below is at (0, 400). Scroll it into view, and then scroll the contents. The content should be fully rendered in high-resolution. + + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1285070.html b/gfx/layers/apz/test/mochitest/helper_bug1285070.html new file mode 100644 index 0000000000..0df3a77f4a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1285070.html @@ -0,0 +1,44 @@ + + + + + + Test pointer events are dispatched once for touch tap + + + + + + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1299195.html b/gfx/layers/apz/test/mochitest/helper_bug1299195.html new file mode 100644 index 0000000000..b1aad7ef11 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1299195.html @@ -0,0 +1,47 @@ + + + + + + Test pointer events are dispatched once for touch tap + + + + + + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1326290.html b/gfx/layers/apz/test/mochitest/helper_bug1326290.html new file mode 100644 index 0000000000..17b5a36eaa --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1326290.html @@ -0,0 +1,63 @@ + + + + + + Dragging the mouse on a inactive scrollframe's scrollbar + + + + + + + +
+
Some content inside the inactive scrollframe
+
+
Some content to ensure the root scrollframe is scrollable and the overflow:scroll div remains inactive
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1331693.html b/gfx/layers/apz/test/mochitest/helper_bug1331693.html new file mode 100644 index 0000000000..6dd0de13cb --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1331693.html @@ -0,0 +1,71 @@ + + + + + + Dragging the mouse on a scrollframe inside an SVGEffects + + + + + + + +
A div that generate an svg effects display item +
+
Some content inside the scrollframe
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1346632.html b/gfx/layers/apz/test/mochitest/helper_bug1346632.html new file mode 100644 index 0000000000..f91f8159b5 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1346632.html @@ -0,0 +1,89 @@ + + + + + + Dragging the scrollbar on a page with a fixed-positioned element just past the right edge of the content + + + + + + + +
+

+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1414336.html b/gfx/layers/apz/test/mochitest/helper_bug1414336.html new file mode 100644 index 0000000000..636328b7e4 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1414336.html @@ -0,0 +1,97 @@ + + + + + + Test for Bug 1414336 + + + + + + + + +Mozilla Bug 1414336 +

+
+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+

Test bug1414336

+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1462961.html b/gfx/layers/apz/test/mochitest/helper_bug1462961.html new file mode 100644 index 0000000000..d37d041800 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1462961.html @@ -0,0 +1,74 @@ + + + + + + Dragging the mouse on a transformed scrollframe inside a fixed-pos element + + + + + + + +
+
+
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1473108.html b/gfx/layers/apz/test/mochitest/helper_bug1473108.html new file mode 100644 index 0000000000..118ac3fc54 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1473108.html @@ -0,0 +1,50 @@ + + + + + + + Test for Bug 1473108 + + + + + + + + Mozilla Bug 1473108 + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1490393-2.html b/gfx/layers/apz/test/mochitest/helper_bug1490393-2.html new file mode 100644 index 0000000000..749110449e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1490393-2.html @@ -0,0 +1,65 @@ + + + + + + Dragging the mouse on a scrollbar for a scrollframe inside nested transforms + + + + + + +
+
+
+
+
Yay text
+
+
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1490393.html b/gfx/layers/apz/test/mochitest/helper_bug1490393.html new file mode 100644 index 0000000000..6c18a2d24e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1490393.html @@ -0,0 +1,64 @@ + + + + + + Dragging the mouse on a scrollbar for a scrollframe inside nested transforms + + + + + + +
+
+
+
+
Yay text
+
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1502010_unconsumed_pan.html b/gfx/layers/apz/test/mochitest/helper_bug1502010_unconsumed_pan.html new file mode 100644 index 0000000000..73badf4bc7 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1502010_unconsumed_pan.html @@ -0,0 +1,76 @@ + + + + + + Test pointercancel doesn't get sent for horizontal panning on a pan-y element + + + + + + + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1506497_touch_action_fixed_on_fixed.html b/gfx/layers/apz/test/mochitest/helper_bug1506497_touch_action_fixed_on_fixed.html new file mode 100644 index 0000000000..cc73fe99ea --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1506497_touch_action_fixed_on_fixed.html @@ -0,0 +1,96 @@ + + + + + + Test for Bug 1506497 + + + + + + + + +
+
+
Touch here and drag up
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1509575.html b/gfx/layers/apz/test/mochitest/helper_bug1509575.html new file mode 100644 index 0000000000..4c85d1db42 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1509575.html @@ -0,0 +1,71 @@ + + + + + + + Test for Bug 1509575 + + + + + + +
+ Now you're scrolled, now you're not? +
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1519339_hidden_smoothscroll.html b/gfx/layers/apz/test/mochitest/helper_bug1519339_hidden_smoothscroll.html new file mode 100644 index 0000000000..225182f749 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1519339_hidden_smoothscroll.html @@ -0,0 +1,61 @@ + + + + + Test for bug 1519339 + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1544966_zoom_on_touch_action_none.html b/gfx/layers/apz/test/mochitest/helper_bug1544966_zoom_on_touch_action_none.html new file mode 100644 index 0000000000..7adfd5ba0f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1544966_zoom_on_touch_action_none.html @@ -0,0 +1,89 @@ + + + + + + Test for Bug 1544966 + + + + + + + + +
+ Put down two fingers at the same time and do a pinch action. +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1550510.html b/gfx/layers/apz/test/mochitest/helper_bug1550510.html new file mode 100644 index 0000000000..2f3be5dd2c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1550510.html @@ -0,0 +1,66 @@ + + + + + + Dragging the mouse on a scrollbar for a transformed, filtered scrollframe + + + + + + +
+
+
+
+
+ yay text +
+
+
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1637113_main_thread_hit_test.html b/gfx/layers/apz/test/mochitest/helper_bug1637113_main_thread_hit_test.html new file mode 100644 index 0000000000..c58c7e40b6 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1637113_main_thread_hit_test.html @@ -0,0 +1,70 @@ + + + + + + + Test for Bug 1637113 + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1637135_narrow_viewport.html b/gfx/layers/apz/test/mochitest/helper_bug1637135_narrow_viewport.html new file mode 100644 index 0000000000..7b57416c04 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1637135_narrow_viewport.html @@ -0,0 +1,60 @@ + + + + + + + Test for Bug 1637135 + + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1638441_fixed_pos_hit_test.html b/gfx/layers/apz/test/mochitest/helper_bug1638441_fixed_pos_hit_test.html new file mode 100644 index 0000000000..3665ef5a31 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1638441_fixed_pos_hit_test.html @@ -0,0 +1,67 @@ + + + + + + + Test for Bug 1638441 + + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1638458_contextmenu.html b/gfx/layers/apz/test/mochitest/helper_bug1638458_contextmenu.html new file mode 100644 index 0000000000..a5a43f7ca1 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1638458_contextmenu.html @@ -0,0 +1,82 @@ + + + + + + + Test for Bug 1638458 + + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1648491_no_pointercancel_with_dtc.html b/gfx/layers/apz/test/mochitest/helper_bug1648491_no_pointercancel_with_dtc.html new file mode 100644 index 0000000000..3d8fdcf76e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1648491_no_pointercancel_with_dtc.html @@ -0,0 +1,89 @@ + + + + + + Test for Bug 1648491 + + + + + + + + +
+ A two-finger pinch action here should send pointer events to content. +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1662800.html b/gfx/layers/apz/test/mochitest/helper_bug1662800.html new file mode 100644 index 0000000000..eb804a40f6 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1662800.html @@ -0,0 +1,61 @@ + + + + + + Dragging the mouse on a scrollbar for a scrollframe inside nested transforms with a scale component + + + + + + +
+
+
+
+
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1663731_no_pointercancel_on_second_touchstart.html b/gfx/layers/apz/test/mochitest/helper_bug1663731_no_pointercancel_on_second_touchstart.html new file mode 100644 index 0000000000..e0690c12c6 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1663731_no_pointercancel_on_second_touchstart.html @@ -0,0 +1,82 @@ + + + + + + Test for Bug 1663731 + + + + + + + + + A two-finger pinch action here should send pointer events to content and not do browser zooming. + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1669625.html b/gfx/layers/apz/test/mochitest/helper_bug1669625.html new file mode 100644 index 0000000000..95d2a4bc2c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1669625.html @@ -0,0 +1,79 @@ + + + + + + Scrolling doesn't cause extra SchedulePaint calls + + + + + + + +
spacer
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1674935.html b/gfx/layers/apz/test/mochitest/helper_bug1674935.html new file mode 100644 index 0000000000..f5efa16d5f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1674935.html @@ -0,0 +1,76 @@ + + + + + Tests that keyboard arrow keys scroll a very specific page + + + + + + + + +
+
+ + + + + +
+
+
+

Firefox

+
+ + + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1682170_pointercancel_on_touchaction_pinchzoom.html b/gfx/layers/apz/test/mochitest/helper_bug1682170_pointercancel_on_touchaction_pinchzoom.html new file mode 100644 index 0000000000..b9c31dfe89 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1682170_pointercancel_on_touchaction_pinchzoom.html @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + A two-finger pinch action here should trigger browser zoom and trigger a pointercancel to content. + Note that the code does a zoom-out and the page is already at min zoom, so + the zoom doesn't produce any visual effect. But the DOM events should be the + same either way. + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1695598.html b/gfx/layers/apz/test/mochitest/helper_bug1695598.html new file mode 100644 index 0000000000..fb6102e33d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1695598.html @@ -0,0 +1,123 @@ + + + Test for bug 1695598 + + + + + + + + +
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1714934_mouseevent_buttons.html b/gfx/layers/apz/test/mochitest/helper_bug1714934_mouseevent_buttons.html new file mode 100644 index 0000000000..35cea7de4f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1714934_mouseevent_buttons.html @@ -0,0 +1,40 @@ + + + + + + + Test for Bug 1714934 + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1719330.html b/gfx/layers/apz/test/mochitest/helper_bug1719330.html new file mode 100644 index 0000000000..c99b6e1012 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1719330.html @@ -0,0 +1,65 @@ + + + + + Tests that the arrow down key does not scroll by more than 1 element + + + + + + + +
+ + + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1756529.html b/gfx/layers/apz/test/mochitest/helper_bug1756529.html new file mode 100644 index 0000000000..e1767f4f57 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1756529.html @@ -0,0 +1,226 @@ + + + + + + Page scrolling bug test, helper page + + + + + + + + SmoothScrollPage not honored with MSD physics bug. + +
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Tellus in metus vulputate eu. Vestibulum morbi blandit cursus risus at ultrices mi tempus imperdiet. Congue quisque egestas diam in. Pretium vulputate sapien nec sagittis aliquam malesuada bibendum arcu. Eleifend mi in nulla posuere. Proin libero nunc consequat interdum varius. Risus pretium quam vulputate dignissim suspendisse in est. Lacus vel facilisis volutpat est. Donec pretium vulputate sapien nec. Feugiat sed lectus vestibulum mattis. Platea dictumst quisque sagittis purus. Vulputate eu scelerisque felis imperdiet proin fermentum leo vel. Enim facilisis gravida neque convallis a cras semper auctor. Placerat orci nulla pellentesque dignissim enim sit.

+

Augue neque gravida in fermentum et sollicitudin ac. Mattis enim ut tellus elementum sagittis vitae et. Malesuada nunc vel risus commodo viverra maecenas accumsan. Viverra nibh cras pulvinar mattis nunc sed. Lectus nulla at volutpat diam ut venenatis tellus in. Non tellus orci ac auctor. Magna etiam tempor orci eu lobortis. Malesuada nunc vel risus commodo viverra maecenas accumsan lacus vel. Sagittis orci a scelerisque purus. Tellus pellentesque eu tincidunt tortor. Vulputate dignissim suspendisse in est ante in. Tristique et egestas quis ipsum suspendisse. Quisque egestas diam in arcu cursus. Massa massa ultricies mi quis hendrerit dolor magna eget. Mattis nunc sed blandit libero volutpat sed. Consectetur purus ut faucibus pulvinar elementum integer enim.

+

Vestibulum lorem sed risus ultricies tristique nulla. Imperdiet nulla malesuada pellentesque elit eget gravida. Feugiat nisl pretium fusce id velit ut tortor pretium. Commodo ullamcorper a lacus vestibulum sed arcu non odio. Id nibh tortor id aliquet lectus proin nibh nisl condimentum. Amet volutpat consequat mauris nunc congue nisi vitae suscipit tellus. Neque ornare aenean euismod elementum. Semper quis lectus nulla at. Massa sed elementum tempus egestas. Praesent elementum facilisis leo vel fringilla est ullamcorper eget nulla. Pellentesque elit eget gravida cum sociis natoque penatibus et. Massa enim nec dui nunc mattis enim. Laoreet suspendisse interdum consectetur libero id faucibus nisl. Fusce ut placerat orci nulla.

+

Vitae tempus quam pellentesque nec nam aliquam. Vestibulum mattis ullamcorper velit sed ullamcorper morbi tincidunt ornare. Nam libero justo laoreet sit amet. Arcu non sodales neque sodales. Nec ultrices dui sapien eget mi proin sed. Parturient montes nascetur ridiculus mus mauris vitae ultricies. Lacus sed viverra tellus in hac habitasse. Orci phasellus egestas tellus rutrum. Leo a diam sollicitudin tempor id eu nisl. Diam phasellus vestibulum lorem sed risus ultricies tristique nulla. Lectus nulla at volutpat diam ut venenatis tellus in. Cursus metus aliquam eleifend mi in nulla. Et ultrices neque ornare aenean euismod. Sit amet aliquam id diam maecenas ultricies mi. Volutpat diam ut venenatis tellus in metus vulputate eu.

+

Pellentesque elit ullamcorper dignissim cras tincidunt. Morbi tincidunt augue interdum velit euismod. Diam vel quam elementum pulvinar etiam non quam. Eget duis at tellus at urna. Posuere ac ut consequat semper viverra nam libero justo laoreet. Ac turpis egestas maecenas pharetra convallis posuere. Ultrices tincidunt arcu non sodales neque sodales ut etiam sit. In eu mi bibendum neque egestas. Pellentesque sit amet porttitor eget dolor morbi. Ac tortor dignissim convallis aenean et tortor at. Elementum tempus egestas sed sed risus pretium quam. Nisi scelerisque eu ultrices vitae auctor eu augue. Urna duis convallis convallis tellus id interdum velit laoreet id. Auctor eu augue ut lectus arcu bibendum at varius vel.

+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1780701.html b/gfx/layers/apz/test/mochitest/helper_bug1780701.html new file mode 100644 index 0000000000..f445f9abe6 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1780701.html @@ -0,0 +1,70 @@ + + + + + + Test that scroll snap wont't happen on zoomed content + + + + + + +
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1783936.html b/gfx/layers/apz/test/mochitest/helper_bug1783936.html new file mode 100644 index 0000000000..8aec5eafef --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1783936.html @@ -0,0 +1,74 @@ + + + + + + Test that scroll snap happens on pan end without fling + + + + + + +
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug982141.html b/gfx/layers/apz/test/mochitest/helper_bug982141.html new file mode 100644 index 0000000000..8ffda2dd2f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug982141.html @@ -0,0 +1,130 @@ + + + + + + + Test for Bug 982141, helper page + + + + + + Mozilla Bug 982141 + +
+
+ Wide content so that the vertical scrollbar for the parent div + doesn't eat into the 50px width and reduce the width of the + displayport. +
+ Line 1
+ Line 2
+ Line 3
+ Line 4
+ Line 5
+ Line 6
+ Line 7
+ Line 8
+ Line 9
+ Line 10
+ Line 11
+ Line 12
+ Line 13
+ Line 14
+ Line 15
+ Line 16
+ Line 17
+ Line 18
+ Line 19
+ Line 20
+ Line 21
+ Line 22
+ Line 23
+ Line 24
+ Line 25
+ Line 26
+ Line 27
+ Line 28
+ Line 29
+ Line 30
+ Line 31
+ Line 32
+ Line 33
+ Line 34
+ Line 35
+ Line 36
+ Line 37
+ Line 38
+ Line 39
+ Line 40
+ Line 41
+ Line 42
+ Line 43
+ Line 44
+ Line 45
+ Line 46
+ Line 40
+ Line 48
+ Line 49
+ Line 50
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_check_dp_size.html b/gfx/layers/apz/test/mochitest/helper_check_dp_size.html new file mode 100644 index 0000000000..0a81a69958 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_check_dp_size.html @@ -0,0 +1,124 @@ + + + + + + Test for Bug 1689492, helper page + + + + + + + Mozilla Bug 1689492 + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_checkerboard_apzforcedisabled.html b/gfx/layers/apz/test/mochitest/helper_checkerboard_apzforcedisabled.html new file mode 100644 index 0000000000..404368a803 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_checkerboard_apzforcedisabled.html @@ -0,0 +1,93 @@ + + + + + + Checkerboarding while root scrollframe async-scrolls and a + subframe has APZ force disabled + + + + + + + +
+
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_checkerboard_no_multiplier.html b/gfx/layers/apz/test/mochitest/helper_checkerboard_no_multiplier.html new file mode 100644 index 0000000000..149ef9fbba --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_checkerboard_no_multiplier.html @@ -0,0 +1,57 @@ + + + +Testcase for checkerboarding with displayport multipliers dropped to zero + + + + + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_checkerboard_scrollinfo.html b/gfx/layers/apz/test/mochitest/helper_checkerboard_scrollinfo.html new file mode 100644 index 0000000000..2e718ad44b --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_checkerboard_scrollinfo.html @@ -0,0 +1,91 @@ + + + + Scrolling a scrollinfo layer and making sure it doesn't checkerboard + + + + + + + +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_checkerboard_zoom_during_load.html b/gfx/layers/apz/test/mochitest/helper_checkerboard_zoom_during_load.html new file mode 100644 index 0000000000..650b1c265d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_checkerboard_zoom_during_load.html @@ -0,0 +1,55 @@ + + + +Testcase for checkerboarding after zooming during page load + + + + + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_checkerboard_zoomoverflowhidden.html b/gfx/layers/apz/test/mochitest/helper_checkerboard_zoomoverflowhidden.html new file mode 100644 index 0000000000..63a07ebe9b --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_checkerboard_zoomoverflowhidden.html @@ -0,0 +1,150 @@ + + + + + + Checkerboarding in while scrolling a subframe when root scrollframe has + overflow hidden and pinch zoomed in + + + + + + + +
+
+

STR:

+
    +
  1. set apz.allow_zoom to true
  2. +
  3. visit any bugzilla site (like this one)
  4. +
  5. zoom into the page and observe the left edge of the viewport
  6. +
+

ER: content should be shown
+ AR: foreground content seems to disappear, looks like it's being cut off +

+

I attached a video of the STR to show the problem a little bit better. So far, I could only reproduce this on bugzilla. Words words words words words words words words words words words words words words words words words words words words words words.

+ +
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_click.html b/gfx/layers/apz/test/mochitest/helper_click.html new file mode 100644 index 0000000000..7c4501cb46 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_click.html @@ -0,0 +1,42 @@ + + + + + + Sanity mouse-clicking test + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_click_interrupt_animation.html b/gfx/layers/apz/test/mochitest/helper_click_interrupt_animation.html new file mode 100644 index 0000000000..adba0d90ea --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_click_interrupt_animation.html @@ -0,0 +1,96 @@ + + + + + + Clicking on the content (not scrollbar) should interrupt animations + + + + + + + + +
+
+ The above div is sized to 3x screen height so the linear gradient is more steep in terms of + color/pixel. We only scroll a few pages worth so we don't need the gradient all the way down. + And then we use a bottom-margin to make the page really big so the scrollthumb is + relatively small, giving us lots of space to click on the scrolltrack. + + diff --git a/gfx/layers/apz/test/mochitest/helper_content_response_timeout.html b/gfx/layers/apz/test/mochitest/helper_content_response_timeout.html new file mode 100644 index 0000000000..41a0319699 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_content_response_timeout.html @@ -0,0 +1,26 @@ + + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_disallow_doubletap_zoom_inside_oopif.html b/gfx/layers/apz/test/mochitest/helper_disallow_doubletap_zoom_inside_oopif.html new file mode 100644 index 0000000000..2c83cb1a1f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_disallow_doubletap_zoom_inside_oopif.html @@ -0,0 +1,58 @@ + + + + + + Check that double tapping inside an oop iframe doesn't work if the top + level content document doesn't allow zooming + + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_displayport_expiry.html b/gfx/layers/apz/test/mochitest/helper_displayport_expiry.html new file mode 100644 index 0000000000..023786a270 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_displayport_expiry.html @@ -0,0 +1,77 @@ + + + + Test for DisplayPort Expiry + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_div_pan.html b/gfx/layers/apz/test/mochitest/helper_div_pan.html new file mode 100644 index 0000000000..b740b2f16a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_div_pan.html @@ -0,0 +1,43 @@ + + + + + + Sanity panning test for scrollable div + + + + + + +
+
+ This div makes the |outer| div scrollable. +
+
+
+ This div makes the top-level page scrollable. +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_dommousescroll.html b/gfx/layers/apz/test/mochitest/helper_dommousescroll.html new file mode 100644 index 0000000000..390db367f5 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_dommousescroll.html @@ -0,0 +1,33 @@ + + + Test that Direct Manipulation generated pan gesture events generate DOMMouseScroll events with reasonable line scroll amounts + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom.html new file mode 100644 index 0000000000..4cd613edbb --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom.html @@ -0,0 +1,50 @@ + + + + + + Sanity check for double-tap zooming + + + + + + + +
Text before the div.
+
+
Text after the div.
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_bug1702464.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_bug1702464.html new file mode 100644 index 0000000000..34d06fc039 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_bug1702464.html @@ -0,0 +1,90 @@ + + + + + + Check that double tapping internal calculations correctly convert the tap point + + + + + + + +
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_fixedpos.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_fixedpos.html new file mode 100644 index 0000000000..1da5607d7e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_fixedpos.html @@ -0,0 +1,88 @@ + + + + + + Check that double tapping active scrollable elements in fixed pos work + + + + + + + + +
+
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_fixedpos_overflow.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_fixedpos_overflow.html new file mode 100644 index 0000000000..0658c562c1 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_fixedpos_overflow.html @@ -0,0 +1,113 @@ + + + + + + Check that double tapping elements with large overflow inside active scrollable elements in fixed pos work + + + + + + + + +
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. + Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. + Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. + Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. + Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. + Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_gencon.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_gencon.html new file mode 100644 index 0000000000..01b1f060d8 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_gencon.html @@ -0,0 +1,101 @@ + + + + + + Check that on generated content works + + + + + + + + +
some text
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_horizontal_center.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_horizontal_center.html new file mode 100644 index 0000000000..ab945fab1f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_horizontal_center.html @@ -0,0 +1,50 @@ + + + + + + Check that double tapping an element that doesn't fill the width of the viewport as maximum zoom centers it + + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_hscrollable.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_hscrollable.html new file mode 100644 index 0000000000..21c3fb7e70 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_hscrollable.html @@ -0,0 +1,85 @@ + + + + + + Check that tall element wider than the viewport doesn't scroll to the top + + + + + + + +
+
+ +
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_hscrollable2.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_hscrollable2.html new file mode 100644 index 0000000000..fc74ff1c89 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_hscrollable2.html @@ -0,0 +1,109 @@ + + + + + + Check that tall element wider than the viewport after zooming in doesn't scroll up + + + + + + + +
+
+ +
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_htmlelement.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_htmlelement.html new file mode 100644 index 0000000000..8fadc4eb3e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_htmlelement.html @@ -0,0 +1,76 @@ + + + + + + Check that double tapping on a scrollbar does not scroll to top + + + + + + + + +
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_img.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_img.html new file mode 100644 index 0000000000..2559a3dd23 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_img.html @@ -0,0 +1,43 @@ + + + + + + Check that double tapping img works + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_large_overflow.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_large_overflow.html new file mode 100644 index 0000000000..02c4ca52f8 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_large_overflow.html @@ -0,0 +1,300 @@ + + + + + + Check that double tapping on overflow centers the zoom where we double tap + + + + + + + + +
+Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text +Text text text text text text text text text text text text text text text text + +
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_noscroll.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_noscroll.html new file mode 100644 index 0000000000..00e3638ba7 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_noscroll.html @@ -0,0 +1,59 @@ + + + + + + Check that double tapping something tall that we are already zoomed to doesn't scroll (it zooms out) + + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_nothing.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_nothing.html new file mode 100644 index 0000000000..12005b3552 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_nothing.html @@ -0,0 +1,46 @@ + + + + + + Check that double tapping when zoomed out and there is nothing to zoom to zooms in + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_nothing_listener.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_nothing_listener.html new file mode 100644 index 0000000000..82805fe321 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_nothing_listener.html @@ -0,0 +1,47 @@ + + + + + + Check that double tapping when zoomed out and there is nothing to zoom to does not zoom in if this is a non-passive wheel listener + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_oopif.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_oopif.html new file mode 100644 index 0000000000..b6a51e8229 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_oopif.html @@ -0,0 +1,50 @@ + + + + + + Check that double tapping inside an oop iframe works + + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_scrolled_overflowhidden.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_scrolled_overflowhidden.html new file mode 100644 index 0000000000..bba8eeec08 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_scrolled_overflowhidden.html @@ -0,0 +1,82 @@ + + + + + + Check that double tapping when the page is overflow hidden and has been scrolled down by js works + + + + + + + + +
+
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_shadowdom.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_shadowdom.html new file mode 100644 index 0000000000..eed45028e2 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_shadowdom.html @@ -0,0 +1,69 @@ + + + + + + Check that double tapping shadow dom works + + + + + + + + +
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_small.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_small.html new file mode 100644 index 0000000000..1a2a52aff8 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_small.html @@ -0,0 +1,43 @@ + + + + + + Check that double tapping a small element works + + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_smooth.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_smooth.html new file mode 100644 index 0000000000..65b4c6698f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_smooth.html @@ -0,0 +1,161 @@ + + + + + + Check that double tapping zoom out animation is smooth + + + + + + + + +
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_square.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_square.html new file mode 100644 index 0000000000..c8278093af --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_square.html @@ -0,0 +1,61 @@ + + + + + + Check that double tapping on a square img doesn't cut off parts of the image + + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_tablecell.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_tablecell.html new file mode 100644 index 0000000000..f578ccc592 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_tablecell.html @@ -0,0 +1,110 @@ + + + + + + Check that double tapping small table cells does not zoom + + + + + + + + + + + + + + + + + + + +
The table header
The table body
with two columns
+ + + + + + + + + + + + + +
The table header
The table body
with two columns
+ + + + + + + + + + + + + +
The table header
The table bodywith two columns
+ + + + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_tallwide.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_tallwide.html new file mode 100644 index 0000000000..438c63b0b0 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_tallwide.html @@ -0,0 +1,85 @@ + + + + + + Check that double tapping on a tall element that is >90% width of viewport doesn't scroll to the top of it when scrolled down + + + + + + + + +
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_textarea.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_textarea.html new file mode 100644 index 0000000000..99616d9834 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_textarea.html @@ -0,0 +1,43 @@ + + + + + + Check that double tapping textarea works + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_drag_bug1719913.html b/gfx/layers/apz/test/mochitest/helper_drag_bug1719913.html new file mode 100644 index 0000000000..36192aed6d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_drag_bug1719913.html @@ -0,0 +1,91 @@ + + + + + + Test for bug 1719913 + + + + + + + +
+
+
+
+
+
+
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_drag_click.html b/gfx/layers/apz/test/mochitest/helper_drag_click.html new file mode 100644 index 0000000000..01722c798d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_drag_click.html @@ -0,0 +1,69 @@ + + + + + + Sanity mouse-drag click test + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_drag_root_scrollbar.html b/gfx/layers/apz/test/mochitest/helper_drag_root_scrollbar.html new file mode 100644 index 0000000000..1665e168cb --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_drag_root_scrollbar.html @@ -0,0 +1,61 @@ + + + + + + Dragging the mouse on the viewport's scrollbar + + + + + + + +
Some content to ensure the root scrollframe is scrollable
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_drag_scroll.html b/gfx/layers/apz/test/mochitest/helper_drag_scroll.html new file mode 100644 index 0000000000..15ca680ad6 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_drag_scroll.html @@ -0,0 +1,653 @@ + + + + + + Dragging the mouse on a content-implemented scrollbar + + + + + + + + +
Drag up and down on this bar. The background/scrollbar shouldn't glitch
+This is a tall page
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_drag_scrollbar_hittest.html b/gfx/layers/apz/test/mochitest/helper_drag_scrollbar_hittest.html new file mode 100644 index 0000000000..7a1cab6dba --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_drag_scrollbar_hittest.html @@ -0,0 +1,100 @@ + + + + + + Test that the scrollbar thumb remains under the cursor during scrollbar dragging + + + + + + + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_empty.html b/gfx/layers/apz/test/mochitest/helper_empty.html new file mode 100644 index 0000000000..68cd9179f5 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_empty.html @@ -0,0 +1,4 @@ + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_animation_styling_in_oopif.html b/gfx/layers/apz/test/mochitest/helper_fission_animation_styling_in_oopif.html new file mode 100644 index 0000000000..12221d0703 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_animation_styling_in_oopif.html @@ -0,0 +1,166 @@ + + + + + Test for scrolled out of view animation optimization in an OOPIF + + + + + + +
+
+ + + +
+ diff --git a/gfx/layers/apz/test/mochitest/helper_fission_animation_styling_in_transformed_oopif.html b/gfx/layers/apz/test/mochitest/helper_fission_animation_styling_in_transformed_oopif.html new file mode 100644 index 0000000000..d85d77f552 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_animation_styling_in_transformed_oopif.html @@ -0,0 +1,130 @@ + + + + + Test for scrolled out of view animation optimization in an OOPIF transformed by rotate(45deg) + + + + + + +
+
+
+ +
+
+
+ diff --git a/gfx/layers/apz/test/mochitest/helper_fission_basic.html b/gfx/layers/apz/test/mochitest/helper_fission_basic.html new file mode 100644 index 0000000000..dbc41477b9 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_basic.html @@ -0,0 +1,40 @@ + + + + + Basic sanity test that runs inside a fission-enabled window + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_checkerboard_severity.html b/gfx/layers/apz/test/mochitest/helper_fission_checkerboard_severity.html new file mode 100644 index 0000000000..ef7943b29f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_checkerboard_severity.html @@ -0,0 +1,138 @@ + + + + + + A test to make sure checkerboard severity isn't reported for non-scrollable + OOP iframe + + + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_empty.html b/gfx/layers/apz/test/mochitest/helper_fission_empty.html new file mode 100644 index 0000000000..6a95f76339 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_empty.html @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_event_region_override.html b/gfx/layers/apz/test/mochitest/helper_fission_event_region_override.html new file mode 100644 index 0000000000..82f529bebd --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_event_region_override.html @@ -0,0 +1,84 @@ + + + + + Ensure the event region override flags work properly + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_force_empty_hit_region.html b/gfx/layers/apz/test/mochitest/helper_fission_force_empty_hit_region.html new file mode 100644 index 0000000000..7baa552c37 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_force_empty_hit_region.html @@ -0,0 +1,82 @@ + + + + + Ensure the ForceEmptyHitRegion flag works properly + + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_inactivescroller_positionedcontent.html b/gfx/layers/apz/test/mochitest/helper_fission_inactivescroller_positionedcontent.html new file mode 100644 index 0000000000..fc43ace0f1 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_inactivescroller_positionedcontent.html @@ -0,0 +1,120 @@ + + + + + Ensure positioned content inside inactive scollframes but on top of OOPIFs hit-test properly + + + + + + + + + +
+
inside scroller
+
abspos inside scroller
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_inactivescroller_under_oopif.html b/gfx/layers/apz/test/mochitest/helper_fission_inactivescroller_under_oopif.html new file mode 100644 index 0000000000..c3099f52ef --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_inactivescroller_under_oopif.html @@ -0,0 +1,88 @@ + + + + + Ensure inactive scollframes under OOPIFs hit-test properly + + + + + + + + + +
+
inside scroller
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_initial_displayport.html b/gfx/layers/apz/test/mochitest/helper_fission_initial_displayport.html new file mode 100644 index 0000000000..bb03ad4eb7 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_initial_displayport.html @@ -0,0 +1,105 @@ + + + + + Test that OOP iframe's displayport is initially set + + + + + + + + + +
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_irregular_areas.html b/gfx/layers/apz/test/mochitest/helper_fission_irregular_areas.html new file mode 100644 index 0000000000..8ef3367c06 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_irregular_areas.html @@ -0,0 +1,101 @@ + + + + + Ensure irregular areas on top of OOPIFs hit-test properly + + + + + + + + + + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_large_subframe.html b/gfx/layers/apz/test/mochitest/helper_fission_large_subframe.html new file mode 100644 index 0000000000..3d5595f48e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_large_subframe.html @@ -0,0 +1,67 @@ + + + + + Test that large OOPIF does not get a too-large displayport + + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_scroll_handoff.html b/gfx/layers/apz/test/mochitest/helper_fission_scroll_handoff.html new file mode 100644 index 0000000000..3f75706778 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_scroll_handoff.html @@ -0,0 +1,50 @@ + + + + + scroll handoff + + + + + + + + + + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_scroll_oopif.html b/gfx/layers/apz/test/mochitest/helper_fission_scroll_oopif.html new file mode 100644 index 0000000000..2911b1eaf0 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_scroll_oopif.html @@ -0,0 +1,158 @@ + + + + + Test for async-scrolling an OOPIF and ensuring hit-testing still works + + + + + + + + + +
tall div to make the page scrollable
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_setResolution.html b/gfx/layers/apz/test/mochitest/helper_fission_setResolution.html new file mode 100644 index 0000000000..6bcf3fa2ce --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_setResolution.html @@ -0,0 +1,59 @@ + + + + + setResolutionAndScaleTo is properly delivered to OOP iframes + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_tap.html b/gfx/layers/apz/test/mochitest/helper_fission_tap.html new file mode 100644 index 0000000000..fab04bab26 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_tap.html @@ -0,0 +1,87 @@ + + + + + Test to ensure events get untransformed properly for OOP iframes + + + + + + + + + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_tap_in_nested_iframe_on_zoomed.html b/gfx/layers/apz/test/mochitest/helper_fission_tap_in_nested_iframe_on_zoomed.html new file mode 100644 index 0000000000..47a9c4bf8e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_tap_in_nested_iframe_on_zoomed.html @@ -0,0 +1,106 @@ + + + + + Test to ensure events get delivered properly for a nested OOP iframe + + + + + + + + + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_tap_on_zoomed.html b/gfx/layers/apz/test/mochitest/helper_fission_tap_on_zoomed.html new file mode 100644 index 0000000000..5d99b972c2 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_tap_on_zoomed.html @@ -0,0 +1,93 @@ + + + + + Test to ensure events get delivered properly for an OOP iframe + + + + + + + + + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_touch.html b/gfx/layers/apz/test/mochitest/helper_fission_touch.html new file mode 100644 index 0000000000..fa317b9f1f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_touch.html @@ -0,0 +1,99 @@ + + + + + Test to ensure touch events for OOP iframes + + + + + + + + + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_transforms.html b/gfx/layers/apz/test/mochitest/helper_fission_transforms.html new file mode 100644 index 0000000000..193b5650fd --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_transforms.html @@ -0,0 +1,89 @@ + + + + + Test to ensure events get untransformed properly for OOP iframes + + + + + + + + + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_utils.js b/gfx/layers/apz/test/mochitest/helper_fission_utils.js new file mode 100644 index 0000000000..ddcab62253 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_utils.js @@ -0,0 +1,130 @@ +// loadOOPIFrame expects apz_test_utils.js to be loaded as well, for promiseOneEvent. +/* import-globals-from apz_test_utils.js */ + +function fission_subtest_init() { + // Silence SimpleTest warning about missing assertions by having it wait + // indefinitely. We don't need to give it an explicit finish because the + // entire window this test runs in will be closed after subtestDone is called. + SimpleTest.waitForExplicitFinish(); + + // This is the point at which we inject the ok, is, subtestDone, etc. functions + // into this window. In particular this function should run after SimpleTest.js + // is imported, otherwise SimpleTest.js will clobber the functions with its + // own versions. This is implicitly enforced because if we call this function + // before SimpleTest.js is imported, the above line will throw an exception. + window.dispatchEvent(new Event("FissionTestHelper:Init")); +} + +/** + * Starts loading the given `iframePage` in the iframe element with the given + * id, and waits for it to load. + * Note that calling this function doesn't do the load directly; instead it + * returns an async function which can be added to a thenable chain. + */ +function loadOOPIFrame(iframeElementId, iframePage) { + return async function () { + if (window.location.href.startsWith("https://example.com/")) { + dump( + `WARNING: Calling loadOOPIFrame from ${window.location.href} so the iframe may not be OOP\n` + ); + ok(false, "Current origin is not example.com:443"); + } + + let url = + "https://example.com/browser/gfx/layers/apz/test/mochitest/" + iframePage; + let loadPromise = promiseOneEvent(window, "OOPIF:Load", function (e) { + return typeof e.data.content == "string" && e.data.content == url; + }); + let elem = document.getElementById(iframeElementId); + elem.src = url; + await loadPromise; + }; +} + +/** + * This is similar to the hitTest function in apz_test_utils.js, in that it + * does a hit-test for a point and returns the result. The difference is that + * in the fission world, the hit-test may land on an OOPIF, which means the + * result information will be in the APZ test data for the OOPIF process. This + * function checks both the current process and OOPIF process to see which one + * got a hit result, and returns the result regardless of which process got it. + * The caller is expected to check the layers id which will allow distinguishing + * the two cases. + */ +async function fissionHitTest(point, iframeElement) { + let get_iframe_compositor_test_data = function () { + let utils = SpecialPowers.getDOMWindowUtils(window); + return JSON.stringify(utils.getCompositorAPZTestData()); + }; + + let utils = SpecialPowers.getDOMWindowUtils(window); + + // Get the test data before doing the actual hit-test, to get a baseline + // of what we can ignore. + let oldParentTestData = utils.getCompositorAPZTestData(); + let oldIframeTestData = JSON.parse( + await FissionTestHelper.sendToOopif( + iframeElement, + `(${get_iframe_compositor_test_data})()` + ) + ); + + // Now do the hit-test + dump(`Hit-testing point (${point.x}, ${point.y}) in fission context\n`); + utils.sendMouseEvent( + "MozMouseHittest", + point.x, + point.y, + 0, + 0, + 0, + true, + 0, + 0, + true, + true + ); + + // Collect the new test data + let newParentTestData = utils.getCompositorAPZTestData(); + let newIframeTestData = JSON.parse( + await FissionTestHelper.sendToOopif( + iframeElement, + `(${get_iframe_compositor_test_data})()` + ) + ); + + // See which test data has new hit results + let hitResultCount = function (testData) { + return Object.keys(testData.hitResults).length; + }; + + let hitIframe = + hitResultCount(newIframeTestData) > hitResultCount(oldIframeTestData); + let hitParent = + hitResultCount(newParentTestData) > hitResultCount(oldParentTestData); + + // Extract the results from the appropriate test data + let lastHitResult = function (testData) { + let lastHit = + testData.hitResults[Object.keys(testData.hitResults).length - 1]; + return { + hitInfo: lastHit.hitResult, + scrollId: lastHit.scrollId, + layersId: lastHit.layersId, + }; + }; + if (hitIframe && hitParent) { + throw new Error( + "Both iframe and parent got hit-results, that is unexpected!" + ); + } else if (hitIframe) { + return lastHitResult(newIframeTestData); + } else if (hitParent) { + return lastHitResult(newParentTestData); + } else { + throw new Error( + "Neither iframe nor parent got the hit-result, that is unexpected!" + ); + } +} diff --git a/gfx/layers/apz/test/mochitest/helper_fixed_html_hittest.html b/gfx/layers/apz/test/mochitest/helper_fixed_html_hittest.html new file mode 100644 index 0000000000..27add6debb --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fixed_html_hittest.html @@ -0,0 +1,61 @@ + + + + + + Hittest position:fixed zoomed scroll + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_fixed_pos_displayport.html b/gfx/layers/apz/test/mochitest/helper_fixed_pos_displayport.html new file mode 100644 index 0000000000..adae691096 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fixed_pos_displayport.html @@ -0,0 +1,101 @@ + + + + + + position:fixed display port sizing + + + + + + +
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_fixed_position_scroll_hittest.html b/gfx/layers/apz/test/mochitest/helper_fixed_position_scroll_hittest.html new file mode 100644 index 0000000000..05cc2d0262 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fixed_position_scroll_hittest.html @@ -0,0 +1,51 @@ + + + + + + Hittest position:fixed zoomed scroll + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_fullscreen.html b/gfx/layers/apz/test/mochitest/helper_fullscreen.html new file mode 100644 index 0000000000..32de4979f2 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fullscreen.html @@ -0,0 +1,53 @@ + + + + + + Tests that layout viewport is not larger than visual viewport on fullscreen + + + + + +
+
overflowed element
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_backface_hidden.html b/gfx/layers/apz/test/mochitest/helper_hittest_backface_hidden.html new file mode 100644 index 0000000000..0e84282e54 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_backface_hidden.html @@ -0,0 +1,67 @@ + + + + APZ hit-testing with backface-visibility:hidden + + + + + + + +
+
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_basic.html b/gfx/layers/apz/test/mochitest/helper_hittest_basic.html new file mode 100644 index 0000000000..a9e9f0c07f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_basic.html @@ -0,0 +1,141 @@ + + + + Various tests to exercise the APZ hit-testing codepaths + + + + + + +
+
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_bug1119497.html b/gfx/layers/apz/test/mochitest/helper_hittest_bug1119497.html new file mode 100644 index 0000000000..1d1e0a922f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_bug1119497.html @@ -0,0 +1,54 @@ + + + + A hit testing test for the scenario in bug 1119497 + + + + + + + +
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_bug1257288.html b/gfx/layers/apz/test/mochitest/helper_hittest_bug1257288.html new file mode 100644 index 0000000000..d9b813dddf --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_bug1257288.html @@ -0,0 +1,74 @@ + + + + A hit testing test for the scenario in bug 1257288 + + + + + + + +
+
+
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_bug1715187.html b/gfx/layers/apz/test/mochitest/helper_hittest_bug1715187.html new file mode 100644 index 0000000000..6cb58b6413 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_bug1715187.html @@ -0,0 +1,69 @@ + + + + + + Check hittesting fission oop iframe with transform and pinch zoom works bug 1715187 + + + + + + + +
+ +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_bug1715187_oopif.html b/gfx/layers/apz/test/mochitest/helper_hittest_bug1715187_oopif.html new file mode 100644 index 0000000000..c0f5baf467 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_bug1715187_oopif.html @@ -0,0 +1,13 @@ + +
+
+
+ diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_bug1715369.html b/gfx/layers/apz/test/mochitest/helper_hittest_bug1715369.html new file mode 100644 index 0000000000..d1128fa946 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_bug1715369.html @@ -0,0 +1,74 @@ + + + + + + Check hittesting fission oop iframe with transform works bug 1715369 + + + + + + + + + +
+
+
+ +
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_bug1715369_iframe.html b/gfx/layers/apz/test/mochitest/helper_hittest_bug1715369_iframe.html new file mode 100644 index 0000000000..034eb0429f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_bug1715369_iframe.html @@ -0,0 +1,13 @@ + + + +
diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_bug1715369_oopif.html b/gfx/layers/apz/test/mochitest/helper_hittest_bug1715369_oopif.html new file mode 100644 index 0000000000..1a6582ce60 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_bug1715369_oopif.html @@ -0,0 +1,13 @@ + +
+
+
+ diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_bug1730606-1.html b/gfx/layers/apz/test/mochitest/helper_hittest_bug1730606-1.html new file mode 100644 index 0000000000..b84cb6b634 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_bug1730606-1.html @@ -0,0 +1,124 @@ + + + + A simple hit testing test that doesn't involve any transforms + + + + + + + +
+
+
+
+
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_bug1730606-2.html b/gfx/layers/apz/test/mochitest/helper_hittest_bug1730606-2.html new file mode 100644 index 0000000000..8cd3388534 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_bug1730606-2.html @@ -0,0 +1,157 @@ + + + + A more involved hit testing test that involves css and async transforms + + + + + + + +
+
+
+
+
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_bug1730606-3.html b/gfx/layers/apz/test/mochitest/helper_hittest_bug1730606-3.html new file mode 100644 index 0000000000..0ae01864a5 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_bug1730606-3.html @@ -0,0 +1,56 @@ + + + + A hit testing test involving a scenario with a scale transform + + + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_bug1730606-4.html b/gfx/layers/apz/test/mochitest/helper_hittest_bug1730606-4.html new file mode 100644 index 0000000000..26ec487b3e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_bug1730606-4.html @@ -0,0 +1,194 @@ + + + + A hit testing test involving a scenario with a scale transform + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_checkerboard.html b/gfx/layers/apz/test/mochitest/helper_hittest_checkerboard.html new file mode 100644 index 0000000000..eb2d583276 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_checkerboard.html @@ -0,0 +1,57 @@ + + + + APZ hit-testing over a checkerboarded area + + + + + + +
+ +
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_clippath.html b/gfx/layers/apz/test/mochitest/helper_hittest_clippath.html new file mode 100644 index 0000000000..0f8cfca519 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_clippath.html @@ -0,0 +1,118 @@ + + + + Hit-testing an iframe covered by an element with a clip-path + + + + + + + + + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_clipped_fixed_modal.html b/gfx/layers/apz/test/mochitest/helper_hittest_clipped_fixed_modal.html new file mode 100644 index 0000000000..122863967a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_clipped_fixed_modal.html @@ -0,0 +1,85 @@ + + + + Hit-testing on content covered by a fullscreen fixed-position item clipped away + + + + + + + +
+
+ Filler to make the content div scrollable +
+
+ + + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_deep_scene_stack.html b/gfx/layers/apz/test/mochitest/helper_hittest_deep_scene_stack.html new file mode 100644 index 0000000000..a04b1d3e83 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_deep_scene_stack.html @@ -0,0 +1,57 @@ + + + + Exercising the APZ/WR hit-test with a deep scene that produces many results + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_fixed-2.html b/gfx/layers/apz/test/mochitest/helper_hittest_fixed-2.html new file mode 100644 index 0000000000..0f20719d46 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_fixed-2.html @@ -0,0 +1,74 @@ + + + + APZ hit-testing of fixed content when async-scrolled + + + + + + + +
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_fixed-3.html b/gfx/layers/apz/test/mochitest/helper_hittest_fixed-3.html new file mode 100644 index 0000000000..2004ea9ae4 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_fixed-3.html @@ -0,0 +1,113 @@ + + + + APZ hit-testing of fixed content when async-scrolled + + + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_fixed.html b/gfx/layers/apz/test/mochitest/helper_hittest_fixed.html new file mode 100644 index 0000000000..530c53fd7a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_fixed.html @@ -0,0 +1,82 @@ + + + + APZ hit-testing of fixed content when async-scrolled + + + + + + + +
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_fixed_bg.html b/gfx/layers/apz/test/mochitest/helper_hittest_fixed_bg.html new file mode 100644 index 0000000000..1eae84305d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_fixed_bg.html @@ -0,0 +1,53 @@ + + + + + + Hit-testing of fixed background image + + + + + + +
+
+ + + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_fixed_in_scrolled_transform.html b/gfx/layers/apz/test/mochitest/helper_hittest_fixed_in_scrolled_transform.html new file mode 100644 index 0000000000..93d1e6064d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_fixed_in_scrolled_transform.html @@ -0,0 +1,91 @@ + + + + Hit-testing on the special setup from fixed-pos-scrolled-clip-3.html + + + + + + + + +
+
+
+
+
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_fixed_item_over_oop_iframe.html b/gfx/layers/apz/test/mochitest/helper_hittest_fixed_item_over_oop_iframe.html new file mode 100644 index 0000000000..a8b66cb835 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_fixed_item_over_oop_iframe.html @@ -0,0 +1,61 @@ + + + +Hit-testing of positioned item on top of oop iframe + + + + + +
+ Link +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_float_bug1434846.html b/gfx/layers/apz/test/mochitest/helper_hittest_float_bug1434846.html new file mode 100644 index 0000000000..986c7b8477 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_float_bug1434846.html @@ -0,0 +1,56 @@ + + + + APZ hit-testing with floated subframe + + + + + + + +
+
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_float_bug1443518.html b/gfx/layers/apz/test/mochitest/helper_hittest_float_bug1443518.html new file mode 100644 index 0000000000..0e2e754375 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_float_bug1443518.html @@ -0,0 +1,56 @@ + + + + APZ hit-testing with floated subframe + + + + + + + +
+
+
+
A line of text that overflows because it's sufficiently long
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_hidden_inactive_scrollframe.html b/gfx/layers/apz/test/mochitest/helper_hittest_hidden_inactive_scrollframe.html new file mode 100644 index 0000000000..0abed82156 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_hidden_inactive_scrollframe.html @@ -0,0 +1,55 @@ + + + + APZ hit-testing with an inactive scrollframe that is visibility:hidden (bug 1673505) + + + + + + +
+
+
+ The body of this document is scrollable and is the main scrollable + element. On top of that we have a hidden fixed-pos item containing another + scrollframe, but this nested scrollframe is inactive. + Since the fixed-pos item is hidden, the nested scrollframe is hidden + too and shouldn't be the target of hit-testing. However, because it is + an inactive scrollframe, code to generate the "this is an inactive + scrollframe" area was marking it as hit-testable. This bug led to hit- + tests being mis-targeted to the nested scrollframe's layers id instead + of whatever was underneath. +
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_hoisted_scrollinfo.html b/gfx/layers/apz/test/mochitest/helper_hittest_hoisted_scrollinfo.html new file mode 100644 index 0000000000..3427c8da47 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_hoisted_scrollinfo.html @@ -0,0 +1,81 @@ + + + + Hit-testing on a scrollframe forced to be inactive by being inside a filter + + + + + + + +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective-2.html b/gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective-2.html new file mode 100644 index 0000000000..9838a02aa9 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective-2.html @@ -0,0 +1,69 @@ + + + Test that events are delivered to the correct document near an iframe inide a perspective transform + + + + + + +
+
+ +
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective-3.html b/gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective-3.html new file mode 100644 index 0000000000..7fb423ca1c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective-3.html @@ -0,0 +1,70 @@ + + + Test that events are delivered with correct coordinates to an iframe inide a perspective transform + + + + + + +
+
+ +
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective.html b/gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective.html new file mode 100644 index 0000000000..d7858d4b00 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective.html @@ -0,0 +1,60 @@ + + + Test that events are delivered with correct coordinates to an iframe inide a perspective transform + + + + + + +
+ +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective_child.html b/gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective_child.html new file mode 100644 index 0000000000..37f20ad725 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective_child.html @@ -0,0 +1,13 @@ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_nested_transforms_bug1459696.html b/gfx/layers/apz/test/mochitest/helper_hittest_nested_transforms_bug1459696.html new file mode 100644 index 0000000000..9d0a9fa50c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_nested_transforms_bug1459696.html @@ -0,0 +1,80 @@ + + + + APZ hit-testing with nested inactive transforms (bug 1459696) + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_obscuration.html b/gfx/layers/apz/test/mochitest/helper_hittest_obscuration.html new file mode 100644 index 0000000000..377b191359 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_obscuration.html @@ -0,0 +1,77 @@ + + + + Test hit-testing on content which is revealed by async scrolling + + + + + + + +
+
+
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_overscroll.html b/gfx/layers/apz/test/mochitest/helper_hittest_overscroll.html new file mode 100644 index 0000000000..c245258b68 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_overscroll.html @@ -0,0 +1,249 @@ + + + + Test APZ hit-testing while overscrolled + + + + + + + +
+
+ +
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_overscroll_contextmenu.html b/gfx/layers/apz/test/mochitest/helper_hittest_overscroll_contextmenu.html new file mode 100644 index 0000000000..8aff3103dd --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_overscroll_contextmenu.html @@ -0,0 +1,129 @@ + + + + Test APZ hit-testing while overscrolled + + + + + + + +
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_overscroll_subframe.html b/gfx/layers/apz/test/mochitest/helper_hittest_overscroll_subframe.html new file mode 100644 index 0000000000..36918b3682 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_overscroll_subframe.html @@ -0,0 +1,132 @@ + + + + Test APZ hit-testing while overscrolled + + + + + + + +
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_pointerevents_svg.html b/gfx/layers/apz/test/mochitest/helper_hittest_pointerevents_svg.html new file mode 100644 index 0000000000..22b880736d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_pointerevents_svg.html @@ -0,0 +1,177 @@ + + + + Hit-testing a scrollframe covered by nonrectangular and pointer-events:none things + + + + + + + + +
+
+ + + +
+
+
+ + + +
+
+ +
+
+ + + +
+
+
+ + + +
+
+ +
+
+ + + +
+
+
+ + + +
+
+ +
+
+ + + +
+
+
+ + + +
+
+ +
+ Each of the gradients should be scrollable, except where the black stuff on the right cover them. + The brown square should not prevent scrolling. Similarly, the content on the left (which goes + underneath the scroller) shouldn't matter. +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_spam.html b/gfx/layers/apz/test/mochitest/helper_hittest_spam.html new file mode 100644 index 0000000000..cace5491a5 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_spam.html @@ -0,0 +1,100 @@ + + + + Test doing lots of hit-testing on a rapidly changing page + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_sticky_bug1478304.html b/gfx/layers/apz/test/mochitest/helper_hittest_sticky_bug1478304.html new file mode 100644 index 0000000000..395c688a12 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_sticky_bug1478304.html @@ -0,0 +1,58 @@ + + + + APZ hit-testing with sticky element inside a transform (bug 1478304) + + + + + + + +
+
+
sticky with transformed parent (click me or hover me and try a scroll)
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_touchaction.html b/gfx/layers/apz/test/mochitest/helper_hittest_touchaction.html new file mode 100644 index 0000000000..acabfcc07b --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_touchaction.html @@ -0,0 +1,353 @@ + + + + Testing APZ hit-test with touch-action boxes + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_horizontal_checkerboard.html b/gfx/layers/apz/test/mochitest/helper_horizontal_checkerboard.html new file mode 100644 index 0000000000..0355243738 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_horizontal_checkerboard.html @@ -0,0 +1,65 @@ + + + +Testcase for checkerboarding during horizontal scrolling + + + + + + +
+ + + + diff --git a/gfx/layers/apz/test/mochitest/helper_iframe1.html b/gfx/layers/apz/test/mochitest/helper_iframe1.html new file mode 100644 index 0000000000..047da96bd4 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_iframe1.html @@ -0,0 +1,14 @@ + + + + + + + +
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_iframe2.html b/gfx/layers/apz/test/mochitest/helper_iframe2.html new file mode 100644 index 0000000000..fee3883e95 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_iframe2.html @@ -0,0 +1,14 @@ + + + + + + + +
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_iframe_pan.html b/gfx/layers/apz/test/mochitest/helper_iframe_pan.html new file mode 100644 index 0000000000..032133c255 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_iframe_pan.html @@ -0,0 +1,49 @@ + + + + + + Sanity panning test for scrollable div + + + + + + + +
+ This div makes the top-level page scrollable. +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_iframe_textarea.html b/gfx/layers/apz/test/mochitest/helper_iframe_textarea.html new file mode 100644 index 0000000000..f4a934db74 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_iframe_textarea.html @@ -0,0 +1,12 @@ + + + + + + +
ABC
+ + +
ABC
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_interrupted_reflow.html b/gfx/layers/apz/test/mochitest/helper_interrupted_reflow.html new file mode 100644 index 0000000000..0dcaf1d67d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_interrupted_reflow.html @@ -0,0 +1,712 @@ + + + + + Test for bug 1292781 + + + + + + + + +Mozilla Bug 1292781 +

+
+

The frame reconstruction should not leave this scrollframe in a bad state

+
+
+ this is the top of the scrollframe. +
this is a box
+
this is a box
+
this is a box
+
this is a box
+ this is near the top of the scrollframe. +
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+ this is near the bottom of the scrollframe. +
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+ this is the bottom of the scrollframe. +
+
+
+ +
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/helper_key_scroll.html b/gfx/layers/apz/test/mochitest/helper_key_scroll.html
new file mode 100644
index 0000000000..021e2803b7
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_key_scroll.html
@@ -0,0 +1,109 @@
+
+
+
+
+  
+  Async key scrolling test, helper page
+  
+  
+  
+  
+
+
+  Async key scrolling test
+  
+  
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_long_tap.html b/gfx/layers/apz/test/mochitest/helper_long_tap.html new file mode 100644 index 0000000000..2fdb2472ec --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_long_tap.html @@ -0,0 +1,166 @@ + + + + + + Ensure we get a touch-cancel after a contextmenu comes up + + + + + + + Link to nowhere + + diff --git a/gfx/layers/apz/test/mochitest/helper_main_thread_smooth_scroll_scrollend.html b/gfx/layers/apz/test/mochitest/helper_main_thread_smooth_scroll_scrollend.html new file mode 100644 index 0000000000..4f07db516e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_main_thread_smooth_scroll_scrollend.html @@ -0,0 +1,47 @@ + + + + + + + + + + + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_mainthread_scroll_bug1662379.html b/gfx/layers/apz/test/mochitest/helper_mainthread_scroll_bug1662379.html new file mode 100644 index 0000000000..bef8f05f17 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_mainthread_scroll_bug1662379.html @@ -0,0 +1,168 @@ + + + + + + +
+
+
    +
  • Test item 1
  • +
  • Test item 2
  • +
  • Test item 3
  • +
  • Test item 4
  • +
  • Test item 5
  • +
  • Test item 6
  • +
  • Test item 7
  • +
  • Test item 8
  • +
  • Test item 9
  • +
  • Test item 10
  • +
  • Test item 11
  • +
  • Test item 13
  • +
  • Test item 14
  • +
  • Test item 15
  • +
  • Test item 16
  • +
  • Test item 17
  • +
  • Test item 18
  • +
  • Test item 19
  • +
+
+
+
+ Steps to reproduce: +
    +
  1. Scroll the list of "test items" all the way to the bottom +
  2. Click on the reparent button below +
  3. Click on one of the test items +
  4. The `clickTarget` JS variable should match the thing you clicked on +
+
+ +
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_minimum_scale_1_0.html b/gfx/layers/apz/test/mochitest/helper_minimum_scale_1_0.html new file mode 100644 index 0000000000..17ccb3a54d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_minimum_scale_1_0.html @@ -0,0 +1,46 @@ + + + + + + Tests that the layout viewport is expanted to the minimum scale size (minimim-scale >= 1.0) + + + + + +
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_no_scalable_with_initial_scale.html b/gfx/layers/apz/test/mochitest/helper_no_scalable_with_initial_scale.html new file mode 100644 index 0000000000..7280a26006 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_no_scalable_with_initial_scale.html @@ -0,0 +1,48 @@ + + + + + + Tests that the layout viewport is not expanted to the minimum scale size if user-scalable=no is specified + + + + + +
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_onetouchpinch_nested.html b/gfx/layers/apz/test/mochitest/helper_onetouchpinch_nested.html new file mode 100644 index 0000000000..54b578b496 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_onetouchpinch_nested.html @@ -0,0 +1,103 @@ + + + + + + One-touch pinch zooming while on a non-root scroller + + + + + + + + + Here is some text outside the scrollable div. +
+ Here is some text inside the scrollable div. +
This div actually makes it overflow.
+
+
This div makes the body scrollable.
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_overflowhidden_zoom.html b/gfx/layers/apz/test/mochitest/helper_overflowhidden_zoom.html new file mode 100644 index 0000000000..6c12008e4b --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_overflowhidden_zoom.html @@ -0,0 +1,83 @@ + + + + + + Tests that zooming in and out doesn't change the scroll position on an overflow hidden document + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_override_root.html b/gfx/layers/apz/test/mochitest/helper_override_root.html new file mode 100644 index 0000000000..81c1b34938 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_override_root.html @@ -0,0 +1,62 @@ + + + + + + Simple wheel scroll cancellation + + + + + + + This page should not be wheel-scrollable. + + diff --git a/gfx/layers/apz/test/mochitest/helper_override_subdoc.html b/gfx/layers/apz/test/mochitest/helper_override_subdoc.html new file mode 100644 index 0000000000..910f36ddc3 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_override_subdoc.html @@ -0,0 +1,15 @@ + + + + + + Wheel scroll cancellation inside iframe + + + + + This just loads helper_override_root in an iframe, so that we test event + regions overriding on in-process subdocuments. + + + diff --git a/gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1425573.html b/gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1425573.html new file mode 100644 index 0000000000..817522f9c3 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1425573.html @@ -0,0 +1,44 @@ + + + Wheel-scrolling over inactive subframe with overscroll-behavior + + + + + + + +
+
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1425603.html b/gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1425603.html new file mode 100644 index 0000000000..8324530c95 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1425603.html @@ -0,0 +1,76 @@ + + + + Scrolling over checkerboarded area respects overscroll-behavior + + + + + + + +
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1494440.html b/gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1494440.html new file mode 100644 index 0000000000..3f12e36102 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1494440.html @@ -0,0 +1,50 @@ + + + Inactive iframe with overscroll-behavior + + + + + + +
+ + + + diff --git a/gfx/layers/apz/test/mochitest/helper_overscroll_in_apz_test_data.html b/gfx/layers/apz/test/mochitest/helper_overscroll_in_apz_test_data.html new file mode 100644 index 0000000000..ed05f25819 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_overscroll_in_apz_test_data.html @@ -0,0 +1,29 @@ + + + +A simple test checks "overscrolled" info in APZTestData +Tests scroll anchoring updates in-progress wheel scrolling __relatively__ + + + +
+ diff --git a/gfx/layers/apz/test/mochitest/helper_overscroll_in_subscroller.html b/gfx/layers/apz/test/mochitest/helper_overscroll_in_subscroller.html new file mode 100644 index 0000000000..5936de97f7 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_overscroll_in_subscroller.html @@ -0,0 +1,165 @@ + + + + + Tests that the overscroll gutter in a sub scroll container is restored if it's + no longer target scroll container + + + + + + +
+
+
+
+ diff --git a/gfx/layers/apz/test/mochitest/helper_position_fixed_scroll_handoff-1.html b/gfx/layers/apz/test/mochitest/helper_position_fixed_scroll_handoff-1.html new file mode 100644 index 0000000000..6c986b0ca1 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_position_fixed_scroll_handoff-1.html @@ -0,0 +1,88 @@ + + + + APZ overscroll handoff for fixed elements + + + + + + + +
+
+
+
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_position_fixed_scroll_handoff-2.html b/gfx/layers/apz/test/mochitest/helper_position_fixed_scroll_handoff-2.html new file mode 100644 index 0000000000..29b11072ca --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_position_fixed_scroll_handoff-2.html @@ -0,0 +1,65 @@ + + + APZ overscroll handoff for fixed elements + + + + + + +
+
+
+
+ diff --git a/gfx/layers/apz/test/mochitest/helper_position_fixed_scroll_handoff-3.html b/gfx/layers/apz/test/mochitest/helper_position_fixed_scroll_handoff-3.html new file mode 100644 index 0000000000..4a0687ba20 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_position_fixed_scroll_handoff-3.html @@ -0,0 +1,77 @@ + + + APZ overscroll handoff for fixed elements + + + + + + +
+
+
+
+
+
+
+
+ diff --git a/gfx/layers/apz/test/mochitest/helper_position_fixed_scroll_handoff-4.html b/gfx/layers/apz/test/mochitest/helper_position_fixed_scroll_handoff-4.html new file mode 100644 index 0000000000..7394984ce3 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_position_fixed_scroll_handoff-4.html @@ -0,0 +1,79 @@ + + + APZ overscroll handoff for fixed elements + + + + + + +
+
+
+
+
+
+
+
+
+
+ diff --git a/gfx/layers/apz/test/mochitest/helper_position_fixed_scroll_handoff-5.html b/gfx/layers/apz/test/mochitest/helper_position_fixed_scroll_handoff-5.html new file mode 100644 index 0000000000..3d62287c7c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_position_fixed_scroll_handoff-5.html @@ -0,0 +1,110 @@ + + + + APZ overscroll handoff for fixed elements in a subdoc + + + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_position_sticky_flicker.html b/gfx/layers/apz/test/mochitest/helper_position_sticky_flicker.html new file mode 100644 index 0000000000..28495a7122 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_position_sticky_flicker.html @@ -0,0 +1,25 @@ + + + + + + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_position_sticky_scroll_handoff.html b/gfx/layers/apz/test/mochitest/helper_position_sticky_scroll_handoff.html new file mode 100644 index 0000000000..ae7815a2fc --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_position_sticky_scroll_handoff.html @@ -0,0 +1,88 @@ + + + + APZ overscroll handoff for sticky elements + + + + + + + +
+
+
+
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_programmatic_scroll_behavior.html b/gfx/layers/apz/test/mochitest/helper_programmatic_scroll_behavior.html new file mode 100644 index 0000000000..721ce7e538 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_programmatic_scroll_behavior.html @@ -0,0 +1,81 @@ + + + + + + + + + + + +
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_relative_scroll_smoothness.html b/gfx/layers/apz/test/mochitest/helper_relative_scroll_smoothness.html new file mode 100644 index 0000000000..c8907c6d5d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_relative_scroll_smoothness.html @@ -0,0 +1,141 @@ + + + + + + + + +What happens if main thread scrolls? + + + diff --git a/gfx/layers/apz/test/mochitest/helper_reset_zoom_bug1818967.html b/gfx/layers/apz/test/mochitest/helper_reset_zoom_bug1818967.html new file mode 100644 index 0000000000..4a4d7e34ca --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_reset_zoom_bug1818967.html @@ -0,0 +1,55 @@ + + + + +Test that we do not checkerboard after resetting the pinch-zoom scale + + + + + +
+ + + + diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_anchoring_on_wheel.html b/gfx/layers/apz/test/mochitest/helper_scroll_anchoring_on_wheel.html new file mode 100644 index 0000000000..12d307fe57 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_anchoring_on_wheel.html @@ -0,0 +1,59 @@ + + + +Tests scroll anchoring updates in-progress wheel scrolling __relatively__ + + + + +
+
+ diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_anchoring_smooth_scroll.html b/gfx/layers/apz/test/mochitest/helper_scroll_anchoring_smooth_scroll.html new file mode 100644 index 0000000000..7bdfa83f24 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_anchoring_smooth_scroll.html @@ -0,0 +1,54 @@ + + + +Tests scroll anchoring interaction with smooth visual scrolling. + + + + +
+
+ diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_anchoring_smooth_scroll_with_set_timeout.html b/gfx/layers/apz/test/mochitest/helper_scroll_anchoring_smooth_scroll_with_set_timeout.html new file mode 100644 index 0000000000..07ba816c36 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_anchoring_smooth_scroll_with_set_timeout.html @@ -0,0 +1,56 @@ + + + +Tests scroll anchoring interaction with smooth visual scrolling with set timeout. + + + + +
+
+ diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_inactive_perspective.html b/gfx/layers/apz/test/mochitest/helper_scroll_inactive_perspective.html new file mode 100644 index 0000000000..727d0e4fd1 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_inactive_perspective.html @@ -0,0 +1,45 @@ + + + Wheel-scrolling over inactive subframe with perspective + + + + + + + +
+
+
+
+ diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_inactive_zindex.html b/gfx/layers/apz/test/mochitest/helper_scroll_inactive_zindex.html new file mode 100644 index 0000000000..44c3cf3217 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_inactive_zindex.html @@ -0,0 +1,46 @@ + + + Wheel-scrolling over inactive subframe with z-index + + + + + + + +
+
+
+
+ diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_into_view_bug1516056.html b/gfx/layers/apz/test/mochitest/helper_scroll_into_view_bug1516056.html new file mode 100644 index 0000000000..99952dddd4 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_into_view_bug1516056.html @@ -0,0 +1,62 @@ + + + + + + Test for bug 1516056: "scroll into view" respects bounds on layout scroll position + + + + + +
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_into_view_bug1562757.html b/gfx/layers/apz/test/mochitest/helper_scroll_into_view_bug1562757.html new file mode 100644 index 0000000000..4aff2901d7 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_into_view_bug1562757.html @@ -0,0 +1,64 @@ + + + + + + Test for bug 1562757: "scroll into view" in iframe respects bounds on layout scroll position + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_linked_effect_by_wheel.html b/gfx/layers/apz/test/mochitest/helper_scroll_linked_effect_by_wheel.html new file mode 100644 index 0000000000..f9303253b8 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_linked_effect_by_wheel.html @@ -0,0 +1,65 @@ + + + + + + + +A scroll linked effect scrolled by wheel events + +
+ diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_linked_effect_detector.html b/gfx/layers/apz/test/mochitest/helper_scroll_linked_effect_detector.html new file mode 100644 index 0000000000..aaa4b43829 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_linked_effect_detector.html @@ -0,0 +1,108 @@ + + + + + + +ScrollLinkedEffectDetector tests + +
+ diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html b/gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html new file mode 100644 index 0000000000..5fbbc1437f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html @@ -0,0 +1,60 @@ + + + Wheel-scrolling over position:fixed and position:sticky elements, in the top-level document as well as iframes + + + + + + +
sticky
+
fixed
+ + +
+
scrollable content inside a fixed-pos item
+
+ diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_over_scrollbar.html b/gfx/layers/apz/test/mochitest/helper_scroll_over_scrollbar.html new file mode 100644 index 0000000000..b7a8698cf8 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_over_scrollbar.html @@ -0,0 +1,48 @@ + + + Wheel-scrolling over scrollbar + + + + + + + +
+
+
+ diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_snap_no_valid_snap_position.html b/gfx/layers/apz/test/mochitest/helper_scroll_snap_no_valid_snap_position.html new file mode 100644 index 0000000000..00f5e7d344 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_snap_no_valid_snap_position.html @@ -0,0 +1,45 @@ + + + + + + No snapping occurs if there is no valid snap position + + + + + + +
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_snap_not_resnap_during_panning.html b/gfx/layers/apz/test/mochitest/helper_scroll_snap_not_resnap_during_panning.html new file mode 100644 index 0000000000..f28a2f9396 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_snap_not_resnap_during_panning.html @@ -0,0 +1,93 @@ + + + + + + Skip re-snapping during pan gesture + + + + + + +
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_snap_not_resnap_during_scrollbar_dragging.html b/gfx/layers/apz/test/mochitest/helper_scroll_snap_not_resnap_during_scrollbar_dragging.html new file mode 100644 index 0000000000..ca2be3916f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_snap_not_resnap_during_scrollbar_dragging.html @@ -0,0 +1,105 @@ + + + + + + Skip re-snapping during scrollbar dragging + + + + + + +
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_snap_on_page_down_scroll.html b/gfx/layers/apz/test/mochitest/helper_scroll_snap_on_page_down_scroll.html new file mode 100644 index 0000000000..d0779cdb2d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_snap_on_page_down_scroll.html @@ -0,0 +1,84 @@ + + + + + + Page scroll snaps a snap point in the same page rather than the one in the next page + + + + + + + +
1
+
2
+
3
+
4
+
5
+ + + + diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_snap_resnap_after_async_scroll.html b/gfx/layers/apz/test/mochitest/helper_scroll_snap_resnap_after_async_scroll.html new file mode 100644 index 0000000000..ff1c78bef5 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_snap_resnap_after_async_scroll.html @@ -0,0 +1,81 @@ + + + + + + Re-snapping to the last snapped element on APZ + + + + + + +
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_snap_resnap_after_async_scrollBy.html b/gfx/layers/apz/test/mochitest/helper_scroll_snap_resnap_after_async_scrollBy.html new file mode 100644 index 0000000000..2b034da933 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_snap_resnap_after_async_scrollBy.html @@ -0,0 +1,72 @@ + + + + + + Re-snapping to the last snapped element on APZ + + + + + + +
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_tables_perspective.html b/gfx/layers/apz/test/mochitest/helper_scroll_tables_perspective.html new file mode 100644 index 0000000000..404274d3f4 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_tables_perspective.html @@ -0,0 +1,66 @@ + + + + + + + + + +
+
+
+
+
+ A
+ B
+ C
+ D
+ E
+ f
+ g
+ h
+ i
+ j
+
+
+ diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_thumb_dragging.html b/gfx/layers/apz/test/mochitest/helper_scroll_thumb_dragging.html new file mode 100644 index 0000000000..821cf00be7 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_thumb_dragging.html @@ -0,0 +1,20 @@ + + + + + + +
+ +
+ diff --git a/gfx/layers/apz/test/mochitest/helper_scrollbar_snap_bug1501062.html b/gfx/layers/apz/test/mochitest/helper_scrollbar_snap_bug1501062.html new file mode 100644 index 0000000000..ec18fd856d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scrollbar_snap_bug1501062.html @@ -0,0 +1,135 @@ + + + + + + Exercising the slider.snapMultiplier code + + + + + +
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_scrollbarbutton_repeat.html b/gfx/layers/apz/test/mochitest/helper_scrollbarbutton_repeat.html new file mode 100644 index 0000000000..723b250bd8 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scrollbarbutton_repeat.html @@ -0,0 +1,101 @@ + + + + + + Basic test that click and hold on a scrollbar button works as expected + + + + + + + +
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_scrollbarbuttonclick_checkerboard.html b/gfx/layers/apz/test/mochitest/helper_scrollbarbuttonclick_checkerboard.html new file mode 100644 index 0000000000..e7b7895966 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scrollbarbuttonclick_checkerboard.html @@ -0,0 +1,75 @@ + + + + + + Test that repeated scrollbar button clicks do not cause checkerboarding + + + + + + + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_scrollby_bug1531796.html b/gfx/layers/apz/test/mochitest/helper_scrollby_bug1531796.html new file mode 100644 index 0000000000..5e6f0e1833 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scrollby_bug1531796.html @@ -0,0 +1,36 @@ + + + + + + Test that scrollBy() doesn't scroll more than it should + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_scrollend_bubbles.html b/gfx/layers/apz/test/mochitest/helper_scrollend_bubbles.html new file mode 100644 index 0000000000..d0d763b474 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scrollend_bubbles.html @@ -0,0 +1,99 @@ + + + + + + + + + + + +
+
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_scrollframe_activation_on_load.html b/gfx/layers/apz/test/mochitest/helper_scrollframe_activation_on_load.html new file mode 100644 index 0000000000..1947a89a8f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scrollframe_activation_on_load.html @@ -0,0 +1,89 @@ + + + + + + Test for Bug 1151663, helper page + + + + + + + Mozilla Bug 1151663 +
+ +
+
+ +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_scrollto_tap.html b/gfx/layers/apz/test/mochitest/helper_scrollto_tap.html new file mode 100644 index 0000000000..a1fb3f67a6 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scrollto_tap.html @@ -0,0 +1,59 @@ + + + + + + Sanity touch-tapping test + + + + + + +
spacer
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_self_closer.html b/gfx/layers/apz/test/mochitest/helper_self_closer.html new file mode 100644 index 0000000000..09a9286c06 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_self_closer.html @@ -0,0 +1,12 @@ + + + + + + +See ya! (This window will close itself) diff --git a/gfx/layers/apz/test/mochitest/helper_smoothscroll_spam.html b/gfx/layers/apz/test/mochitest/helper_smoothscroll_spam.html new file mode 100644 index 0000000000..154ed3cafe --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_smoothscroll_spam.html @@ -0,0 +1,51 @@ + + + + + + Test for scenario in bug 1228407 + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_smoothscroll_spam_interleaved.html b/gfx/layers/apz/test/mochitest/helper_smoothscroll_spam_interleaved.html new file mode 100644 index 0000000000..003ae49ea5 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_smoothscroll_spam_interleaved.html @@ -0,0 +1,57 @@ + + + + + + Test for scenario in bug 1228407 with two scrollframes + + + + + + + +
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_subframe_style.css b/gfx/layers/apz/test/mochitest/helper_subframe_style.css new file mode 100644 index 0000000000..5af9640802 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_subframe_style.css @@ -0,0 +1,15 @@ +body { + height: 500px; +} + +.inner-frame { + margin-top: 50px; /* this should be at least 30px */ + height: 200%; + width: 75%; + overflow: scroll; +} +.inner-content { + height: 200%; + width: 200%; + background: repeating-linear-gradient(#EEE, #EEE 100px, #DDD 100px, #DDD 200px); +} diff --git a/gfx/layers/apz/test/mochitest/helper_tall.html b/gfx/layers/apz/test/mochitest/helper_tall.html new file mode 100644 index 0000000000..7fde795fdc --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_tall.html @@ -0,0 +1,504 @@ + + +This is a tall page
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_tap.html b/gfx/layers/apz/test/mochitest/helper_tap.html new file mode 100644 index 0000000000..f987299447 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_tap.html @@ -0,0 +1,32 @@ + + + + + + Sanity touch-tapping test + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_tap_default_passive.html b/gfx/layers/apz/test/mochitest/helper_tap_default_passive.html new file mode 100644 index 0000000000..a1f276224b --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_tap_default_passive.html @@ -0,0 +1,81 @@ + + + + + + Ensure APZ doesn't wait for passive listeners + + + + + + + Link to nowhere + + + diff --git a/gfx/layers/apz/test/mochitest/helper_tap_fullzoom.html b/gfx/layers/apz/test/mochitest/helper_tap_fullzoom.html new file mode 100644 index 0000000000..7d739924f0 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_tap_fullzoom.html @@ -0,0 +1,33 @@ + + + + + + Sanity touch-tapping test with fullzoom + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_tap_passive.html b/gfx/layers/apz/test/mochitest/helper_tap_passive.html new file mode 100644 index 0000000000..647564c08a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_tap_passive.html @@ -0,0 +1,66 @@ + + + + + + Ensure APZ doesn't wait for passive listeners + + + + + + + Link to nowhere + + diff --git a/gfx/layers/apz/test/mochitest/helper_test_autoscrolling_in_oop_frame.html b/gfx/layers/apz/test/mochitest/helper_test_autoscrolling_in_oop_frame.html new file mode 100644 index 0000000000..c1826f583f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_test_autoscrolling_in_oop_frame.html @@ -0,0 +1,9 @@ + + + + + + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_test_reset_scaling_zoom.html b/gfx/layers/apz/test/mochitest/helper_test_reset_scaling_zoom.html new file mode 100644 index 0000000000..31779410da --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_test_reset_scaling_zoom.html @@ -0,0 +1,23 @@ + + + + + + + + +Here is some text to stare at as the test runs. It serves no functional +purpose, but gives you an idea of the zoom level. It's harder to tell what +the zoom level is when the page is just solid white. + diff --git a/gfx/layers/apz/test/mochitest/helper_test_select_popup_position.html b/gfx/layers/apz/test/mochitest/helper_test_select_popup_position.html new file mode 100644 index 0000000000..c810751b2f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_test_select_popup_position.html @@ -0,0 +1,23 @@ + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_test_select_popup_position_transformed_in_parent.html b/gfx/layers/apz/test/mochitest/helper_test_select_popup_position_transformed_in_parent.html new file mode 100644 index 0000000000..860ca079de --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_test_select_popup_position_transformed_in_parent.html @@ -0,0 +1,25 @@ + + + + + +
+ +
+ diff --git a/gfx/layers/apz/test/mochitest/helper_test_select_popup_position_zoomed.html b/gfx/layers/apz/test/mochitest/helper_test_select_popup_position_zoomed.html new file mode 100644 index 0000000000..2ec079369e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_test_select_popup_position_zoomed.html @@ -0,0 +1,25 @@ + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_test_select_zoom.html b/gfx/layers/apz/test/mochitest/helper_test_select_zoom.html new file mode 100644 index 0000000000..d3fc5fcb67 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_test_select_zoom.html @@ -0,0 +1,43 @@ + + + + + + + + +Here is some text to stare at as the test runs. It serves no functional +purpose, but gives you an idea of the zoom level. It's harder to tell what +the zoom level is when the page is just solid white. + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_test_tab_drag_zoom.html b/gfx/layers/apz/test/mochitest/helper_test_tab_drag_zoom.html new file mode 100644 index 0000000000..9f0175468c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_test_tab_drag_zoom.html @@ -0,0 +1,18 @@ + + + + + + + +Here is some text to stare at as the test runs. It serves no functional +purpose, but gives you an idea of the zoom level. It's harder to tell what +the zoom level is when the page is just solid white. + + + diff --git a/gfx/layers/apz/test/mochitest/helper_touch_action.html b/gfx/layers/apz/test/mochitest/helper_touch_action.html new file mode 100644 index 0000000000..10038de29f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_touch_action.html @@ -0,0 +1,123 @@ + + + + + + Sanity touch-action test + + + + + + +
+ This div makes the page scrollable on both axes.
+ This is the second line of text.
+ This is the third line of text.
+ This is the fourth line of text. +
+ +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_touch_action_complex.html b/gfx/layers/apz/test/mochitest/helper_touch_action_complex.html new file mode 100644 index 0000000000..b8df34bfca --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_touch_action_complex.html @@ -0,0 +1,137 @@ + + + + + + Complex touch-action test + + + + + + +
+
+
+ + + + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_touch_action_ordering_block.html b/gfx/layers/apz/test/mochitest/helper_touch_action_ordering_block.html new file mode 100644 index 0000000000..ca593c0db5 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_touch_action_ordering_block.html @@ -0,0 +1,39 @@ + + + + + + Touch-action with sorted element + + + + + + +
+
+ +
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_touch_action_ordering_zindex.html b/gfx/layers/apz/test/mochitest/helper_touch_action_ordering_zindex.html new file mode 100644 index 0000000000..5b0d57a18d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_touch_action_ordering_zindex.html @@ -0,0 +1,37 @@ + + + + + + Touch-action with sorted element + + + + + + +
+
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_touch_action_regions.html b/gfx/layers/apz/test/mochitest/helper_touch_action_regions.html new file mode 100644 index 0000000000..6a8a09e55a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_touch_action_regions.html @@ -0,0 +1,345 @@ + + + + + + Test to ensure APZ doesn't always wait for touch-action + + + + + + +
+
+ This is a colored div that will move on the screen as the scroller scrolls. +
+
+ This is a large div to make the scroller scrollable. +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_touch_action_zero_opacity_bug1500864.html b/gfx/layers/apz/test/mochitest/helper_touch_action_zero_opacity_bug1500864.html new file mode 100644 index 0000000000..d31483f4f6 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_touch_action_zero_opacity_bug1500864.html @@ -0,0 +1,45 @@ + + + + + + Touch-action on a zero-opacity element + + + + + + +
+ Inside the black border is a zero-opacity touch-action none. +
+
+
+
this text shouldn't be visible
+
+
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_touch_drag_root_scrollbar.html b/gfx/layers/apz/test/mochitest/helper_touch_drag_root_scrollbar.html new file mode 100644 index 0000000000..018ef78087 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_touch_drag_root_scrollbar.html @@ -0,0 +1,51 @@ + + + + + + Touch Drag on the viewport's scrollbar + + + + + + + +
Some content to ensure the root scrollframe is scrollable
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_touchpad_pinch_and_pan.html b/gfx/layers/apz/test/mochitest/helper_touchpad_pinch_and_pan.html new file mode 100644 index 0000000000..24eb920a74 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_touchpad_pinch_and_pan.html @@ -0,0 +1,49 @@ + + + + + + Sanity check for Touchpad pinch zooming and panning + + + + + + + + Here is some text to stare at as the test runs. It serves no functional + purpose, but gives you an idea of the zoom level. It's harder to tell what + the zoom level is when the page is just solid white. + + diff --git a/gfx/layers/apz/test/mochitest/helper_transform_end_on_keyboard_scroll.html b/gfx/layers/apz/test/mochitest/helper_transform_end_on_keyboard_scroll.html new file mode 100644 index 0000000000..20bee3cefd --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_transform_end_on_keyboard_scroll.html @@ -0,0 +1,58 @@ + + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_transform_end_on_wheel_scroll.html b/gfx/layers/apz/test/mochitest/helper_transform_end_on_wheel_scroll.html new file mode 100644 index 0000000000..af4f72cf44 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_transform_end_on_wheel_scroll.html @@ -0,0 +1,28 @@ + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_visual_scrollbars_pagescroll.html b/gfx/layers/apz/test/mochitest/helper_visual_scrollbars_pagescroll.html new file mode 100644 index 0000000000..ae3025930f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_visual_scrollbars_pagescroll.html @@ -0,0 +1,119 @@ + + + + + + Clicking on the scrollbar track in quick succession should scroll the right amount + + + + + + + + +
+
+ The above div is sized to 3x screen height so the linear gradient is more steep in terms of + color/pixel. We only scroll a few pages worth so we don't need the gradient all the way down. + And then we use a bottom-margin to make the page really big so the scrollthumb is + relatively small, giving us lots of space to click on the scrolltrack. + + diff --git a/gfx/layers/apz/test/mochitest/helper_visual_smooth_scroll.html b/gfx/layers/apz/test/mochitest/helper_visual_smooth_scroll.html new file mode 100644 index 0000000000..9d278ee23c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_visual_smooth_scroll.html @@ -0,0 +1,53 @@ + + + + + + Tests that the (internal) visual smooth scrolling API is not restricted to the layout scroll range + + + + + + +
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_visualscroll_clamp_restore.html b/gfx/layers/apz/test/mochitest/helper_visualscroll_clamp_restore.html new file mode 100644 index 0000000000..69c0590a56 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_visualscroll_clamp_restore.html @@ -0,0 +1,63 @@ + + + +Tests scroll position is properly synchronized when visual position is temporarily clamped on the main thread + + + +
This test runs automatically in automation. To run manually, follow the steps: 1. scroll all the way down
+
3. move the mouse. this div should have a hover effect exactly when the mouse is on top of it
+
+ diff --git a/gfx/layers/apz/test/mochitest/helper_visualscroll_nonrcd_rsf.html b/gfx/layers/apz/test/mochitest/helper_visualscroll_nonrcd_rsf.html new file mode 100644 index 0000000000..9955e49e2a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_visualscroll_nonrcd_rsf.html @@ -0,0 +1,88 @@ + + + +Tests that pending visual scroll positions on RSFs of non-RCDs get cleared properly + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_wheelevents_handoff_on_iframe.html b/gfx/layers/apz/test/mochitest/helper_wheelevents_handoff_on_iframe.html new file mode 100644 index 0000000000..79101b2ca5 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_wheelevents_handoff_on_iframe.html @@ -0,0 +1,52 @@ + + + Test that wheel events on an unscrollable OOP iframe are handoff-ed + + + + + + +
+ +
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_wheelevents_handoff_on_iframe_child.html b/gfx/layers/apz/test/mochitest/helper_wheelevents_handoff_on_iframe_child.html new file mode 100644 index 0000000000..aca9dfdbd4 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_wheelevents_handoff_on_iframe_child.html @@ -0,0 +1,11 @@ + + +
overflow: hidden on html
diff --git a/gfx/layers/apz/test/mochitest/helper_wheelevents_handoff_on_non_scrollable_iframe.html b/gfx/layers/apz/test/mochitest/helper_wheelevents_handoff_on_non_scrollable_iframe.html new file mode 100644 index 0000000000..22963459a0 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_wheelevents_handoff_on_non_scrollable_iframe.html @@ -0,0 +1,113 @@ + + + + + scroll handoff on non scrollable iframe document with overscroll-behavior: none + + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_wide_crossorigin_iframe.html b/gfx/layers/apz/test/mochitest/helper_wide_crossorigin_iframe.html new file mode 100644 index 0000000000..bdcef59229 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_wide_crossorigin_iframe.html @@ -0,0 +1,33 @@ + + + + +Test cross origin fission iframes get displayport that covers whole width + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_wide_crossorigin_iframe_child.html b/gfx/layers/apz/test/mochitest/helper_wide_crossorigin_iframe_child.html new file mode 100644 index 0000000000..edf3ad4728 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_wide_crossorigin_iframe_child.html @@ -0,0 +1,71 @@ + + + + + + + + +
diff --git a/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_fixed_bug1673511.html b/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_fixed_bug1673511.html new file mode 100644 index 0000000000..c63794fdb1 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_fixed_bug1673511.html @@ -0,0 +1,42 @@ + + + + + Checking zoomToFocusedInput scrolls on position: fixed + + + + + +
+
+
ABC
+ +
+ +
ABC
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_iframe.html b/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_iframe.html new file mode 100644 index 0000000000..d916c35efb --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_iframe.html @@ -0,0 +1,68 @@ + + + + + Checking zoomToFocusedInput scrolls that focused element is into iframe + + + + + +
ABC
+ +
+ +
ABC
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_multiline.html b/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_multiline.html new file mode 100644 index 0000000000..ff5912dcee --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_multiline.html @@ -0,0 +1,94 @@ + + + + Checking zoomToFocusedInput scrolls that focused non-input element is visible position + + + + + +
ABC
+
+
+ +
ABC
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_nozoom.html b/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_nozoom.html new file mode 100644 index 0000000000..5edd181a2d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_nozoom.html @@ -0,0 +1,39 @@ + + + + Checking zoomToFocusedInput does not zoom if meta viewport does not allow it + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_nozoom_bug1738696.html b/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_nozoom_bug1738696.html new file mode 100644 index 0000000000..4320e391b7 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_nozoom_bug1738696.html @@ -0,0 +1,51 @@ + + + + Checking zoomToFocusedInput does not zoom is meta viewport does not allow it + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_scroll.html b/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_scroll.html new file mode 100644 index 0000000000..4054b51657 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_scroll.html @@ -0,0 +1,51 @@ + + + + Checking zoomToFocusedInput scrolls that focused input element is visible position + + + + + +
ABC
+ + +
ABC
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_touch-action.html b/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_touch-action.html new file mode 100644 index 0000000000..bdb49f4b84 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_touch-action.html @@ -0,0 +1,67 @@ + + + + Checking zoomToFocusedInput zooms if touch-action allows it + + + + + + + +
+ +
+
+
+ +
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_zoom.html b/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_zoom.html new file mode 100644 index 0000000000..c74fe521b4 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_zoom.html @@ -0,0 +1,39 @@ + + + + Checking zoomToFocusedInput zooms if meta viewport allows it + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoom_after_gpu_process_restart.html b/gfx/layers/apz/test/mochitest/helper_zoom_after_gpu_process_restart.html new file mode 100644 index 0000000000..0ce6113710 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoom_after_gpu_process_restart.html @@ -0,0 +1,63 @@ + + + + + + Sanity check for pinch zooming after GPU process restart + + + + + + + Here is some text to stare at as the test runs. It serves no functional + purpose, but gives you an idea of the zoom level. It's harder to tell what + the zoom level is when the page is just solid white. + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoom_keyboardscroll.html b/gfx/layers/apz/test/mochitest/helper_zoom_keyboardscroll.html new file mode 100644 index 0000000000..1a7b38fabd --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoom_keyboardscroll.html @@ -0,0 +1,74 @@ + + + + + + Tests that keyboard arrow keys scroll after zooming in when there was no scrollable overflow before zooming + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoom_oopif.html b/gfx/layers/apz/test/mochitest/helper_zoom_oopif.html new file mode 100644 index 0000000000..788fa31bbb --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoom_oopif.html @@ -0,0 +1,54 @@ + + + + + + Sanity check for pinch zooming oop iframe + + + + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoom_out_clamped_scrollpos.html b/gfx/layers/apz/test/mochitest/helper_zoom_out_clamped_scrollpos.html new file mode 100644 index 0000000000..2948df9ae3 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoom_out_clamped_scrollpos.html @@ -0,0 +1,85 @@ + + + + + + Tests that zooming out with an unchanging scroll pos still works properly + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoom_out_with_mainthread_clamping.html b/gfx/layers/apz/test/mochitest/helper_zoom_out_with_mainthread_clamping.html new file mode 100644 index 0000000000..c15622872a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoom_out_with_mainthread_clamping.html @@ -0,0 +1,110 @@ + + + + + + Tests that zooming out in a way that triggers main-thread scroll re-clamping works properly + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoom_prevented.html b/gfx/layers/apz/test/mochitest/helper_zoom_prevented.html new file mode 100644 index 0000000000..b756c873f2 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoom_prevented.html @@ -0,0 +1,75 @@ + + + + + + Checking prevent-default for zooming + + + + + + + Here is some text to stare at as the test runs. It serves no functional + purpose, but gives you an idea of the zoom level. It's harder to tell what + the zoom level is when the page is just solid white. + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoom_restore_position_tabswitch.html b/gfx/layers/apz/test/mochitest/helper_zoom_restore_position_tabswitch.html new file mode 100644 index 0000000000..0373b321da --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoom_restore_position_tabswitch.html @@ -0,0 +1,74 @@ + + + + + + Switching tabs back to a zoomed page should restore visual offset + + + + + + + + Here is some text to stare at as the test runs. It serves no functional + purpose, but gives you an idea of the zoom level. It's harder to tell what + the zoom level is when the page is just solid white. + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoom_with_dynamic_toolbar.html b/gfx/layers/apz/test/mochitest/helper_zoom_with_dynamic_toolbar.html new file mode 100644 index 0000000000..261bca1377 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoom_with_dynamic_toolbar.html @@ -0,0 +1,45 @@ + + + + + + Zooming out to the initial scale with the dynamic toolbar + + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoom_with_touchpad.html b/gfx/layers/apz/test/mochitest/helper_zoom_with_touchpad.html new file mode 100644 index 0000000000..6836864964 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoom_with_touchpad.html @@ -0,0 +1,110 @@ + + + + + + Sanity check for Touchpad pinch zooming + + + + + + + + Here is some text to stare at as the test runs. It serves no functional + purpose, but gives you an idea of the zoom level. It's harder to tell what + the zoom level is when the page is just solid white. + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoomed_pan.html b/gfx/layers/apz/test/mochitest/helper_zoomed_pan.html new file mode 100644 index 0000000000..98547fb73f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoomed_pan.html @@ -0,0 +1,79 @@ + + + + + + Ensure layout viewport responds to panning while pinched + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/mochitest/mochitest.ini b/gfx/layers/apz/test/mochitest/mochitest.ini new file mode 100644 index 0000000000..af357a0682 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/mochitest.ini @@ -0,0 +1,125 @@ +[DEFAULT] + prefs = + gfx.font_loader.delay=0 + support-files = + apz_test_native_event_utils.js + apz_test_utils.js + green100x100.png + helper_*.* + tags = apz +[test_abort_smooth_scroll_by_instant_scroll.html] +[test_bug1151667.html] + skip-if = + os == 'android' # wheel events not supported on mobile +[test_bug1253683.html] + skip-if = + os == 'android' # wheel events not supported on mobile +[test_bug1277814.html] + skip-if = + os == 'android' # wheel events not supported on mobile +[test_bug1304689-2.html] +[test_bug1304689.html] +[test_frame_reconstruction.html] +[test_group_bug1534549.html] +[test_group_checkerboarding.html] + skip-if = + http3 +[test_group_displayport.html] +[test_group_double_tap_zoom-2.html] + run-if = ((os == 'android') || (os == 'mac')) # FIXME: enable on more desktop platforms (see bug 1608506 comment 4) +[test_group_double_tap_zoom.html] + run-if = ((os == 'android') || (os == 'mac')) # FIXME: enable on more desktop platforms (see bug 1608506 comment 4) +[test_group_fullscreen.html] + run-if = (os == 'android') +[test_group_hittest-1.html] + skip-if = + toolkit == 'android' # mouse events not supported on mobile +[test_group_hittest-2.html] + skip-if = + toolkit == 'android' # mouse events not supported on mobile + os == 'win' && (bits == 32 || asan) + os == 'linux' && asan # stack is not large enough for the test + http3 +[test_group_hittest-3.html] + skip-if = + toolkit == 'android' # mouse events not supported on mobile + os == 'win' && (bits == 32 || asan) + http3 +[test_group_hittest-overscroll.html] + skip-if = + toolkit == 'android' # mouse events not supported on mobile +[test_group_keyboard-2.html] +[test_group_keyboard.html] +[test_group_mainthread.html] +[test_group_minimum_scale_size.html] + run-if = (os == 'android') +[test_group_mouseevents.html] + skip-if = + toolkit == 'android' # mouse events not supported on mobile +[test_group_overrides.html] + skip-if = + toolkit == 'android' # wheel events not supported on mobile +[test_group_overscroll.html] + skip-if = + toolkit == 'android' # wheel events not supported on mobile +[test_group_overscroll_handoff.html] + skip-if = + toolkit == 'android' # wheel events not supported on mobile + http3 +[test_group_pointerevents.html] + skip-if = (os == 'win' && os_version == '10.0') # Bug 1404836 +[test_group_programmatic_scroll_behavior.html] +[test_group_scroll_linked_effect.html] + skip-if = + (toolkit == 'android') # wheel events not supported on mobile + http3 +[test_group_scroll_snap.html] + skip-if = (os == 'android') # wheel events not supported on mobile +[test_group_scrollend.html] + skip-if = (toolkit == 'android') # wheel events not supported on mobile +[test_group_scrollframe_activation.html] +[test_group_touchevents-2.html] +[test_group_touchevents-3.html] +[test_group_touchevents-4.html] +[test_group_touchevents-5.html] +[test_group_touchevents.html] +[test_group_wheelevents.html] + skip-if = (toolkit == 'android') # wheel events not supported on mobile +[test_group_zoom-2.html] + skip-if = (os == 'win') # see bug 1495580 for Windows +[test_group_zoom.html] + skip-if = + os == 'win' # see bug 1495580 for Windows +[test_group_zoomToFocusedInput.html] +[test_interrupted_reflow.html] +[test_layerization.html] + skip-if = + os == 'android' # wheel events not supported on mobile + os == 'linux' && fission && headless # Bug 1722907 +[test_relative_update.html] + skip-if = + os == 'android' # wheel events not supported on mobile +[test_scroll_inactive_bug1190112.html] + skip-if = (os == 'android') # wheel events not supported on mobile +[test_scroll_inactive_flattened_frame.html] + skip-if = (os == 'android') # wheel events not supported on mobile +[test_scroll_subframe_scrollbar.html] + skip-if = (os == 'android') # wheel events not supported on mobile +[test_smoothness.html] + # hardware vsync only on win/mac + # Frame Uniformity recording is not implemented for webrender + skip-if = + debug + (os != 'mac' && os != 'win') + verify + true # Don't run in CI yet, see bug 1657477 +[test_touch_listeners_impacting_wheel.html] + skip-if = + toolkit == 'android' # wheel events not supported on mobile + toolkit == 'cocoa' # synthesized wheel smooth-scrolling not supported on OS X +[test_wheel_scroll.html] + skip-if = + os == 'android' # wheel events not supported on mobile +[test_wheel_transactions.html] + skip-if = + toolkit == 'android' # wheel events not supported on mobile diff --git a/gfx/layers/apz/test/mochitest/test_abort_smooth_scroll_by_instant_scroll.html b/gfx/layers/apz/test/mochitest/test_abort_smooth_scroll_by_instant_scroll.html new file mode 100644 index 0000000000..650c21cac7 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_abort_smooth_scroll_by_instant_scroll.html @@ -0,0 +1,51 @@ + + + + Test to make sure an on-going smooth scroll is aborted by a new + instant absolute scroll operation + + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/mochitest/test_bug1151667.html b/gfx/layers/apz/test/mochitest/test_bug1151667.html new file mode 100644 index 0000000000..12a46b9094 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_bug1151667.html @@ -0,0 +1,63 @@ + + + + + Test for Bug 1151667 + + + + + + + + + +Mozilla Bug 1151667 +

+
+ +
+
+ +
+
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/test_bug1253683.html b/gfx/layers/apz/test/mochitest/test_bug1253683.html new file mode 100644 index 0000000000..f12455fb3a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_bug1253683.html @@ -0,0 +1,59 @@ + + + + + Test to ensure non-scrollable frames don't get layerized + + + + + + + + +

+
+
sample code here
+
spacer to make the 'container' div the root scrollable element
+
+
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/test_bug1277814.html b/gfx/layers/apz/test/mochitest/test_bug1277814.html new file mode 100644 index 0000000000..28c4619e4e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_bug1277814.html @@ -0,0 +1,105 @@ + + + + + + Test for Bug 1277814 + + + + + + + + + + +
+ CoolCmd
CoolCmd
CoolCmd
CoolCmd
+ CoolCmd
CoolCmd
CoolCmd
CoolCmd
+ CoolCmd
CoolCmd
CoolCmd
CoolCmd
+ CoolCmd
CoolCmd
CoolCmd
CoolCmd
+ CoolCmd
CoolCmd
CoolCmd
CoolCmd
+ + + diff --git a/gfx/layers/apz/test/mochitest/test_bug1304689-2.html b/gfx/layers/apz/test/mochitest/test_bug1304689-2.html new file mode 100644 index 0000000000..1c7b1255d9 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_bug1304689-2.html @@ -0,0 +1,130 @@ + + + + + + Test for Bug 1285070 + + + + + + + + +
+
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/test_bug1304689.html b/gfx/layers/apz/test/mochitest/test_bug1304689.html new file mode 100644 index 0000000000..85ca3d5503 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_bug1304689.html @@ -0,0 +1,134 @@ + + + + + + Test for Bug 1285070 + + + + + + + + +
+
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/test_frame_reconstruction.html b/gfx/layers/apz/test/mochitest/test_frame_reconstruction.html new file mode 100644 index 0000000000..1031701a3b --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_frame_reconstruction.html @@ -0,0 +1,218 @@ + + + + + Test for bug 1235899 + + + + + + + + + +Mozilla Bug 1235899 +

+
+

You should be able to fling this list without it stopping abruptly

+
+
+
    +
  1. Some text
  2. +
  3. Some text
  4. +
  5. Some text
  6. +
  7. Some text
  8. +
  9. Some text
  10. +
  11. Some text
  12. +
  13. Some text
  14. +
  15. Some text
  16. +
  17. Some text
  18. +
  19. Some text
  20. +
  21. Some text
  22. +
  23. Some text
  24. +
  25. Some text
  26. +
  27. Some text
  28. +
  29. Some text
  30. +
  31. Some text
  32. +
  33. Some text
  34. +
  35. Some text
  36. +
  37. Some text
  38. +
  39. Some text
  40. +
  41. Some text
  42. +
  43. Some text
  44. +
  45. Some text
  46. +
  47. Some text
  48. +
  49. Some text
  50. +
  51. Some text
  52. +
  53. Some text
  54. +
  55. Some text
  56. +
  57. Some text
  58. +
  59. Some text
  60. +
  61. Some text
  62. +
  63. Some text
  64. +
  65. Some text
  66. +
  67. Some text
  68. +
  69. Some text
  70. +
  71. Some text
  72. +
  73. Some text
  74. +
  75. Some text
  76. +
  77. Some text
  78. +
  79. Some text
  80. +
  81. Some text
  82. +
  83. Some text
  84. +
  85. Some text
  86. +
  87. Some text
  88. +
  89. Some text
  90. +
  91. Some text
  92. +
  93. Some text
  94. +
  95. Some text
  96. +
  97. Some text
  98. +
  99. Some text
  100. +
  101. Some text
  102. +
  103. Some text
  104. +
  105. Some text
  106. +
  107. Some text
  108. +
  109. Some text
  110. +
  111. Some text
  112. +
  113. Some text
  114. +
  115. Some text
  116. +
  117. Some text
  118. +
  119. Some text
  120. +
  121. Some text
  122. +
  123. Some text
  124. +
  125. Some text
  126. +
  127. Some text
  128. +
  129. Some text
  130. +
  131. Some text
  132. +
  133. Some text
  134. +
  135. Some text
  136. +
  137. Some text
  138. +
  139. Some text
  140. +
  141. Some text
  142. +
  143. Some text
  144. +
  145. Some text
  146. +
  147. Some text
  148. +
  149. Some text
  150. +
  151. Some text
  152. +
  153. Some text
  154. +
  155. Some text
  156. +
  157. Some text
  158. +
  159. Some text
  160. +
  161. Some text
  162. +
  163. Some text
  164. +
  165. Some text
  166. +
  167. Some text
  168. +
  169. Some text
  170. +
  171. Some text
  172. +
  173. Some text
  174. +
  175. Some text
  176. +
  177. Some text
  178. +
  179. Some text
  180. +
  181. Some text
  182. +
  183. Some text
  184. +
  185. Some text
  186. +
  187. Some text
  188. +
  189. Some text
  190. +
  191. Some text
  192. +
  193. Some text
  194. +
  195. Some text
  196. +
  197. Some text
  198. +
  199. Some text
  200. +
  201. Some text
  202. +
  203. Some text
  204. +
+
+
+
+ +
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_bug1534549.html b/gfx/layers/apz/test/mochitest/test_group_bug1534549.html
new file mode 100644
index 0000000000..6ab8afa6f7
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_bug1534549.html
@@ -0,0 +1,37 @@
+
+
+
+    
+    Tests for bug 1534549
+    
+    
+    
+    
+    
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_checkerboarding.html b/gfx/layers/apz/test/mochitest/test_group_checkerboarding.html
new file mode 100644
index 0000000000..5386ffd740
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_checkerboarding.html
@@ -0,0 +1,83 @@
+
+
+
+  
+  
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_displayport.html b/gfx/layers/apz/test/mochitest/test_group_displayport.html
new file mode 100644
index 0000000000..9ff8c524ad
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_displayport.html
@@ -0,0 +1,31 @@
+
+
+
+    
+    Tests for DisplayPorts
+    
+    
+    
+    
+    
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_double_tap_zoom-2.html b/gfx/layers/apz/test/mochitest/test_group_double_tap_zoom-2.html
new file mode 100644
index 0000000000..3fb4f9163c
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_double_tap_zoom-2.html
@@ -0,0 +1,89 @@
+
+
+
+  
+  Various zoom-related tests that spawn in new windows
+  
+  
+    
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_double_tap_zoom.html b/gfx/layers/apz/test/mochitest/test_group_double_tap_zoom.html
new file mode 100644
index 0000000000..fe4a0784a9
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_double_tap_zoom.html
@@ -0,0 +1,66 @@
+
+
+
+  
+  Various zoom-related tests that spawn in new windows
+  
+  
+    
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_fullscreen.html b/gfx/layers/apz/test/mochitest/test_group_fullscreen.html
new file mode 100644
index 0000000000..c31a3abffb
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_fullscreen.html
@@ -0,0 +1,32 @@
+
+
+
+  
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_hittest-1.html b/gfx/layers/apz/test/mochitest/test_group_hittest-1.html
new file mode 100644
index 0000000000..34bf245d6c
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_hittest-1.html
@@ -0,0 +1,59 @@
+
+
+
+  
+  Various hit-testing tests that spawn in new windows - Part 1
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_hittest-2.html b/gfx/layers/apz/test/mochitest/test_group_hittest-2.html
new file mode 100644
index 0000000000..d144aab840
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_hittest-2.html
@@ -0,0 +1,72 @@
+
+
+
+  
+  Various hit-testing tests that spawn in new windows - Part 2
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_hittest-3.html b/gfx/layers/apz/test/mochitest/test_group_hittest-3.html
new file mode 100644
index 0000000000..f5675ee790
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_hittest-3.html
@@ -0,0 +1,50 @@
+
+
+
+  
+  Various hit-testing tests that spawn in new windows - Part 3
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_hittest-overscroll.html b/gfx/layers/apz/test/mochitest/test_group_hittest-overscroll.html
new file mode 100644
index 0000000000..ee40c5dcdb
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_hittest-overscroll.html
@@ -0,0 +1,54 @@
+
+
+
+  
+  Various hit-testing tests relevant with overscroll that spawn in new windows
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_keyboard-2.html b/gfx/layers/apz/test/mochitest/test_group_keyboard-2.html
new file mode 100644
index 0000000000..c07607e1dc
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_keyboard-2.html
@@ -0,0 +1,46 @@
+
+
+
+  
+  Various keyboard scrolling tests
+  
+  
+  
+  
+  
+
+
+  Async key scrolling test
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_keyboard.html b/gfx/layers/apz/test/mochitest/test_group_keyboard.html
new file mode 100644
index 0000000000..4f1bc0d869
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_keyboard.html
@@ -0,0 +1,60 @@
+
+
+
+  
+  Various keyboard scrolling tests
+  
+  
+  
+  
+  
+
+
+  Async key scrolling test
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_mainthread.html b/gfx/layers/apz/test/mochitest/test_group_mainthread.html
new file mode 100644
index 0000000000..3e1225cadf
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_mainthread.html
@@ -0,0 +1,51 @@
+
+
+
+  
+  Tests that perform main-thread scrolling
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_minimum_scale_size.html b/gfx/layers/apz/test/mochitest/test_group_minimum_scale_size.html
new file mode 100644
index 0000000000..2de924d6bd
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_minimum_scale_size.html
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_mouseevents.html b/gfx/layers/apz/test/mochitest/test_group_mouseevents.html
new file mode 100644
index 0000000000..c7c86b76b5
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_mouseevents.html
@@ -0,0 +1,82 @@
+
+
+
+  
+  Various mouse tests that spawn in new windows
+  
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_overrides.html b/gfx/layers/apz/test/mochitest/test_group_overrides.html
new file mode 100644
index 0000000000..434c32d24e
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_overrides.html
@@ -0,0 +1,37 @@
+
+
+
+  
+  Various tests for event regions overrides
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_overscroll.html b/gfx/layers/apz/test/mochitest/test_group_overscroll.html
new file mode 100644
index 0000000000..d94cd3b65f
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_overscroll.html
@@ -0,0 +1,35 @@
+
+
+
+  
+  Tests for overscroll
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_overscroll_handoff.html b/gfx/layers/apz/test/mochitest/test_group_overscroll_handoff.html
new file mode 100644
index 0000000000..2df4ee37b8
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_overscroll_handoff.html
@@ -0,0 +1,46 @@
+
+
+
+  
+  Tests for overscroll handoff
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_pointerevents.html b/gfx/layers/apz/test/mochitest/test_group_pointerevents.html
new file mode 100644
index 0000000000..21fa4585de
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_pointerevents.html
@@ -0,0 +1,43 @@
+
+
+
+
+  
+  Test for Bug 1285070
+  
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_programmatic_scroll_behavior.html b/gfx/layers/apz/test/mochitest/test_group_programmatic_scroll_behavior.html
new file mode 100644
index 0000000000..13bdb4efc4
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_programmatic_scroll_behavior.html
@@ -0,0 +1,38 @@
+
+
+
+  
+  Various programmatic scroll tests that spawn in new windows
+  
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_scroll_linked_effect.html b/gfx/layers/apz/test/mochitest/test_group_scroll_linked_effect.html
new file mode 100644
index 0000000000..f29a382fb8
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_scroll_linked_effect.html
@@ -0,0 +1,33 @@
+
+
+
+  
+  Tests for scroll linked effect
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_scroll_snap.html b/gfx/layers/apz/test/mochitest/test_group_scroll_snap.html
new file mode 100644
index 0000000000..9a1341c503
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_scroll_snap.html
@@ -0,0 +1,67 @@
+
+
+
+  
+  Various tests for scroll snap
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_scrollend.html b/gfx/layers/apz/test/mochitest/test_group_scrollend.html
new file mode 100644
index 0000000000..9c2ee71a45
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_scrollend.html
@@ -0,0 +1,58 @@
+
+
+
+  
+  Various scrollend tests that spawn in new windows
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_scrollframe_activation.html b/gfx/layers/apz/test/mochitest/test_group_scrollframe_activation.html
new file mode 100644
index 0000000000..2e75f45fc8
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_scrollframe_activation.html
@@ -0,0 +1,49 @@
+
+
+
+
+
+  
+  Tests related to scrollframe activation
+  
+  
+  
+  
+  
+
+
+
+  Mozilla Bug 1151663
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_touchevents-2.html b/gfx/layers/apz/test/mochitest/test_group_touchevents-2.html
new file mode 100644
index 0000000000..decd615795
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_touchevents-2.html
@@ -0,0 +1,69 @@
+
+
+
+  
+  Various touch tests that spawn in new windows (2)
+  
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_touchevents-3.html b/gfx/layers/apz/test/mochitest/test_group_touchevents-3.html
new file mode 100644
index 0000000000..a86c80a2ec
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_touchevents-3.html
@@ -0,0 +1,47 @@
+
+
+
+  
+  Various touch tests that spawn in new windows (3)
+  
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_touchevents-4.html b/gfx/layers/apz/test/mochitest/test_group_touchevents-4.html
new file mode 100644
index 0000000000..266fc72ee8
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_touchevents-4.html
@@ -0,0 +1,54 @@
+
+
+
+  
+  Various touch tests that spawn in new windows (4)
+  
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_touchevents-5.html b/gfx/layers/apz/test/mochitest/test_group_touchevents-5.html
new file mode 100644
index 0000000000..d4ae624a69
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_touchevents-5.html
@@ -0,0 +1,51 @@
+
+
+
+  
+  Various touch tests that spawn in new windows (5)
+  
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_touchevents.html b/gfx/layers/apz/test/mochitest/test_group_touchevents.html
new file mode 100644
index 0000000000..df24e24f3d
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_touchevents.html
@@ -0,0 +1,55 @@
+
+
+
+  
+  Various touch tests that spawn in new windows
+  
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_wheelevents.html b/gfx/layers/apz/test/mochitest/test_group_wheelevents.html
new file mode 100644
index 0000000000..34e243f679
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_wheelevents.html
@@ -0,0 +1,84 @@
+
+
+
+  
+  Various wheel-scrolling tests that spawn in new windows
+  
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_zoom-2.html b/gfx/layers/apz/test/mochitest/test_group_zoom-2.html
new file mode 100644
index 0000000000..1f94b9b5b6
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_zoom-2.html
@@ -0,0 +1,81 @@
+
+
+
+  
+  Various zoom-related tests that spawn in new windows
+  
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_zoom.html b/gfx/layers/apz/test/mochitest/test_group_zoom.html
new file mode 100644
index 0000000000..03f9bbebf8
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_zoom.html
@@ -0,0 +1,80 @@
+
+
+
+  
+  Various zoom-related tests that spawn in new windows
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_zoomToFocusedInput.html b/gfx/layers/apz/test/mochitest/test_group_zoomToFocusedInput.html
new file mode 100644
index 0000000000..72fd9dcc11
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_zoomToFocusedInput.html
@@ -0,0 +1,49 @@
+
+
+
+  
+  Various zoomToFocusedInput tests
+  
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_interrupted_reflow.html b/gfx/layers/apz/test/mochitest/test_interrupted_reflow.html
new file mode 100644
index 0000000000..8fc72e05a5
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_interrupted_reflow.html
@@ -0,0 +1,38 @@
+
+
+ 
+ 
+  Test for bug 1292781
+  
+  
+  
+ 
+ 
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_layerization.html b/gfx/layers/apz/test/mochitest/test_layerization.html
new file mode 100644
index 0000000000..0ff76de317
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_layerization.html
@@ -0,0 +1,312 @@
+
+
+
+
+  Test for layerization
+  
+  
+  
+  
+  
+  
+  
+  
+
+
+APZ layerization tests
+

+
+
+
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/test_relative_update.html b/gfx/layers/apz/test/mochitest/test_relative_update.html new file mode 100644 index 0000000000..01c0ee1f9b --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_relative_update.html @@ -0,0 +1,92 @@ + + + + + Test for relative scroll offset updates (Bug 1453425) + + + + + + + + + +
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html b/gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html new file mode 100644 index 0000000000..de54cf93fe --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html @@ -0,0 +1,553 @@ + + + + Test scrolling flattened inactive frames + + + + + + + + +
+
+
+
+

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+ +

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+ +

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+ +

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+ +

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+ +

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+ +

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+ +

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+ +

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+ +

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+ +

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+ +

+
+ + + diff --git a/gfx/layers/apz/test/mochitest/test_scroll_inactive_flattened_frame.html b/gfx/layers/apz/test/mochitest/test_scroll_inactive_flattened_frame.html new file mode 100644 index 0000000000..47207cbb9f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_scroll_inactive_flattened_frame.html @@ -0,0 +1,50 @@ + + + + Test scrolling flattened inactive frames + + + + + + + +
+
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/test_scroll_subframe_scrollbar.html b/gfx/layers/apz/test/mochitest/test_scroll_subframe_scrollbar.html new file mode 100644 index 0000000000..10d53e9d04 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_scroll_subframe_scrollbar.html @@ -0,0 +1,116 @@ + + + + Test scrolling subframe scrollbars + + + + + + + + +

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+

+ + + diff --git a/gfx/layers/apz/test/mochitest/test_smoothness.html b/gfx/layers/apz/test/mochitest/test_smoothness.html new file mode 100644 index 0000000000..6db80d365e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_smoothness.html @@ -0,0 +1,71 @@ + + + Test Frame Uniformity While Scrolling + + + + + + + + + + + +
+
+ + diff --git a/gfx/layers/apz/test/mochitest/test_touch_listeners_impacting_wheel.html b/gfx/layers/apz/test/mochitest/test_touch_listeners_impacting_wheel.html new file mode 100644 index 0000000000..71147d5238 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_touch_listeners_impacting_wheel.html @@ -0,0 +1,119 @@ + + + + + Test for Bug 1203140 + + + + + + + + + +Mozilla Bug 1203140 +

+
+

The box below has a touch listener and a passive wheel listener. With touch events disabled, APZ shouldn't wait for any listeners.

+
+
Div to make 'content' scrollable
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/test_wheel_scroll.html b/gfx/layers/apz/test/mochitest/test_wheel_scroll.html new file mode 100644 index 0000000000..1b50c223ed --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_wheel_scroll.html @@ -0,0 +1,109 @@ + + + + + Test for Bug 1013412 and 1168182 + + + + + + + + + +Mozilla Bug 1013412 +Mozilla Bug 1168182 +

+
+

Scrolling the page should be async, but scrolling over the dark circle should not scroll the page and instead rotate the white ball.

+
+
+
+
+
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/test_wheel_transactions.html b/gfx/layers/apz/test/mochitest/test_wheel_transactions.html new file mode 100644 index 0000000000..f015ea20be --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_wheel_transactions.html @@ -0,0 +1,150 @@ + + + + + Test for Bug 1175585 + + + + + + + + + +APZ wheel transactions test +

+
+
+
+
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-h-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-h-ref.html new file mode 100644 index 0000000000..62d99b6dfe --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-h-ref.html @@ -0,0 +1,8 @@ + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl-ref.html new file mode 100644 index 0000000000..e40ac8debb --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl-ref.html @@ -0,0 +1,9 @@ + + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl.html new file mode 100644 index 0000000000..81f7f77817 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl.html @@ -0,0 +1,13 @@ + + + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-h.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-h.html new file mode 100644 index 0000000000..5d30584acd --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-h.html @@ -0,0 +1,12 @@ + + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-v-fullzoom-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-v-fullzoom-ref.html new file mode 100644 index 0000000000..6226a95070 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-v-fullzoom-ref.html @@ -0,0 +1,8 @@ + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-v-fullzoom.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-v-fullzoom.html new file mode 100644 index 0000000000..50c6d0854d --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-v-fullzoom.html @@ -0,0 +1,14 @@ + + + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-v-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-v-ref.html new file mode 100644 index 0000000000..aec5f89cbc --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-v-ref.html @@ -0,0 +1,8 @@ + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl-ref.html new file mode 100644 index 0000000000..81be67146f --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl-ref.html @@ -0,0 +1,9 @@ + + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl.html new file mode 100644 index 0000000000..24e7705723 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl.html @@ -0,0 +1,13 @@ + + + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-v.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-v.html new file mode 100644 index 0000000000..268f3b92e3 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-v.html @@ -0,0 +1,12 @@ + + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-ref.html new file mode 100644 index 0000000000..35922e3253 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-ref.html @@ -0,0 +1,8 @@ + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl-ref.html new file mode 100644 index 0000000000..22bf3cf1c8 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl-ref.html @@ -0,0 +1,9 @@ + + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl.html new file mode 100644 index 0000000000..09fce0bbe9 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl.html @@ -0,0 +1,13 @@ + + + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-vh.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh.html new file mode 100644 index 0000000000..a8d28ec414 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh.html @@ -0,0 +1,12 @@ + + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping-ref.html b/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping-ref.html new file mode 100644 index 0000000000..3db9f2969e --- /dev/null +++ b/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping-ref.html @@ -0,0 +1,27 @@ + + + + +
+ This is the top of the page. +
+ This is the bottom of the page. + diff --git a/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping.html b/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping.html new file mode 100644 index 0000000000..479363f3fb --- /dev/null +++ b/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping.html @@ -0,0 +1,53 @@ + + + + + +
+ This is the top of the page. +
+ This is the bottom of the page. + diff --git a/gfx/layers/apz/test/reftest/iframe-zoomed-child.html b/gfx/layers/apz/test/reftest/iframe-zoomed-child.html new file mode 100644 index 0000000000..4d51f46399 --- /dev/null +++ b/gfx/layers/apz/test/reftest/iframe-zoomed-child.html @@ -0,0 +1,12 @@ + +
+
+
+
diff --git a/gfx/layers/apz/test/reftest/iframe-zoomed-ref.html b/gfx/layers/apz/test/reftest/iframe-zoomed-ref.html new file mode 100644 index 0000000000..2c98a7eb6a --- /dev/null +++ b/gfx/layers/apz/test/reftest/iframe-zoomed-ref.html @@ -0,0 +1,20 @@ + + + +
+ +
+ diff --git a/gfx/layers/apz/test/reftest/iframe-zoomed.html b/gfx/layers/apz/test/reftest/iframe-zoomed.html new file mode 100644 index 0000000000..d3d5d914ba --- /dev/null +++ b/gfx/layers/apz/test/reftest/iframe-zoomed.html @@ -0,0 +1,25 @@ + + + +
+ + +
+ diff --git a/gfx/layers/apz/test/reftest/initial-scale-1-ref.html b/gfx/layers/apz/test/reftest/initial-scale-1-ref.html new file mode 100644 index 0000000000..cb51966a28 --- /dev/null +++ b/gfx/layers/apz/test/reftest/initial-scale-1-ref.html @@ -0,0 +1,9 @@ + + + + + +This tests that an initial-scale of 0 (i.e. garbage) is overridden
+with something a little more sane. + + diff --git a/gfx/layers/apz/test/reftest/initial-scale-1.html b/gfx/layers/apz/test/reftest/initial-scale-1.html new file mode 100644 index 0000000000..58babe9403 --- /dev/null +++ b/gfx/layers/apz/test/reftest/initial-scale-1.html @@ -0,0 +1,9 @@ + + + + + +This tests that an initial-scale of 0 (i.e. garbage) is overridden
+with something a little more sane. + + diff --git a/gfx/layers/apz/test/reftest/pinch-zoom-position-fixed-ref.html b/gfx/layers/apz/test/reftest/pinch-zoom-position-fixed-ref.html new file mode 100644 index 0000000000..f7d485c509 --- /dev/null +++ b/gfx/layers/apz/test/reftest/pinch-zoom-position-fixed-ref.html @@ -0,0 +1,23 @@ + + + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/pinch-zoom-position-fixed.html b/gfx/layers/apz/test/reftest/pinch-zoom-position-fixed.html new file mode 100644 index 0000000000..c4476f4872 --- /dev/null +++ b/gfx/layers/apz/test/reftest/pinch-zoom-position-fixed.html @@ -0,0 +1,37 @@ + + + + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/pinch-zoom-position-sticky-ref.html b/gfx/layers/apz/test/reftest/pinch-zoom-position-sticky-ref.html new file mode 100644 index 0000000000..c430b532df --- /dev/null +++ b/gfx/layers/apz/test/reftest/pinch-zoom-position-sticky-ref.html @@ -0,0 +1,27 @@ + + + + + + + +
+
+ + diff --git a/gfx/layers/apz/test/reftest/pinch-zoom-position-sticky.html b/gfx/layers/apz/test/reftest/pinch-zoom-position-sticky.html new file mode 100644 index 0000000000..245e0d775e --- /dev/null +++ b/gfx/layers/apz/test/reftest/pinch-zoom-position-sticky.html @@ -0,0 +1,30 @@ + + + + + + + + +
+
+ + diff --git a/gfx/layers/apz/test/reftest/reftest.list b/gfx/layers/apz/test/reftest/reftest.list new file mode 100644 index 0000000000..b346f54057 --- /dev/null +++ b/gfx/layers/apz/test/reftest/reftest.list @@ -0,0 +1,50 @@ +# The following tests test the async positioning of the scrollbars. +# Basic root-frame scrollbar with async scrolling +# First make sure that we are actually drawing scrollbars +skip-if(!asyncPan) pref(apz.allow_zooming,true) != async-scrollbar-1-v.html about:blank +skip-if(!asyncPan) pref(apz.allow_zooming,true) != async-scrollbar-1-v-ref.html about:blank +fuzzy-if(Android,0-5,0-6) fuzzy-if(gtkWidget,1-8,8-32) fuzzy-if(cocoaWidget,16-22,20-44) skip-if(!asyncPan) pref(apz.allow_zooming,true) == async-scrollbar-1-v.html async-scrollbar-1-v-ref.html +fuzzy-if(Android,0-13,0-10) fuzzy-if(gtkWidget,1-30,4-32) fuzzy-if(cocoaWidget,14-22,20-44) skip-if(!asyncPan) pref(apz.allow_zooming,true) == async-scrollbar-1-h.html async-scrollbar-1-h-ref.html +fuzzy-if(Android,0-13,0-21) fuzzy-if(gtkWidget,1-4,4-24) fuzzy-if(cocoaWidget,11-18,44-88) skip-if(!asyncPan) pref(apz.allow_zooming,true) == async-scrollbar-1-vh.html async-scrollbar-1-vh-ref.html +fuzzy-if(Android,0-5,0-6) fuzzy-if(gtkWidget,1-8,8-32) fuzzy-if(cocoaWidget,16-22,20-44) skip-if(!asyncPan) pref(apz.allow_zooming,true) == async-scrollbar-1-v-rtl.html async-scrollbar-1-v-rtl-ref.html +fuzzy-if(Android,0-14,0-10) fuzzy-if(gtkWidget,1-30,12-32) fuzzy-if(cocoaWidget,14-22,20-44) skip-if(!asyncPan) pref(apz.allow_zooming,true) == async-scrollbar-1-h-rtl.html async-scrollbar-1-h-rtl-ref.html +fuzzy-if(Android,0-43,0-26) fuzzy-if(gtkWidget,0-14,12-32) fuzzy-if(cocoaWidget,11-18,26-76) skip-if(!asyncPan) pref(apz.allow_zooming,true) == async-scrollbar-1-vh-rtl.html async-scrollbar-1-vh-rtl-ref.html + +# Different async zoom levels. Since the scrollthumb gets async-scaled in the +# compositor, the border-radius ends of the scrollthumb are going to be a little +# off, hence the fuzzy-if clauses. +skip-if(Android) fuzzy(0-107,0-72) pref(apz.allow_zooming,true) pref(apz.scrollthumb.recalc,true) == root-scrollbar-async-zoomed-in.html root-scrollbar-async-zoomed-in-ref.html +skip-if(Android) fuzzy(0-107,0-167) pref(apz.allow_zooming,true) pref(apz.scrollthumb.recalc,true) == root-scrollbar-async-zoomed-out.html root-scrollbar-async-zoomed-out-ref.html +skip-if(!Android) fuzzy(0-54,0-33) pref(apz.allow_zooming,true) == root-scrollbar-async-zoomed-in.html root-scrollbar-async-zoomed-in-ref.html +skip-if(!Android) fuzzy(0-53,0-30) pref(apz.allow_zooming,true) == root-scrollbar-async-zoomed-out.html root-scrollbar-async-zoomed-out-ref.html + +# Test that the compositor thumb sizing calculations handle a non-default device scale correctly +fuzzy-if(Android,0-31,0-29) fuzzy-if(gtkWidget,0-18,0-49) fuzzy-if(cocoaWidget,0-21,0-53) == async-scrollbar-1-v-fullzoom.html async-scrollbar-1-v-fullzoom-ref.html + +# Test scrollbars working properly with pinch-zooming, i.e. different document resolutions. +# As above, the end of the scrollthumb won't match perfectly, but the bulk of the scrollbar should be present and identical. +# On desktop, even more fuzz is needed because thumb scaling is not exactly proportional: making the page twice as long +# won't make the thumb exactly twice as short, which is what the test expects. That's fine, as the purpose of the test is +# to catch more fundamental scrollbar rendering bugs such as the entire track being mispositioned or the thumb being +# clipped away. +fuzzy-if(Android,0-54,0-22) fuzzy-if(!Android,0-128,0-137) pref(apz.allow_zooming,true) == root-scrollbar-zoomed-in.html root-scrollbar-zoomed-in-ref.html +fuzzy-if(Android,0-54,0-22) fuzzy-if(!Android,0-128,0-137) pref(apz.allow_zooming,true) pref(apz.allow_zooming_out,true) == root-scrollbar-zoomed-out.html root-scrollbar-zoomed-out-ref.html +fuzzy-if(Android,0-54,0-27) fuzzy-if(!Android,0-128,0-137) pref(apz.allow_zooming,true) == root-scrollbar-zoomed-in-async-scroll.html root-scrollbar-zoomed-in-ref.html +fuzzy-if(Android,0-54,0-25) fuzzy-if(!Android,0-128,0-137) pref(apz.allow_zooming,true) pref(apz.allow_zooming_out,true) == root-scrollbar-zoomed-out-async-scroll.html root-scrollbar-zoomed-out-ref.html +fuzzy-if(Android,0-51,0-50) fuzzy-if(!Android,0-128,0-137) pref(apz.allow_zooming,true) == subframe-scrollbar-zoomed-in-async-scroll.html subframe-scrollbar-zoomed-in-async-scroll-ref.html +fuzzy-if(Android,0-28,0-23) fuzzy-if(!Android,0-107,0-34) pref(apz.allow_zooming,true) pref(apz.allow_zooming_out,true) == subframe-scrollbar-zoomed-out-async-scroll.html subframe-scrollbar-zoomed-out-async-scroll-ref.html + +# Meta-viewport tag support +skip-if(!Android) pref(apz.allow_zooming,true) == initial-scale-1.html initial-scale-1-ref.html + +skip-if(!asyncPan) == frame-reconstruction-scroll-clamping.html frame-reconstruction-scroll-clamping-ref.html + +# Test that position:fixed and position:sticky elements are attached to the +# layout viewport. +skip-if(winWidget&&isCoverageBuild) pref(apz.allow_zooming,true) == pinch-zoom-position-fixed.html pinch-zoom-position-fixed-ref.html +skip-if(winWidget&&isCoverageBuild) pref(apz.allow_zooming,true) == pinch-zoom-position-sticky.html pinch-zoom-position-sticky-ref.html + +pref(apz.allow_zooming,true) == iframe-zoomed.html iframe-zoomed-ref.html +pref(apz.allow_zooming,true) == scaled-iframe-zoomed.html scaled-iframe-zoomed-ref.html + +== root-scrollbars-1.html root-scrollbars-1-ref.html diff --git a/gfx/layers/apz/test/reftest/root-scrollbar-async-zoomed-in-ref.html b/gfx/layers/apz/test/reftest/root-scrollbar-async-zoomed-in-ref.html new file mode 100644 index 0000000000..9568836459 --- /dev/null +++ b/gfx/layers/apz/test/reftest/root-scrollbar-async-zoomed-in-ref.html @@ -0,0 +1,8 @@ + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/root-scrollbar-async-zoomed-in.html b/gfx/layers/apz/test/reftest/root-scrollbar-async-zoomed-in.html new file mode 100644 index 0000000000..31e1e99a3d --- /dev/null +++ b/gfx/layers/apz/test/reftest/root-scrollbar-async-zoomed-in.html @@ -0,0 +1,13 @@ + + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/root-scrollbar-async-zoomed-out-ref.html b/gfx/layers/apz/test/reftest/root-scrollbar-async-zoomed-out-ref.html new file mode 100644 index 0000000000..9568836459 --- /dev/null +++ b/gfx/layers/apz/test/reftest/root-scrollbar-async-zoomed-out-ref.html @@ -0,0 +1,8 @@ + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/root-scrollbar-async-zoomed-out.html b/gfx/layers/apz/test/reftest/root-scrollbar-async-zoomed-out.html new file mode 100644 index 0000000000..4032c3c638 --- /dev/null +++ b/gfx/layers/apz/test/reftest/root-scrollbar-async-zoomed-out.html @@ -0,0 +1,13 @@ + + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/root-scrollbar-zoomed-in-async-scroll.html b/gfx/layers/apz/test/reftest/root-scrollbar-zoomed-in-async-scroll.html new file mode 100644 index 0000000000..04c829d427 --- /dev/null +++ b/gfx/layers/apz/test/reftest/root-scrollbar-zoomed-in-async-scroll.html @@ -0,0 +1,12 @@ + + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/root-scrollbar-zoomed-in-ref.html b/gfx/layers/apz/test/reftest/root-scrollbar-zoomed-in-ref.html new file mode 100644 index 0000000000..9568836459 --- /dev/null +++ b/gfx/layers/apz/test/reftest/root-scrollbar-zoomed-in-ref.html @@ -0,0 +1,8 @@ + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/root-scrollbar-zoomed-in.html b/gfx/layers/apz/test/reftest/root-scrollbar-zoomed-in.html new file mode 100644 index 0000000000..c9cb6e80a7 --- /dev/null +++ b/gfx/layers/apz/test/reftest/root-scrollbar-zoomed-in.html @@ -0,0 +1,8 @@ + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/root-scrollbar-zoomed-out-async-scroll.html b/gfx/layers/apz/test/reftest/root-scrollbar-zoomed-out-async-scroll.html new file mode 100644 index 0000000000..465fac6211 --- /dev/null +++ b/gfx/layers/apz/test/reftest/root-scrollbar-zoomed-out-async-scroll.html @@ -0,0 +1,12 @@ + + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/root-scrollbar-zoomed-out-ref.html b/gfx/layers/apz/test/reftest/root-scrollbar-zoomed-out-ref.html new file mode 100644 index 0000000000..9568836459 --- /dev/null +++ b/gfx/layers/apz/test/reftest/root-scrollbar-zoomed-out-ref.html @@ -0,0 +1,8 @@ + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/root-scrollbar-zoomed-out.html b/gfx/layers/apz/test/reftest/root-scrollbar-zoomed-out.html new file mode 100644 index 0000000000..0e3ec7173d --- /dev/null +++ b/gfx/layers/apz/test/reftest/root-scrollbar-zoomed-out.html @@ -0,0 +1,8 @@ + + + + + +
+ + diff --git a/gfx/layers/apz/test/reftest/root-scrollbars-1-ref.html b/gfx/layers/apz/test/reftest/root-scrollbars-1-ref.html new file mode 100644 index 0000000000..435609f8a3 --- /dev/null +++ b/gfx/layers/apz/test/reftest/root-scrollbars-1-ref.html @@ -0,0 +1,14 @@ + + + + +In this file the scrollbars that appear are non-root scrollbars + + +
+ + diff --git a/gfx/layers/apz/test/reftest/root-scrollbars-1.html b/gfx/layers/apz/test/reftest/root-scrollbars-1.html new file mode 100644 index 0000000000..e2560d48a9 --- /dev/null +++ b/gfx/layers/apz/test/reftest/root-scrollbars-1.html @@ -0,0 +1,14 @@ + + + + +In this file the scrollbars that appear are the root scrollbars + + +
+ + diff --git a/gfx/layers/apz/test/reftest/scaled-iframe-zoomed-ref.html b/gfx/layers/apz/test/reftest/scaled-iframe-zoomed-ref.html new file mode 100644 index 0000000000..39847320e2 --- /dev/null +++ b/gfx/layers/apz/test/reftest/scaled-iframe-zoomed-ref.html @@ -0,0 +1,21 @@ + + + +
+ +
+ diff --git a/gfx/layers/apz/test/reftest/scaled-iframe-zoomed.html b/gfx/layers/apz/test/reftest/scaled-iframe-zoomed.html new file mode 100644 index 0000000000..89b09047f7 --- /dev/null +++ b/gfx/layers/apz/test/reftest/scaled-iframe-zoomed.html @@ -0,0 +1,26 @@ + + + +
+ + +
+ diff --git a/gfx/layers/apz/test/reftest/subframe-scrollbar-zoomed-in-async-scroll-ref.html b/gfx/layers/apz/test/reftest/subframe-scrollbar-zoomed-in-async-scroll-ref.html new file mode 100644 index 0000000000..f2d640bc2e --- /dev/null +++ b/gfx/layers/apz/test/reftest/subframe-scrollbar-zoomed-in-async-scroll-ref.html @@ -0,0 +1,10 @@ + + + + + +
+
+
+ + diff --git a/gfx/layers/apz/test/reftest/subframe-scrollbar-zoomed-in-async-scroll.html b/gfx/layers/apz/test/reftest/subframe-scrollbar-zoomed-in-async-scroll.html new file mode 100644 index 0000000000..2aa2a2627c --- /dev/null +++ b/gfx/layers/apz/test/reftest/subframe-scrollbar-zoomed-in-async-scroll.html @@ -0,0 +1,15 @@ + + + + + + +
+
+
+ + diff --git a/gfx/layers/apz/test/reftest/subframe-scrollbar-zoomed-out-async-scroll-ref.html b/gfx/layers/apz/test/reftest/subframe-scrollbar-zoomed-out-async-scroll-ref.html new file mode 100644 index 0000000000..4283952f78 --- /dev/null +++ b/gfx/layers/apz/test/reftest/subframe-scrollbar-zoomed-out-async-scroll-ref.html @@ -0,0 +1,10 @@ + + + + + +
+
+
+ + diff --git a/gfx/layers/apz/test/reftest/subframe-scrollbar-zoomed-out-async-scroll.html b/gfx/layers/apz/test/reftest/subframe-scrollbar-zoomed-out-async-scroll.html new file mode 100644 index 0000000000..a0f1e08cf9 --- /dev/null +++ b/gfx/layers/apz/test/reftest/subframe-scrollbar-zoomed-out-async-scroll.html @@ -0,0 +1,15 @@ + + + + + + +
+
+
+ + -- cgit v1.2.3