| /* |
| * Copyright (C) 2011, 2012 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "third_party/blink/public/web/web_view.h" |
| |
| #include <limits> |
| #include <memory> |
| #include <string> |
| |
| #include "base/callback_helpers.h" |
| #include "base/optional.h" |
| #include "base/stl_util.h" |
| #include "base/test/test_mock_time_task_runner.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "cc/test/test_ukm_recorder_factory.h" |
| #include "cc/trees/layer_tree_host.h" |
| #include "gin/handle.h" |
| #include "gin/object_template_builder.h" |
| #include "gin/wrappable.h" |
| #include "mojo/public/cpp/bindings/pending_associated_receiver.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/receiver.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/browser_interface_broker_proxy.h" |
| #include "third_party/blink/public/common/input/web_coalesced_input_event.h" |
| #include "third_party/blink/public/common/input/web_input_event.h" |
| #include "third_party/blink/public/common/input/web_keyboard_event.h" |
| #include "third_party/blink/public/common/page/drag_operation.h" |
| #include "third_party/blink/public/common/page/page_zoom.h" |
| #include "third_party/blink/public/common/tokens/tokens.h" |
| #include "third_party/blink/public/common/widget/device_emulation_params.h" |
| #include "third_party/blink/public/mojom/frame/frame_owner_element_type.mojom-blink.h" |
| #include "third_party/blink/public/mojom/frame/tree_scope_type.mojom-blink.h" |
| #include "third_party/blink/public/mojom/input/focus_type.mojom-blink.h" |
| #include "third_party/blink/public/mojom/input/touch_event.mojom-blink.h" |
| #include "third_party/blink/public/mojom/manifest/display_mode.mojom-shared.h" |
| #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h" |
| #include "third_party/blink/public/platform/web_drag_data.h" |
| #include "third_party/blink/public/platform/web_size.h" |
| #include "third_party/blink/public/platform/web_url_loader_mock_factory.h" |
| #include "third_party/blink/public/public_buildflags.h" |
| #include "third_party/blink/public/test/test_web_frame_content_dumper.h" |
| #include "third_party/blink/public/web/web_autofill_client.h" |
| #include "third_party/blink/public/web/web_console_message.h" |
| #include "third_party/blink/public/web/web_document.h" |
| #include "third_party/blink/public/web/web_element.h" |
| #include "third_party/blink/public/web/web_frame.h" |
| #include "third_party/blink/public/web/web_hit_test_result.h" |
| #include "third_party/blink/public/web/web_input_method_controller.h" |
| #include "third_party/blink/public/web/web_local_frame.h" |
| #include "third_party/blink/public/web/web_local_frame_client.h" |
| #include "third_party/blink/public/web/web_non_composited_widget_client.h" |
| #include "third_party/blink/public/web/web_print_params.h" |
| #include "third_party/blink/public/web/web_script_source.h" |
| #include "third_party/blink/public/web/web_settings.h" |
| #include "third_party/blink/public/web/web_view_client.h" |
| #include "third_party/blink/public/web/web_widget.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_document.h" |
| #include "third_party/blink/renderer/core/css/media_query_list_listener.h" |
| #include "third_party/blink/renderer/core/css/media_query_matcher.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/element.h" |
| #include "third_party/blink/renderer/core/dom/focus_params.h" |
| #include "third_party/blink/renderer/core/dom/node_computed_style.h" |
| #include "third_party/blink/renderer/core/editing/frame_selection.h" |
| #include "third_party/blink/renderer/core/editing/ime/input_method_controller.h" |
| #include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h" |
| #include "third_party/blink/renderer/core/exported/web_settings_impl.h" |
| #include "third_party/blink/renderer/core/exported/web_view_impl.h" |
| #include "third_party/blink/renderer/core/frame/event_handler_registry.h" |
| #include "third_party/blink/renderer/core/frame/frame_test_helpers.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/settings.h" |
| #include "third_party/blink/renderer/core/frame/visual_viewport.h" |
| #include "third_party/blink/renderer/core/frame/web_frame_widget_impl.h" |
| #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" |
| #include "third_party/blink/renderer/core/fullscreen/fullscreen.h" |
| #include "third_party/blink/renderer/core/html/forms/external_date_time_chooser.h" |
| #include "third_party/blink/renderer/core/html/forms/html_input_element.h" |
| #include "third_party/blink/renderer/core/html/forms/html_text_area_element.h" |
| #include "third_party/blink/renderer/core/html/html_document.h" |
| #include "third_party/blink/renderer/core/html/html_iframe_element.h" |
| #include "third_party/blink/renderer/core/html/html_object_element.h" |
| #include "third_party/blink/renderer/core/html/html_span_element.h" |
| #include "third_party/blink/renderer/core/inspector/dev_tools_emulator.h" |
| #include "third_party/blink/renderer/core/layout/layout_theme.h" |
| #include "third_party/blink/renderer/core/layout/layout_view.h" |
| #include "third_party/blink/renderer/core/loader/document_loader.h" |
| #include "third_party/blink/renderer/core/loader/frame_load_request.h" |
| #include "third_party/blink/renderer/core/loader/interactive_detector.h" |
| #include "third_party/blink/renderer/core/page/chrome_client.h" |
| #include "third_party/blink/renderer/core/page/context_menu_controller.h" |
| #include "third_party/blink/renderer/core/page/focus_controller.h" |
| #include "third_party/blink/renderer/core/page/page.h" |
| #include "third_party/blink/renderer/core/page/page_hidden_state.h" |
| #include "third_party/blink/renderer/core/page/print_context.h" |
| #include "third_party/blink/renderer/core/page/scoped_page_pauser.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer_painter.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" |
| #include "third_party/blink/renderer/core/scroll/scroll_types.h" |
| #include "third_party/blink/renderer/core/testing/color_scheme_helper.h" |
| #include "third_party/blink/renderer/core/testing/fake_web_plugin.h" |
| #include "third_party/blink/renderer/core/testing/mock_clipboard_host.h" |
| #include "third_party/blink/renderer/core/testing/page_test_base.h" |
| #include "third_party/blink/renderer/core/timing/dom_window_performance.h" |
| #include "third_party/blink/renderer/core/timing/event_timing.h" |
| #include "third_party/blink/renderer/core/timing/window_performance.h" |
| #include "third_party/blink/renderer/platform/cursors.h" |
| #include "third_party/blink/renderer/platform/geometry/int_rect.h" |
| #include "third_party/blink/renderer/platform/geometry/int_size.h" |
| #include "third_party/blink/renderer/platform/graphics/color.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_context.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_layer.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/paint_record_builder.h" |
| #include "third_party/blink/renderer/platform/keyboard_codes.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/thread.h" |
| #include "third_party/blink/renderer/platform/testing/histogram_tester.h" |
| #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" |
| #include "third_party/blink/renderer/platform/testing/url_test_helpers.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "third_party/skia/include/core/SkCanvas.h" |
| #include "ui/base/cursor/cursor.h" |
| #include "ui/base/cursor/mojom/cursor_type.mojom-blink.h" |
| #include "ui/base/dragdrop/mojom/drag_drop_types.mojom-blink.h" |
| #include "ui/base/mojom/ui_base_types.mojom-shared.h" |
| #include "ui/events/keycodes/dom/dom_key.h" |
| #include "v8/include/v8.h" |
| |
| #if BUILDFLAG(ENABLE_UNHANDLED_TAP) |
| #include "third_party/blink/public/mojom/unhandled_tap_notifier/unhandled_tap_notifier.mojom-blink.h" |
| #include "third_party/blink/renderer/platform/testing/testing_platform_support.h" |
| #endif // BUILDFLAG(ENABLE_UNHANDLED_TAP) |
| |
| using blink::frame_test_helpers::LoadFrame; |
| using blink::test::RunPendingTasks; |
| using blink::url_test_helpers::RegisterMockedURLLoad; |
| using blink::url_test_helpers::ToKURL; |
| |
| namespace blink { |
| |
| enum HorizontalScrollbarState { |
| kNoHorizontalScrollbar, |
| kVisibleHorizontalScrollbar, |
| }; |
| |
| enum VerticalScrollbarState { |
| kNoVerticalScrollbar, |
| kVisibleVerticalScrollbar, |
| }; |
| |
| class TestData { |
| public: |
| void SetWebView(WebView* web_view) { |
| web_view_ = static_cast<WebViewImpl*>(web_view); |
| } |
| void SetSize(const gfx::Size& new_size) { size_ = new_size; } |
| HorizontalScrollbarState GetHorizontalScrollbarState() const { |
| return web_view_->HasHorizontalScrollbar() ? kVisibleHorizontalScrollbar |
| : kNoHorizontalScrollbar; |
| } |
| VerticalScrollbarState GetVerticalScrollbarState() const { |
| return web_view_->HasVerticalScrollbar() ? kVisibleVerticalScrollbar |
| : kNoVerticalScrollbar; |
| } |
| int Width() const { return size_.width(); } |
| int Height() const { return size_.height(); } |
| |
| private: |
| gfx::Size size_; |
| WebViewImpl* web_view_; |
| }; |
| |
| class AutoResizeWebViewClient : public frame_test_helpers::TestWebViewClient { |
| public: |
| // WebViewClient methods |
| void DidAutoResize(const gfx::Size& new_size) override { |
| test_data_.SetSize(new_size); |
| } |
| |
| // Local methods |
| TestData& GetTestData() { return test_data_; } |
| |
| private: |
| TestData test_data_; |
| }; |
| |
| class WebViewTest : public testing::Test { |
| public: |
| explicit WebViewTest(frame_test_helpers::CreateTestWebFrameWidgetCallback |
| create_web_frame_callback = base::NullCallback()) |
| : web_view_helper_(std::move(create_web_frame_callback)) {} |
| |
| void SetUp() override { |
| test_task_runner_ = base::MakeRefCounted<base::TestMockTimeTaskRunner>(); |
| // Advance clock so time is not 0. |
| test_task_runner_->FastForwardBy(base::TimeDelta::FromSeconds(1)); |
| EventTiming::SetTickClockForTesting(test_task_runner_->GetMockTickClock()); |
| } |
| |
| void TearDown() override { |
| EventTiming::SetTickClockForTesting(nullptr); |
| url_test_helpers::UnregisterAllURLsAndClearMemoryCache(); |
| } |
| |
| protected: |
| void SetViewportSize(const gfx::Size& size) { |
| cc::LayerTreeHost* layer_tree_host = web_view_helper_.GetLayerTreeHost(); |
| layer_tree_host->SetViewportRectAndScale( |
| gfx::Rect(static_cast<gfx::Size>(size)), /*device_scale_factor=*/1.f, |
| layer_tree_host->local_surface_id_from_parent()); |
| } |
| |
| std::string RegisterMockedHttpURLLoad(const std::string& file_name) { |
| // TODO(crbug.com/751425): We should use the mock functionality |
| // via |web_view_helper_|. |
| return url_test_helpers::RegisterMockedURLLoadFromBase( |
| WebString::FromUTF8(base_url_), test::CoreTestDataPath(), |
| WebString::FromUTF8(file_name)) |
| .GetString() |
| .Utf8(); |
| } |
| |
| void TestAutoResize(const gfx::Size& min_auto_resize, |
| const gfx::Size& max_auto_resize, |
| const std::string& page_width, |
| const std::string& page_height, |
| int expected_width, |
| int expected_height, |
| HorizontalScrollbarState expected_horizontal_state, |
| VerticalScrollbarState expected_vertical_state); |
| |
| void TestTextInputType(WebTextInputType expected_type, |
| const std::string& html_file); |
| void TestInputMode(WebTextInputMode expected_input_mode, |
| const std::string& html_file); |
| void TestInputAction(ui::TextInputAction expected_input_action, |
| const std::string& html_file); |
| bool TapElement(WebInputEvent::Type, Element*); |
| bool TapElementById(WebInputEvent::Type, const WebString& id); |
| IntSize PrintICBSizeFromPageSize(const FloatSize& page_size); |
| |
| ExternalDateTimeChooser* GetExternalDateTimeChooser( |
| WebViewImpl* web_view_impl); |
| |
| void UpdateAllLifecyclePhases() { |
| web_view_helper_.GetWebView()->MainFrameWidget()->UpdateAllLifecyclePhases( |
| DocumentUpdateReason::kTest); |
| } |
| |
| InteractiveDetector* GetTestInteractiveDetector(Document& document) { |
| InteractiveDetector* detector(InteractiveDetector::From(document)); |
| EXPECT_NE(nullptr, detector); |
| detector->SetTaskRunnerForTesting(test_task_runner_); |
| detector->SetTickClockForTesting(test_task_runner_->GetMockTickClock()); |
| return detector; |
| } |
| |
| std::string base_url_{"http://www.test.com/"}; |
| frame_test_helpers::WebViewHelper web_view_helper_; |
| scoped_refptr<base::TestMockTimeTaskRunner> test_task_runner_; |
| }; |
| |
| static bool HitTestIsContentEditable(WebView* view, int x, int y) { |
| gfx::PointF hit_point(x, y); |
| WebHitTestResult hit_test_result = |
| view->MainFrameWidget()->HitTestResultAt(hit_point); |
| return hit_test_result.IsContentEditable(); |
| } |
| |
| static std::string HitTestElementId(WebView* view, int x, int y) { |
| gfx::PointF hit_point(x, y); |
| WebHitTestResult hit_test_result = |
| view->MainFrameWidget()->HitTestResultAt(hit_point); |
| return hit_test_result.GetNode().To<WebElement>().GetAttribute("id").Utf8(); |
| } |
| |
| static Color OutlineColor(Element* element) { |
| return element->GetComputedStyle()->VisitedDependentColor( |
| GetCSSPropertyOutlineColor()); |
| } |
| |
| TEST_F(WebViewTest, HitTestVideo) { |
| // Test that hit tests on parts of a video element result in hits on the video |
| // element itself as opposed to its child elements. |
| std::string url = RegisterMockedHttpURLLoad("video_200x200.html"); |
| WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(url); |
| web_view->MainFrameViewWidget()->Resize(gfx::Size(200, 200)); |
| |
| // Center of video. |
| EXPECT_EQ("video", HitTestElementId(web_view, 100, 100)); |
| |
| // Play button. |
| EXPECT_EQ("video", HitTestElementId(web_view, 10, 195)); |
| |
| // Timeline bar. |
| EXPECT_EQ("video", HitTestElementId(web_view, 100, 195)); |
| } |
| |
| TEST_F(WebViewTest, HitTestContentEditableImageMaps) { |
| std::string url = |
| RegisterMockedHttpURLLoad("content-editable-image-maps.html"); |
| WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(url); |
| web_view->MainFrameViewWidget()->Resize(gfx::Size(500, 500)); |
| |
| EXPECT_EQ("areaANotEditable", HitTestElementId(web_view, 25, 25)); |
| EXPECT_FALSE(HitTestIsContentEditable(web_view, 25, 25)); |
| EXPECT_EQ("imageANotEditable", HitTestElementId(web_view, 75, 25)); |
| EXPECT_FALSE(HitTestIsContentEditable(web_view, 75, 25)); |
| |
| EXPECT_EQ("areaBNotEditable", HitTestElementId(web_view, 25, 125)); |
| EXPECT_FALSE(HitTestIsContentEditable(web_view, 25, 125)); |
| EXPECT_EQ("imageBEditable", HitTestElementId(web_view, 75, 125)); |
| EXPECT_TRUE(HitTestIsContentEditable(web_view, 75, 125)); |
| |
| EXPECT_EQ("areaCNotEditable", HitTestElementId(web_view, 25, 225)); |
| EXPECT_FALSE(HitTestIsContentEditable(web_view, 25, 225)); |
| EXPECT_EQ("imageCNotEditable", HitTestElementId(web_view, 75, 225)); |
| EXPECT_FALSE(HitTestIsContentEditable(web_view, 75, 225)); |
| |
| EXPECT_EQ("areaDEditable", HitTestElementId(web_view, 25, 325)); |
| EXPECT_TRUE(HitTestIsContentEditable(web_view, 25, 325)); |
| EXPECT_EQ("imageDNotEditable", HitTestElementId(web_view, 75, 325)); |
| EXPECT_FALSE(HitTestIsContentEditable(web_view, 75, 325)); |
| } |
| |
| static std::string HitTestAbsoluteUrl(WebView* view, int x, int y) { |
| gfx::PointF hit_point(x, y); |
| WebHitTestResult hit_test_result = |
| view->MainFrameWidget()->HitTestResultAt(hit_point); |
| return hit_test_result.AbsoluteImageURL().GetString().Utf8(); |
| } |
| |
| static WebElement HitTestUrlElement(WebView* view, int x, int y) { |
| gfx::PointF hit_point(x, y); |
| WebHitTestResult hit_test_result = |
| view->MainFrameWidget()->HitTestResultAt(hit_point); |
| return hit_test_result.UrlElement(); |
| } |
| |
| TEST_F(WebViewTest, ImageMapUrls) { |
| std::string url = RegisterMockedHttpURLLoad("image-map.html"); |
| WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(url); |
| web_view->MainFrameViewWidget()->Resize(gfx::Size(400, 400)); |
| |
| std::string image_url = |
| "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="; |
| |
| EXPECT_EQ("area", HitTestElementId(web_view, 25, 25)); |
| EXPECT_EQ("area", |
| HitTestUrlElement(web_view, 25, 25).GetAttribute("id").Utf8()); |
| EXPECT_EQ(image_url, HitTestAbsoluteUrl(web_view, 25, 25)); |
| |
| EXPECT_EQ("image", HitTestElementId(web_view, 75, 25)); |
| EXPECT_TRUE(HitTestUrlElement(web_view, 75, 25).IsNull()); |
| EXPECT_EQ(image_url, HitTestAbsoluteUrl(web_view, 75, 25)); |
| } |
| |
| TEST_F(WebViewTest, BrokenImage) { |
| url_test_helpers::RegisterMockedErrorURLLoad( |
| KURL(ToKURL(base_url_), "non_existent.png")); |
| std::string url = RegisterMockedHttpURLLoad("image-broken.html"); |
| |
| WebViewImpl* web_view = web_view_helper_.Initialize(); |
| web_view->GetSettings()->SetLoadsImagesAutomatically(true); |
| LoadFrame(web_view->MainFrameImpl(), url); |
| web_view->MainFrameViewWidget()->Resize(gfx::Size(400, 400)); |
| |
| std::string image_url = "http://www.test.com/non_existent.png"; |
| |
| EXPECT_EQ("image", HitTestElementId(web_view, 25, 25)); |
| EXPECT_TRUE(HitTestUrlElement(web_view, 25, 25).IsNull()); |
| EXPECT_EQ(image_url, HitTestAbsoluteUrl(web_view, 25, 25)); |
| } |
| |
| TEST_F(WebViewTest, BrokenInputImage) { |
| url_test_helpers::RegisterMockedErrorURLLoad( |
| KURL(ToKURL(base_url_), "non_existent.png")); |
| std::string url = RegisterMockedHttpURLLoad("input-image-broken.html"); |
| |
| WebViewImpl* web_view = web_view_helper_.Initialize(); |
| web_view->GetSettings()->SetLoadsImagesAutomatically(true); |
| LoadFrame(web_view->MainFrameImpl(), url); |
| web_view->MainFrameViewWidget()->Resize(gfx::Size(400, 400)); |
| |
| std::string image_url = "http://www.test.com/non_existent.png"; |
| |
| EXPECT_EQ("image", HitTestElementId(web_view, 25, 25)); |
| EXPECT_TRUE(HitTestUrlElement(web_view, 25, 25).IsNull()); |
| EXPECT_EQ(image_url, HitTestAbsoluteUrl(web_view, 25, 25)); |
| } |
| |
| TEST_F(WebViewTest, SetBaseBackgroundColor) { |
| const SkColor kDarkCyan = SkColorSetARGB(0xFF, 0x22, 0x77, 0x88); |
| const SkColor kTranslucentPutty = SkColorSetARGB(0x80, 0xBF, 0xB1, 0x96); |
| |
| WebViewImpl* web_view = web_view_helper_.Initialize(); |
| EXPECT_EQ(SK_ColorWHITE, web_view->BackgroundColor()); |
| |
| web_view->SetBaseBackgroundColor(SK_ColorBLUE); |
| EXPECT_EQ(SK_ColorBLUE, web_view->BackgroundColor()); |
| |
| WebURL base_url = url_test_helpers::ToKURL("http://example.com/"); |
| frame_test_helpers::LoadHTMLString( |
| web_view->MainFrameImpl(), |
| "<html><head><style>body " |
| "{background-color:#227788}</style></head></" |
| "html>", |
| base_url); |
| EXPECT_EQ(kDarkCyan, web_view->BackgroundColor()); |
| |
| frame_test_helpers::LoadHTMLString(web_view->MainFrameImpl(), |
| "<html><head><style>body " |
| "{background-color:rgba(255,0,0,0.5)}</" |
| "style></head></html>", |
| base_url); |
| // Expected: red (50% alpha) blended atop base of SK_ColorBLUE. |
| EXPECT_EQ(0xFF80007F, web_view->BackgroundColor()); |
| |
| web_view->SetBaseBackgroundColor(kTranslucentPutty); |
| // Expected: red (50% alpha) blended atop kTranslucentPutty. Note the alpha. |
| EXPECT_EQ(0xBFE93A31, web_view->BackgroundColor()); |
| |
| web_view->SetBaseBackgroundColor(SK_ColorTRANSPARENT); |
| frame_test_helpers::LoadHTMLString(web_view->MainFrameImpl(), |
| "<html><head><style>body " |
| "{background-color:transparent}</style></" |
| "head></html>", |
| base_url); |
| // Expected: transparent on top of transparent will still be transparent. |
| EXPECT_EQ(SK_ColorTRANSPARENT, web_view->BackgroundColor()); |
| |
| LocalFrame* frame = web_view->MainFrameImpl()->GetFrame(); |
| // The shutdown() calls are a hack to prevent this test |
| // from violating invariants about frame state during navigation/detach. |
| frame->GetDocument()->Shutdown(); |
| |
| // Creating a new frame view with the background color having 0 alpha. |
| frame->CreateView(IntSize(1024, 768), Color::kTransparent); |
| EXPECT_EQ(SK_ColorTRANSPARENT, frame->View()->BaseBackgroundColor()); |
| frame->View()->Dispose(); |
| |
| const Color transparent_red(100, 0, 0, 0); |
| frame->CreateView(IntSize(1024, 768), transparent_red); |
| EXPECT_EQ(transparent_red, frame->View()->BaseBackgroundColor()); |
| frame->View()->Dispose(); |
| } |
| |
| TEST_F(WebViewTest, SetBaseBackgroundColorBeforeMainFrame) { |
| // Note: this test doesn't use WebViewHelper since it intentionally runs |
| // initialization code between WebView and WebLocalFrame creation. |
| frame_test_helpers::TestWebViewClient web_view_client; |
| WebViewImpl* web_view = static_cast<WebViewImpl*>( |
| WebView::Create(&web_view_client, |
| /*is_hidden=*/false, |
| /*is_inside_portal=*/false, |
| /*compositing_enabled=*/true, |
| /*opener=*/nullptr, mojo::NullAssociatedReceiver(), |
| web_view_helper_.GetAgentGroupScheduler())); |
| |
| EXPECT_NE(SK_ColorBLUE, web_view->BackgroundColor()); |
| // WebView does not have a frame yet, but we should still be able to set the |
| // background color. |
| web_view->SetBaseBackgroundColor(SK_ColorBLUE); |
| EXPECT_EQ(SK_ColorBLUE, web_view->BackgroundColor()); |
| |
| frame_test_helpers::TestWebFrameClient web_frame_client; |
| WebLocalFrame* frame = WebLocalFrame::CreateMainFrame( |
| web_view, &web_frame_client, nullptr, LocalFrameToken(), nullptr); |
| web_frame_client.Bind(frame); |
| |
| frame_test_helpers::TestWebFrameWidget* widget = |
| web_view_helper_.CreateFrameWidgetAndInitializeCompositing(frame); |
| web_view->DidAttachLocalMainFrame(); |
| |
| // The color should be passed to the compositor. |
| cc::LayerTreeHost* host = widget->LayerTreeHostForTesting(); |
| EXPECT_EQ(SK_ColorBLUE, web_view->BackgroundColor()); |
| EXPECT_EQ(SK_ColorBLUE, host->background_color()); |
| |
| web_view->Close(); |
| } |
| |
| TEST_F(WebViewTest, SetBaseBackgroundColorAndBlendWithExistingContent) { |
| const SkColor kAlphaRed = SkColorSetARGB(0x80, 0xFF, 0x00, 0x00); |
| const SkColor kAlphaGreen = SkColorSetARGB(0x80, 0x00, 0xFF, 0x00); |
| const int kWidth = 100; |
| const int kHeight = 100; |
| |
| WebViewImpl* web_view = web_view_helper_.Initialize(); |
| |
| // Set WebView background to green with alpha. |
| web_view->SetBaseBackgroundColor(kAlphaGreen); |
| web_view->GetSettings()->SetShouldClearDocumentBackground(false); |
| web_view->MainFrameViewWidget()->Resize(gfx::Size(kWidth, kHeight)); |
| UpdateAllLifecyclePhases(); |
| |
| // Set canvas background to red with alpha. |
| SkBitmap bitmap; |
| bitmap.allocN32Pixels(kWidth, kHeight); |
| SkCanvas canvas(bitmap, SkSurfaceProps{}); |
| canvas.clear(kAlphaRed); |
| |
| PaintRecordBuilder builder; |
| |
| // Paint the root of the main frame in the way that CompositedLayerMapping |
| // would. |
| LocalFrameView* view = web_view_helper_.LocalMainFrame()->GetFrameView(); |
| PaintLayer* root_layer = view->GetLayoutView()->Layer(); |
| CullRect paint_rect(IntRect(0, 0, kWidth, kHeight)); |
| PaintLayerPaintingInfo painting_info( |
| root_layer, paint_rect, kGlobalPaintNormalPhase, PhysicalOffset()); |
| |
| view->GetLayoutView()->GetDocument().Lifecycle().AdvanceTo( |
| DocumentLifecycle::kInPaint); |
| PaintLayerPainter(*root_layer) |
| .PaintLayerContents(builder.Context(), painting_info, |
| kPaintLayerPaintingCompositingAllPhases); |
| view->GetLayoutView()->GetDocument().Lifecycle().AdvanceTo( |
| DocumentLifecycle::kPaintClean); |
| builder.EndRecording()->Playback(&canvas); |
| |
| // The result should be a blend of red and green. |
| SkColor color = bitmap.getColor(kWidth / 2, kHeight / 2); |
| EXPECT_TRUE(RedChannel(color)); |
| EXPECT_TRUE(GreenChannel(color)); |
| } |
| |
| TEST_F(WebViewTest, SetBaseBackgroundColorWithColorScheme) { |
| WebViewImpl* web_view = web_view_helper_.Initialize(); |
| ColorSchemeHelper color_scheme_helper(*(web_view->GetPage())); |
| color_scheme_helper.SetPreferredColorScheme( |
| mojom::blink::PreferredColorScheme::kLight); |
| web_view->SetBaseBackgroundColor(SK_ColorBLUE); |
| |
| WebURL base_url = url_test_helpers::ToKURL("http://example.com/"); |
| frame_test_helpers::LoadHTMLString( |
| web_view->MainFrameImpl(), |
| "<style>:root { color-scheme: light dark }<style>", base_url); |
| UpdateAllLifecyclePhases(); |
| |
| LocalFrameView* frame_view = web_view->MainFrameImpl()->GetFrame()->View(); |
| EXPECT_EQ(Color(0, 0, 255), frame_view->BaseBackgroundColor()); |
| |
| color_scheme_helper.SetPreferredColorScheme( |
| mojom::blink::PreferredColorScheme::kDark); |
| UpdateAllLifecyclePhases(); |
| EXPECT_EQ(Color(0x12, 0x12, 0x12), frame_view->BaseBackgroundColor()); |
| |
| // Don't let dark color-scheme override a transparent background. |
| web_view->SetBaseBackgroundColor(SK_ColorTRANSPARENT); |
| EXPECT_EQ(Color::kTransparent, frame_view->BaseBackgroundColor()); |
| web_view->SetBaseBackgroundColor(SK_ColorBLUE); |
| EXPECT_EQ(Color(0x12, 0x12, 0x12), frame_view->BaseBackgroundColor()); |
| |
| color_scheme_helper.SetForcedColors(*(web_view->GetPage()), |
| ForcedColors::kActive); |
| UpdateAllLifecyclePhases(); |
| |
| Color system_background_color = LayoutTheme::GetTheme().SystemColor( |
| CSSValueID::kCanvas, mojom::blink::ColorScheme::kLight); |
| EXPECT_EQ(system_background_color, frame_view->BaseBackgroundColor()); |
| |
| color_scheme_helper.SetForcedColors(*(web_view->GetPage()), |
| ForcedColors::kNone); |
| UpdateAllLifecyclePhases(); |
| EXPECT_EQ(Color(0x12, 0x12, 0x12), frame_view->BaseBackgroundColor()); |
| |
| color_scheme_helper.SetPreferredColorScheme( |
| mojom::blink::PreferredColorScheme::kLight); |
| UpdateAllLifecyclePhases(); |
| EXPECT_EQ(Color(0, 0, 255), frame_view->BaseBackgroundColor()); |
| } |
| |
| TEST_F(WebViewTest, FocusIsInactive) { |
| RegisterMockedHttpURLLoad("visible_iframe.html"); |
| WebViewImpl* web_view = |
| web_view_helper_.InitializeAndLoad(base_url_ + "visible_iframe.html"); |
| |
| web_view->MainFrameWidget()->SetFocus(true); |
| web_view->SetIsActive(true); |
| WebLocalFrameImpl* frame = web_view->MainFrameImpl(); |
| EXPECT_TRUE(IsA<HTMLDocument>(frame->GetFrame()->GetDocument())); |
| |
| Document* document = frame->GetFrame()->GetDocument(); |
| EXPECT_TRUE(document->hasFocus()); |
| web_view->MainFrameWidget()->SetFocus(false); |
| web_view->SetIsActive(false); |
| EXPECT_FALSE(document->hasFocus()); |
| web_view->MainFrameWidget()->SetFocus(true); |
| web_view->SetIsActive(true); |
| EXPECT_TRUE(document->hasFocus()); |
| web_view->MainFrameWidget()->SetFocus(true); |
| web_view->SetIsActive(false); |
| EXPECT_FALSE(document->hasFocus()); |
| web_view->MainFrameWidget()->SetFocus(false); |
| web_view->SetIsActive(true); |
| EXPECT_FALSE(document->hasFocus()); |
| web_view->SetIsActive(false); |
| web_view->MainFrameWidget()->SetFocus(true); |
| EXPECT_TRUE(document->hasFocus()); |
| web_view->SetIsActive(true); |
| web_view->MainFrameWidget()->SetFocus(false); |
| EXPECT_FALSE(document->hasFocus()); |
| } |
| |
| TEST_F(WebViewTest, DocumentHasFocus) { |
| WebViewImpl* web_view = web_view_helper_.Initialize(); |
| web_view->MainFrameWidget()->SetFocus(true); |
| |
| WebURL base_url = url_test_helpers::ToKURL("http://example.com/"); |
| frame_test_helpers::LoadHTMLString( |
| web_view->MainFrameImpl(), |
| "<input id=input></input>" |
| "<div id=log></div>" |
| "<script>" |
| " document.getElementById('input').addEventListener('focus', () => {" |
| " document.getElementById('log').textContent = 'document.hasFocus(): " |
| "' + document.hasFocus();" |
| " });" |
| " document.getElementById('input').addEventListener('blur', () => {" |
| " document.getElementById('log').textContent = '';" |
| " });" |
| " document.getElementById('input').focus();" |
| "</script>", |
| base_url); |
| |
| WebLocalFrameImpl* frame = web_view->MainFrameImpl(); |
| Document* document = frame->GetFrame()->GetDocument(); |
| WebElement log_element = frame->GetDocument().GetElementById("log"); |
| EXPECT_TRUE(document->hasFocus()); |
| EXPECT_EQ("document.hasFocus(): true", log_element.TextContent()); |
| |
| web_view->SetIsActive(false); |
| web_view->MainFrameWidget()->SetFocus(false); |
| EXPECT_FALSE(document->hasFocus()); |
| EXPECT_TRUE(log_element.TextContent().IsEmpty()); |
| |
| web_view->MainFrameWidget()->SetFocus(true); |
| EXPECT_TRUE(document->hasFocus()); |
| EXPECT_EQ("document.hasFocus(): true", log_element.TextContent()); |
| } |
| |
| TEST_F(WebViewTest, PlatformColorsChangedOnDeviceEmulation) { |
| WebViewImpl* web_view_impl = web_view_helper_.Initialize(); |
| WebURL base_url = url_test_helpers::ToKURL("http://example.com/"); |
| frame_test_helpers::LoadHTMLString( |
| web_view_impl->MainFrameImpl(), |
| "<style>" |
| " span { outline-color: -webkit-focus-ring-color; }" |
| "</style>" |
| "<span id='span1'></span>", |
| base_url); |
| UpdateAllLifecyclePhases(); |
| |
| DeviceEmulationParams params; |
| params.screen_type = mojom::EmulatedScreenType::kMobile; |
| |
| Document& document = |
| *web_view_impl->MainFrameImpl()->GetFrame()->GetDocument(); |
| |
| Element* span1 = document.getElementById("span1"); |
| ASSERT_TRUE(span1); |
| |
| // Check non-MobileLayoutTheme color. |
| // TODO(crbug.com/929098) Need to pass an appropriate color scheme here. |
| Color original = LayoutTheme::GetTheme().FocusRingColor( |
| ComputedStyle::InitialStyle().UsedColorScheme()); |
| EXPECT_EQ(original, OutlineColor(span1)); |
| |
| // Set the focus ring color for the mobile theme to something known. |
| Color custom_color = MakeRGB(123, 145, 167); |
| { |
| ScopedMobileLayoutThemeForTest mobile_layout_theme_enabled(true); |
| LayoutTheme::GetTheme().SetCustomFocusRingColor(custom_color); |
| } |
| |
| EXPECT_NE(custom_color, original); |
| web_view_impl->EnableDeviceEmulation(params); |
| |
| // All <span>s should have the custom outline color, and not (for example) |
| // the original color fetched from cache. |
| auto* span2 = MakeGarbageCollected<HTMLSpanElement>(document); |
| document.body()->AppendChild(span2); |
| UpdateAllLifecyclePhases(); |
| EXPECT_EQ(custom_color, OutlineColor(span1)); |
| EXPECT_EQ(custom_color, OutlineColor(span2)); |
| |
| // Disable mobile emulation. All <span>s should once again have the |
| // original outline color. |
| web_view_impl->DisableDeviceEmulation(); |
| auto* span3 = MakeGarbageCollected<HTMLSpanElement>(document); |
| document.body()->AppendChild(span3); |
| UpdateAllLifecyclePhases(); |
| EXPECT_EQ(original, OutlineColor(span1)); |
| EXPECT_EQ(original, OutlineColor(span2)); |
| EXPECT_EQ(original, OutlineColor(span3)); |
| } |
| |
| TEST_F(WebViewTest, ActiveState) { |
| RegisterMockedHttpURLLoad("visible_iframe.html"); |
| WebView* web_view = |
| web_view_helper_.InitializeAndLoad(base_url_ + "visible_iframe.html"); |
| |
| ASSERT_TRUE(web_view); |
| |
| web_view->SetIsActive(true); |
| EXPECT_TRUE(web_view->IsActive()); |
| |
| web_view->SetIsActive(false); |
| EXPECT_FALSE(web_view->IsActive()); |
| |
| web_view->SetIsActive(true); |
| EXPECT_TRUE(web_view->IsActive()); |
| } |
| |
| TEST_F(WebViewTest, HitTestResultAtWithPageScale) { |
| std::string url = base_url_ + "specify_size.html?" + "50px" + ":" + "50px"; |
| url_test_helpers::RegisterMockedURLLoad( |
| ToKURL(url), test::CoreTestDataPath("specify_size.html")); |
| WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(url); |
| web_view->MainFrameViewWidget()->Resize(gfx::Size(100, 100)); |
| gfx::PointF hit_point(75, 75); |
| |
| // Image is at top left quandrant, so should not hit it. |
| WebHitTestResult negative_result = |
| web_view->MainFrameWidget()->HitTestResultAt(hit_point); |
| EXPECT_FALSE( |
| negative_result.GetNode().To<WebElement>().HasHTMLTagName("img")); |
| negative_result.Reset(); |
| |
| // Scale page up 2x so image should occupy the whole viewport. |
| web_view->SetPageScaleFactor(2.0f); |
| WebHitTestResult positive_result = |
| web_view->MainFrameWidget()->HitTestResultAt(hit_point); |
| EXPECT_TRUE(positive_result.GetNode().To<WebElement>().HasHTMLTagName("img")); |
| positive_result.Reset(); |
| } |
| |
| TEST_F(WebViewTest, HitTestResultAtWithPageScaleAndPan) { |
| std::string url = base_url_ + "specify_size.html?" + "50px" + ":" + "50px"; |
| url_test_helpers::RegisterMockedURLLoad( |
| ToKURL(url), test::CoreTestDataPath("specify_size.html")); |
| WebViewImpl* web_view = web_view_helper_.Initialize(); |
| LoadFrame(web_view->MainFrameImpl(), url); |
| web_view->MainFrameViewWidget()->Resize(gfx::Size(100, 100)); |
| gfx::PointF hit_point(75, 75); |
| |
| // Image is at top left quandrant, so should not hit it. |
| WebHitTestResult negative_result = |
| web_view->MainFrameWidget()->HitTestResultAt(hit_point); |
| EXPECT_FALSE( |
| negative_result.GetNode().To<WebElement>().HasHTMLTagName("img")); |
| negative_result.Reset(); |
| |
| // Scale page up 2x so image should occupy the whole viewport. |
| web_view->SetPageScaleFactor(2.0f); |
| WebHitTestResult positive_result = |
| web_view->MainFrameWidget()->HitTestResultAt(hit_point); |
| EXPECT_TRUE(positive_result.GetNode().To<WebElement>().HasHTMLTagName("img")); |
| positive_result.Reset(); |
| |
| // Pan around the zoomed in page so the image is not visible in viewport. |
| web_view->SetVisualViewportOffset(gfx::PointF(100, 100)); |
| WebHitTestResult negative_result2 = |
| web_view->MainFrameWidget()->HitTestResultAt(hit_point); |
| EXPECT_FALSE( |
| negative_result2.GetNode().To<WebElement>().HasHTMLTagName("img")); |
| negative_result2.Reset(); |
| } |
| |
| TEST_F(WebViewTest, HitTestResultForTapWithTapArea) { |
| std::string url = RegisterMockedHttpURLLoad("hit_test.html"); |
| WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(url); |
| web_view->MainFrameViewWidget()->Resize(gfx::Size(100, 100)); |
| gfx::Point hit_point(55, 55); |
| |
| // Image is at top left quandrant, so should not hit it. |
| WebHitTestResult negative_result = |
| web_view->MainFrameWidget()->HitTestResultAt(gfx::PointF(hit_point)); |
| EXPECT_FALSE( |
| negative_result.GetNode().To<WebElement>().HasHTMLTagName("img")); |
| negative_result.Reset(); |
| |
| // The tap area is 20 by 20 square, centered at 55, 55. |
| gfx::Size tap_area(20, 20); |
| WebHitTestResult positive_result = |
| web_view->HitTestResultForTap(hit_point, tap_area); |
| EXPECT_TRUE(positive_result.GetNode().To<WebElement>().HasHTMLTagName("img")); |
| positive_result.Reset(); |
| |
| // Move the hit point the image is just outside the tapped area now. |
| hit_point = gfx::Point(61, 61); |
| WebHitTestResult negative_result2 = |
| web_view->HitTestResultForTap(hit_point, tap_area); |
| EXPECT_FALSE( |
| negative_result2.GetNode().To<WebElement>().HasHTMLTagName("img")); |
| negative_result2.Reset(); |
| } |
| |
| TEST_F(WebViewTest, HitTestResultForTapWithTapAreaPageScaleAndPan) { |
| std::string url = RegisterMockedHttpURLLoad("hit_test.html"); |
| WebViewImpl* web_view = web_view_helper_.Initialize(); |
| LoadFrame(web_view->MainFrameImpl(), url); |
| web_view->MainFrameViewWidget()->Resize(gfx::Size(100, 100)); |
| gfx::Point hit_point(55, 55); |
| |
| // Image is at top left quandrant, so should not hit it. |
| WebHitTestResult negative_result = |
| web_view->MainFrameWidget()->HitTestResultAt(gfx::PointF(hit_point)); |
| EXPECT_FALSE( |
| negative_result.GetNode().To<WebElement>().HasHTMLTagName("img")); |
| negative_result.Reset(); |
| |
| // The tap area is 20 by 20 square, centered at 55, 55. |
| gfx::Size tap_area(20, 20); |
| WebHitTestResult positive_result = |
| web_view->HitTestResultForTap(hit_point, tap_area); |
| EXPECT_TRUE(positive_result.GetNode().To<WebElement>().HasHTMLTagName("img")); |
| positive_result.Reset(); |
| |
| // Zoom in and pan around the page so the image is not visible in viewport. |
| web_view->SetPageScaleFactor(2.0f); |
| web_view->SetVisualViewportOffset(gfx::PointF(100, 100)); |
| WebHitTestResult negative_result2 = |
| web_view->HitTestResultForTap(hit_point, tap_area); |
| EXPECT_FALSE( |
| negative_result2.GetNode().To<WebElement>().HasHTMLTagName("img")); |
| negative_result2.Reset(); |
| } |
| |
| void WebViewTest::TestAutoResize( |
| const gfx::Size& min_auto_resize, |
| const gfx::Size& max_auto_resize, |
| const std::string& page_width, |
| const std::string& page_height, |
| int expected_width, |
| int expected_height, |
| HorizontalScrollbarState expected_horizontal_state, |
| VerticalScrollbarState expected_vertical_state) { |
| AutoResizeWebViewClient client; |
| std::string url = |
| base_url_ + "specify_size.html?" + page_width + ":" + page_height; |
| url_test_helpers::RegisterMockedURLLoad( |
| ToKURL(url), test::CoreTestDataPath("specify_size.html")); |
| WebViewImpl* web_view = |
| web_view_helper_.InitializeAndLoad(url, nullptr, &client); |
| client.GetTestData().SetWebView(web_view); |
| |
| WebLocalFrameImpl* frame = web_view->MainFrameImpl(); |
| LocalFrameView* frame_view = frame->GetFrame()->View(); |
| frame_view->UpdateLayout(); |
| EXPECT_FALSE(frame_view->LayoutPending()); |
| EXPECT_FALSE(frame_view->NeedsLayout()); |
| |
| web_view->EnableAutoResizeMode(min_auto_resize, max_auto_resize); |
| EXPECT_TRUE(frame_view->LayoutPending()); |
| EXPECT_TRUE(frame_view->NeedsLayout()); |
| frame_view->UpdateLayout(); |
| |
| EXPECT_TRUE(frame->GetFrame()->GetDocument()->IsHTMLDocument()); |
| |
| EXPECT_EQ(expected_width, client.GetTestData().Width()); |
| EXPECT_EQ(expected_height, client.GetTestData().Height()); |
| |
| // Android disables main frame scrollbars. |
| #if !defined(OS_ANDROID) |
| EXPECT_EQ(expected_horizontal_state, |
| client.GetTestData().GetHorizontalScrollbarState()); |
| EXPECT_EQ(expected_vertical_state, |
| client.GetTestData().GetVerticalScrollbarState()); |
| #endif |
| |
| // Explicitly reset to break dependency on locally scoped client. |
| web_view_helper_.Reset(); |
| } |
| |
| TEST_F(WebViewTest, AutoResizeMinimumSize) { |
| gfx::Size min_auto_resize(91, 56); |
| gfx::Size max_auto_resize(403, 302); |
| std::string page_width = "91px"; |
| std::string page_height = "56px"; |
| int expected_width = 91; |
| int expected_height = 56; |
| TestAutoResize(min_auto_resize, max_auto_resize, page_width, page_height, |
| expected_width, expected_height, kNoHorizontalScrollbar, |
| kNoVerticalScrollbar); |
| } |
| |
| TEST_F(WebViewTest, AutoResizeHeightOverflowAndFixedWidth) { |
| gfx::Size min_auto_resize(90, 95); |
| gfx::Size max_auto_resize(90, 100); |
| std::string page_width = "60px"; |
| std::string page_height = "200px"; |
| int expected_width = 90; |
| int expected_height = 100; |
| TestAutoResize(min_auto_resize, max_auto_resize, page_width, page_height, |
| expected_width, expected_height, kNoHorizontalScrollbar, |
| kVisibleVerticalScrollbar); |
| } |
| |
| TEST_F(WebViewTest, AutoResizeFixedHeightAndWidthOverflow) { |
| gfx::Size min_auto_resize(90, 100); |
| gfx::Size max_auto_resize(200, 100); |
| std::string page_width = "300px"; |
| std::string page_height = "80px"; |
| int expected_width = 200; |
| int expected_height = 100; |
| TestAutoResize(min_auto_resize, max_auto_resize, page_width, page_height, |
| expected_width, expected_height, kVisibleHorizontalScrollbar, |
| kNoVerticalScrollbar); |
| } |
| |
| // Next three tests disabled for https://bugs.webkit.org/show_bug.cgi?id=92318 . |
| // It seems we can run three AutoResize tests, then the next one breaks. |
| TEST_F(WebViewTest, AutoResizeInBetweenSizes) { |
| gfx::Size min_auto_resize(90, 95); |
| gfx::Size max_auto_resize(200, 300); |
| std::string page_width = "100px"; |
| std::string page_height = "200px"; |
| int expected_width = 100; |
| int expected_height = 200; |
| TestAutoResize(min_auto_resize, max_auto_resize, page_width, page_height, |
| expected_width, expected_height, kNoHorizontalScrollbar, |
| kNoVerticalScrollbar); |
| } |
| |
| TEST_F(WebViewTest, AutoResizeOverflowSizes) { |
| gfx::Size min_auto_resize(90, 95); |
| gfx::Size max_auto_resize(200, 300); |
| std::string page_width = "300px"; |
| std::string page_height = "400px"; |
| int expected_width = 200; |
| int expected_height = 300; |
| TestAutoResize(min_auto_resize, max_auto_resize, page_width, page_height, |
| expected_width, expected_height, kVisibleHorizontalScrollbar, |
| kVisibleVerticalScrollbar); |
| } |
| |
| TEST_F(WebViewTest, AutoResizeMaxSize) { |
| gfx::Size min_auto_resize(90, 95); |
| gfx::Size max_auto_resize(200, 300); |
| std::string page_width = "200px"; |
| std::string page_height = "300px"; |
| int expected_width = 200; |
| int expected_height = 300; |
| TestAutoResize(min_auto_resize, max_auto_resize, page_width, page_height, |
| expected_width, expected_height, kNoHorizontalScrollbar, |
| kNoVerticalScrollbar); |
| } |
| |
| void WebViewTest::TestTextInputType(WebTextInputType expected_type, |
| const std::string& html_file) { |
| RegisterMockedHttpURLLoad(html_file); |
| WebViewImpl* web_view = |
| web_view_helper_.InitializeAndLoad(base_url_ + html_file); |
| WebInputMethodController* controller = |
| web_view->MainFrameImpl()->GetInputMethodController(); |
| EXPECT_EQ(kWebTextInputTypeNone, controller->TextInputType()); |
| EXPECT_EQ(kWebTextInputTypeNone, controller->TextInputInfo().type); |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| EXPECT_EQ(expected_type, controller->TextInputType()); |
| EXPECT_EQ(expected_type, controller->TextInputInfo().type); |
| web_view->FocusedElement()->blur(); |
| EXPECT_EQ(kWebTextInputTypeNone, controller->TextInputType()); |
| EXPECT_EQ(kWebTextInputTypeNone, controller->TextInputInfo().type); |
| } |
| |
| TEST_F(WebViewTest, TextInputType) { |
| TestTextInputType(kWebTextInputTypeText, "input_field_default.html"); |
| TestTextInputType(kWebTextInputTypePassword, "input_field_password.html"); |
| TestTextInputType(kWebTextInputTypeEmail, "input_field_email.html"); |
| TestTextInputType(kWebTextInputTypeSearch, "input_field_search.html"); |
| TestTextInputType(kWebTextInputTypeNumber, "input_field_number.html"); |
| TestTextInputType(kWebTextInputTypeTelephone, "input_field_tel.html"); |
| TestTextInputType(kWebTextInputTypeURL, "input_field_url.html"); |
| } |
| |
| TEST_F(WebViewTest, TextInputInfoUpdateStyleAndLayout) { |
| frame_test_helpers::WebViewHelper web_view_helper; |
| WebViewImpl* web_view_impl = web_view_helper.Initialize(); |
| |
| WebURL base_url = url_test_helpers::ToKURL("http://example.com/"); |
| // Here, we need to construct a document that has a special property: |
| // Adding id="foo" to the <path> element will trigger creation of an SVG |
| // instance tree for the use <use> element. |
| // This is significant, because SVG instance trees are actually created lazily |
| // during Document::updateStyleAndLayout code, thus incrementing the DOM tree |
| // version and freaking out the EphemeralRange (invalidating it). |
| frame_test_helpers::LoadHTMLString( |
| web_view_impl->MainFrameImpl(), |
| "<svg height='100%' version='1.1' viewBox='0 0 14 14' width='100%'>" |
| "<use xmlns:xlink='http://www.w3.org/1999/xlink' xlink:href='#foo'></use>" |
| "<path d='M 100 100 L 300 100 L 200 300 z' fill='#000'></path>" |
| "</svg>" |
| "<input>", |
| base_url); |
| web_view_impl->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| |
| // Add id="foo" to <path>, thus triggering the condition described above. |
| Document* document = |
| web_view_impl->MainFrameImpl()->GetFrame()->GetDocument(); |
| document->body() |
| ->QuerySelector("path", ASSERT_NO_EXCEPTION) |
| ->SetIdAttribute("foo"); |
| |
| // This should not DCHECK. |
| EXPECT_EQ(kWebTextInputTypeText, web_view_impl->MainFrameImpl() |
| ->GetInputMethodController() |
| ->TextInputInfo() |
| .type); |
| } |
| |
| void WebViewTest::TestInputMode(WebTextInputMode expected_input_mode, |
| const std::string& html_file) { |
| RegisterMockedHttpURLLoad(html_file); |
| WebViewImpl* web_view_impl = |
| web_view_helper_.InitializeAndLoad(base_url_ + html_file); |
| web_view_impl->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| EXPECT_EQ(expected_input_mode, web_view_impl->MainFrameImpl() |
| ->GetInputMethodController() |
| ->TextInputInfo() |
| .input_mode); |
| } |
| |
| TEST_F(WebViewTest, InputMode) { |
| TestInputMode(WebTextInputMode::kWebTextInputModeDefault, |
| "input_mode_default.html"); |
| TestInputMode(WebTextInputMode::kWebTextInputModeDefault, |
| "input_mode_default_unknown.html"); |
| TestInputMode(WebTextInputMode::kWebTextInputModeNone, |
| "input_mode_type_none.html"); |
| TestInputMode(WebTextInputMode::kWebTextInputModeText, |
| "input_mode_type_text.html"); |
| TestInputMode(WebTextInputMode::kWebTextInputModeTel, |
| "input_mode_type_tel.html"); |
| TestInputMode(WebTextInputMode::kWebTextInputModeUrl, |
| "input_mode_type_url.html"); |
| TestInputMode(WebTextInputMode::kWebTextInputModeEmail, |
| "input_mode_type_email.html"); |
| TestInputMode(WebTextInputMode::kWebTextInputModeNumeric, |
| "input_mode_type_numeric.html"); |
| TestInputMode(WebTextInputMode::kWebTextInputModeDecimal, |
| "input_mode_type_decimal.html"); |
| TestInputMode(WebTextInputMode::kWebTextInputModeSearch, |
| "input_mode_type_search.html"); |
| } |
| |
| void WebViewTest::TestInputAction(ui::TextInputAction expected_input_action, |
| const std::string& html_file) { |
| RegisterMockedHttpURLLoad(html_file); |
| WebViewImpl* web_view_impl = |
| web_view_helper_.InitializeAndLoad(base_url_ + html_file); |
| web_view_impl->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| EXPECT_EQ(expected_input_action, web_view_impl->MainFrameImpl() |
| ->GetInputMethodController() |
| ->TextInputInfo() |
| .action); |
| } |
| |
| TEST_F(WebViewTest, TextInputAction) { |
| TestInputAction(ui::TextInputAction::kDefault, "enter_key_hint_default.html"); |
| TestInputAction(ui::TextInputAction::kDefault, |
| "enter_key_hint_default_unknown.html"); |
| TestInputAction(ui::TextInputAction::kEnter, "enter_key_hint_enter.html"); |
| TestInputAction(ui::TextInputAction::kGo, "enter_key_hint_go.html"); |
| TestInputAction(ui::TextInputAction::kDone, "enter_key_hint_done.html"); |
| TestInputAction(ui::TextInputAction::kNext, "enter_key_hint_next.html"); |
| TestInputAction(ui::TextInputAction::kPrevious, |
| "enter_key_hint_previous.html"); |
| TestInputAction(ui::TextInputAction::kSearch, "enter_key_hint_search.html"); |
| TestInputAction(ui::TextInputAction::kSend, "enter_key_hint_send.html"); |
| TestInputAction(ui::TextInputAction::kNext, "enter_key_hint_mixed_case.html"); |
| } |
| |
| TEST_F(WebViewTest, TextInputInfoWithReplacedElements) { |
| std::string url = RegisterMockedHttpURLLoad("div_with_image.html"); |
| url_test_helpers::RegisterMockedURLLoad( |
| ToKURL("http://www.test.com/foo.png"), |
| test::CoreTestDataPath("white-1x1.png")); |
| WebViewImpl* web_view_impl = web_view_helper_.InitializeAndLoad(url); |
| web_view_impl->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| WebTextInputInfo info = web_view_impl->MainFrameImpl() |
| ->GetInputMethodController() |
| ->TextInputInfo(); |
| |
| EXPECT_EQ("foo\xef\xbf\xbc", info.value.Utf8()); |
| } |
| |
| TEST_F(WebViewTest, SetEditableSelectionOffsetsAndTextInputInfo) { |
| RegisterMockedHttpURLLoad("input_field_populated.html"); |
| WebViewImpl* web_view = web_view_helper_.InitializeAndLoad( |
| base_url_ + "input_field_populated.html"); |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| WebLocalFrameImpl* frame = web_view->MainFrameImpl(); |
| WebInputMethodController* active_input_method_controller = |
| frame->GetInputMethodController(); |
| frame->SetEditableSelectionOffsets(5, 13); |
| EXPECT_EQ("56789abc", frame->SelectionAsText()); |
| WebTextInputInfo info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("0123456789abcdefghijklmnopqrstuvwxyz", info.value); |
| EXPECT_EQ(5, info.selection_start); |
| EXPECT_EQ(13, info.selection_end); |
| EXPECT_EQ(-1, info.composition_start); |
| EXPECT_EQ(-1, info.composition_end); |
| |
| RegisterMockedHttpURLLoad("content_editable_populated.html"); |
| web_view = web_view_helper_.InitializeAndLoad( |
| base_url_ + "content_editable_populated.html"); |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| frame = web_view->MainFrameImpl(); |
| active_input_method_controller = frame->GetInputMethodController(); |
| frame->SetEditableSelectionOffsets(8, 19); |
| EXPECT_EQ("89abcdefghi", frame->SelectionAsText()); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("0123456789abcdefghijklmnopqrstuvwxyz", info.value); |
| EXPECT_EQ(8, info.selection_start); |
| EXPECT_EQ(19, info.selection_end); |
| EXPECT_EQ(-1, info.composition_start); |
| EXPECT_EQ(-1, info.composition_end); |
| } |
| |
| // Regression test for crbug.com/663645 |
| TEST_F(WebViewTest, FinishComposingTextDoesNotAssert) { |
| RegisterMockedHttpURLLoad("input_field_default.html"); |
| WebViewImpl* web_view = web_view_helper_.InitializeAndLoad( |
| base_url_ + "input_field_default.html"); |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| |
| WebInputMethodController* active_input_method_controller = |
| web_view->MainFrameImpl() |
| ->FrameWidget() |
| ->GetActiveWebInputMethodController(); |
| |
| // The test requires non-empty composition. |
| std::string composition_text("hello"); |
| WebVector<ui::ImeTextSpan> empty_ime_text_spans; |
| active_input_method_controller->SetComposition( |
| WebString::FromUTF8(composition_text.c_str()), empty_ime_text_spans, |
| WebRange(), 5, 5); |
| |
| // Do arbitrary change to make layout dirty. |
| Document& document = *web_view->MainFrameImpl()->GetFrame()->GetDocument(); |
| Element* br = document.CreateRawElement(html_names::kBrTag); |
| document.body()->AppendChild(br); |
| |
| // Should not hit assertion when calling |
| // WebInputMethodController::finishComposingText with non-empty composition |
| // and dirty layout. |
| active_input_method_controller->FinishComposingText( |
| WebInputMethodController::kKeepSelection); |
| } |
| |
| // Regression test for https://crbug.com/873999 |
| TEST_F(WebViewTest, LongPressOutsideInputShouldNotSelectPlaceholderText) { |
| RegisterMockedHttpURLLoad("input_placeholder.html"); |
| WebViewImpl* web_view = |
| web_view_helper_.InitializeAndLoad(base_url_ + "input_placeholder.html"); |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| web_view->MainFrameViewWidget()->Resize(gfx::Size(500, 300)); |
| UpdateAllLifecyclePhases(); |
| RunPendingTasks(); |
| |
| WebString input_id = WebString::FromUTF8("input"); |
| |
| // Focus in input. |
| EXPECT_TRUE(TapElementById(WebInputEvent::Type::kGestureTap, input_id)); |
| |
| // Long press below input. |
| WebGestureEvent event(WebInputEvent::Type::kGestureLongPress, |
| WebInputEvent::kNoModifiers, |
| WebInputEvent::GetStaticTimeStampForTests(), |
| WebGestureDevice::kTouchscreen); |
| event.SetPositionInWidget(gfx::PointF(100, 150)); |
| EXPECT_EQ(WebInputEventResult::kHandledSystem, |
| web_view->MainFrameWidget()->HandleInputEvent( |
| WebCoalescedInputEvent(event, ui::LatencyInfo()))); |
| EXPECT_TRUE(web_view->MainFrameImpl()->SelectionAsText().IsEmpty()); |
| } |
| |
| TEST_F(WebViewTest, FinishComposingTextCursorPositionChange) { |
| RegisterMockedHttpURLLoad("input_field_populated.html"); |
| WebViewImpl* web_view = web_view_helper_.InitializeAndLoad( |
| base_url_ + "input_field_populated.html"); |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| |
| // Set up a composition that needs to be committed. |
| std::string composition_text("hello"); |
| |
| WebInputMethodController* active_input_method_controller = |
| web_view->MainFrameImpl() |
| ->FrameWidget() |
| ->GetActiveWebInputMethodController(); |
| WebVector<ui::ImeTextSpan> empty_ime_text_spans; |
| active_input_method_controller->SetComposition( |
| WebString::FromUTF8(composition_text.c_str()), empty_ime_text_spans, |
| WebRange(), 3, 3); |
| |
| WebTextInputInfo info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("hello", info.value.Utf8()); |
| EXPECT_EQ(3, info.selection_start); |
| EXPECT_EQ(3, info.selection_end); |
| EXPECT_EQ(0, info.composition_start); |
| EXPECT_EQ(5, info.composition_end); |
| |
| active_input_method_controller->FinishComposingText( |
| WebInputMethodController::kKeepSelection); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ(3, info.selection_start); |
| EXPECT_EQ(3, info.selection_end); |
| EXPECT_EQ(-1, info.composition_start); |
| EXPECT_EQ(-1, info.composition_end); |
| |
| active_input_method_controller->SetComposition( |
| WebString::FromUTF8(composition_text.c_str()), empty_ime_text_spans, |
| WebRange(), 3, 3); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("helhellolo", info.value.Utf8()); |
| EXPECT_EQ(6, info.selection_start); |
| EXPECT_EQ(6, info.selection_end); |
| EXPECT_EQ(3, info.composition_start); |
| EXPECT_EQ(8, info.composition_end); |
| |
| active_input_method_controller->FinishComposingText( |
| WebInputMethodController::kDoNotKeepSelection); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ(8, info.selection_start); |
| EXPECT_EQ(8, info.selection_end); |
| EXPECT_EQ(-1, info.composition_start); |
| EXPECT_EQ(-1, info.composition_end); |
| } |
| |
| TEST_F(WebViewTest, SetCompositionForNewCaretPositions) { |
| RegisterMockedHttpURLLoad("input_field_populated.html"); |
| WebViewImpl* web_view = web_view_helper_.InitializeAndLoad( |
| base_url_ + "input_field_populated.html"); |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| WebInputMethodController* active_input_method_controller = |
| web_view->MainFrameImpl() |
| ->FrameWidget() |
| ->GetActiveWebInputMethodController(); |
| |
| WebVector<ui::ImeTextSpan> empty_ime_text_spans; |
| |
| active_input_method_controller->CommitText("hello", empty_ime_text_spans, |
| WebRange(), 0); |
| active_input_method_controller->CommitText("world", empty_ime_text_spans, |
| WebRange(), -5); |
| WebTextInputInfo info = active_input_method_controller->TextInputInfo(); |
| |
| EXPECT_EQ("helloworld", info.value.Utf8()); |
| EXPECT_EQ(5, info.selection_start); |
| EXPECT_EQ(5, info.selection_end); |
| EXPECT_EQ(-1, info.composition_start); |
| EXPECT_EQ(-1, info.composition_end); |
| |
| // Set up a composition that needs to be committed. |
| std::string composition_text("ABC"); |
| |
| // Caret is on the left of composing text. |
| active_input_method_controller->SetComposition( |
| WebString::FromUTF8(composition_text.c_str()), empty_ime_text_spans, |
| WebRange(), 0, 0); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("helloABCworld", info.value.Utf8()); |
| EXPECT_EQ(5, info.selection_start); |
| EXPECT_EQ(5, info.selection_end); |
| EXPECT_EQ(5, info.composition_start); |
| EXPECT_EQ(8, info.composition_end); |
| |
| // Caret is on the right of composing text. |
| active_input_method_controller->SetComposition( |
| WebString::FromUTF8(composition_text.c_str()), empty_ime_text_spans, |
| WebRange(), 3, 3); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("helloABCworld", info.value.Utf8()); |
| EXPECT_EQ(8, info.selection_start); |
| EXPECT_EQ(8, info.selection_end); |
| EXPECT_EQ(5, info.composition_start); |
| EXPECT_EQ(8, info.composition_end); |
| |
| // Caret is between composing text and left boundary. |
| active_input_method_controller->SetComposition( |
| WebString::FromUTF8(composition_text.c_str()), empty_ime_text_spans, |
| WebRange(), -2, -2); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("helloABCworld", info.value.Utf8()); |
| EXPECT_EQ(3, info.selection_start); |
| EXPECT_EQ(3, info.selection_end); |
| EXPECT_EQ(5, info.composition_start); |
| EXPECT_EQ(8, info.composition_end); |
| |
| // Caret is between composing text and right boundary. |
| active_input_method_controller->SetComposition( |
| WebString::FromUTF8(composition_text.c_str()), empty_ime_text_spans, |
| WebRange(), 5, 5); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("helloABCworld", info.value.Utf8()); |
| EXPECT_EQ(10, info.selection_start); |
| EXPECT_EQ(10, info.selection_end); |
| EXPECT_EQ(5, info.composition_start); |
| EXPECT_EQ(8, info.composition_end); |
| |
| // Caret is on the left boundary. |
| active_input_method_controller->SetComposition( |
| WebString::FromUTF8(composition_text.c_str()), empty_ime_text_spans, |
| WebRange(), -5, -5); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("helloABCworld", info.value.Utf8()); |
| EXPECT_EQ(0, info.selection_start); |
| EXPECT_EQ(0, info.selection_end); |
| EXPECT_EQ(5, info.composition_start); |
| EXPECT_EQ(8, info.composition_end); |
| |
| // Caret is on the right boundary. |
| active_input_method_controller->SetComposition( |
| WebString::FromUTF8(composition_text.c_str()), empty_ime_text_spans, |
| WebRange(), 8, 8); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("helloABCworld", info.value.Utf8()); |
| EXPECT_EQ(13, info.selection_start); |
| EXPECT_EQ(13, info.selection_end); |
| EXPECT_EQ(5, info.composition_start); |
| EXPECT_EQ(8, info.composition_end); |
| |
| // Caret exceeds the left boundary. |
| active_input_method_controller->SetComposition( |
| WebString::FromUTF8(composition_text.c_str()), empty_ime_text_spans, |
| WebRange(), -100, -100); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("helloABCworld", info.value.Utf8()); |
| EXPECT_EQ(0, info.selection_start); |
| EXPECT_EQ(0, info.selection_end); |
| EXPECT_EQ(5, info.composition_start); |
| EXPECT_EQ(8, info.composition_end); |
| |
| // Caret exceeds the right boundary. |
| active_input_method_controller->SetComposition( |
| WebString::FromUTF8(composition_text.c_str()), empty_ime_text_spans, |
| WebRange(), 100, 100); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("helloABCworld", info.value.Utf8()); |
| EXPECT_EQ(13, info.selection_start); |
| EXPECT_EQ(13, info.selection_end); |
| EXPECT_EQ(5, info.composition_start); |
| EXPECT_EQ(8, info.composition_end); |
| } |
| |
| TEST_F(WebViewTest, SetCompositionWithEmptyText) { |
| RegisterMockedHttpURLLoad("input_field_populated.html"); |
| WebViewImpl* web_view = web_view_helper_.InitializeAndLoad( |
| base_url_ + "input_field_populated.html"); |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| WebInputMethodController* active_input_method_controller = |
| web_view->MainFrameImpl() |
| ->FrameWidget() |
| ->GetActiveWebInputMethodController(); |
| |
| WebVector<ui::ImeTextSpan> empty_ime_text_spans; |
| |
| active_input_method_controller->CommitText("hello", empty_ime_text_spans, |
| WebRange(), 0); |
| WebTextInputInfo info = active_input_method_controller->TextInputInfo(); |
| |
| EXPECT_EQ("hello", info.value.Utf8()); |
| EXPECT_EQ(5, info.selection_start); |
| EXPECT_EQ(5, info.selection_end); |
| EXPECT_EQ(-1, info.composition_start); |
| EXPECT_EQ(-1, info.composition_end); |
| |
| active_input_method_controller->SetComposition( |
| WebString::FromUTF8(""), empty_ime_text_spans, WebRange(), 0, 0); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("hello", info.value.Utf8()); |
| EXPECT_EQ(5, info.selection_start); |
| EXPECT_EQ(5, info.selection_end); |
| EXPECT_EQ(-1, info.composition_start); |
| EXPECT_EQ(-1, info.composition_end); |
| |
| active_input_method_controller->SetComposition( |
| WebString::FromUTF8(""), empty_ime_text_spans, WebRange(), -2, -2); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("hello", info.value.Utf8()); |
| EXPECT_EQ(3, info.selection_start); |
| EXPECT_EQ(3, info.selection_end); |
| EXPECT_EQ(-1, info.composition_start); |
| EXPECT_EQ(-1, info.composition_end); |
| } |
| |
| TEST_F(WebViewTest, CommitTextForNewCaretPositions) { |
| RegisterMockedHttpURLLoad("input_field_populated.html"); |
| WebViewImpl* web_view = web_view_helper_.InitializeAndLoad( |
| base_url_ + "input_field_populated.html"); |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| WebInputMethodController* active_input_method_controller = |
| web_view->MainFrameImpl() |
| ->FrameWidget() |
| ->GetActiveWebInputMethodController(); |
| |
| WebVector<ui::ImeTextSpan> empty_ime_text_spans; |
| |
| // Caret is on the left of composing text. |
| active_input_method_controller->CommitText("ab", empty_ime_text_spans, |
| WebRange(), -2); |
| WebTextInputInfo info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("ab", info.value.Utf8()); |
| EXPECT_EQ(0, info.selection_start); |
| EXPECT_EQ(0, info.selection_end); |
| EXPECT_EQ(-1, info.composition_start); |
| EXPECT_EQ(-1, info.composition_end); |
| |
| // Caret is on the right of composing text. |
| active_input_method_controller->CommitText("c", empty_ime_text_spans, |
| WebRange(), 1); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("cab", info.value.Utf8()); |
| EXPECT_EQ(2, info.selection_start); |
| EXPECT_EQ(2, info.selection_end); |
| EXPECT_EQ(-1, info.composition_start); |
| EXPECT_EQ(-1, info.composition_end); |
| |
| // Caret is on the left boundary. |
| active_input_method_controller->CommitText("def", empty_ime_text_spans, |
| WebRange(), -5); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("cadefb", info.value.Utf8()); |
| EXPECT_EQ(0, info.selection_start); |
| EXPECT_EQ(0, info.selection_end); |
| EXPECT_EQ(-1, info.composition_start); |
| EXPECT_EQ(-1, info.composition_end); |
| |
| // Caret is on the right boundary. |
| active_input_method_controller->CommitText("g", empty_ime_text_spans, |
| WebRange(), 6); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("gcadefb", info.value.Utf8()); |
| EXPECT_EQ(7, info.selection_start); |
| EXPECT_EQ(7, info.selection_end); |
| EXPECT_EQ(-1, info.composition_start); |
| EXPECT_EQ(-1, info.composition_end); |
| |
| // Caret exceeds the left boundary. |
| active_input_method_controller->CommitText("hi", empty_ime_text_spans, |
| WebRange(), -100); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("gcadefbhi", info.value.Utf8()); |
| EXPECT_EQ(0, info.selection_start); |
| EXPECT_EQ(0, info.selection_end); |
| EXPECT_EQ(-1, info.composition_start); |
| EXPECT_EQ(-1, info.composition_end); |
| |
| // Caret exceeds the right boundary. |
| active_input_method_controller->CommitText("jk", empty_ime_text_spans, |
| WebRange(), 100); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("jkgcadefbhi", info.value.Utf8()); |
| EXPECT_EQ(11, info.selection_start); |
| EXPECT_EQ(11, info.selection_end); |
| EXPECT_EQ(-1, info.composition_start); |
| EXPECT_EQ(-1, info.composition_end); |
| } |
| |
| TEST_F(WebViewTest, CommitTextWhileComposing) { |
| RegisterMockedHttpURLLoad("input_field_populated.html"); |
| WebViewImpl* web_view = web_view_helper_.InitializeAndLoad( |
| base_url_ + "input_field_populated.html"); |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| WebInputMethodController* active_input_method_controller = |
| web_view->MainFrameImpl() |
| ->FrameWidget() |
| ->GetActiveWebInputMethodController(); |
| |
| WebVector<ui::ImeTextSpan> empty_ime_text_spans; |
| active_input_method_controller->SetComposition( |
| WebString::FromUTF8("abc"), empty_ime_text_spans, WebRange(), 0, 0); |
| WebTextInputInfo info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("abc", info.value.Utf8()); |
| EXPECT_EQ(0, info.selection_start); |
| EXPECT_EQ(0, info.selection_end); |
| EXPECT_EQ(0, info.composition_start); |
| EXPECT_EQ(3, info.composition_end); |
| |
| // Deletes ongoing composition, inserts the specified text and moves the |
| // caret. |
| active_input_method_controller->CommitText("hello", empty_ime_text_spans, |
| WebRange(), -2); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("hello", info.value.Utf8()); |
| EXPECT_EQ(3, info.selection_start); |
| EXPECT_EQ(3, info.selection_end); |
| EXPECT_EQ(-1, info.composition_start); |
| EXPECT_EQ(-1, info.composition_end); |
| |
| active_input_method_controller->SetComposition( |
| WebString::FromUTF8("abc"), empty_ime_text_spans, WebRange(), 0, 0); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("helabclo", info.value.Utf8()); |
| EXPECT_EQ(3, info.selection_start); |
| EXPECT_EQ(3, info.selection_end); |
| EXPECT_EQ(3, info.composition_start); |
| EXPECT_EQ(6, info.composition_end); |
| |
| // Deletes ongoing composition and moves the caret. |
| active_input_method_controller->CommitText("", empty_ime_text_spans, |
| WebRange(), 2); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("hello", info.value.Utf8()); |
| EXPECT_EQ(5, info.selection_start); |
| EXPECT_EQ(5, info.selection_end); |
| EXPECT_EQ(-1, info.composition_start); |
| EXPECT_EQ(-1, info.composition_end); |
| |
| // Inserts the specified text and moves the caret. |
| active_input_method_controller->CommitText("world", empty_ime_text_spans, |
| WebRange(), -5); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("helloworld", info.value.Utf8()); |
| EXPECT_EQ(5, info.selection_start); |
| EXPECT_EQ(5, info.selection_end); |
| EXPECT_EQ(-1, info.composition_start); |
| EXPECT_EQ(-1, info.composition_end); |
| |
| // Only moves the caret. |
| active_input_method_controller->CommitText("", empty_ime_text_spans, |
| WebRange(), 5); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("helloworld", info.value.Utf8()); |
| EXPECT_EQ(10, info.selection_start); |
| EXPECT_EQ(10, info.selection_end); |
| EXPECT_EQ(-1, info.composition_start); |
| EXPECT_EQ(-1, info.composition_end); |
| } |
| |
| TEST_F(WebViewTest, FinishCompositionDoesNotRevealSelection) { |
| RegisterMockedHttpURLLoad("form_with_input.html"); |
| WebViewImpl* web_view = |
| web_view_helper_.InitializeAndLoad(base_url_ + "form_with_input.html"); |
| web_view->MainFrameViewWidget()->Resize(gfx::Size(800, 600)); |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| EXPECT_EQ(0, web_view->MainFrameImpl()->GetScrollOffset().width); |
| EXPECT_EQ(0, web_view->MainFrameImpl()->GetScrollOffset().height); |
| |
| // Set up a composition from existing text that needs to be committed. |
| Vector<ImeTextSpan> empty_ime_text_spans; |
| WebLocalFrameImpl* frame = web_view->MainFrameImpl(); |
| frame->GetFrame()->GetInputMethodController().SetCompositionFromExistingText( |
| empty_ime_text_spans, 0, 3); |
| |
| // Scroll the input field out of the viewport. |
| Element* element = static_cast<Element*>( |
| web_view->MainFrameImpl()->GetDocument().GetElementById("btn")); |
| element->scrollIntoView(); |
| float offset_height = web_view->MainFrameImpl()->GetScrollOffset().height; |
| EXPECT_EQ(0, web_view->MainFrameImpl()->GetScrollOffset().width); |
| EXPECT_LT(0, offset_height); |
| |
| WebTextInputInfo info = frame->GetInputMethodController()->TextInputInfo(); |
| EXPECT_EQ("hello", info.value.Utf8()); |
| |
| // Verify that the input field is not scrolled back into the viewport. |
| frame->FrameWidget() |
| ->GetActiveWebInputMethodController() |
| ->FinishComposingText(WebInputMethodController::kDoNotKeepSelection); |
| EXPECT_EQ(0, web_view->MainFrameImpl()->GetScrollOffset().width); |
| EXPECT_EQ(offset_height, web_view->MainFrameImpl()->GetScrollOffset().height); |
| } |
| |
| TEST_F(WebViewTest, InsertNewLinePlacementAfterFinishComposingText) { |
| RegisterMockedHttpURLLoad("text_area_populated.html"); |
| WebViewImpl* web_view = web_view_helper_.InitializeAndLoad( |
| base_url_ + "text_area_populated.html"); |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| |
| WebVector<ui::ImeTextSpan> empty_ime_text_spans; |
| |
| WebLocalFrameImpl* frame = web_view->MainFrameImpl(); |
| WebInputMethodController* active_input_method_controller = |
| frame->GetInputMethodController(); |
| frame->SetEditableSelectionOffsets(4, 4); |
| frame->SetCompositionFromExistingText(8, 12, empty_ime_text_spans); |
| |
| WebTextInputInfo info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("0123456789abcdefghijklmnopqrstuvwxyz", info.value.Utf8()); |
| EXPECT_EQ(4, info.selection_start); |
| EXPECT_EQ(4, info.selection_end); |
| EXPECT_EQ(8, info.composition_start); |
| EXPECT_EQ(12, info.composition_end); |
| |
| active_input_method_controller->FinishComposingText( |
| WebInputMethodController::kKeepSelection); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ(4, info.selection_start); |
| EXPECT_EQ(4, info.selection_end); |
| EXPECT_EQ(-1, info.composition_start); |
| EXPECT_EQ(-1, info.composition_end); |
| |
| std::string composition_text("\n"); |
| active_input_method_controller->CommitText( |
| WebString::FromUTF8(composition_text.c_str()), empty_ime_text_spans, |
| WebRange(), 0); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ(5, info.selection_start); |
| EXPECT_EQ(5, info.selection_end); |
| EXPECT_EQ(-1, info.composition_start); |
| EXPECT_EQ(-1, info.composition_end); |
| EXPECT_EQ("0123\n456789abcdefghijklmnopqrstuvwxyz", info.value.Utf8()); |
| } |
| |
| TEST_F(WebViewTest, ExtendSelectionAndDelete) { |
| RegisterMockedHttpURLLoad("input_field_populated.html"); |
| WebViewImpl* web_view = web_view_helper_.InitializeAndLoad( |
| base_url_ + "input_field_populated.html"); |
| WebLocalFrameImpl* frame = web_view->MainFrameImpl(); |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| frame->SetEditableSelectionOffsets(10, 10); |
| frame->ExtendSelectionAndDelete(5, 8); |
| WebInputMethodController* active_input_method_controller = |
| frame->GetInputMethodController(); |
| WebTextInputInfo info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("01234ijklmnopqrstuvwxyz", info.value.Utf8()); |
| EXPECT_EQ(5, info.selection_start); |
| EXPECT_EQ(5, info.selection_end); |
| frame->ExtendSelectionAndDelete(10, 0); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("ijklmnopqrstuvwxyz", info.value.Utf8()); |
| } |
| |
| TEST_F(WebViewTest, DeleteSurroundingText) { |
| RegisterMockedHttpURLLoad("input_field_populated.html"); |
| WebViewImpl* web_view = web_view_helper_.InitializeAndLoad( |
| base_url_ + "input_field_populated.html"); |
| auto* frame = To<WebLocalFrameImpl>(web_view->MainFrame()); |
| WebInputMethodController* active_input_method_controller = |
| frame->GetInputMethodController(); |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| |
| frame->SetEditableSelectionOffsets(10, 10); |
| frame->DeleteSurroundingText(5, 8); |
| WebTextInputInfo info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("01234ijklmnopqrstuvwxyz", info.value.Utf8()); |
| EXPECT_EQ(5, info.selection_start); |
| EXPECT_EQ(5, info.selection_end); |
| |
| frame->SetEditableSelectionOffsets(5, 10); |
| frame->DeleteSurroundingText(3, 5); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("01ijklmstuvwxyz", info.value.Utf8()); |
| EXPECT_EQ(2, info.selection_start); |
| EXPECT_EQ(7, info.selection_end); |
| |
| frame->SetEditableSelectionOffsets(5, 5); |
| frame->DeleteSurroundingText(10, 0); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("lmstuvwxyz", info.value.Utf8()); |
| EXPECT_EQ(0, info.selection_start); |
| EXPECT_EQ(0, info.selection_end); |
| |
| frame->DeleteSurroundingText(0, 20); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("", info.value.Utf8()); |
| EXPECT_EQ(0, info.selection_start); |
| EXPECT_EQ(0, info.selection_end); |
| |
| frame->DeleteSurroundingText(10, 10); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("", info.value.Utf8()); |
| EXPECT_EQ(0, info.selection_start); |
| EXPECT_EQ(0, info.selection_end); |
| } |
| |
| TEST_F(WebViewTest, SetCompositionFromExistingText) { |
| RegisterMockedHttpURLLoad("input_field_populated.html"); |
| WebViewImpl* web_view = web_view_helper_.InitializeAndLoad( |
| base_url_ + "input_field_populated.html"); |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| WebVector<ui::ImeTextSpan> ime_text_spans(static_cast<size_t>(1)); |
| ime_text_spans[0] = |
| ui::ImeTextSpan(ui::ImeTextSpan::Type::kComposition, 0, 4, |
| ui::ImeTextSpan::Thickness::kThin, |
| ui::ImeTextSpan::UnderlineStyle::kSolid, 0, 0); |
| WebLocalFrameImpl* frame = web_view->MainFrameImpl(); |
| WebInputMethodController* active_input_method_controller = |
| frame->GetInputMethodController(); |
| frame->SetEditableSelectionOffsets(4, 10); |
| frame->SetCompositionFromExistingText(8, 12, ime_text_spans); |
| WebTextInputInfo info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ(4, info.selection_start); |
| EXPECT_EQ(10, info.selection_end); |
| EXPECT_EQ(8, info.composition_start); |
| EXPECT_EQ(12, info.composition_end); |
| WebVector<ui::ImeTextSpan> empty_ime_text_spans; |
| frame->SetCompositionFromExistingText(0, 0, empty_ime_text_spans); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ(4, info.selection_start); |
| EXPECT_EQ(10, info.selection_end); |
| EXPECT_EQ(-1, info.composition_start); |
| EXPECT_EQ(-1, info.composition_end); |
| } |
| |
| TEST_F(WebViewTest, SetCompositionFromExistingTextInTextArea) { |
| RegisterMockedHttpURLLoad("text_area_populated.html"); |
| WebViewImpl* web_view = web_view_helper_.InitializeAndLoad( |
| base_url_ + "text_area_populated.html"); |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| WebVector<ui::ImeTextSpan> ime_text_spans(static_cast<size_t>(1)); |
| ime_text_spans[0] = |
| ui::ImeTextSpan(ui::ImeTextSpan::Type::kComposition, 0, 4, |
| ui::ImeTextSpan::Thickness::kThin, |
| ui::ImeTextSpan::UnderlineStyle::kSolid, 0, 0); |
| WebLocalFrameImpl* frame = web_view->MainFrameImpl(); |
| WebInputMethodController* active_input_method_controller = |
| frame->FrameWidget()->GetActiveWebInputMethodController(); |
| frame->SetEditableSelectionOffsets(27, 27); |
| std::string new_line_text("\n"); |
| WebVector<ui::ImeTextSpan> empty_ime_text_spans; |
| active_input_method_controller->CommitText( |
| WebString::FromUTF8(new_line_text.c_str()), empty_ime_text_spans, |
| WebRange(), 0); |
| WebTextInputInfo info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("0123456789abcdefghijklmnopq\nrstuvwxyz", info.value.Utf8()); |
| |
| frame->SetEditableSelectionOffsets(31, 31); |
| frame->SetCompositionFromExistingText(30, 34, ime_text_spans); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("0123456789abcdefghijklmnopq\nrstuvwxyz", info.value.Utf8()); |
| EXPECT_EQ(31, info.selection_start); |
| EXPECT_EQ(31, info.selection_end); |
| EXPECT_EQ(30, info.composition_start); |
| EXPECT_EQ(34, info.composition_end); |
| |
| std::string composition_text("yolo"); |
| active_input_method_controller->CommitText( |
| WebString::FromUTF8(composition_text.c_str()), empty_ime_text_spans, |
| WebRange(), 0); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("0123456789abcdefghijklmnopq\nrsyoloxyz", info.value.Utf8()); |
| EXPECT_EQ(34, info.selection_start); |
| EXPECT_EQ(34, info.selection_end); |
| EXPECT_EQ(-1, info.composition_start); |
| EXPECT_EQ(-1, info.composition_end); |
| } |
| |
| TEST_F(WebViewTest, SetCompositionFromExistingTextInRichText) { |
| RegisterMockedHttpURLLoad("content_editable_rich_text.html"); |
| WebViewImpl* web_view = web_view_helper_.InitializeAndLoad( |
| base_url_ + "content_editable_rich_text.html"); |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| WebVector<ui::ImeTextSpan> ime_text_spans(static_cast<size_t>(1)); |
| ime_text_spans[0] = |
| ui::ImeTextSpan(ui::ImeTextSpan::Type::kComposition, 0, 4, |
| ui::ImeTextSpan::Thickness::kThin, |
| ui::ImeTextSpan::UnderlineStyle::kSolid, 0, 0); |
| WebLocalFrameImpl* frame = web_view->MainFrameImpl(); |
| frame->SetEditableSelectionOffsets(1, 1); |
| WebDocument document = web_view->MainFrameImpl()->GetDocument(); |
| EXPECT_FALSE(document.GetElementById("bold").IsNull()); |
| frame->SetCompositionFromExistingText(0, 4, ime_text_spans); |
| EXPECT_FALSE(document.GetElementById("bold").IsNull()); |
| } |
| |
| TEST_F(WebViewTest, SetEditableSelectionOffsetsKeepsComposition) { |
| RegisterMockedHttpURLLoad("input_field_populated.html"); |
| WebViewImpl* web_view = web_view_helper_.InitializeAndLoad( |
| base_url_ + "input_field_populated.html"); |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| |
| std::string composition_text_first("hello "); |
| std::string composition_text_second("world"); |
| WebVector<ui::ImeTextSpan> empty_ime_text_spans; |
| WebInputMethodController* active_input_method_controller = |
| web_view->MainFrameImpl() |
| ->FrameWidget() |
| ->GetActiveWebInputMethodController(); |
| active_input_method_controller->CommitText( |
| WebString::FromUTF8(composition_text_first.c_str()), empty_ime_text_spans, |
| WebRange(), 0); |
| active_input_method_controller->SetComposition( |
| WebString::FromUTF8(composition_text_second.c_str()), |
| empty_ime_text_spans, WebRange(), 5, 5); |
| |
| WebTextInputInfo info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("hello world", info.value.Utf8()); |
| EXPECT_EQ(11, info.selection_start); |
| EXPECT_EQ(11, info.selection_end); |
| EXPECT_EQ(6, info.composition_start); |
| EXPECT_EQ(11, info.composition_end); |
| |
| WebLocalFrameImpl* frame = web_view->MainFrameImpl(); |
| frame->SetEditableSelectionOffsets(6, 6); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("hello world", info.value.Utf8()); |
| EXPECT_EQ(6, info.selection_start); |
| EXPECT_EQ(6, info.selection_end); |
| EXPECT_EQ(6, info.composition_start); |
| EXPECT_EQ(11, info.composition_end); |
| |
| frame->SetEditableSelectionOffsets(8, 8); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("hello world", info.value.Utf8()); |
| EXPECT_EQ(8, info.selection_start); |
| EXPECT_EQ(8, info.selection_end); |
| EXPECT_EQ(6, info.composition_start); |
| EXPECT_EQ(11, info.composition_end); |
| |
| frame->SetEditableSelectionOffsets(11, 11); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("hello world", info.value.Utf8()); |
| EXPECT_EQ(11, info.selection_start); |
| EXPECT_EQ(11, info.selection_end); |
| EXPECT_EQ(6, info.composition_start); |
| EXPECT_EQ(11, info.composition_end); |
| |
| frame->SetEditableSelectionOffsets(6, 11); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("hello world", info.value.Utf8()); |
| EXPECT_EQ(6, info.selection_start); |
| EXPECT_EQ(11, info.selection_end); |
| EXPECT_EQ(6, info.composition_start); |
| EXPECT_EQ(11, info.composition_end); |
| |
| frame->SetEditableSelectionOffsets(2, 2); |
| info = active_input_method_controller->TextInputInfo(); |
| EXPECT_EQ("hello world", info.value.Utf8()); |
| EXPECT_EQ(2, info.selection_start); |
| EXPECT_EQ(2, info.selection_end); |
| // Composition range should be reset by browser process or keyboard apps. |
| EXPECT_EQ(6, info.composition_start); |
| EXPECT_EQ(11, info.composition_end); |
| } |
| |
| TEST_F(WebViewTest, IsSelectionAnchorFirst) { |
| // TODO(xidachen): crbug.com/1150389, Make this test work with the feature. |
| if (RuntimeEnabledFeatures::FractionalScrollOffsetsEnabled()) |
| return; |
| RegisterMockedHttpURLLoad("input_field_populated.html"); |
| WebViewImpl* web_view = web_view_helper_.InitializeAndLoad( |
| base_url_ + "input_field_populated.html"); |
| WebLocalFrame* frame = web_view->MainFrameImpl(); |
| |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| frame->SetEditableSelectionOffsets(4, 10); |
| EXPECT_TRUE(frame->IsSelectionAnchorFirst()); |
| gfx::Rect anchor; |
| gfx::Rect focus; |
| web_view->MainFrameViewWidget()->CalculateSelectionBounds(anchor, focus); |
| frame->SelectRange(focus.origin(), anchor.origin()); |
| EXPECT_FALSE(frame->IsSelectionAnchorFirst()); |
| } |
| |
| TEST_F( |
| WebViewTest, |
| MoveFocusToNextFocusableElementInFormWithKeyEventListenersAndNonEditableElements) { |
| const std::string test_file = |
| "advance_focus_in_form_with_key_event_listeners.html"; |
| RegisterMockedHttpURLLoad(test_file); |
| WebViewImpl* web_view = |
| web_view_helper_.InitializeAndLoad(base_url_ + test_file); |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| Document* document = web_view->MainFrameImpl()->GetFrame()->GetDocument(); |
| WebInputMethodController* active_input_method_controller = |
| web_view->MainFrameImpl() |
| ->FrameWidget() |
| ->GetActiveWebInputMethodController(); |
| const int default_text_input_flags = kWebTextInputFlagNone; |
| |
| struct FocusedElement { |
| AtomicString element_id; |
| int next_previous_flags; |
| } focused_elements[] = { |
| {"input1", |
| default_text_input_flags | kWebTextInputFlagHaveNextFocusableElement}, |
| {"contenteditable1", kWebTextInputFlagHaveNextFocusableElement | |
| kWebTextInputFlagHavePreviousFocusableElement}, |
| {"input2", default_text_input_flags | |
| kWebTextInputFlagHaveNextFocusableElement | |
| kWebTextInputFlagHavePreviousFocusableElement}, |
| {"textarea1", default_text_input_flags | |
| kWebTextInputFlagHaveNextFocusableElement | |
| kWebTextInputFlagHavePreviousFocusableElement}, |
| {"input3", default_text_input_flags | |
| kWebTextInputFlagHaveNextFocusableElement | |
| kWebTextInputFlagHavePreviousFocusableElement}, |
| {"textarea2", default_text_input_flags | |
| kWebTextInputFlagHavePreviousFocusableElement}, |
| }; |
| |
| // Forward Navigation in form1 with NEXT |
| Element* input1 = document->getElementById("input1"); |
| input1->focus(); |
| Element* current_focus = nullptr; |
| Element* next_focus = nullptr; |
| int next_previous_flags; |
| for (size_t i = 0; i < base::size(focused_elements); ++i) { |
| current_focus = document->getElementById(focused_elements[i].element_id); |
| EXPECT_EQ(current_focus, document->FocusedElement()); |
| next_previous_flags = |
| active_input_method_controller->ComputeWebTextInputNextPreviousFlags(); |
| EXPECT_EQ(focused_elements[i].next_previous_flags, next_previous_flags); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| current_focus, mojom::blink::FocusType::kForward); |
| if (next_focus) { |
| EXPECT_EQ(next_focus->GetIdAttribute(), |
| focused_elements[i + 1].element_id); |
| } |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kForward); |
| } |
| // Now focus will stay on previous focus itself, because it has no next |
| // element. |
| EXPECT_EQ(current_focus, document->FocusedElement()); |
| |
| // Backward Navigation in form1 with PREVIOUS |
| for (size_t i = base::size(focused_elements); i-- > 0;) { |
| current_focus = document->getElementById(focused_elements[i].element_id); |
| EXPECT_EQ(current_focus, document->FocusedElement()); |
| next_previous_flags = |
| active_input_method_controller->ComputeWebTextInputNextPreviousFlags(); |
| EXPECT_EQ(focused_elements[i].next_previous_flags, next_previous_flags); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| current_focus, mojom::blink::FocusType::kBackward); |
| if (next_focus) { |
| EXPECT_EQ(next_focus->GetIdAttribute(), |
| focused_elements[i - 1].element_id); |
| } |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kBackward); |
| } |
| // Now focus will stay on previous focus itself, because it has no previous |
| // element. |
| EXPECT_EQ(current_focus, document->FocusedElement()); |
| |
| // Setting a non editable element as focus in form1, and ensuring editable |
| // navigation is fine in forward and backward. |
| Element* button1 = document->getElementById("button1"); |
| button1->focus(); |
| next_previous_flags = |
| active_input_method_controller->ComputeWebTextInputNextPreviousFlags(); |
| EXPECT_EQ(kWebTextInputFlagHaveNextFocusableElement | |
| kWebTextInputFlagHavePreviousFocusableElement, |
| next_previous_flags); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| button1, mojom::blink::FocusType::kForward); |
| EXPECT_EQ(next_focus->GetIdAttribute(), "contenteditable1"); |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kForward); |
| Element* content_editable1 = document->getElementById("contenteditable1"); |
| EXPECT_EQ(content_editable1, document->FocusedElement()); |
| button1->focus(); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| button1, mojom::blink::FocusType::kBackward); |
| EXPECT_EQ(next_focus->GetIdAttribute(), "input1"); |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kBackward); |
| EXPECT_EQ(input1, document->FocusedElement()); |
| |
| Element* anchor1 = document->getElementById("anchor1"); |
| anchor1->focus(); |
| next_previous_flags = |
| active_input_method_controller->ComputeWebTextInputNextPreviousFlags(); |
| // No Next/Previous element for elements outside form. |
| EXPECT_EQ(0, next_previous_flags); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| anchor1, mojom::blink::FocusType::kForward); |
| EXPECT_EQ(next_focus, nullptr); |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kForward); |
| // Since anchor is not a form control element, next/previous element will |
| // be null, hence focus will stay same as it is. |
| EXPECT_EQ(anchor1, document->FocusedElement()); |
| |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| anchor1, mojom::blink::FocusType::kBackward); |
| EXPECT_EQ(next_focus, nullptr); |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kBackward); |
| EXPECT_EQ(anchor1, document->FocusedElement()); |
| |
| // Navigation of elements which is not part of any forms. |
| Element* text_area3 = document->getElementById("textarea3"); |
| text_area3->focus(); |
| next_previous_flags = |
| active_input_method_controller->ComputeWebTextInputNextPreviousFlags(); |
| // No Next/Previous element for elements outside form. |
| EXPECT_EQ(default_text_input_flags, next_previous_flags); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| text_area3, mojom::blink::FocusType::kForward); |
| EXPECT_EQ(next_focus, nullptr); |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kForward); |
| // No Next/Previous element to this element because it's not part of any |
| // form. Hence focus won't change wrt NEXT/PREVIOUS. |
| EXPECT_EQ(text_area3, document->FocusedElement()); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| text_area3, mojom::blink::FocusType::kBackward); |
| EXPECT_EQ(next_focus, nullptr); |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kBackward); |
| EXPECT_EQ(text_area3, document->FocusedElement()); |
| |
| // Navigation from an element which is part of a form but not an editable |
| // element. |
| Element* button2 = document->getElementById("button2"); |
| button2->focus(); |
| next_previous_flags = |
| active_input_method_controller->ComputeWebTextInputNextPreviousFlags(); |
| // No Next element for this element, due to last element outside the form. |
| EXPECT_EQ(kWebTextInputFlagHavePreviousFocusableElement, next_previous_flags); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| button2, mojom::blink::FocusType::kForward); |
| EXPECT_EQ(next_focus, nullptr); |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kForward); |
| // No Next element to this element because it's not part of any form. |
| // Hence focus won't change wrt NEXT. |
| EXPECT_EQ(button2, document->FocusedElement()); |
| Element* text_area2 = document->getElementById("textarea2"); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| button2, mojom::blink::FocusType::kBackward); |
| EXPECT_EQ(next_focus, text_area2); |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kBackward); |
| // Since button is a form control element from form1, ensuring focus is set |
| // at correct position. |
| EXPECT_EQ(text_area2, document->FocusedElement()); |
| |
| Element* content_editable2 = document->getElementById("contenteditable2"); |
| document->SetFocusedElement( |
| content_editable2, FocusParams(SelectionBehaviorOnFocus::kNone, |
| mojom::blink::FocusType::kNone, nullptr)); |
| next_previous_flags = |
| active_input_method_controller->ComputeWebTextInputNextPreviousFlags(); |
| // No Next/Previous element for elements outside form. |
| EXPECT_EQ(0, next_previous_flags); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| content_editable2, mojom::blink::FocusType::kForward); |
| EXPECT_EQ(next_focus, nullptr); |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kForward); |
| // No Next/Previous element to this element because it's not part of any |
| // form. Hence focus won't change wrt NEXT/PREVIOUS. |
| EXPECT_EQ(content_editable2, document->FocusedElement()); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| content_editable2, mojom::blink::FocusType::kBackward); |
| EXPECT_EQ(next_focus, nullptr); |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kBackward); |
| EXPECT_EQ(content_editable2, document->FocusedElement()); |
| |
| // Navigation of elements which is having invalid form attribute and hence |
| // not part of any forms. |
| Element* text_area4 = document->getElementById("textarea4"); |
| text_area4->focus(); |
| next_previous_flags = |
| active_input_method_controller->ComputeWebTextInputNextPreviousFlags(); |
| // No Next/Previous element for elements which is having invalid form |
| // attribute. |
| EXPECT_EQ(default_text_input_flags, next_previous_flags); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| text_area4, mojom::blink::FocusType::kForward); |
| EXPECT_EQ(next_focus, nullptr); |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kForward); |
| // No Next/Previous element to this element because it's not part of any |
| // form. Hence focus won't change wrt NEXT/PREVIOUS. |
| EXPECT_EQ(text_area4, document->FocusedElement()); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| text_area4, mojom::blink::FocusType::kBackward); |
| EXPECT_EQ(next_focus, nullptr); |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kBackward); |
| EXPECT_EQ(text_area4, document->FocusedElement()); |
| |
| web_view_helper_.Reset(); |
| } |
| |
| TEST_F( |
| WebViewTest, |
| MoveFocusToNextFocusableElementInFormWithNonEditableNonFormControlElements) { |
| const std::string test_file = |
| "advance_focus_in_form_with_key_event_listeners.html"; |
| RegisterMockedHttpURLLoad(test_file); |
| WebViewImpl* web_view = |
| web_view_helper_.InitializeAndLoad(base_url_ + test_file); |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| Document* document = web_view->MainFrameImpl()->GetFrame()->GetDocument(); |
| WebInputMethodController* active_input_method_controller = |
| web_view->MainFrameImpl() |
| ->FrameWidget() |
| ->GetActiveWebInputMethodController(); |
| const int default_text_input_flags = kWebTextInputFlagNone; |
| |
| struct FocusedElement { |
| const char* element_id; |
| int next_previous_flags; |
| } focused_elements[] = { |
| {"textarea5", |
| default_text_input_flags | kWebTextInputFlagHaveNextFocusableElement}, |
| {"input4", default_text_input_flags | |
| kWebTextInputFlagHaveNextFocusableElement | |
| kWebTextInputFlagHavePreviousFocusableElement}, |
| {"contenteditable3", kWebTextInputFlagHaveNextFocusableElement | |
| kWebTextInputFlagHavePreviousFocusableElement}, |
| {"input5", kWebTextInputFlagHavePreviousFocusableElement}, |
| }; |
| |
| // Forward Navigation in form2 with NEXT |
| Element* text_area5 = document->getElementById("textarea5"); |
| text_area5->focus(); |
| Element* current_focus = nullptr; |
| Element* next_focus = nullptr; |
| int next_previous_flags; |
| for (size_t i = 0; i < base::size(focused_elements); ++i) { |
| current_focus = document->getElementById(focused_elements[i].element_id); |
| EXPECT_EQ(current_focus, document->FocusedElement()); |
| next_previous_flags = |
| active_input_method_controller->ComputeWebTextInputNextPreviousFlags(); |
| EXPECT_EQ(focused_elements[i].next_previous_flags, next_previous_flags); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| current_focus, mojom::blink::FocusType::kForward); |
| if (next_focus) { |
| EXPECT_EQ(next_focus->GetIdAttribute(), |
| focused_elements[i + 1].element_id); |
| } |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kForward); |
| } |
| // Now focus will stay on previous focus itself, because it has no next |
| // element. |
| EXPECT_EQ(current_focus, document->FocusedElement()); |
| |
| // Backward Navigation in form1 with PREVIOUS |
| for (size_t i = base::size(focused_elements); i-- > 0;) { |
| current_focus = document->getElementById(focused_elements[i].element_id); |
| EXPECT_EQ(current_focus, document->FocusedElement()); |
| next_previous_flags = |
| active_input_method_controller->ComputeWebTextInputNextPreviousFlags(); |
| EXPECT_EQ(focused_elements[i].next_previous_flags, next_previous_flags); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| current_focus, mojom::blink::FocusType::kBackward); |
| if (next_focus) { |
| EXPECT_EQ(next_focus->GetIdAttribute(), |
| focused_elements[i - 1].element_id); |
| } |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kBackward); |
| } |
| // Now focus will stay on previous focus itself, because it has no previous |
| // element. |
| EXPECT_EQ(current_focus, document->FocusedElement()); |
| |
| // Setting a non editable element as focus in form1, and ensuring editable |
| // navigation is fine in forward and backward. |
| Element* anchor2 = document->getElementById("anchor2"); |
| anchor2->focus(); |
| next_previous_flags = |
| active_input_method_controller->ComputeWebTextInputNextPreviousFlags(); |
| // No Next/Previous element for non-form control elements inside form. |
| EXPECT_EQ(0, next_previous_flags); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| anchor2, mojom::blink::FocusType::kForward); |
| EXPECT_EQ(next_focus, nullptr); |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kForward); |
| // Since anchor is not a form control element, next/previous element will |
| // be null, hence focus will stay same as it is. |
| EXPECT_EQ(anchor2, document->FocusedElement()); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| anchor2, mojom::blink::FocusType::kBackward); |
| EXPECT_EQ(next_focus, nullptr); |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kBackward); |
| EXPECT_EQ(anchor2, document->FocusedElement()); |
| |
| web_view_helper_.Reset(); |
| } |
| |
| TEST_F(WebViewTest, MoveFocusToNextFocusableElementInFormWithTabIndexElements) { |
| const std::string test_file = |
| "advance_focus_in_form_with_tabindex_elements.html"; |
| RegisterMockedHttpURLLoad(test_file); |
| WebViewImpl* web_view = |
| web_view_helper_.InitializeAndLoad(base_url_ + test_file); |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| Document* document = web_view->MainFrameImpl()->GetFrame()->GetDocument(); |
| WebInputMethodController* active_input_method_controller = |
| web_view->MainFrameImpl() |
| ->FrameWidget() |
| ->GetActiveWebInputMethodController(); |
| const int default_text_input_flags = kWebTextInputFlagNone; |
| |
| struct FocusedElement { |
| const char* element_id; |
| int next_previous_flags; |
| } focused_elements[] = { |
| {"textarea6", |
| default_text_input_flags | kWebTextInputFlagHaveNextFocusableElement}, |
| {"input5", default_text_input_flags | |
| kWebTextInputFlagHaveNextFocusableElement | |
| kWebTextInputFlagHavePreviousFocusableElement}, |
| {"contenteditable4", kWebTextInputFlagHaveNextFocusableElement | |
| kWebTextInputFlagHavePreviousFocusableElement}, |
| {"input6", default_text_input_flags | |
| kWebTextInputFlagHavePreviousFocusableElement}, |
| }; |
| |
| // Forward Navigation in form with NEXT which has tabindex attribute |
| // which differs visual order. |
| Element* text_area6 = document->getElementById("textarea6"); |
| text_area6->focus(); |
| Element* current_focus = nullptr; |
| Element* next_focus = nullptr; |
| int next_previous_flags; |
| for (size_t i = 0; i < base::size(focused_elements); ++i) { |
| current_focus = document->getElementById(focused_elements[i].element_id); |
| EXPECT_EQ(current_focus, document->FocusedElement()); |
| next_previous_flags = |
| active_input_method_controller->ComputeWebTextInputNextPreviousFlags(); |
| EXPECT_EQ(focused_elements[i].next_previous_flags, next_previous_flags); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| current_focus, mojom::blink::FocusType::kForward); |
| if (next_focus) { |
| EXPECT_EQ(next_focus->GetIdAttribute(), |
| focused_elements[i + 1].element_id); |
| } |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kForward); |
| } |
| // No next editable element which is focusable with proper tab index, hence |
| // staying on previous focus. |
| EXPECT_EQ(current_focus, document->FocusedElement()); |
| |
| // Backward Navigation in form with PREVIOUS which has tabindex attribute |
| // which differs visual order. |
| for (size_t i = base::size(focused_elements); i-- > 0;) { |
| current_focus = document->getElementById(focused_elements[i].element_id); |
| EXPECT_EQ(current_focus, document->FocusedElement()); |
| next_previous_flags = |
| active_input_method_controller->ComputeWebTextInputNextPreviousFlags(); |
| EXPECT_EQ(focused_elements[i].next_previous_flags, next_previous_flags); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| current_focus, mojom::blink::FocusType::kBackward); |
| if (next_focus) { |
| EXPECT_EQ(next_focus->GetIdAttribute(), |
| focused_elements[i - 1].element_id); |
| } |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kBackward); |
| } |
| // Now focus will stay on previous focus itself, because it has no previous |
| // element. |
| EXPECT_EQ(current_focus, document->FocusedElement()); |
| |
| // Setting an element which has invalid tabindex and ensuring it is not |
| // modifying further navigation. |
| Element* content_editable5 = document->getElementById("contenteditable5"); |
| content_editable5->focus(); |
| Element* input6 = document->getElementById("input6"); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| content_editable5, mojom::blink::FocusType::kForward); |
| EXPECT_EQ(next_focus, input6); |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kForward); |
| EXPECT_EQ(input6, document->FocusedElement()); |
| content_editable5->focus(); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| content_editable5, mojom::blink::FocusType::kBackward); |
| EXPECT_EQ(next_focus, text_area6); |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kBackward); |
| EXPECT_EQ(text_area6, document->FocusedElement()); |
| |
| web_view_helper_.Reset(); |
| } |
| |
| TEST_F(WebViewTest, |
| MoveFocusToNextFocusableElementInFormWithDisabledAndReadonlyElements) { |
| const std::string test_file = |
| "advance_focus_in_form_with_disabled_and_readonly_elements.html"; |
| RegisterMockedHttpURLLoad(test_file); |
| WebViewImpl* web_view = |
| web_view_helper_.InitializeAndLoad(base_url_ + test_file); |
| web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false); |
| Document* document = web_view->MainFrameImpl()->GetFrame()->GetDocument(); |
| WebInputMethodController* active_input_method_controller = |
| web_view->MainFrameImpl() |
| ->FrameWidget() |
| ->GetActiveWebInputMethodController(); |
| |
| struct FocusedElement { |
| const char* element_id; |
| int next_previous_flags; |
| } focused_elements[] = { |
| {"contenteditable6", kWebTextInputFlagHaveNextFocusableElement}, |
| {"contenteditable7", kWebTextInputFlagHavePreviousFocusableElement}, |
| }; |
| // Forward Navigation in form with NEXT which has has disabled/enabled |
| // elements which will gets skipped during navigation. |
| Element* content_editable6 = document->getElementById("contenteditable6"); |
| content_editable6->focus(); |
| Element* current_focus = nullptr; |
| Element* next_focus = nullptr; |
| int next_previous_flags; |
| for (size_t i = 0; i < base::size(focused_elements); ++i) { |
| current_focus = document->getElementById(focused_elements[i].element_id); |
| EXPECT_EQ(current_focus, document->FocusedElement()); |
| next_previous_flags = |
| active_input_method_controller->ComputeWebTextInputNextPreviousFlags(); |
| EXPECT_EQ(focused_elements[i].next_previous_flags, next_previous_flags); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| current_focus, mojom::blink::FocusType::kForward); |
| if (next_focus) { |
| EXPECT_EQ(next_focus->GetIdAttribute(), |
| focused_elements[i + 1].element_id); |
| } |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kForward); |
| } |
| // No next editable element which is focusable, hence staying on previous |
| // focus. |
| EXPECT_EQ(current_focus, document->FocusedElement()); |
| |
| // Backward Navigation in form with PREVIOUS which has has |
| // disabled/enabled elements which will gets skipped during navigation. |
| for (size_t i = base::size(focused_elements); i-- > 0;) { |
| current_focus = document->getElementById(focused_elements[i].element_id); |
| EXPECT_EQ(current_focus, document->FocusedElement()); |
| next_previous_flags = |
| active_input_method_controller->ComputeWebTextInputNextPreviousFlags(); |
| EXPECT_EQ(focused_elements[i].next_previous_flags, next_previous_flags); |
| next_focus = |
| document->GetPage()->GetFocusController().NextFocusableElementInForm( |
| current_focus, mojom::blink::FocusType::kBackward); |
| if (next_focus) { |
| EXPECT_EQ(next_focus->GetIdAttribute(), |
| focused_elements[i - 1].element_id); |
| } |
| web_view->MainFrameImpl()->GetFrame()->AdvanceFocusInForm( |
| mojom::blink::FocusType::kBackward); |
| } |
| // Now focus will stay on previous focus itself, because it has no previous |
| // element. |
| EXPECT_EQ(current_focus, document->FocusedElement()); |
| |
| web_view_helper_.Reset(); |
| } |
| |
| TEST_F(WebViewTest, ExitingDeviceEmulationResetsPageScale) { |
| RegisterMockedHttpURLLoad("200-by-300.html"); |
| WebViewImpl* web_view_impl = |
| web_view_helper_.InitializeAndLoad(base_url_ + "200-by-300.html"); |
| web_view_impl->MainFrameViewWidget()->Resize(gfx::Size(200, 300)); |
| |
| float page_scale_expected = web_view_impl->PageScaleFactor(); |
| |
| DeviceEmulationParams params; |
| params.screen_type = mojom::EmulatedScreenType::kDesktop; |
| params.device_scale_factor = 0; |
| params.scale = 1; |
| |
| web_view_impl->EnableDeviceEmulation(params); |
| |
| web_view_impl->SetPageScaleFactor(2); |
| |
| web_view_impl->DisableDeviceEmulation(); |
| |
| EXPECT_EQ(page_scale_expected, web_view_impl->PageScaleFactor()); |
| } |
| |
| TEST_F(WebViewTest, HistoryResetScrollAndScaleState) { |
| RegisterMockedHttpURLLoad("200-by-300.html"); |
| WebViewImpl* web_view_impl = |
| web_view_helper_.InitializeAndLoad(base_url_ + "200-by-300.html"); |
| web_view_impl->MainFrameViewWidget()->Resize(gfx::Size(100, 150)); |
| UpdateAllLifecyclePhases(); |
| EXPECT_EQ(0, web_view_impl->MainFrameImpl()->GetScrollOffset().width); |
| EXPECT_EQ(0, web_view_impl->MainFrameImpl()->GetScrollOffset().height); |
| |
| // Make the page scale and scroll with the given paremeters. |
| web_view_impl->SetPageScaleFactor(2.0f); |
| web_view_impl->MainFrameImpl()->SetScrollOffset(WebSize(94, 111)); |
| EXPECT_EQ(2.0f, web_view_impl->PageScaleFactor()); |
| EXPECT_EQ(94, web_view_impl->MainFrameImpl()->GetScrollOffset().width); |
| EXPECT_EQ(111, web_view_impl->MainFrameImpl()->GetScrollOffset().height); |
| auto* main_frame_local = |
| To<LocalFrame>(web_view_impl->GetPage()->MainFrame()); |
| main_frame_local->Loader().SaveScrollState(); |
| EXPECT_EQ(2.0f, main_frame_local->Loader() |
| .GetDocumentLoader() |
| ->GetHistoryItem() |
| ->GetViewState() |
| ->page_scale_factor_); |
| EXPECT_EQ(94, main_frame_local->Loader() |
| .GetDocumentLoader() |
| ->GetHistoryItem() |
| ->GetViewState() |
| ->scroll_offset_.Width()); |
| EXPECT_EQ(111, main_frame_local->Loader() |
| .GetDocumentLoader() |
| ->GetHistoryItem() |
| ->GetViewState() |
| ->scroll_offset_.Height()); |
| |
| // Confirm that resetting the page state resets the saved scroll position. |
| web_view_impl->ResetScrollAndScaleState(); |
| EXPECT_EQ(1.0f, web_view_impl->PageScaleFactor()); |
| EXPECT_EQ(0, web_view_impl->MainFrameImpl()->GetScrollOffset().width); |
| EXPECT_EQ(0, web_view_impl->MainFrameImpl()->GetScrollOffset().height); |
| EXPECT_FALSE(main_frame_local->Loader() |
| .GetDocumentLoader() |
| ->GetHistoryItem() |
| ->GetViewState() |
| .has_value()); |
| } |
| |
| TEST_F(WebViewTest, BackForwardRestoreScroll) { |
| RegisterMockedHttpURLLoad("back_forward_restore_scroll.html"); |
| WebViewImpl* web_view_impl = web_view_helper_.InitializeAndLoad( |
| base_url_ + "back_forward_restore_scroll.html"); |
| web_view_impl->MainFrameViewWidget()->Resize(gfx::Size(640, 480)); |
| web_view_impl->MainFrameWidget()->UpdateAllLifecyclePhases( |
| DocumentUpdateReason::kTest); |
| |
| // Emulate a user scroll |
| web_view_impl->MainFrameImpl()->SetScrollOffset(WebSize(0, 900)); |
| auto* main_frame_local = |
| To<LocalFrame>(web_view_impl->GetPage()->MainFrame()); |
| Persistent<HistoryItem> item1 = |
| main_frame_local->Loader().GetDocumentLoader()->GetHistoryItem(); |
| |
| // Click an anchor |
| FrameLoadRequest request_a( |
| main_frame_local->DomWindow(), |
| ResourceRequest(main_frame_local->GetDocument()->CompleteURL("#a"))); |
| main_frame_local->Loader().StartNavigation(request_a); |
| Persistent<HistoryItem> item2 = |
| main_frame_local->Loader().GetDocumentLoader()->GetHistoryItem(); |
| |
| // Go back, then forward, then back again. |
| main_frame_local->Loader().GetDocumentLoader()->CommitSameDocumentNavigation( |
| item1->Url(), WebFrameLoadType::kBackForward, item1.Get(), |
| ClientRedirectPolicy::kNotClientRedirect, |
| false /* has_transient_user_activation */, nullptr, false /* has_event */, |
| nullptr); |
| main_frame_local->Loader().GetDocumentLoader()->CommitSameDocumentNavigation( |
| item2->Url(), WebFrameLoadType::kBackForward, item2.Get(), |
| ClientRedirectPolicy::kNotClientRedirect, |
| false /* has_transient_user_activation */, nullptr, false /* has_event */, |
| nullptr); |
| main_frame_local->Loader().GetDocumentLoader()->CommitSameDocumentNavigation( |
| item1->Url(), WebFrameLoadType::kBackForward, item1.Get(), |
| ClientRedirectPolicy::kNotClientRedirect, |
| false /* has_transient_user_activation */, nullptr, false /* has_event */, |
| nullptr); |
| web_view_impl->MainFrameWidget()->UpdateAllLifecyclePhases( |
| DocumentUpdateReason::kTest); |
| |
| // Click a different anchor |
| FrameLoadRequest request_b( |
| main_frame_local->DomWindow(), |
| ResourceRequest(main_frame_local->GetDocument()->CompleteURL("#b"))); |
| main_frame_local->Loader().StartNavigation(request_b); |
| Persistent<HistoryItem> item3 = |
| main_frame_local->Loader().GetDocumentLoader()->GetHistoryItem(); |
| web_view_impl->MainFrameWidget()->UpdateAllLifecyclePhases( |
| DocumentUpdateReason::kTest); |
| |
| // Go back, then forward. The scroll position should be properly set on the |
| // forward navigation. |
| main_frame_local->Loader().GetDocumentLoader()->CommitSameDocumentNavigation( |
| item1->Url(), WebFrameLoadType::kBackForward, item1.Get(), |
| ClientRedirectPolicy::kNotClientRedirect, |
| false /* has_transient_user_activation */, nullptr, false /* has_event */, |
| nullptr); |
| |
| main_frame_local->Loader().GetDocumentLoader()->CommitSameDocumentNavigation( |
| item3->Url(), WebFrameLoadType::kBackForward, item3.Get(), |
| ClientRedirectPolicy::kNotClientRedirect, |
| false /* has_transient_user_activation */, nullptr, false /* has_event */, |
| nullptr); |
| // The scroll offset is only applied via invoking the anchor via the main |
| // lifecycle, or a forced layout. |
| // TODO(chrishtr): At the moment, WebLocalFrameImpl::GetScrollOffset() does |
| // not force a layout. Script-exposed scroll offset-reading methods do, |
| // however. It seems wrong not to force a layout. |
| EXPECT_EQ(0, web_view_impl->MainFrameImpl()->GetScrollOffset().width); |
| EXPECT_GT(web_view_impl->MainFrameImpl()->GetScrollOffset().height, 2000); |
| } |
| |
| // Tests that scroll offset modified during fullscreen is preserved when |
| // exiting fullscreen. |
| TEST_F(WebViewTest, FullscreenNoResetScroll) { |
| RegisterMockedHttpURLLoad("fullscreen_style.html"); |
| WebViewImpl* web_view_impl = |
| web_view_helper_.InitializeAndLoad(base_url_ + "fullscreen_style.html"); |
| web_view_impl->MainFrameViewWidget()->Resize(gfx::Size(800, 600)); |
| UpdateAllLifecyclePhases(); |
| |
| // Scroll the page down. |
| web_view_impl->MainFrameImpl()->SetScrollOffset(WebSize(0, 2000)); |
| ASSERT_EQ(2000, web_view_impl->MainFrameImpl()->GetScrollOffset().height); |
| |
| // Enter fullscreen. |
| LocalFrame* frame = web_view_impl->MainFrameImpl()->GetFrame(); |
| Element* element = frame->GetDocument()->documentElement(); |
| LocalFrame::NotifyUserActivation( |
| frame, mojom::UserActivationNotificationType::kTest); |
| Fullscreen::RequestFullscreen(*element); |
| web_view_impl->DidEnterFullscreen(); |
| UpdateAllLifecyclePhases(); |
| |
| // Assert the scroll position on the document element doesn't change. |
| ASSERT_EQ(2000, web_view_impl->MainFrameImpl()->GetScrollOffset().height); |
| |
| web_view_impl->MainFrameImpl()->SetScrollOffset(WebSize(0, 2100)); |
| |
| web_view_impl->DidExitFullscreen(); |
| UpdateAllLifecyclePhases(); |
| |
| EXPECT_EQ(2100, web_view_impl->MainFrameImpl()->GetScrollOffset().height); |
| } |
| |
| // Tests that background color is read from the backdrop on fullscreen. |
| TEST_F(WebViewTest, FullscreenBackgroundColor) { |
| RegisterMockedHttpURLLoad("fullscreen_style.html"); |
| WebViewImpl* web_view_impl = |
| web_view_helper_.InitializeAndLoad(base_url_ + "fullscreen_style.html"); |
| web_view_impl->MainFrameViewWidget()->Resize(gfx::Size(800, 600)); |
| UpdateAllLifecyclePhases(); |
| EXPECT_EQ(SK_ColorWHITE, web_view_impl->BackgroundColor()); |
| |
| // Enter fullscreen. |
| LocalFrame* frame = web_view_impl->MainFrameImpl()->GetFrame(); |
| Element* element = frame->GetDocument()->getElementById("fullscreenElement"); |
| ASSERT_TRUE(element); |
| LocalFrame::NotifyUserActivation( |
| frame, mojom::UserActivationNotificationType::kTest); |
| Fullscreen::RequestFullscreen(*element); |
| web_view_impl->DidEnterFullscreen(); |
| UpdateAllLifecyclePhases(); |
| |
| EXPECT_EQ(SK_ColorYELLOW, web_view_impl->BackgroundColor()); |
| } |
| |
| class PrintWebViewClient : public frame_test_helpers::TestWebViewClient { |
| public: |
| PrintWebViewClient() : print_called_(false) {} |
| |
| // WebViewClient methods |
| void PrintPage(WebLocalFrame*) override { print_called_ = true; } |
| |
| bool PrintCalled() const { return print_called_; } |
| |
| private: |
| bool print_called_; |
| }; |
| |
| TEST_F(WebViewTest, PrintWithXHRInFlight) { |
| PrintWebViewClient client; |
| RegisterMockedHttpURLLoad("print_with_xhr_inflight.html"); |
| WebViewImpl* web_view_impl = web_view_helper_.InitializeAndLoad( |
| base_url_ + "print_with_xhr_inflight.html", nullptr, &client); |
| |
| ASSERT_TRUE(To<LocalFrame>(web_view_impl->GetPage()->MainFrame()) |
| ->GetDocument() |
| ->LoadEventFinished()); |
| EXPECT_TRUE(client.PrintCalled()); |
| web_view_helper_.Reset(); |
| } |
| |
| static void DragAndDropURL(WebViewImpl* web_view, const std::string& url) { |
| WebDragData drag_data; |
| WebDragData::Item item; |
| item.storage_type = WebDragData::Item::kStorageTypeString; |
| item.string_type = "text/uri-list"; |
| item.string_data = WebString::FromUTF8(url); |
| drag_data.AddItem(item); |
| |
| const gfx::PointF client_point; |
| const gfx::PointF screen_point; |
| WebFrameWidget* widget = web_view->MainFrameImpl()->FrameWidget(); |
| widget->DragTargetDragEnter(drag_data, client_point, screen_point, |
| kDragOperationCopy, 0, base::DoNothing()); |
| widget->DragTargetDrop(drag_data, client_point, screen_point, 0); |
| frame_test_helpers::PumpPendingRequestsForFrameToLoad( |
| web_view->MainFrameImpl()); |
| } |
| |
| TEST_F(WebViewTest, DragDropURL) { |
| RegisterMockedHttpURLLoad("foo.html"); |
| RegisterMockedHttpURLLoad("bar.html"); |
| |
| const std::string foo_url = base_url_ + "foo.html"; |
| const std::string bar_url = base_url_ + "bar.html"; |
| |
| WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(foo_url); |
| |
| ASSERT_TRUE(web_view); |
| |
| // Drag and drop barUrl and verify that we've navigated to it. |
| DragAndDropURL(web_view, bar_url); |
| EXPECT_EQ(bar_url, |
| web_view->MainFrameImpl()->GetDocument().Url().GetString().Utf8()); |
| |
| // Drag and drop fooUrl and verify that we've navigated back to it. |
| DragAndDropURL(web_view, foo_url); |
| EXPECT_EQ(foo_url, |
| web_view->MainFrameImpl()->GetDocument().Url().GetString().Utf8()); |
| |
| // Disable navigation on drag-and-drop. |
| web_view->SettingsImpl()->SetNavigateOnDragDrop(false); |
| |
| // Attempt to drag and drop to barUrl and verify that no navigation has |
| // occurred. |
| DragAndDropURL(web_view, bar_url); |
| EXPECT_EQ(foo_url, |
| web_view->MainFrameImpl()->GetDocument().Url().GetString().Utf8()); |
| } |
| |
| bool WebViewTest::TapElement(WebInputEvent::Type type, Element* element) { |
| if (!element || !element->GetLayoutObject()) |
| return false; |
| |
| DCHECK(web_view_helper_.GetWebView()); |
| element->scrollIntoViewIfNeeded(); |
| |
| FloatPoint center( |
| web_view_helper_.GetWebView() |
| ->MainFrameImpl() |
| ->GetFrameView() |
| ->FrameToScreen(element->GetLayoutObject()->AbsoluteBoundingBoxRect()) |
| .Center()); |
| |
| WebGestureEvent event(type, WebInputEvent::kNoModifiers, |
| WebInputEvent::GetStaticTimeStampForTests(), |
| WebGestureDevice::kTouchscreen); |
| event.SetPositionInWidget(center); |
| |
| web_view_helper_.GetWebView()->MainFrameWidget()->HandleInputEvent( |
| WebCoalescedInputEvent(event, ui::LatencyInfo())); |
| RunPendingTasks(); |
| return true; |
| } |
| |
| bool WebViewTest::TapElementById(WebInputEvent::Type type, |
| const WebString& id) { |
| DCHECK(web_view_helper_.GetWebView()); |
| Element* element = static_cast<Element*>( |
| web_view_helper_.LocalMainFrame()->GetDocument().GetElementById(id)); |
| return TapElement(type, element); |
| } |
| |
| IntSize WebViewTest::PrintICBSizeFromPageSize(const FloatSize& page_size) { |
| // The expected layout size comes from the calculation done in |
| // ResizePageRectsKeepingRatio() which is used from PrintContext::begin() to |
| // scale the page size. |
| const float ratio = page_size.Height() / (float)page_size.Width(); |
| const int icb_width = |
| floor(page_size.Width() * PrintContext::kPrintingMinimumShrinkFactor); |
| const int icb_height = floor(icb_width * ratio); |
| return IntSize(icb_width, icb_height); |
| } |
| |
| ExternalDateTimeChooser* WebViewTest::GetExternalDateTimeChooser( |
| WebViewImpl* web_view_impl) { |
| return web_view_impl->GetChromeClient() |
| .GetExternalDateTimeChooserForTesting(); |
| } |
| |
| TEST_F(WebViewTest, ClientTapHandlingNullWebViewClient) { |
| // Note: this test doesn't use WebViewHelper since WebViewHelper creates an |
| // internal WebViewClient on demand if the supplied WebViewClient is null. |
| WebViewImpl* web_view = static_cast<WebViewImpl*>(WebView::Create( |
| /*client=*/nullptr, /*is_hidden=*/false, /*is_inside_portal=*/false, |
| /*compositing_enabled=*/false, /*opener=*/nullptr, |
| mojo::NullAssociatedReceiver(), |
| web_view_helper_.GetAgentGroupScheduler())); |
| frame_test_helpers::TestWebFrameClient web_frame_client; |
| WebLocalFrame* local_frame = WebLocalFrame::CreateMainFrame( |
| web_view, &web_frame_client, nullptr, LocalFrameToken(), nullptr); |
| web_frame_client.Bind(local_frame); |
| WebNonCompositedWidgetClient widget_client; |
| frame_test_helpers::TestWebFrameWidget* widget = |
| web_view_helper_.CreateFrameWidget(local_frame); |
| widget->InitializeNonCompositing(&widget_client); |
| web_view->DidAttachLocalMainFrame(); |
| |
| WebGestureEvent event(WebInputEvent::Type::kGestureTap, |
| WebInputEvent::kNoModifiers, |
| WebInputEvent::GetStaticTimeStampForTests(), |
| WebGestureDevice::kTouchscreen); |
| event.SetPositionInWidget(gfx::PointF(3, 8)); |
| EXPECT_EQ(WebInputEventResult::kNotHandled, |
| web_view->MainFrameWidget()->HandleInputEvent( |
| WebCoalescedInputEvent(event, ui::LatencyInfo()))); |
| web_view->Close(); |
| } |
| |
| TEST_F(WebViewTest, LongPressEmptyDiv) { |
|