blob: ceeba555963c7b20ad8a296ebe8b3ac1cf0e21d6 [file] [log] [blame]
/*
* 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) {
RegisterMockedHttpURLLoad("long_press_empty_div.html");
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "long_press_empty_div.html");
web_view->SettingsImpl()->SetAlwaysShowContextMenuOnTouch(false);
web_view->MainFrameViewWidget()->Resize(gfx::Size(500, 300));
UpdateAllLifecyclePhases();
RunPendingTasks();
WebGestureEvent event(WebInputEvent::Type::kGestureLongPress,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
WebGestureDevice::kTouchscreen);
event.SetPositionInWidget(gfx::PointF(250, 150));
EXPECT_EQ(WebInputEventResult::kNotHandled,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(event, ui::LatencyInfo())));
}
TEST_F(WebViewTest, LongPressEmptyDivAlwaysShow) {
RegisterMockedHttpURLLoad("long_press_empty_div.html");
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "long_press_empty_div.html");
web_view->SettingsImpl()->SetAlwaysShowContextMenuOnTouch(true);
web_view->MainFrameViewWidget()->Resize(gfx::Size(500, 300));
UpdateAllLifecyclePhases();
RunPendingTasks();
WebGestureEvent event(WebInputEvent::Type::kGestureLongPress,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
WebGestureDevice::kTouchscreen);
event.SetPositionInWidget(gfx::PointF(250, 150));
EXPECT_EQ(WebInputEventResult::kHandledSystem,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(event, ui::LatencyInfo())));
}
TEST_F(WebViewTest, LongPressObject) {
RegisterMockedHttpURLLoad("long_press_object.html");
WebViewImpl* web_view =
web_view_helper_.InitializeAndLoad(base_url_ + "long_press_object.html");
web_view->SettingsImpl()->SetAlwaysShowContextMenuOnTouch(true);
web_view->MainFrameViewWidget()->Resize(gfx::Size(500, 300));
UpdateAllLifecyclePhases();
RunPendingTasks();
WebGestureEvent event(WebInputEvent::Type::kGestureLongPress,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
WebGestureDevice::kTouchscreen);
event.SetPositionInWidget(gfx::PointF(10, 10));
EXPECT_NE(WebInputEventResult::kHandledSystem,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(event, ui::LatencyInfo())));
auto* element = To<HTMLElement>(static_cast<Node*>(
web_view->MainFrameImpl()->GetDocument().GetElementById("obj")));
EXPECT_FALSE(element->CanStartSelection());
}
TEST_F(WebViewTest, LongPressObjectFallback) {
RegisterMockedHttpURLLoad("long_press_object_fallback.html");
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "long_press_object_fallback.html");
web_view->SettingsImpl()->SetAlwaysShowContextMenuOnTouch(true);
web_view->MainFrameViewWidget()->Resize(gfx::Size(500, 300));
UpdateAllLifecyclePhases();
RunPendingTasks();
WebGestureEvent event(WebInputEvent::Type::kGestureLongPress,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
WebGestureDevice::kTouchscreen);
event.SetPositionInWidget(gfx::PointF(10, 10));
EXPECT_EQ(WebInputEventResult::kHandledSystem,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(event, ui::LatencyInfo())));
auto* element = To<HTMLElement>(static_cast<Node*>(
web_view->MainFrameImpl()->GetDocument().GetElementById("obj")));
EXPECT_TRUE(element->CanStartSelection());
}
TEST_F(WebViewTest, LongPressImage) {
RegisterMockedHttpURLLoad("long_press_image.html");
WebViewImpl* web_view =
web_view_helper_.InitializeAndLoad(base_url_ + "long_press_image.html");
web_view->SettingsImpl()->SetAlwaysShowContextMenuOnTouch(false);
web_view->MainFrameViewWidget()->Resize(gfx::Size(500, 300));
UpdateAllLifecyclePhases();
RunPendingTasks();
WebGestureEvent event(WebInputEvent::Type::kGestureLongPress,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
WebGestureDevice::kTouchscreen);
event.SetPositionInWidget(gfx::PointF(10, 10));
EXPECT_EQ(WebInputEventResult::kHandledSystem,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(event, ui::LatencyInfo())));
EXPECT_TRUE(
web_view->GetPage()->GetContextMenuController().ContextMenuNodeForFrame(
web_view->MainFrameImpl()->GetFrame()));
}
TEST_F(WebViewTest, LongPressVideo) {
RegisterMockedHttpURLLoad("long_press_video.html");
WebViewImpl* web_view =
web_view_helper_.InitializeAndLoad(base_url_ + "long_press_video.html");
web_view->SettingsImpl()->SetAlwaysShowContextMenuOnTouch(false);
web_view->MainFrameViewWidget()->Resize(gfx::Size(500, 300));
UpdateAllLifecyclePhases();
RunPendingTasks();
WebGestureEvent event(WebInputEvent::Type::kGestureLongPress,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
WebGestureDevice::kTouchscreen);
event.SetPositionInWidget(gfx::PointF(10, 10));
EXPECT_EQ(WebInputEventResult::kHandledSystem,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(event, ui::LatencyInfo())));
}
TEST_F(WebViewTest, LongPressLink) {
RegisterMockedHttpURLLoad("long_press_link.html");
WebViewImpl* web_view =
web_view_helper_.InitializeAndLoad(base_url_ + "long_press_link.html");
web_view->SettingsImpl()->SetAlwaysShowContextMenuOnTouch(false);
web_view->MainFrameViewWidget()->Resize(gfx::Size(500, 300));
UpdateAllLifecyclePhases();
RunPendingTasks();
WebGestureEvent event(WebInputEvent::Type::kGestureLongPress,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
WebGestureDevice::kTouchscreen);
event.SetPositionInWidget(gfx::PointF(500, 300));
EXPECT_EQ(WebInputEventResult::kHandledSystem,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(event, ui::LatencyInfo())));
}
// Tests that we send touchcancel when drag start by long press.
TEST_F(WebViewTest, TouchCancelOnStartDragging) {
RegisterMockedHttpURLLoad("long_press_draggable_div.html");
url_test_helpers::RegisterMockedURLLoad(
ToKURL("http://www.test.com/foo.png"),
test::CoreTestDataPath("white-1x1.png"));
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "long_press_draggable_div.html");
web_view->SettingsImpl()->SetTouchDragDropEnabled(true);
web_view->MainFrameViewWidget()->Resize(gfx::Size(500, 300));
UpdateAllLifecyclePhases();
RunPendingTasks();
WebPointerEvent pointer_down(
WebInputEvent::Type::kPointerDown,
WebPointerProperties(1, WebPointerProperties::PointerType::kTouch), 5, 5);
pointer_down.SetPositionInWidget(250, 8);
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(pointer_down, ui::LatencyInfo()));
web_view->MainFrameWidget()->DispatchBufferedTouchEvents();
WebString target_id = WebString::FromUTF8("target");
// Send long press to start dragging
EXPECT_TRUE(
TapElementById(WebInputEvent::Type::kGestureLongPress, target_id));
EXPECT_EQ("dragstart", web_view->MainFrameImpl()->GetDocument().Title());
// Check pointer cancel is sent to dom.
WebPointerEvent pointer_cancel(
WebInputEvent::Type::kPointerCancel,
WebPointerProperties(1, WebPointerProperties::PointerType::kTouch), 5, 5);
pointer_cancel.SetPositionInWidget(250, 8);
EXPECT_NE(WebInputEventResult::kHandledSuppressed,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(pointer_cancel, ui::LatencyInfo())));
web_view->MainFrameWidget()->DispatchBufferedTouchEvents();
EXPECT_EQ("touchcancel", web_view->MainFrameImpl()->GetDocument().Title());
}
// Tests that a touch drag context menu is enabled, a dragend shows a context
// menu when there is no drag.
TEST_F(WebViewTest, TouchDragContextMenuWithoutDrag) {
RegisterMockedHttpURLLoad("long_press_draggable_div.html");
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "long_press_draggable_div.html");
web_view->SettingsImpl()->SetTouchDragDropEnabled(true);
web_view->SettingsImpl()->SetTouchDragEndContextMenu(true);
web_view->MainFrameViewWidget()->Resize(gfx::Size(500, 300));
UpdateAllLifecyclePhases();
RunPendingTasks();
WebPointerEvent pointer_down(
WebInputEvent::Type::kPointerDown,
WebPointerProperties(1, WebPointerProperties::PointerType::kTouch), 5, 5);
pointer_down.SetPositionInWidget(250, 8);
pointer_down.SetPositionInScreen(250, 8);
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(pointer_down, ui::LatencyInfo()));
web_view->MainFrameWidget()->DispatchBufferedTouchEvents();
WebString target_id = WebString::FromUTF8("target");
// Simulate long press to start dragging.
EXPECT_TRUE(
TapElementById(WebInputEvent::Type::kGestureLongPress, target_id));
EXPECT_EQ("dragstart", web_view->MainFrameImpl()->GetDocument().Title());
// Simulate the end of a non-moving drag.
const gfx::PointF dragend_point(250, 8);
web_view->MainFrameViewWidget()->DragSourceEndedAt(
dragend_point, dragend_point, ui::mojom::blink::DragOperation::kNone);
EXPECT_TRUE(
web_view->GetPage()->GetContextMenuController().ContextMenuNodeForFrame(
web_view->MainFrameImpl()->GetFrame()));
}
// Tests that a touch drag context menu is enabled, a dragend does not show a
// context menu after a drag.
TEST_F(WebViewTest, TouchDragContextMenuWithDrag) {
RegisterMockedHttpURLLoad("long_press_draggable_div.html");
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "long_press_draggable_div.html");
web_view->SettingsImpl()->SetTouchDragDropEnabled(true);
web_view->SettingsImpl()->SetTouchDragEndContextMenu(true);
web_view->MainFrameViewWidget()->Resize(gfx::Size(500, 300));
UpdateAllLifecyclePhases();
RunPendingTasks();
WebPointerEvent pointer_down(
WebInputEvent::Type::kPointerDown,
WebPointerProperties(1, WebPointerProperties::PointerType::kTouch), 5, 5);
pointer_down.SetPositionInWidget(250, 8);
pointer_down.SetPositionInScreen(250, 8);
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(pointer_down, ui::LatencyInfo()));
web_view->MainFrameWidget()->DispatchBufferedTouchEvents();
WebString target_id = WebString::FromUTF8("target");
// Simulate long press to start dragging.
EXPECT_TRUE(
TapElementById(WebInputEvent::Type::kGestureLongPress, target_id));
EXPECT_EQ("dragstart", web_view->MainFrameImpl()->GetDocument().Title());
// Simulate the end of a drag.
const gfx::PointF dragend_point(270, 28);
web_view->MainFrameViewWidget()->DragSourceEndedAt(
dragend_point, dragend_point, ui::mojom::blink::DragOperation::kNone);
EXPECT_FALSE(
web_view->GetPage()->GetContextMenuController().ContextMenuNodeForFrame(
web_view->MainFrameImpl()->GetFrame()));
}
TEST_F(WebViewTest, showContextMenuOnLongPressingLinks) {
RegisterMockedHttpURLLoad("long_press_links_and_images.html");
url_test_helpers::RegisterMockedURLLoad(
ToKURL("http://www.test.com/foo.png"),
test::CoreTestDataPath("white-1x1.png"));
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "long_press_links_and_images.html");
web_view->SettingsImpl()->SetTouchDragDropEnabled(true);
web_view->MainFrameViewWidget()->Resize(gfx::Size(500, 300));
UpdateAllLifecyclePhases();
RunPendingTasks();
WebString anchor_tag_id = WebString::FromUTF8("anchorTag");
WebString image_tag_id = WebString::FromUTF8("imageTag");
EXPECT_TRUE(
TapElementById(WebInputEvent::Type::kGestureLongPress, anchor_tag_id));
EXPECT_EQ("anchor contextmenu",
web_view->MainFrameImpl()->GetDocument().Title());
EXPECT_TRUE(
TapElementById(WebInputEvent::Type::kGestureLongPress, image_tag_id));
EXPECT_EQ("image contextmenu",
web_view->MainFrameImpl()->GetDocument().Title());
}
TEST_F(WebViewTest, LongPressEmptyEditableSelection) {
RegisterMockedHttpURLLoad("long_press_empty_editable_selection.html");
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "long_press_empty_editable_selection.html");
web_view->SettingsImpl()->SetAlwaysShowContextMenuOnTouch(false);
web_view->MainFrameViewWidget()->Resize(gfx::Size(500, 300));
UpdateAllLifecyclePhases();
RunPendingTasks();
WebGestureEvent event(WebInputEvent::Type::kGestureLongPress,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
WebGestureDevice::kTouchscreen);
event.SetPositionInWidget(gfx::PointF(10, 10));
EXPECT_EQ(WebInputEventResult::kHandledSystem,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(event, ui::LatencyInfo())));
}
TEST_F(WebViewTest, LongPressEmptyNonEditableSelection) {
RegisterMockedHttpURLLoad("long_press_image.html");
WebViewImpl* web_view =
web_view_helper_.InitializeAndLoad(base_url_ + "long_press_image.html");
web_view->MainFrameViewWidget()->Resize(gfx::Size(500, 500));
UpdateAllLifecyclePhases();
RunPendingTasks();
WebGestureEvent event(WebInputEvent::Type::kGestureLongPress,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
WebGestureDevice::kTouchscreen);
event.SetPositionInWidget(gfx::PointF(300, 300));
WebLocalFrameImpl* frame = web_view->MainFrameImpl();
EXPECT_EQ(WebInputEventResult::kHandledSystem,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(event, ui::LatencyInfo())));
EXPECT_TRUE(frame->SelectionAsText().IsEmpty());
}
TEST_F(WebViewTest, LongPressSelection) {
RegisterMockedHttpURLLoad("longpress_selection.html");
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "longpress_selection.html");
web_view->MainFrameViewWidget()->Resize(gfx::Size(500, 300));
UpdateAllLifecyclePhases();
RunPendingTasks();
WebString target = WebString::FromUTF8("target");
WebString onselectstartfalse = WebString::FromUTF8("onselectstartfalse");
WebLocalFrameImpl* frame = web_view->MainFrameImpl();
EXPECT_TRUE(TapElementById(WebInputEvent::Type::kGestureLongPress,
onselectstartfalse));
EXPECT_EQ("", frame->SelectionAsText().Utf8());
EXPECT_TRUE(TapElementById(WebInputEvent::Type::kGestureLongPress, target));
EXPECT_EQ("testword", frame->SelectionAsText().Utf8());
}
TEST_F(WebViewTest, FinishComposingTextDoesNotDismissHandles) {
RegisterMockedHttpURLLoad("longpress_selection.html");
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "longpress_selection.html");
web_view->MainFrameViewWidget()->Resize(gfx::Size(500, 300));
UpdateAllLifecyclePhases();
RunPendingTasks();
WebString target = WebString::FromUTF8("target");
WebLocalFrameImpl* frame = web_view->MainFrameImpl();
web_view->SetPageFocus(true);
WebInputMethodController* active_input_method_controller =
frame->FrameWidget()->GetActiveWebInputMethodController();
EXPECT_TRUE(TapElementById(WebInputEvent::Type::kGestureTap, target));
WebVector<ui::ImeTextSpan> empty_ime_text_spans;
frame->SetEditableSelectionOffsets(8, 8);
EXPECT_TRUE(active_input_method_controller->SetComposition(
"12345", empty_ime_text_spans, WebRange(), 8, 13));
EXPECT_TRUE(frame->GetFrame()->GetInputMethodController().HasComposition());
EXPECT_EQ("", frame->SelectionAsText().Utf8());
EXPECT_FALSE(frame->GetFrame()->Selection().IsHandleVisible());
EXPECT_TRUE(frame->GetFrame()->GetInputMethodController().HasComposition());
EXPECT_TRUE(TapElementById(WebInputEvent::Type::kGestureLongPress, target));
EXPECT_EQ("testword12345", frame->SelectionAsText().Utf8());
EXPECT_TRUE(frame->GetFrame()->Selection().IsHandleVisible());
EXPECT_TRUE(frame->GetFrame()->GetInputMethodController().HasComposition());
// Check that finishComposingText(KeepSelection) does not dismiss handles.
active_input_method_controller->FinishComposingText(
WebInputMethodController::kKeepSelection);
EXPECT_TRUE(frame->GetFrame()->Selection().IsHandleVisible());
}
#if !defined(OS_MAC)
TEST_F(WebViewTest, TouchDoesntSelectEmptyTextarea) {
RegisterMockedHttpURLLoad("longpress_textarea.html");
WebViewImpl* web_view =
web_view_helper_.InitializeAndLoad(base_url_ + "longpress_textarea.html");
web_view->MainFrameViewWidget()->Resize(gfx::Size(500, 300));
UpdateAllLifecyclePhases();
RunPendingTasks();
WebString blanklinestextbox = WebString::FromUTF8("blanklinestextbox");
WebLocalFrameImpl* frame = web_view->MainFrameImpl();
// Long-press on carriage returns.
EXPECT_TRUE(TapElementById(WebInputEvent::Type::kGestureLongPress,
blanklinestextbox));
EXPECT_TRUE(frame->SelectionAsText().IsEmpty());
// Double-tap on carriage returns.
WebGestureEvent event(WebInputEvent::Type::kGestureTap,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
WebGestureDevice::kTouchscreen);
event.SetPositionInWidget(gfx::PointF(100, 25));
event.data.tap.tap_count = 2;
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(event, ui::LatencyInfo()));
EXPECT_TRUE(frame->SelectionAsText().IsEmpty());
auto* text_area_element = To<HTMLTextAreaElement>(static_cast<Node*>(
web_view->MainFrameImpl()->GetDocument().GetElementById(
blanklinestextbox)));
text_area_element->setValue("hello");
// Long-press past last word of textbox.
EXPECT_TRUE(TapElementById(WebInputEvent::Type::kGestureLongPress,
blanklinestextbox));
EXPECT_TRUE(frame->SelectionAsText().IsEmpty());
// Double-tap past last word of textbox.
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(event, ui::LatencyInfo()));
EXPECT_TRUE(frame->SelectionAsText().IsEmpty());
}
#endif
TEST_F(WebViewTest, LongPressImageTextarea) {
RegisterMockedHttpURLLoad("longpress_image_contenteditable.html");
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "longpress_image_contenteditable.html");
web_view->MainFrameViewWidget()->Resize(gfx::Size(500, 300));
UpdateAllLifecyclePhases();
RunPendingTasks();
WebString image = WebString::FromUTF8("purpleimage");
EXPECT_TRUE(TapElementById(WebInputEvent::Type::kGestureLongPress, image));
WebRange range = web_view->MainFrameImpl()
->GetInputMethodController()
->GetSelectionOffsets();
EXPECT_FALSE(range.IsNull());
EXPECT_EQ(0, range.StartOffset());
EXPECT_EQ(1, range.length());
}
TEST_F(WebViewTest, BlinkCaretAfterLongPress) {
RegisterMockedHttpURLLoad("blink_caret_on_typing_after_long_press.html");
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "blink_caret_on_typing_after_long_press.html");
web_view->MainFrameViewWidget()->Resize(gfx::Size(640, 480));
UpdateAllLifecyclePhases();
RunPendingTasks();
WebString target = WebString::FromUTF8("target");
WebLocalFrameImpl* main_frame = web_view->MainFrameImpl();
EXPECT_TRUE(TapElementById(WebInputEvent::Type::kGestureLongPress, target));
EXPECT_FALSE(main_frame->GetFrame()->Selection().IsCaretBlinkingSuspended());
}
TEST_F(WebViewTest, BlinkCaretOnClosingContextMenu) {
RegisterMockedHttpURLLoad("form.html");
WebViewImpl* web_view =
web_view_helper_.InitializeAndLoad(base_url_ + "form.html");
web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false);
RunPendingTasks();
// We suspend caret blinking when pressing with mouse right button.
// Note that we do not send MouseUp event here since it will be consumed
// by the context menu once it shows up.
WebMouseEvent mouse_event(WebInputEvent::Type::kMouseDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
mouse_event.button = WebMouseEvent::Button::kRight;
mouse_event.SetPositionInWidget(1, 1);
mouse_event.click_count = 1;
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(mouse_event, ui::LatencyInfo()));
RunPendingTasks();
WebLocalFrameImpl* main_frame = web_view->MainFrameImpl();
EXPECT_TRUE(main_frame->GetFrame()->Selection().IsCaretBlinkingSuspended());
// Caret blinking is still suspended after showing context menu.
web_view->MainFrameImpl()->LocalRootFrameWidget()->ShowContextMenu(
ui::mojom::MenuSourceType::MOUSE,
web_view->MainFrameImpl()->GetPositionInViewportForTesting());
EXPECT_TRUE(main_frame->GetFrame()->Selection().IsCaretBlinkingSuspended());
// Caret blinking will be resumed only after context menu is closed.
web_view->DidCloseContextMenu();
EXPECT_FALSE(main_frame->GetFrame()->Selection().IsCaretBlinkingSuspended());
}
TEST_F(WebViewTest, SelectionOnReadOnlyInput) {
RegisterMockedHttpURLLoad("selection_readonly.html");
WebViewImpl* web_view =
web_view_helper_.InitializeAndLoad(base_url_ + "selection_readonly.html");
web_view->MainFrameViewWidget()->Resize(gfx::Size(640, 480));
UpdateAllLifecyclePhases();
RunPendingTasks();
std::string test_word = "This text should be selected.";
WebLocalFrameImpl* frame = web_view->MainFrameImpl();
EXPECT_EQ(test_word, frame->SelectionAsText().Utf8());
WebRange range = web_view->MainFrameImpl()
->GetInputMethodController()
->GetSelectionOffsets();
EXPECT_FALSE(range.IsNull());
EXPECT_EQ(0, range.StartOffset());
EXPECT_EQ(static_cast<int>(test_word.length()), range.length());
}
TEST_F(WebViewTest, KeyDownScrollsHandled) {
RegisterMockedHttpURLLoad("content-width-1000.html");
WebViewImpl* web_view =
web_view_helper_.InitializeAndLoad(base_url_ + "content-width-1000.html");
web_view->MainFrameViewWidget()->Resize(gfx::Size(100, 100));
UpdateAllLifecyclePhases();
RunPendingTasks();
WebKeyboardEvent key_event(WebInputEvent::Type::kRawKeyDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
// RawKeyDown pagedown should be handled.
key_event.windows_key_code = VKEY_NEXT;
EXPECT_EQ(WebInputEventResult::kHandledSystem,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo())));
key_event.SetType(WebInputEvent::Type::kKeyUp);
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo()));
// Coalesced KeyDown arrow-down should be handled.
key_event.windows_key_code = VKEY_DOWN;
key_event.SetType(WebInputEvent::Type::kKeyDown);
EXPECT_EQ(WebInputEventResult::kHandledSystem,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo())));
key_event.SetType(WebInputEvent::Type::kKeyUp);
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo()));
// Ctrl-Home should be handled...
key_event.windows_key_code = VKEY_HOME;
key_event.SetModifiers(WebInputEvent::kControlKey);
key_event.SetType(WebInputEvent::Type::kRawKeyDown);
EXPECT_EQ(WebInputEventResult::kNotHandled,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo())));
key_event.SetType(WebInputEvent::Type::kKeyUp);
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo()));
// But Ctrl-Down should not.
key_event.windows_key_code = VKEY_DOWN;
key_event.SetModifiers(WebInputEvent::kControlKey);
key_event.SetType(WebInputEvent::Type::kRawKeyDown);
EXPECT_EQ(WebInputEventResult::kNotHandled,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo())));
key_event.SetType(WebInputEvent::Type::kKeyUp);
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo()));
// Shift, meta, and alt should not be handled.
key_event.windows_key_code = VKEY_NEXT;
key_event.SetModifiers(WebInputEvent::kShiftKey);
key_event.SetType(WebInputEvent::Type::kRawKeyDown);
EXPECT_EQ(WebInputEventResult::kNotHandled,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo())));
key_event.SetType(WebInputEvent::Type::kKeyUp);
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo()));
key_event.windows_key_code = VKEY_NEXT;
key_event.SetModifiers(WebInputEvent::kMetaKey);
key_event.SetType(WebInputEvent::Type::kRawKeyDown);
EXPECT_EQ(WebInputEventResult::kNotHandled,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo())));
key_event.SetType(WebInputEvent::Type::kKeyUp);
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo()));
key_event.windows_key_code = VKEY_NEXT;
key_event.SetModifiers(WebInputEvent::kAltKey);
key_event.SetType(WebInputEvent::Type::kRawKeyDown);
EXPECT_EQ(WebInputEventResult::kNotHandled,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo())));
key_event.SetType(WebInputEvent::Type::kKeyUp);
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo()));
// System-key labeled Alt-Down (as in Windows) should do nothing,
// but non-system-key labeled Alt-Down (as in Mac) should be handled
// as a page-down.
key_event.windows_key_code = VKEY_DOWN;
key_event.SetModifiers(WebInputEvent::kAltKey);
key_event.is_system_key = true;
key_event.SetType(WebInputEvent::Type::kRawKeyDown);
EXPECT_EQ(WebInputEventResult::kNotHandled,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo())));
key_event.SetType(WebInputEvent::Type::kKeyUp);
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo()));
key_event.windows_key_code = VKEY_DOWN;
key_event.SetModifiers(WebInputEvent::kAltKey);
key_event.is_system_key = false;
key_event.SetType(WebInputEvent::Type::kRawKeyDown);
EXPECT_EQ(WebInputEventResult::kHandledSystem,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo())));
key_event.SetType(WebInputEvent::Type::kKeyUp);
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo()));
}
class MiddleClickAutoscrollWebFrameWidget
: public frame_test_helpers::TestWebFrameWidget {
public:
template <typename... Args>
explicit MiddleClickAutoscrollWebFrameWidget(Args&&... args)
: frame_test_helpers::TestWebFrameWidget(std::forward<Args>(args)...) {}
// FrameWidget overrides:
void DidChangeCursor(const ui::Cursor& cursor) override {
last_cursor_type_ = cursor.type();
}
ui::mojom::blink::CursorType GetLastCursorType() const {
return last_cursor_type_;
}
private:
ui::mojom::blink::CursorType last_cursor_type_ =
ui::mojom::blink::CursorType::kPointer;
};
class MiddleClickWebViewTest : public WebViewTest {
public:
MiddleClickWebViewTest()
: WebViewTest(base::BindRepeating(
&frame_test_helpers::WebViewHelper::CreateTestWebFrameWidget<
MiddleClickAutoscrollWebFrameWidget>)) {}
};
TEST_F(MiddleClickWebViewTest, MiddleClickAutoscrollCursor) {
ScopedMiddleClickAutoscrollForTest middle_click_autoscroll(true);
RegisterMockedHttpURLLoad("content-width-1000.html");
// We will be changing the size of the page to test each of the panning
// cursor variations. For reference, content-width-1000.html is 1000px wide
// and 2000px tall.
// 1. 100 x 100 - The page will be scrollable in both x and y directions, so
// we expect to see the cursor with arrows in all four directions.
// 2. 1010 x 100 - The page will be scrollable in the y direction, but not x,
// so we expect to see the cursor with only the vertical arrows.
// 3. 100 x 2010 - The page will be scrollable in the x direction, but not y,
// so we expect to see the cursor with only the horizontal arrows.
struct CursorTests {
int resize_width;
int resize_height;
ui::mojom::blink::CursorType expected_cursor;
} cursor_tests[] = {{100, 100, MiddlePanningCursor().type()},
{1010, 100, MiddlePanningVerticalCursor().type()},
{100, 2010, MiddlePanningHorizontalCursor().type()}};
for (const CursorTests current_test : cursor_tests) {
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "content-width-1000.html", nullptr, nullptr);
web_view->MainFrameWidget()->Resize(
gfx::Size(current_test.resize_width, current_test.resize_height));
UpdateAllLifecyclePhases();
RunPendingTasks();
MiddleClickAutoscrollWebFrameWidget* widget =
static_cast<MiddleClickAutoscrollWebFrameWidget*>(
web_view_helper_.GetMainFrameWidget());
LocalFrame* local_frame =
To<WebLocalFrameImpl>(web_view->MainFrame())->GetFrame();
// Setup a mock clipboard. On linux, middle click can paste from the
// clipboard, so the input handler below will access the clipboard.
PageTestBase::MockClipboardHostProvider mock_clip_host_provider(
local_frame->GetBrowserInterfaceBroker());
WebMouseEvent mouse_event(WebInputEvent::Type::kMouseDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
mouse_event.button = WebMouseEvent::Button::kMiddle;
mouse_event.SetPositionInWidget(1, 1);
mouse_event.click_count = 1;
// Start middle-click autoscroll.
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(mouse_event, ui::LatencyInfo()));
mouse_event.SetType(WebInputEvent::Type::kMouseUp);
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(mouse_event, ui::LatencyInfo()));
EXPECT_EQ(current_test.expected_cursor, widget->GetLastCursorType());
// Even if a plugin tries to change the cursor type, that should be ignored
// during middle-click autoscroll.
web_view->GetChromeClient().SetCursorForPlugin(PointerCursor(),
local_frame);
EXPECT_EQ(current_test.expected_cursor, widget->GetLastCursorType());
// End middle-click autoscroll.
mouse_event.SetType(WebInputEvent::Type::kMouseDown);
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(mouse_event, ui::LatencyInfo()));
mouse_event.SetType(WebInputEvent::Type::kMouseUp);
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(mouse_event, ui::LatencyInfo()));
web_view->GetChromeClient().SetCursorForPlugin(IBeamCursor(), local_frame);
EXPECT_EQ(IBeamCursor().type(), widget->GetLastCursorType());
}
// Explicitly reset to break dependency on locally scoped client.
web_view_helper_.Reset();
}
static void ConfigueCompositingWebView(WebSettings* settings) {
settings->SetPreferCompositingToLCDTextEnabled(true);
}
TEST_F(WebViewTest, ShowPressOnTransformedLink) {
frame_test_helpers::WebViewHelper web_view_helper;
WebViewImpl* web_view_impl =
web_view_helper.InitializeWithSettings(&ConfigueCompositingWebView);
int page_width = 640;
int page_height = 480;
web_view_impl->MainFrameViewWidget()->Resize(
gfx::Size(page_width, page_height));
WebURL base_url = url_test_helpers::ToKURL("http://example.com/");
frame_test_helpers::LoadHTMLString(
web_view_impl->MainFrameImpl(),
"<a href='http://www.test.com' style='position: absolute; left: 20px; "
"top: 20px; width: 200px; transform:translateZ(0);'>A link to "
"highlight</a>",
base_url);
WebGestureEvent event(WebInputEvent::Type::kGestureShowPress,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
WebGestureDevice::kTouchscreen);
event.SetPositionInWidget(gfx::PointF(20, 20));
// Just make sure we don't hit any asserts.
web_view_impl->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(event, ui::LatencyInfo()));
}
class MockAutofillClient : public WebAutofillClient {
public:
MockAutofillClient() = default;
~MockAutofillClient() override = default;
void TextFieldDidChange(const WebFormControlElement&) override {
++text_changes_;
}
void UserGestureObserved() override { ++user_gesture_notifications_count_; }
bool ShouldSuppressKeyboard(const WebFormControlElement&) override {
return should_suppress_keyboard_;
}
void SetShouldSuppressKeyboard(bool should_suppress_keyboard) {
should_suppress_keyboard_ = should_suppress_keyboard;
}
void ClearChangeCounts() { text_changes_ = 0; }
int TextChanges() { return text_changes_; }
int GetUserGestureNotificationsCount() {
return user_gesture_notifications_count_;
}
private:
int text_changes_ = 0;
int user_gesture_notifications_count_ = 0;
bool should_suppress_keyboard_ = false;
};
TEST_F(WebViewTest, LosingFocusDoesNotTriggerAutofillTextChange) {
RegisterMockedHttpURLLoad("input_field_populated.html");
MockAutofillClient client;
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "input_field_populated.html");
WebLocalFrameImpl* frame = web_view->MainFrameImpl();
frame->SetAutofillClient(&client);
web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false);
// Set up a composition that needs to be committed.
WebVector<ui::ImeTextSpan> empty_ime_text_spans;
frame->SetEditableSelectionOffsets(4, 10);
frame->SetCompositionFromExistingText(8, 12, empty_ime_text_spans);
WebTextInputInfo info = frame->GetInputMethodController()->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);
// Clear the focus and track that the subsequent composition commit does not
// trigger a text changed notification for autofill.
client.ClearChangeCounts();
web_view->MainFrameWidget()->SetFocus(false);
EXPECT_EQ(0, client.TextChanges());
frame->SetAutofillClient(nullptr);
}
static void VerifySelectionAndComposition(WebViewImpl* web_view,
int selection_start,
int selection_end,
int composition_start,
int composition_end,
const char* fail_message) {
WebTextInputInfo info =
web_view->MainFrameImpl()->GetInputMethodController()->TextInputInfo();
EXPECT_EQ(selection_start, info.selection_start) << fail_message;
EXPECT_EQ(selection_end, info.selection_end) << fail_message;
EXPECT_EQ(composition_start, info.composition_start) << fail_message;
EXPECT_EQ(composition_end, info.composition_end) << fail_message;
}
TEST_F(WebViewTest, CompositionNotCancelledByBackspace) {
RegisterMockedHttpURLLoad("composition_not_cancelled_by_backspace.html");
MockAutofillClient client;
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "composition_not_cancelled_by_backspace.html");
WebLocalFrameImpl* frame = web_view->MainFrameImpl();
frame->SetAutofillClient(&client);
web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false);
// Test both input elements.
for (int i = 0; i < 2; ++i) {
// Select composition and do sanity check.
WebVector<ui::ImeTextSpan> empty_ime_text_spans;
frame->SetEditableSelectionOffsets(6, 6);
WebInputMethodController* active_input_method_controller =
frame->FrameWidget()->GetActiveWebInputMethodController();
EXPECT_TRUE(active_input_method_controller->SetComposition(
"fghij", empty_ime_text_spans, WebRange(), 0, 5));
frame->SetEditableSelectionOffsets(11, 11);
VerifySelectionAndComposition(web_view, 11, 11, 6, 11, "initial case");
// Press Backspace and verify composition didn't get cancelled. This is to
// verify the fix for crbug.com/429916.
WebKeyboardEvent key_event(WebInputEvent::Type::kRawKeyDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
key_event.dom_key = ui::DomKey::BACKSPACE;
key_event.windows_key_code = VKEY_BACK;
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo()));
frame->SetEditableSelectionOffsets(6, 6);
EXPECT_TRUE(active_input_method_controller->SetComposition(
"fghi", empty_ime_text_spans, WebRange(), 0, 4));
frame->SetEditableSelectionOffsets(10, 10);
VerifySelectionAndComposition(web_view, 10, 10, 6, 10,
"after pressing Backspace");
key_event.SetType(WebInputEvent::Type::kKeyUp);
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo()));
web_view->AdvanceFocus(false);
}
frame->SetAutofillClient(nullptr);
}
TEST_F(WebViewTest, FinishComposingTextDoesntTriggerAutofillTextChange) {
RegisterMockedHttpURLLoad("input_field_populated.html");
MockAutofillClient client;
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "input_field_populated.html");
WebLocalFrameImpl* frame = web_view->MainFrameImpl();
frame->SetAutofillClient(&client);
web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false);
WebDocument document = web_view->MainFrameImpl()->GetDocument();
auto* form = To<HTMLFormControlElement>(
static_cast<Element*>(document.GetElementById("sample")));
WebInputMethodController* active_input_method_controller =
frame->FrameWidget()->GetActiveWebInputMethodController();
// Set up a composition that needs to be committed.
std::string composition_text("testingtext");
WebVector<ui::ImeTextSpan> empty_ime_text_spans;
active_input_method_controller->SetComposition(
WebString::FromUTF8(composition_text.c_str()), empty_ime_text_spans,
WebRange(), 0, composition_text.length());
WebTextInputInfo info = active_input_method_controller->TextInputInfo();
EXPECT_EQ(0, info.selection_start);
EXPECT_EQ((int)composition_text.length(), info.selection_end);
EXPECT_EQ(0, info.composition_start);
EXPECT_EQ((int)composition_text.length(), info.composition_end);
form->SetAutofillState(blink::WebAutofillState::kAutofilled);
client.ClearChangeCounts();
active_input_method_controller->FinishComposingText(
WebInputMethodController::kKeepSelection);
EXPECT_EQ(0, client.TextChanges());
EXPECT_TRUE(form->IsAutofilled());
frame->SetAutofillClient(nullptr);
}
TEST_F(WebViewTest,
SetCompositionFromExistingTextDoesntTriggerAutofillTextChange) {
RegisterMockedHttpURLLoad("input_field_populated.html");
MockAutofillClient client;
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "input_field_populated.html");
WebLocalFrameImpl* frame = web_view->MainFrameImpl();
frame->SetAutofillClient(&client);
web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false);
WebVector<ui::ImeTextSpan> empty_ime_text_spans;
client.ClearChangeCounts();
frame->SetCompositionFromExistingText(8, 12, empty_ime_text_spans);
WebTextInputInfo info = frame->GetInputMethodController()->TextInputInfo();
EXPECT_EQ("0123456789abcdefghijklmnopqrstuvwxyz", info.value.Utf8());
EXPECT_EQ(8, info.composition_start);
EXPECT_EQ(12, info.composition_end);
EXPECT_EQ(0, client.TextChanges());
WebDocument document = web_view->MainFrameImpl()->GetDocument();
EXPECT_EQ(WebString::FromUTF8("none"),
document.GetElementById("inputEvent").FirstChild().NodeValue());
frame->SetAutofillClient(nullptr);
}
class ViewCreatingWebViewClient : public frame_test_helpers::TestWebViewClient {
public:
ViewCreatingWebViewClient() : did_focus_called_(false) {}
// WebViewClient overrides.
WebView* CreateView(WebLocalFrame* opener,
const WebURLRequest&,
const WebWindowFeatures&,
const WebString& name,
WebNavigationPolicy,
network::mojom::blink::WebSandboxFlags,
const SessionStorageNamespaceId&,
bool& consumed_user_gesture,
const base::Optional<WebImpression>&) override {
return web_view_helper_.InitializeWithOpener(opener);
}
void DidFocus() override { did_focus_called_ = true; }
bool DidFocusCalled() const { return did_focus_called_; }
WebView* CreatedWebView() const { return web_view_helper_.GetWebView(); }
private:
frame_test_helpers::WebViewHelper web_view_helper_;
bool did_focus_called_;
};
TEST_F(WebViewTest, DoNotFocusCurrentFrameOnNavigateFromLocalFrame) {
ViewCreatingWebViewClient client;
frame_test_helpers::WebViewHelper web_view_helper;
WebViewImpl* web_view_impl = web_view_helper.Initialize(nullptr, &client);
WebURL base_url = url_test_helpers::ToKURL("http://example.com/");
frame_test_helpers::LoadHTMLString(
web_view_impl->MainFrameImpl(),
"<html><body><iframe src=\"about:blank\"></iframe></body></html>",
base_url);
// Make a request from a local frame.
WebURLRequest web_url_request_with_target_start(KURL("about:blank"));
LocalFrame* local_frame =
To<WebLocalFrameImpl>(web_view_impl->MainFrame()->FirstChild())
->GetFrame();
FrameLoadRequest request_with_target_start(
local_frame->DomWindow(),
web_url_request_with_target_start.ToResourceRequest());
local_frame->Tree().FindOrCreateFrameForNavigation(request_with_target_start,
"_top");
EXPECT_FALSE(client.DidFocusCalled());
web_view_helper.Reset(); // Remove dependency on locally scoped client.
}
TEST_F(WebViewTest, FocusExistingFrameOnNavigate) {
ViewCreatingWebViewClient client;
frame_test_helpers::WebViewHelper web_view_helper;
WebViewImpl* web_view_impl = web_view_helper.Initialize(nullptr, &client);
WebLocalFrameImpl* frame = web_view_impl->MainFrameImpl();
frame->SetName("_start");
// Make a request that will open a new window
WebURLRequest web_url_request(KURL("about:blank"));
FrameLoadRequest request(nullptr, web_url_request.ToResourceRequest());
To<LocalFrame>(web_view_impl->GetPage()->MainFrame())
->Tree()
.FindOrCreateFrameForNavigation(request, "_blank");
ASSERT_TRUE(client.CreatedWebView());
EXPECT_FALSE(client.DidFocusCalled());
// Make a request from the new window that will navigate the original window.
// The original window should be focused.
WebURLRequest web_url_request_with_target_start(KURL("about:blank"));
FrameLoadRequest request_with_target_start(
nullptr, web_url_request_with_target_start.ToResourceRequest());
To<LocalFrame>(static_cast<WebViewImpl*>(client.CreatedWebView())
->GetPage()
->MainFrame())
->Tree()
.FindOrCreateFrameForNavigation(request_with_target_start, "_start");
EXPECT_TRUE(client.DidFocusCalled());
web_view_helper.Reset(); // Remove dependency on locally scoped client.
}
class ViewReusingWebViewClient : public frame_test_helpers::TestWebViewClient {
public:
ViewReusingWebViewClient() = default;
// WebViewClient methods
WebView* CreateView(WebLocalFrame*,
const WebURLRequest&,
const WebWindowFeatures&,
const WebString& name,
WebNavigationPolicy,
network::mojom::blink::WebSandboxFlags,
const SessionStorageNamespaceId&,
bool& consumed_user_gesture,
const base::Optional<WebImpression>&) override {
return web_view_;
}
void SetWebView(WebView* view) { web_view_ = view; }
private:
WebView* web_view_ = nullptr;
};
TEST_F(WebViewTest,
ReuseExistingWindowOnCreateViewUsesCorrectNavigationPolicy) {
ViewReusingWebViewClient view_client;
frame_test_helpers::WebViewHelper web_view_helper;
WebViewImpl* web_view_impl =
web_view_helper.Initialize(nullptr, &view_client);
view_client.SetWebView(web_view_impl);
LocalFrame* frame = To<LocalFrame>(web_view_impl->GetPage()->MainFrame());
// Request a new window, but the WebViewClient will decline to and instead
// return the current window.
WebURLRequest web_url_request(KURL("about:blank"));
FrameLoadRequest request(frame->DomWindow(),
web_url_request.ToResourceRequest());
FrameTree::FindResult result =
frame->Tree().FindOrCreateFrameForNavigation(request, "_blank");
EXPECT_EQ(frame, result.frame);
EXPECT_EQ(kNavigationPolicyCurrentTab, request.GetNavigationPolicy());
}
TEST_F(WebViewTest, DispatchesFocusOutFocusInOnViewToggleFocus) {
RegisterMockedHttpURLLoad("focusout_focusin_events.html");
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "focusout_focusin_events.html");
web_view->MainFrameWidget()->SetFocus(true);
web_view->MainFrameWidget()->SetFocus(false);
web_view->MainFrameWidget()->SetFocus(true);
WebElement element =
web_view->MainFrameImpl()->GetDocument().GetElementById("message");
EXPECT_EQ("focusoutfocusin", element.TextContent());
}
TEST_F(WebViewTest, DispatchesDomFocusOutDomFocusInOnViewToggleFocus) {
RegisterMockedHttpURLLoad("domfocusout_domfocusin_events.html");
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "domfocusout_domfocusin_events.html");
web_view->MainFrameWidget()->SetFocus(true);
web_view->MainFrameWidget()->SetFocus(false);
web_view->MainFrameWidget()->SetFocus(true);
WebElement element =
web_view->MainFrameImpl()->GetDocument().GetElementById("message");
EXPECT_EQ("DOMFocusOutDOMFocusIn", element.TextContent());
}
static void OpenDateTimeChooser(WebView* web_view,
HTMLInputElement* input_element) {
input_element->focus();
WebKeyboardEvent key_event(WebInputEvent::Type::kRawKeyDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
key_event.dom_key = ui::DomKey::FromCharacter(' ');
key_event.windows_key_code = VKEY_SPACE;
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo()));
key_event.SetType(WebInputEvent::Type::kKeyUp);
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo()));
}
TEST_F(WebViewTest, ChooseValueFromDateTimeChooser) {
ScopedInputMultipleFieldsUIForTest input_multiple_fields_ui(false);
std::string url = RegisterMockedHttpURLLoad("date_time_chooser.html");
WebViewImpl* web_view_impl =
web_view_helper_.InitializeAndLoad(url, nullptr, nullptr);
Document* document =
web_view_impl->MainFrameImpl()->GetFrame()->GetDocument();
auto* input_element = To<HTMLInputElement>(document->getElementById("date"));
OpenDateTimeChooser(web_view_impl, input_element);
GetExternalDateTimeChooser(web_view_impl)->ResponseHandler(true, 0);
EXPECT_EQ("1970-01-01", input_element->value());
OpenDateTimeChooser(web_view_impl, input_element);
GetExternalDateTimeChooser(web_view_impl)
->ResponseHandler(true, std::numeric_limits<double>::quiet_NaN());
EXPECT_EQ("", input_element->value());
input_element =
To<HTMLInputElement>(document->getElementById("datetimelocal"));
OpenDateTimeChooser(web_view_impl, input_element);
GetExternalDateTimeChooser(web_view_impl)->ResponseHandler(true, 0);
EXPECT_EQ("1970-01-01T00:00", input_element->value());
OpenDateTimeChooser(web_view_impl, input_element);
GetExternalDateTimeChooser(web_view_impl)
->ResponseHandler(true, std::numeric_limits<double>::quiet_NaN());
EXPECT_EQ("", input_element->value());
input_element = To<HTMLInputElement>(document->getElementById("month"));
OpenDateTimeChooser(web_view_impl, input_element);
GetExternalDateTimeChooser(web_view_impl)->ResponseHandler(true, 0);
EXPECT_EQ("1970-01", input_element->value());
OpenDateTimeChooser(web_view_impl, input_element);
GetExternalDateTimeChooser(web_view_impl)
->ResponseHandler(true, std::numeric_limits<double>::quiet_NaN());
EXPECT_EQ("", input_element->value());
input_element = To<HTMLInputElement>(document->getElementById("time"));
OpenDateTimeChooser(web_view_impl, input_element);
GetExternalDateTimeChooser(web_view_impl)->ResponseHandler(true, 0);
EXPECT_EQ("00:00", input_element->value());
OpenDateTimeChooser(web_view_impl, input_element);
GetExternalDateTimeChooser(web_view_impl)
->ResponseHandler(true, std::numeric_limits<double>::quiet_NaN());
EXPECT_EQ("", input_element->value());
input_element = To<HTMLInputElement>(document->getElementById("week"));
OpenDateTimeChooser(web_view_impl, input_element);
GetExternalDateTimeChooser(web_view_impl)->ResponseHandler(true, 0);
EXPECT_EQ("1970-W01", input_element->value());
OpenDateTimeChooser(web_view_impl, input_element);
GetExternalDateTimeChooser(web_view_impl)
->ResponseHandler(true, std::numeric_limits<double>::quiet_NaN());
EXPECT_EQ("", input_element->value());
// Clear the WebViewClient from the webViewHelper to avoid use-after-free in
// the WebViewHelper destructor.
web_view_helper_.Reset();
}
TEST_F(WebViewTest, DispatchesFocusBlurOnViewToggle) {
RegisterMockedHttpURLLoad("focus_blur_events.html");
WebViewImpl* web_view =
web_view_helper_.InitializeAndLoad(base_url_ + "focus_blur_events.html");
web_view->MainFrameWidget()->SetFocus(true);
web_view->MainFrameWidget()->SetFocus(false);
web_view->MainFrameWidget()->SetFocus(true);
WebElement element =
web_view->MainFrameImpl()->GetDocument().GetElementById("message");
// Expect not to see duplication of events.
EXPECT_EQ("blurfocus", element.TextContent());
}
class CreateChildCounterFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
CreateChildCounterFrameClient() : count_(0) {}
WebLocalFrame* CreateChildFrame(
mojom::blink::TreeScopeType,
const WebString& name,
const WebString& fallback_name,
const FramePolicy&,
const WebFrameOwnerProperties&,
mojom::blink::FrameOwnerElementType,
WebPolicyContainerBindParams policy_container_bind_params) override;
int Count() const { return count_; }
private:
int count_;
};
WebLocalFrame* CreateChildCounterFrameClient::CreateChildFrame(
mojom::blink::TreeScopeType scope,
const WebString& name,
const WebString& fallback_name,
const FramePolicy& frame_policy,
const WebFrameOwnerProperties& frame_owner_properties,
mojom::blink::FrameOwnerElementType frame_owner_element_type,
WebPolicyContainerBindParams policy_container_bind_params) {
++count_;
return TestWebFrameClient::CreateChildFrame(
scope, name, fallback_name, frame_policy, frame_owner_properties,
frame_owner_element_type, std::move(policy_container_bind_params));
}
TEST_F(WebViewTest, ChangeDisplayMode) {
RegisterMockedHttpURLLoad("display_mode.html");
WebViewImpl* web_view =
web_view_helper_.InitializeAndLoad(base_url_ + "display_mode.html");
String content = TestWebFrameContentDumper::DumpWebViewAsText(web_view, 21);
EXPECT_EQ("regular-ui", content);
web_view->MainFrameImpl()->LocalRootFrameWidget()->SetDisplayMode(
mojom::blink::DisplayMode::kMinimalUi);
content = TestWebFrameContentDumper::DumpWebViewAsText(web_view, 21);
EXPECT_EQ("minimal-ui", content);
web_view_helper_.Reset();
}
TEST_F(WebViewTest, ChangeDisplayModeChildFrame) {
RegisterMockedHttpURLLoad("iframe-display_mode.html");
RegisterMockedHttpURLLoad("display_mode.html");
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "iframe-display_mode.html");
String content = TestWebFrameContentDumper::DumpWebViewAsText(web_view, 21);
// An iframe inserts whitespace into the content.
EXPECT_EQ("regular-ui", content.StripWhiteSpace());
web_view->MainFrameImpl()->LocalRootFrameWidget()->SetDisplayMode(
mojom::blink::DisplayMode::kMinimalUi);
content = TestWebFrameContentDumper::DumpWebViewAsText(web_view, 21);
// An iframe inserts whitespace into the content.
EXPECT_EQ("minimal-ui", content.StripWhiteSpace());
web_view_helper_.Reset();
}
TEST_F(WebViewTest, ChangeDisplayModeAlertsListener) {
RegisterMockedHttpURLLoad("display_mode_listener.html");
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "display_mode_listener.html");
String content = TestWebFrameContentDumper::DumpWebViewAsText(web_view, 21);
EXPECT_EQ("regular-ui", content);
web_view->MainFrameImpl()->LocalRootFrameWidget()->SetDisplayMode(
mojom::blink::DisplayMode::kMinimalUi);
content = TestWebFrameContentDumper::DumpWebViewAsText(web_view, 21);
EXPECT_EQ("minimal-ui", content);
web_view_helper_.Reset();
}
TEST_F(WebViewTest, ChangeDisplayModeChildFrameAlertsListener) {
RegisterMockedHttpURLLoad("iframe-display_mode_listener.html");
RegisterMockedHttpURLLoad("display_mode_listener.html");
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "iframe-display_mode_listener.html");
String content = TestWebFrameContentDumper::DumpWebViewAsText(web_view, 21);
// An iframe inserts whitespace into the content.
EXPECT_EQ("regular-ui", content.StripWhiteSpace());
web_view->MainFrameImpl()->LocalRootFrameWidget()->SetDisplayMode(
mojom::blink::DisplayMode::kMinimalUi);
content = TestWebFrameContentDumper::DumpWebViewAsText(web_view, 21);
// An iframe inserts whitespace into the content.
EXPECT_EQ("minimal-ui", content.StripWhiteSpace());
web_view_helper_.Reset();
}
TEST_F(WebViewTest, AddFrameInCloseUnload) {
CreateChildCounterFrameClient frame_client;
RegisterMockedHttpURLLoad("add_frame_in_unload.html");
web_view_helper_.InitializeAndLoad(base_url_ + "add_frame_in_unload.html",
&frame_client);
web_view_helper_.Reset();
EXPECT_EQ(0, frame_client.Count());
}
TEST_F(WebViewTest, AddFrameInCloseURLUnload) {
CreateChildCounterFrameClient frame_client;
RegisterMockedHttpURLLoad("add_frame_in_unload.html");
web_view_helper_.InitializeAndLoad(base_url_ + "add_frame_in_unload.html",
&frame_client);
// Dispatch unload event.
web_view_helper_.LocalMainFrame()->GetFrame()->ClosePage(base::DoNothing());
EXPECT_EQ(0, frame_client.Count());
web_view_helper_.Reset();
}
TEST_F(WebViewTest, AddFrameInNavigateUnload) {
CreateChildCounterFrameClient frame_client;
RegisterMockedHttpURLLoad("add_frame_in_unload.html");
web_view_helper_.InitializeAndLoad(base_url_ + "add_frame_in_unload.html",
&frame_client);
frame_test_helpers::LoadFrame(web_view_helper_.GetWebView()->MainFrameImpl(),
"about:blank");
EXPECT_EQ(0, frame_client.Count());
web_view_helper_.Reset();
}
TEST_F(WebViewTest, AddFrameInChildInNavigateUnload) {
CreateChildCounterFrameClient frame_client;
RegisterMockedHttpURLLoad("add_frame_in_unload_wrapper.html");
RegisterMockedHttpURLLoad("add_frame_in_unload.html");
web_view_helper_.InitializeAndLoad(
base_url_ + "add_frame_in_unload_wrapper.html", &frame_client);
frame_test_helpers::LoadFrame(web_view_helper_.GetWebView()->MainFrameImpl(),
"about:blank");
EXPECT_EQ(1, frame_client.Count());
web_view_helper_.Reset();
}
class TouchEventConsumersWebFrameWidgetHost
: public frame_test_helpers::TestWebFrameWidgetHost {
public:
int GetAndResetHasTouchEventHandlerCallCount(bool state) {
int value = has_touch_event_handler_count_[state];
has_touch_event_handler_count_[state] = 0;
return value;
}
// mojom::FrameWidgetHost overrides:
void SetHasTouchEventConsumers(
mojom::blink::TouchEventConsumersPtr consumers) override {
// Only count the times the state changes.
bool state = consumers->has_touch_event_handlers;
if (state != has_touch_event_handler_)
has_touch_event_handler_count_[state]++;
has_touch_event_handler_ = state;
}
private:
int has_touch_event_handler_count_[2]{};
bool has_touch_event_handler_ = false;
};
class TouchEventConsumersWebFrameWidget
: public frame_test_helpers::TestWebFrameWidget {
public:
template <typename... Args>
explicit TouchEventConsumersWebFrameWidget(Args&&... args)
: frame_test_helpers::TestWebFrameWidget(std::forward<Args>(args)...) {}
// frame_test_helpers::TestWebFrameWidget overrides.
std::unique_ptr<frame_test_helpers::TestWebFrameWidgetHost> CreateWidgetHost()
override {
return std::make_unique<TouchEventConsumersWebFrameWidgetHost>();
}
TouchEventConsumersWebFrameWidgetHost& TouchEventWidgetHost() {
return static_cast<TouchEventConsumersWebFrameWidgetHost&>(WidgetHost());
}
};
class TouchEventConsumersWebViewTest : public WebViewTest {
public:
TouchEventConsumersWebViewTest()
: WebViewTest(base::BindRepeating(
&frame_test_helpers::WebViewHelper::CreateTestWebFrameWidget<
TouchEventConsumersWebFrameWidget>)) {}
};
// This test verifies that FrameWidgetHost::SetHasTouchEventConsumers is called
// accordingly for various calls to EventHandlerRegistry::did{Add|Remove|
// RemoveAll}EventHandler(..., TouchEvent). Verifying that those calls are made
// correctly is the job of web_tests/fast/events/event-handler-count.html.
TEST_F(TouchEventConsumersWebViewTest, SetHasTouchEventConsumers) {
std::string url = RegisterMockedHttpURLLoad("has_touch_event_handlers.html");
WebViewImpl* web_view_impl = web_view_helper_.InitializeAndLoad(url);
TouchEventConsumersWebFrameWidget* widget =
static_cast<TouchEventConsumersWebFrameWidget*>(
web_view_helper_.GetMainFrameWidget());
TouchEventConsumersWebFrameWidgetHost& frame_widget_host =
widget->TouchEventWidgetHost();
const EventHandlerRegistry::EventHandlerClass kTouchEvent =
EventHandlerRegistry::kTouchStartOrMoveEventBlocking;
// The page is initialized with at least one no-handlers call.
// In practice we get two such calls because WebViewHelper::initializeAndLoad
// first initializes an empty frame, and then loads a document into it, so
// there are two FrameLoader::commitProvisionalLoad calls.
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(false));
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(true));
// Adding the first document handler results in a has-handlers call.
Document* document =
web_view_impl->MainFrameImpl()->GetFrame()->GetDocument();
EventHandlerRegistry* registry =
&document->GetFrame()->GetEventHandlerRegistry();
registry->DidAddEventHandler(*document, kTouchEvent);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(false));
EXPECT_EQ(1,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(true));
// Adding another handler has no effect.
registry->DidAddEventHandler(*document, kTouchEvent);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(false));
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(true));
// Removing the duplicate handler has no effect.
registry->DidRemoveEventHandler(*document, kTouchEvent);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(false));
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(true));
// Removing the final handler results in a no-handlers call.
registry->DidRemoveEventHandler(*document, kTouchEvent);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(false));
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(true));
// Adding a handler on a div results in a has-handlers call.
Element* parent_div = document->getElementById("parentdiv");
DCHECK(parent_div);
registry->DidAddEventHandler(*parent_div, kTouchEvent);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(false));
EXPECT_EQ(1,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(true));
// Adding a duplicate handler on the div, clearing all document handlers
// (of which there are none) and removing the extra handler on the div
// all have no effect.
registry->DidAddEventHandler(*parent_div, kTouchEvent);
registry->DidRemoveAllEventHandlers(*document);
registry->DidRemoveEventHandler(*parent_div, kTouchEvent);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(false));
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(true));
// Removing the final handler on the div results in a no-handlers call.
registry->DidRemoveEventHandler(*parent_div, kTouchEvent);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(false));
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(true));
// Adding two handlers then clearing them in a single call results in a
// has-handlers then no-handlers call.
registry->DidAddEventHandler(*parent_div, kTouchEvent);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(false));
EXPECT_EQ(1,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(true));
registry->DidAddEventHandler(*parent_div, kTouchEvent);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(false));
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(true));
registry->DidRemoveAllEventHandlers(*parent_div);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(false));
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(true));
// Adding a handler inside of a child iframe results in a has-handlers call.
Element* child_frame = document->getElementById("childframe");
DCHECK(child_frame);
Document* child_document =
To<HTMLIFrameElement>(child_frame)->contentDocument();
Element* child_div = child_document->getElementById("childdiv");
DCHECK(child_div);
registry->DidAddEventHandler(*child_div, kTouchEvent);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(false));
EXPECT_EQ(1,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(true));
// Adding and clearing handlers in the parent doc or elsewhere in the child
// doc has no impact.
registry->DidAddEventHandler(*document, kTouchEvent);
registry->DidAddEventHandler(*child_frame, kTouchEvent);
registry->DidAddEventHandler(*child_document, kTouchEvent);
registry->DidRemoveAllEventHandlers(*document);
registry->DidRemoveAllEventHandlers(*child_frame);
registry->DidRemoveAllEventHandlers(*child_document);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(false));
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(true));
// Removing the final handler inside the child frame results in a no-handlers
// call.
registry->DidRemoveAllEventHandlers(*child_div);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(false));
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(true));
// Adding a handler inside the child frame results in a has-handlers call.
registry->DidAddEventHandler(*child_document, kTouchEvent);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(false));
EXPECT_EQ(1,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(true));
// Adding a handler in the parent document and removing the one in the frame
// has no effect.
registry->DidAddEventHandler(*child_frame, kTouchEvent);
registry->DidRemoveEventHandler(*child_document, kTouchEvent);
registry->DidRemoveAllEventHandlers(*child_document);
registry->DidRemoveAllEventHandlers(*document);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(false));
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(true));
// Now removing the handler in the parent document results in a no-handlers
// call.
registry->DidRemoveEventHandler(*child_frame, kTouchEvent);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(false));
EXPECT_EQ(0,
frame_widget_host.GetAndResetHasTouchEventHandlerCallCount(true));
}
// This test checks that deleting nodes which have only non-JS-registered touch
// handlers also removes them from the event handler registry. Note that this
// is different from detaching and re-attaching the same node, which is covered
// by web tests under fast/events/.
TEST_F(WebViewTest, DeleteElementWithRegisteredHandler) {
std::string url = RegisterMockedHttpURLLoad("simple_div.html");
WebViewImpl* web_view_impl = web_view_helper_.InitializeAndLoad(url);
Persistent<Document> document =
web_view_impl->MainFrameImpl()->GetFrame()->GetDocument();
Element* div = document->getElementById("div");
EventHandlerRegistry& registry =
document->GetFrame()->GetEventHandlerRegistry();
registry.DidAddEventHandler(*div, EventHandlerRegistry::kScrollEvent);
EXPECT_TRUE(registry.HasEventHandlers(EventHandlerRegistry::kScrollEvent));
DummyExceptionStateForTesting exception_state;
div->remove(exception_state);
// For oilpan we have to force a GC to ensure the event handlers have been
// removed when checking below. We do a precise GC (collectAllGarbage does not
// scan the stack) to ensure the div element dies. This is also why the
// Document is in a Persistent since we want that to stay around.
ThreadState::Current()->CollectAllGarbageForTesting();
EXPECT_FALSE(registry.HasEventHandlers(EventHandlerRegistry::kScrollEvent));
}
// This test verifies the text input flags are correctly exposed to script.
TEST_F(WebViewTest, TextInputFlags) {
std::string url = RegisterMockedHttpURLLoad("text_input_flags.html");
WebViewImpl* web_view_impl = web_view_helper_.InitializeAndLoad(url);
web_view_impl->MainFrameImpl()->GetFrame()->SetInitialFocus(false);
WebLocalFrameImpl* frame = web_view_impl->MainFrameImpl();
WebInputMethodController* active_input_method_controller =
frame->GetInputMethodController();
Document* document = frame->GetFrame()->GetDocument();
// (A) <input>
// (A.1) Verifies autocorrect/autocomplete/spellcheck flags are Off and
// autocapitalize is set to none.
auto* input_element = To<HTMLInputElement>(document->getElementById("input"));
document->SetFocusedElement(
input_element, FocusParams(SelectionBehaviorOnFocus::kNone,
mojom::blink::FocusType::kNone, nullptr));
web_view_impl->MainFrameWidget()->SetFocus(true);
WebTextInputInfo info1 = active_input_method_controller->TextInputInfo();
EXPECT_EQ(kWebTextInputFlagAutocompleteOff | kWebTextInputFlagAutocorrectOff |
kWebTextInputFlagSpellcheckOff |
kWebTextInputFlagAutocapitalizeNone,
info1.flags);
// (A.2) Verifies autocorrect/autocomplete/spellcheck flags are On and
// autocapitalize is set to sentences.
input_element = To<HTMLInputElement>(document->getElementById("input2"));
document->SetFocusedElement(
input_element, FocusParams(SelectionBehaviorOnFocus::kNone,
mojom::blink::FocusType::kNone, nullptr));
web_view_impl->MainFrameWidget()->SetFocus(true);
WebTextInputInfo info2 = active_input_method_controller->TextInputInfo();
EXPECT_EQ(kWebTextInputFlagAutocompleteOn | kWebTextInputFlagAutocorrectOn |
kWebTextInputFlagSpellcheckOn |
kWebTextInputFlagAutocapitalizeSentences,
info2.flags);
// (B) <textarea> Verifies the default text input flags are
// WebTextInputFlagAutocapitalizeSentences.
auto* text_area_element =
To<HTMLTextAreaElement>(document->getElementById("textarea"));
document->SetFocusedElement(
text_area_element, FocusParams(SelectionBehaviorOnFocus::kNone,
mojom::blink::FocusType::kNone, nullptr));
web_view_impl->MainFrameWidget()->SetFocus(true);
WebTextInputInfo info3 = active_input_method_controller->TextInputInfo();
EXPECT_EQ(kWebTextInputFlagAutocapitalizeSentences, info3.flags);
// (C) Verifies the WebTextInputInfo's don't equal.
EXPECT_FALSE(info1.Equals(info2));
EXPECT_FALSE(info2.Equals(info3));
// Free the webView before freeing the NonUserInputTextUpdateWebViewClient.
web_view_helper_.Reset();
}
// Check that the WebAutofillClient is correctly notified about first user
// gestures after load, following various input events.
TEST_F(WebViewTest, FirstUserGestureObservedKeyEvent) {
RegisterMockedHttpURLLoad("form.html");
MockAutofillClient client;
WebViewImpl* web_view =
web_view_helper_.InitializeAndLoad(base_url_ + "form.html");
WebLocalFrameImpl* frame = web_view->MainFrameImpl();
frame->SetAutofillClient(&client);
web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false);
EXPECT_EQ(0, client.GetUserGestureNotificationsCount());
WebKeyboardEvent key_event(WebInputEvent::Type::kRawKeyDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
key_event.dom_key = ui::DomKey::FromCharacter(' ');
key_event.windows_key_code = VKEY_SPACE;
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo()));
key_event.SetType(WebInputEvent::Type::kKeyUp);
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo()));
EXPECT_EQ(1, client.GetUserGestureNotificationsCount());
frame->SetAutofillClient(nullptr);
}
TEST_F(WebViewTest, FirstUserGestureObservedMouseEvent) {
RegisterMockedHttpURLLoad("form.html");
MockAutofillClient client;
WebViewImpl* web_view =
web_view_helper_.InitializeAndLoad(base_url_ + "form.html");
WebLocalFrameImpl* frame = web_view->MainFrameImpl();
frame->SetAutofillClient(&client);
web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false);
EXPECT_EQ(0, client.GetUserGestureNotificationsCount());
WebMouseEvent mouse_event(WebInputEvent::Type::kMouseDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
mouse_event.button = WebMouseEvent::Button::kLeft;
mouse_event.SetPositionInWidget(1, 1);
mouse_event.click_count = 1;
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(mouse_event, ui::LatencyInfo()));
mouse_event.SetType(WebInputEvent::Type::kMouseUp);
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(mouse_event, ui::LatencyInfo()));
EXPECT_EQ(1, client.GetUserGestureNotificationsCount());
frame->SetAutofillClient(nullptr);
}
TEST_F(WebViewTest, CompositionIsUserGesture) {
RegisterMockedHttpURLLoad("input_field_populated.html");
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "input_field_populated.html");
WebLocalFrameImpl* frame = web_view->MainFrameImpl();
MockAutofillClient client;
frame->SetAutofillClient(&client);
web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false);
EXPECT_EQ(0, client.TextChanges());
EXPECT_TRUE(
frame->FrameWidget()->GetActiveWebInputMethodController()->SetComposition(
WebString::FromUTF8(std::string("hello").c_str()),
WebVector<ui::ImeTextSpan>(), WebRange(), 3, 3));
EXPECT_TRUE(frame->HasTransientUserActivation());
EXPECT_EQ(1, client.TextChanges());
EXPECT_TRUE(frame->HasMarkedText());
frame->SetAutofillClient(nullptr);
}
// Currently, SelectionAsText() is built upon TextIterator, but
// TestWebFrameContentDumper is built upon TextDumperForTests. Their results can
// be different, making the test fail.
// TODO(crbug.com/781434): Build a selection serializer upon TextDumperForTests.
TEST_F(WebViewTest, DISABLED_CompareSelectAllToContentAsText) {
RegisterMockedHttpURLLoad("longpress_selection.html");
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "longpress_selection.html");
WebLocalFrameImpl* frame = web_view->MainFrameImpl();
frame->ExecuteScript(WebScriptSource(
WebString::FromUTF8("document.execCommand('SelectAll', false, null)")));
std::string actual = frame->SelectionAsText().Utf8();
const int kMaxOutputCharacters = 1024;
std::string expected = TestWebFrameContentDumper::DumpWebViewAsText(
web_view, kMaxOutputCharacters)
.Utf8();
EXPECT_EQ(expected, actual);
}
TEST_F(WebViewTest, AutoResizeSubtreeLayout) {
std::string url = RegisterMockedHttpURLLoad("subtree-layout.html");
WebViewImpl* web_view = web_view_helper_.Initialize();
web_view->EnableAutoResizeMode(gfx::Size(200, 200), gfx::Size(200, 200));
LoadFrame(web_view->MainFrameImpl(), url);
LocalFrameView* frame_view =
web_view_helper_.LocalMainFrame()->GetFrameView();
// Auto-resizing used to DCHECK(needsLayout()) in LayoutBlockFlow::layout.
// This EXPECT is merely a dummy. The real test is that we don't trigger
// asserts in debug builds.
EXPECT_FALSE(frame_view->NeedsLayout());
}
TEST_F(WebViewTest, PreferredSize) {
std::string url = base_url_ + "specify_size.html?100px:100px";
url_test_helpers::RegisterMockedURLLoad(
ToKURL(url), test::CoreTestDataPath("specify_size.html"));
WebView* web_view = web_view_helper_.InitializeAndLoad(url);
gfx::Size size = web_view->ContentsPreferredMinimumSize();
EXPECT_EQ(100, size.width());
EXPECT_EQ(100, size.height());
web_view->SetZoomLevel(PageZoomFactorToZoomLevel(2.0));
size = web_view->ContentsPreferredMinimumSize();
EXPECT_EQ(200, size.width());
EXPECT_EQ(200, size.height());
// Verify that both width and height are rounded (in this case up)
web_view->SetZoomLevel(PageZoomFactorToZoomLevel(0.9995));
size = web_view->ContentsPreferredMinimumSize();
EXPECT_EQ(100, size.width());
EXPECT_EQ(100, size.height());
// Verify that both width and height are rounded (in this case down)
web_view->SetZoomLevel(PageZoomFactorToZoomLevel(1.0005));
size = web_view->ContentsPreferredMinimumSize();
EXPECT_EQ(100, size.width());
EXPECT_EQ(100, size.height());
url = base_url_ + "specify_size.html?1.5px:1.5px";
url_test_helpers::RegisterMockedURLLoad(
ToKURL(url), test::CoreTestDataPath("specify_size.html"));
web_view = web_view_helper_.InitializeAndLoad(url);
web_view->SetZoomLevel(PageZoomFactorToZoomLevel(1));
size = web_view->ContentsPreferredMinimumSize();
EXPECT_EQ(2, size.width());
EXPECT_EQ(2, size.height());
}
TEST_F(WebViewTest, PreferredMinimumSizeQuirksMode) {
WebViewImpl* web_view = web_view_helper_.Initialize();
web_view->MainFrameViewWidget()->Resize(gfx::Size(800, 600));
frame_test_helpers::LoadHTMLString(
web_view->MainFrameImpl(),
R"HTML(<html>
<body style="margin: 0px;">
<div style="width: 99px; height: 100px; display: inline-block;"></div>
</body>
</html>)HTML",
url_test_helpers::ToKURL("http://example.com/"));
gfx::Size size = web_view->ContentsPreferredMinimumSize();
EXPECT_EQ(99, size.width());
// When in quirks mode the preferred height stretches to fill the viewport.
EXPECT_EQ(600, size.height());
}
TEST_F(WebViewTest, PreferredSizeWithGrid) {
WebViewImpl* web_view = web_view_helper_.Initialize();
WebURL base_url = url_test_helpers::ToKURL("http://example.com/");
frame_test_helpers::LoadHTMLString(web_view->MainFrameImpl(),
R"HTML(<!DOCTYPE html>
<style>
html { writing-mode: vertical-rl; }
body { margin: 0px; }
</style>
<div style="width: 100px;">
<div style="display: grid; width: 100%;">
<div style="writing-mode: horizontal-tb; height: 100px;"></div>
</div>
</div>
)HTML",
base_url);
gfx::Size size = web_view->ContentsPreferredMinimumSize();
EXPECT_EQ(100, size.width());
EXPECT_EQ(100, size.height());
}
TEST_F(WebViewTest, PreferredSizeWithGridMinWidth) {
WebViewImpl* web_view = web_view_helper_.Initialize();
WebURL base_url = url_test_helpers::ToKURL("http://example.com/");
frame_test_helpers::LoadHTMLString(web_view->MainFrameImpl(),
R"HTML(<!DOCTYPE html>
<body style="margin: 0px;">
<div style="display: inline-grid; min-width: 200px;">
<div>item</div>
</div>
</body>
)HTML",
base_url);
gfx::Size size = web_view->ContentsPreferredMinimumSize();
EXPECT_EQ(200, size.width());
}
TEST_F(WebViewTest, PreferredSizeWithGridMinWidthFlexibleTracks) {
WebViewImpl* web_view = web_view_helper_.Initialize();
WebURL base_url = url_test_helpers::ToKURL("http://example.com/");
frame_test_helpers::LoadHTMLString(web_view->MainFrameImpl(),
R"HTML(<!DOCTYPE html>
<body style="margin: 0px;">
<div style="display: inline-grid; min-width: 200px; grid-template-columns: 1fr;">
<div>item</div>
</div>
</body>
)HTML",
base_url);
gfx::Size size = web_view->ContentsPreferredMinimumSize();
EXPECT_EQ(200, size.width());
}
#if BUILDFLAG(ENABLE_UNHANDLED_TAP)
// Helps set up any test that uses a mock Mojo implementation.
class MojoTestHelper {
public:
MojoTestHelper(const String& test_file,
frame_test_helpers::WebViewHelper& web_view_helper)
: web_view_helper_(web_view_helper) {
web_view_ =
web_view_helper.InitializeAndLoad(test_file.Utf8(), &web_frame_client_);
}
~MojoTestHelper() {
web_view_helper_.Reset(); // Remove dependency on locally scoped client.
}
WebViewImpl* WebView() const { return web_view_; }
private:
WebViewImpl* web_view_;
frame_test_helpers::WebViewHelper& web_view_helper_;
frame_test_helpers::TestWebFrameClient web_frame_client_;
};
// Mock implementation of the UnhandledTapNotifier Mojo receiver, for testing
// the ShowUnhandledTapUIIfNeeded notification.
class MockUnhandledTapNotifierImpl : public mojom::blink::UnhandledTapNotifier {
public:
MockUnhandledTapNotifierImpl() = default;
~MockUnhandledTapNotifierImpl() override = default;
void Bind(mojo::ScopedMessagePipeHandle handle) {
receiver_.Bind(mojo::PendingReceiver<mojom::blink::UnhandledTapNotifier>(
std::move(handle)));
}
void ShowUnhandledTapUIIfNeeded(
mojom::blink::UnhandledTapInfoPtr unhandled_tap_info) override {
was_unhandled_tap_ = true;
tapped_position_ = unhandled_tap_info->tapped_position_in_viewport;
element_text_run_length_ = unhandled_tap_info->element_text_run_length;
font_size_ = unhandled_tap_info->font_size_in_pixels;
}
bool WasUnhandledTap() const { return was_unhandled_tap_; }
int GetTappedXPos() const { return tapped_position_.x(); }
int GetTappedYPos() const { return tapped_position_.y(); }
int GetFontSize() const { return font_size_; }
int GetElementTextRunLength() const { return element_text_run_length_; }
void Reset() {
was_unhandled_tap_ = false;
tapped_position_ = IntPoint();
element_text_run_length_ = 0;
font_size_ = 0;
receiver_.reset();
}
private:
bool was_unhandled_tap_ = false;
gfx::Point tapped_position_;
int element_text_run_length_ = 0;
int font_size_ = 0;
mojo::Receiver<mojom::blink::UnhandledTapNotifier> receiver_{this};
};
// A Test Fixture for testing ShowUnhandledTapUIIfNeeded usages.
class ShowUnhandledTapTest : public WebViewTest {
public:
void SetUp() override {
WebViewTest::SetUp();
std::string test_file = "show_unhandled_tap.html";
RegisterMockedHttpURLLoad("Ahem.ttf");
RegisterMockedHttpURLLoad(test_file);
mojo_test_helper_.reset(new MojoTestHelper(
WebString::FromUTF8(base_url_ + test_file), web_view_helper_));
web_view_ = mojo_test_helper_->WebView();
web_view_->MainFrameViewWidget()->Resize(gfx::Size(500, 300));
web_view_->MainFrameWidget()->UpdateAllLifecyclePhases(
DocumentUpdateReason::kTest);
RunPendingTasks();
WebLocalFrameImpl* web_local_frame = web_view_->MainFrameImpl();
web_local_frame->GetFrame()
->GetBrowserInterfaceBroker()
.SetBinderForTesting(
mojom::blink::UnhandledTapNotifier::Name_,
WTF::BindRepeating(&MockUnhandledTapNotifierImpl::Bind,
WTF::Unretained(&mock_notifier_)));
}
void TearDown() override {
WebLocalFrameImpl* web_local_frame = web_view_->MainFrameImpl();
web_local_frame->GetFrame()
->GetBrowserInterfaceBroker()
.SetBinderForTesting(mojom::blink::UnhandledTapNotifier::Name_, {});
WebViewTest::TearDown();
}
protected:
// Tap on the given element by ID.
void Tap(const String& element_id) {
mock_notifier_.Reset();
EXPECT_TRUE(TapElementById(WebInputEvent::Type::kGestureTap, element_id));
}
// Set up a test script for the given |operation| with the given |handler|.
void SetTestScript(const String& operation, const String& handler) {
String test_key = operation + "-" + handler;
web_view_->MainFrameImpl()->ExecuteScript(
WebScriptSource(String("setTest('" + test_key + "');")));
}
// Test each mouse event combination with the given |handler|, and verify the
// |expected| outcome.
void TestEachMouseEvent(const String& handler, bool expected) {
SetTestScript("mousedown", handler);
Tap("target");
EXPECT_EQ(expected, mock_notifier_.WasUnhandledTap());
SetTestScript("mouseup", handler);
Tap("target");
EXPECT_EQ(expected, mock_notifier_.WasUnhandledTap());
SetTestScript("click", handler);
Tap("target");
EXPECT_EQ(expected, mock_notifier_.WasUnhandledTap());
}
WebViewImpl* web_view_;
MockUnhandledTapNotifierImpl mock_notifier_;
private:
std::unique_ptr<MojoTestHelper> mojo_test_helper_;
};
TEST_F(ShowUnhandledTapTest, ShowUnhandledTapUIIfNeeded) {
// Scroll the bottom into view so we can distinguish window coordinates from
// document coordinates.
Tap("bottom");
EXPECT_TRUE(mock_notifier_.WasUnhandledTap());
EXPECT_EQ(64, mock_notifier_.GetTappedXPos());
EXPECT_EQ(278, mock_notifier_.GetTappedYPos());
EXPECT_EQ(16, mock_notifier_.GetFontSize());
EXPECT_EQ(7, mock_notifier_.GetElementTextRunLength());
// Test basic tap handling and notification.
Tap("target");
EXPECT_TRUE(mock_notifier_.WasUnhandledTap());
EXPECT_EQ(144, mock_notifier_.GetTappedXPos());
EXPECT_EQ(82, mock_notifier_.GetTappedYPos());
// Test correct conversion of coordinates to viewport space under pinch-zoom.
constexpr float scale = 1.5f;
constexpr float visual_x = 6.f;
constexpr float visual_y = 10.f;
web_view_->SetPageScaleFactor(scale);
web_view_->SetVisualViewportOffset(gfx::PointF(visual_x, visual_y));
Tap("target");
// Ensure position didn't change as a result of scroll into view.
ASSERT_EQ(visual_x, web_view_->VisualViewportOffset().x());
ASSERT_EQ(visual_y, web_view_->VisualViewportOffset().y());
EXPECT_TRUE(mock_notifier_.WasUnhandledTap());
constexpr float expected_x = 144 * scale - (scale * visual_x);
constexpr float expected_y = 82 * scale - (scale * visual_y);
EXPECT_EQ(expected_x, mock_notifier_.GetTappedXPos());
EXPECT_EQ(expected_y, mock_notifier_.GetTappedYPos());
EXPECT_EQ(16, mock_notifier_.GetFontSize());
EXPECT_EQ(28, mock_notifier_.GetElementTextRunLength());
}
TEST_F(ShowUnhandledTapTest, ShowUnhandledTapUIIfNeededWithMutateDom) {
// Test dom mutation.
TestEachMouseEvent("mutateDom", false);
// Test without any DOM mutation.
TestEachMouseEvent("none", true);
}
TEST_F(ShowUnhandledTapTest, ShowUnhandledTapUIIfNeededWithMutateStyle) {
// Test style mutation.
TestEachMouseEvent("mutateStyle", false);
// Test checkbox:indeterminate style mutation.
TestEachMouseEvent("mutateIndeterminate", false);
// Test click div with :active style.
Tap("style_active");
EXPECT_FALSE(mock_notifier_.WasUnhandledTap());
}
TEST_F(ShowUnhandledTapTest, ShowUnhandledTapUIIfNeededWithPreventDefault) {
// Test swallowing.
TestEachMouseEvent("preventDefault", false);
// Test without any preventDefault.
TestEachMouseEvent("none", true);
}
TEST_F(ShowUnhandledTapTest, ShowUnhandledTapUIIfNeededWithNonTriggeringNodes) {
Tap("image");
EXPECT_FALSE(mock_notifier_.WasUnhandledTap());
Tap("editable");
EXPECT_FALSE(mock_notifier_.WasUnhandledTap());
Tap("focusable");
EXPECT_FALSE(mock_notifier_.WasUnhandledTap());
}
TEST_F(ShowUnhandledTapTest, ShowUnhandledTapUIIfNeededWithTextSizes) {
Tap("large");
EXPECT_TRUE(mock_notifier_.WasUnhandledTap());
EXPECT_EQ(20, mock_notifier_.GetFontSize());
Tap("small");
EXPECT_TRUE(mock_notifier_.WasUnhandledTap());
EXPECT_EQ(10, mock_notifier_.GetFontSize());
}
#endif // BUILDFLAG(ENABLE_UNHANDLED_TAP)
TEST_F(WebViewTest, ShouldSuppressKeyboardForPasswordField) {
RegisterMockedHttpURLLoad("input_field_password.html");
// Pretend client has fill data for all fields it's queried.
MockAutofillClient client;
client.SetShouldSuppressKeyboard(true);
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "input_field_password.html");
WebLocalFrameImpl* frame = web_view->MainFrameImpl();
frame->SetAutofillClient(&client);
// No field is focused.
EXPECT_FALSE(frame->ShouldSuppressKeyboardForFocusedElement());
// Focusing a field should result in treating it autofillable.
web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false);
EXPECT_TRUE(frame->ShouldSuppressKeyboardForFocusedElement());
// Pretend that |client| no longer has autofill data available.
client.SetShouldSuppressKeyboard(false);
EXPECT_FALSE(frame->ShouldSuppressKeyboardForFocusedElement());
frame->SetAutofillClient(nullptr);
}
TEST_F(WebViewTest, PasswordFieldEditingIsUserGesture) {
RegisterMockedHttpURLLoad("input_field_password.html");
MockAutofillClient client;
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "input_field_password.html");
WebLocalFrameImpl* frame = web_view->MainFrameImpl();
frame->SetAutofillClient(&client);
web_view->MainFrameImpl()->GetFrame()->SetInitialFocus(false);
WebVector<ui::ImeTextSpan> empty_ime_text_spans;
EXPECT_EQ(0, client.TextChanges());
EXPECT_TRUE(
frame->FrameWidget()->GetActiveWebInputMethodController()->CommitText(
WebString::FromUTF8(std::string("hello").c_str()),
empty_ime_text_spans, WebRange(), 0));
EXPECT_TRUE(frame->HasTransientUserActivation());
EXPECT_EQ(1, client.TextChanges());
frame->SetAutofillClient(nullptr);
}
// Verify that a WebView created with a ScopedPagePauser already on the
// stack defers its loads.
TEST_F(WebViewTest, CreatedDuringPagePause) {
{
WebViewImpl* web_view = web_view_helper_.Initialize();
EXPECT_FALSE(web_view->GetPage()->Paused());
}
{
ScopedPagePauser pauser;
WebViewImpl* web_view = web_view_helper_.Initialize();
EXPECT_TRUE(web_view->GetPage()->Paused());
}
}
// Make sure the SubframeBeforeUnloadUseCounter is only incremented on subframe
// unloads. crbug.com/635029.
TEST_F(WebViewTest, SubframeBeforeUnloadUseCounter) {
RegisterMockedHttpURLLoad("visible_iframe.html");
RegisterMockedHttpURLLoad("single_iframe.html");
WebViewImpl* web_view =
web_view_helper_.InitializeAndLoad(base_url_ + "single_iframe.html");
WebLocalFrame* frame = web_view_helper_.LocalMainFrame();
Document* document =
To<LocalFrame>(web_view_helper_.GetWebView()->GetPage()->MainFrame())
->GetDocument();
// Add a beforeunload handler in the main frame. Make sure firing
// beforeunload doesn't increment the subframe use counter.
{
frame->ExecuteScript(
WebScriptSource("addEventListener('beforeunload', function() {});"));
web_view->MainFrameImpl()->DispatchBeforeUnloadEvent(false);
EXPECT_FALSE(
document->IsUseCounted(WebFeature::kSubFrameBeforeUnloadFired));
}
// Add a beforeunload handler in the iframe and dispatch. Make sure we do
// increment the use counter for subframe beforeunloads.
{
frame->ExecuteScript(WebScriptSource(
"document.getElementsByTagName('iframe')[0].contentWindow."
"addEventListener('beforeunload', function() {});"));
To<WebLocalFrameImpl>(
web_view->MainFrame()->FirstChild()->ToWebLocalFrame())
->DispatchBeforeUnloadEvent(false);
Document* child_document = To<LocalFrame>(web_view_helper_.GetWebView()
->GetPage()
->MainFrame()
->Tree()
.FirstChild())
->GetDocument();
EXPECT_TRUE(
child_document->IsUseCounted(WebFeature::kSubFrameBeforeUnloadFired));
}
}
// Verify that page loads are deferred until all ScopedPageLoadDeferrers are
// destroyed.
TEST_F(WebViewTest, NestedPagePauses) {
WebViewImpl* web_view = web_view_helper_.Initialize();
EXPECT_FALSE(web_view->GetPage()->Paused());
{
ScopedPagePauser pauser;
EXPECT_TRUE(web_view->GetPage()->Paused());
{
ScopedPagePauser pauser2;
EXPECT_TRUE(web_view->GetPage()->Paused());
}
EXPECT_TRUE(web_view->GetPage()->Paused());
}
EXPECT_FALSE(web_view->GetPage()->Paused());
}
TEST_F(WebViewTest, ClosingPageIsPaused) {
WebViewImpl* web_view = web_view_helper_.Initialize();
Page* page = web_view_helper_.GetWebView()->GetPage();
EXPECT_FALSE(page->Paused());
web_view->SetOpenedByDOM();
auto* main_frame = To<LocalFrame>(page->MainFrame());
EXPECT_FALSE(main_frame->DomWindow()->closed());
ScriptState* script_state = ToScriptStateForMainWorld(main_frame);
ScriptState::Scope entered_context_scope(script_state);
v8::Context::BackupIncumbentScope incumbent_context_scope(
script_state->GetContext());
main_frame->DomWindow()->close(script_state->GetIsolate());
// The window should be marked closed...
EXPECT_TRUE(main_frame->DomWindow()->closed());
// EXPECT_TRUE(page->isClosing());
// ...but not yet detached.
EXPECT_TRUE(main_frame->GetPage());
{
ScopedPagePauser pauser;
EXPECT_TRUE(page->Paused());
}
}
TEST_F(WebViewTest, ForceAndResetViewport) {
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));
SetViewportSize(gfx::Size(100, 150));
DevToolsEmulator* dev_tools_emulator = web_view_impl->GetDevToolsEmulator();
TransformationMatrix expected_matrix;
expected_matrix.MakeIdentity();
EXPECT_EQ(expected_matrix, web_view_impl->GetDeviceEmulationTransform());
{
IntRect visible_rect(1, 2, 3, 4);
dev_tools_emulator->OverrideVisibleRect(IntSize(), &visible_rect);
EXPECT_EQ(IntRect(1, 2, 3, 4), visible_rect); // Was modified.
}
// Override applies transform, sets visible rect, and disables
// visual viewport clipping.
TransformationMatrix matrix =
dev_tools_emulator->ForceViewportForTesting(gfx::PointF(50, 55), 2.f);
expected_matrix.MakeIdentity().Scale(2.f).Translate(-50, -55);
EXPECT_EQ(expected_matrix, matrix);
{
IntRect visible_rect(1, 2, 3, 4);
dev_tools_emulator->OverrideVisibleRect(IntSize(100, 150), &visible_rect);
EXPECT_EQ(IntRect(50, 55, 100, 150), visible_rect);
}
// Setting new override discards previous one.
matrix = dev_tools_emulator->ForceViewportForTesting(gfx::PointF(5.4f, 10.5f),
1.5f);
expected_matrix.MakeIdentity().Scale(1.5f).Translate(-5.4f, -10.5f);
EXPECT_EQ(expected_matrix, matrix);
{
IntRect visible_rect(1, 2, 3, 4);
dev_tools_emulator->OverrideVisibleRect(IntSize(100, 150), &visible_rect);
EXPECT_EQ(IntRect(5, 10, 101, 151), visible_rect); // Was modified.
}
// Clearing override restores original transform, visible rect and
// visual viewport clipping.
matrix = dev_tools_emulator->ResetViewportForTesting();
expected_matrix.MakeIdentity();
EXPECT_EQ(expected_matrix, matrix);
{
IntRect visible_rect(1, 2, 3, 4);
dev_tools_emulator->OverrideVisibleRect(IntSize(), &visible_rect);
EXPECT_EQ(IntRect(1, 2, 3, 4), visible_rect); // Not modified.
}
}
TEST_F(WebViewTest, ViewportOverrideIntegratesDeviceMetricsOffsetAndScale) {
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));
TransformationMatrix expected_matrix;
expected_matrix.MakeIdentity();
EXPECT_EQ(expected_matrix, web_view_impl->GetDeviceEmulationTransform());
DeviceEmulationParams emulation_params;
emulation_params.scale = 2.f;
web_view_impl->EnableDeviceEmulation(emulation_params);
expected_matrix.MakeIdentity().Scale(2.f);
EXPECT_EQ(expected_matrix, web_view_impl->GetDeviceEmulationTransform());
// Device metrics offset and scale are applied before viewport override.
emulation_params.viewport_offset = gfx::PointF(5, 10);
emulation_params.viewport_scale = 1.5f;
web_view_impl->EnableDeviceEmulation(emulation_params);
expected_matrix.MakeIdentity().Scale(1.5f).Translate(-5, -10).Scale(2.f);
EXPECT_EQ(expected_matrix, web_view_impl->GetDeviceEmulationTransform());
}
TEST_F(WebViewTest, ViewportOverrideAdaptsToScaleAndScroll) {
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));
SetViewportSize(gfx::Size(100, 150));
LocalFrameView* frame_view =
web_view_impl->MainFrameImpl()->GetFrame()->View();
DevToolsEmulator* dev_tools_emulator = web_view_impl->GetDevToolsEmulator();
TransformationMatrix expected_matrix;
expected_matrix.MakeIdentity();
EXPECT_EQ(expected_matrix, web_view_impl->GetDeviceEmulationTransform());
// Initial transform takes current page scale and scroll position into
// account.
web_view_impl->SetPageScaleFactor(1.5f);
frame_view->LayoutViewport()->SetScrollOffset(
ScrollOffset(100, 150), mojom::blink::ScrollType::kProgrammatic,
mojom::blink::ScrollBehavior::kInstant);
DeviceEmulationParams emulation_params;
emulation_params.viewport_offset = gfx::PointF(50, 55);
emulation_params.viewport_scale = 2.f;
web_view_impl->EnableDeviceEmulation(emulation_params);
expected_matrix.MakeIdentity()
.Scale(2.f)
.Translate(-50, -55)
.Translate(100, 150)
.Scale(1. / 1.5f);
EXPECT_EQ(expected_matrix, web_view_impl->GetDeviceEmulationTransform());
// Scale is irrelevant for visible rect.
{
IntRect visible_rect(1, 2, 3, 4);
dev_tools_emulator->OverrideVisibleRect(IntSize(100, 150), &visible_rect);
EXPECT_EQ(IntRect(50 - 100, 55 - 150, 100, 150), visible_rect);
}
// Transform adapts to scroll changes.
frame_view->LayoutViewport()->SetScrollOffset(
ScrollOffset(50, 55), mojom::blink::ScrollType::kProgrammatic,
mojom::blink::ScrollBehavior::kInstant);
expected_matrix.MakeIdentity()
.Scale(2.f)
.Translate(-50, -55)
.Translate(50, 55)
.Scale(1. / 1.5f);
EXPECT_EQ(expected_matrix, web_view_impl->GetDeviceEmulationTransform());
// Visible rect adapts to scroll change.
{
IntRect visible_rect(1, 2, 3, 4);
dev_tools_emulator->OverrideVisibleRect(IntSize(100, 150), &visible_rect);
EXPECT_EQ(IntRect(50 - 50, 55 - 55, 100, 150), visible_rect);
}
// Transform adapts to page scale changes.
web_view_impl->SetPageScaleFactor(2.f);
expected_matrix.MakeIdentity()
.Scale(2.f)
.Translate(-50, -55)
.Translate(50, 55)
.Scale(1. / 2.f);
EXPECT_EQ(expected_matrix, web_view_impl->GetDeviceEmulationTransform());
// Visible rect doesn't change.
{
IntRect visible_rect(1, 2, 3, 4);
dev_tools_emulator->OverrideVisibleRect(IntSize(100, 150), &visible_rect);
EXPECT_EQ(IntRect(50 - 50, 55 - 55, 100, 150), visible_rect);
}
}
TEST_F(WebViewTest, ResizeForPrintingViewportUnits) {
WebViewImpl* web_view = web_view_helper_.Initialize();
web_view->MainFrameViewWidget()->Resize(gfx::Size(800, 600));
WebURL base_url = url_test_helpers::ToKURL("http://example.com/");
frame_test_helpers::LoadHTMLString(web_view->MainFrameImpl(),
"<style>"
" body { margin: 0px; }"
" #vw { width: 100vw; height: 100vh; }"
"</style>"
"<div id=vw></div>",
base_url);
WebLocalFrameImpl* frame = web_view->MainFrameImpl();
Document* document = frame->GetFrame()->GetDocument();
Element* vw_element = document->getElementById("vw");
EXPECT_EQ(800, vw_element->OffsetWidth());
gfx::Size page_size(300, 360);
WebPrintParams print_params;
print_params.print_content_area.set_size(page_size);
IntSize expected_size = PrintICBSizeFromPageSize(FloatSize(page_size));
frame->PrintBegin(print_params, WebNode());
EXPECT_EQ(expected_size.Width(), vw_element->OffsetWidth());
EXPECT_EQ(expected_size.Height(), vw_element->OffsetHeight());
web_view->MainFrameWidget()->Resize(page_size);
EXPECT_EQ(expected_size.Width(), vw_element->OffsetWidth());
EXPECT_EQ(expected_size.Height(), vw_element->OffsetHeight());
web_view->MainFrameViewWidget()->Resize(gfx::Size(800, 600));
frame->PrintEnd();
EXPECT_EQ(800, vw_element->OffsetWidth());
}
TEST_F(WebViewTest, WidthMediaQueryWithPageZoomAfterPrinting) {
WebViewImpl* web_view = web_view_helper_.Initialize();
web_view->MainFrameViewWidget()->Resize(gfx::Size(800, 600));
web_view->SetZoomLevel(PageZoomFactorToZoomLevel(2.0));
WebURL base_url = url_test_helpers::ToKURL("http://example.com/");
frame_test_helpers::LoadHTMLString(web_view->MainFrameImpl(),
"<style>"
" @media (max-width: 600px) {"
" div { color: green }"
" }"
"</style>"
"<div id=d></div>",
base_url);
WebLocalFrameImpl* frame = web_view->MainFrameImpl();
Document* document = frame->GetFrame()->GetDocument();
Element* div = document->getElementById("d");
EXPECT_EQ(MakeRGB(0, 128, 0), div->GetComputedStyle()->VisitedDependentColor(
GetCSSPropertyColor()));
gfx::Size page_size(300, 360);
WebPrintParams print_params;
print_params.print_content_area.set_size(page_size);
frame->PrintBegin(print_params, WebNode());
frame->PrintEnd();
EXPECT_EQ(MakeRGB(0, 128, 0), div->GetComputedStyle()->VisitedDependentColor(
GetCSSPropertyColor()));
}
TEST_F(WebViewTest, ViewportUnitsPrintingWithPageZoom) {
WebViewImpl* web_view = web_view_helper_.Initialize();
web_view->MainFrameViewWidget()->Resize(gfx::Size(800, 600));
web_view->SetZoomLevel(PageZoomFactorToZoomLevel(2.0));
WebURL base_url = url_test_helpers::ToKURL("http://example.com/");
frame_test_helpers::LoadHTMLString(web_view->MainFrameImpl(),
"<style>"
" body { margin: 0 }"
" #t1 { width: 100% }"
" #t2 { width: 100vw }"
"</style>"
"<div id=t1></div>"
"<div id=t2></div>",
base_url);
WebLocalFrameImpl* frame = web_view->MainFrameImpl();
Document* document = frame->GetFrame()->GetDocument();
Element* t1 = document->getElementById("t1");
Element* t2 = document->getElementById("t2");
EXPECT_EQ(400, t1->OffsetWidth());
EXPECT_EQ(400, t2->OffsetWidth());
gfx::Size page_size(600, 720);
int expected_width = PrintICBSizeFromPageSize(FloatSize(page_size)).Width();
WebPrintParams print_params;
print_params.print_content_area.set_size(page_size);
frame->PrintBegin(print_params, WebNode());
EXPECT_EQ(expected_width, t1->OffsetWidth());
EXPECT_EQ(expected_width, t2->OffsetWidth());
frame->PrintEnd();
}
TEST_F(WebViewTest, DeviceEmulationResetScrollbars) {
WebViewImpl* web_view = web_view_helper_.Initialize();
web_view->MainFrameViewWidget()->Resize(gfx::Size(800, 600));
WebURL base_url = url_test_helpers::ToKURL("http://example.com/");
frame_test_helpers::LoadHTMLString(web_view->MainFrameImpl(),
"<!doctype html>"
"<meta name='viewport'"
" content='width=device-width'>"
"<style>"
" body {margin: 0px; height:3000px;}"
"</style>",
base_url);
UpdateAllLifecyclePhases();
WebLocalFrameImpl* frame = web_view->MainFrameImpl();
auto* frame_view = frame->GetFrameView();
EXPECT_FALSE(frame_view->VisualViewportSuppliesScrollbars());
EXPECT_NE(nullptr, frame_view->LayoutViewport()->VerticalScrollbar());
DeviceEmulationParams params;
params.screen_type = mojom::EmulatedScreenType::kMobile;
params.device_scale_factor = 0;
params.scale = 1;
web_view->EnableDeviceEmulation(params);
// The visual viewport should now proivde the scrollbars instead of the view.
EXPECT_TRUE(frame_view->VisualViewportSuppliesScrollbars());
EXPECT_EQ(nullptr, frame_view->LayoutViewport()->VerticalScrollbar());
web_view->DisableDeviceEmulation();
// The view should once again provide the scrollbars.
EXPECT_FALSE(frame_view->VisualViewportSuppliesScrollbars());
EXPECT_NE(nullptr, frame_view->LayoutViewport()->VerticalScrollbar());
}
TEST_F(WebViewTest, SetZoomLevelWhilePluginFocused) {
class PluginCreatingWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
// WebLocalFrameClient overrides:
WebPlugin* CreatePlugin(const WebPluginParams& params) override {
return new FakeWebPlugin(params);
}
};
PluginCreatingWebFrameClient frame_client;
WebViewImpl* web_view = web_view_helper_.Initialize(&frame_client);
WebURL base_url = url_test_helpers::ToKURL("https://example.com/");
frame_test_helpers::LoadHTMLString(
web_view->MainFrameImpl(),
"<!DOCTYPE html><html><body>"
"<object type='application/x-webkit-test-plugin'></object>"
"</body></html>",
base_url);
// Verify the plugin is loaded.
LocalFrame* main_frame = web_view->MainFrameImpl()->GetFrame();
auto* plugin_element =
To<HTMLObjectElement>(main_frame->GetDocument()->body()->firstChild());
EXPECT_TRUE(plugin_element->OwnedPlugin());
// Focus the plugin element, and then change the zoom level on the WebView.
plugin_element->focus();
EXPECT_FLOAT_EQ(1.0f, main_frame->PageZoomFactor());
web_view->SetZoomLevel(-1.0);
// Even though the plugin is focused, the entire frame's zoom factor should
// still be updated.
EXPECT_FLOAT_EQ(5.0f / 6.0f, main_frame->PageZoomFactor());
web_view_helper_.Reset(); // Remove dependency on locally scoped client.
}
// Tests that a layout update that detaches a plugin doesn't crash if the
// plugin tries to execute script while being destroyed.
TEST_F(WebViewTest, DetachPluginInLayout) {
class ScriptInDestroyPlugin : public FakeWebPlugin {
public:
ScriptInDestroyPlugin(WebLocalFrame* frame, const WebPluginParams& params)
: FakeWebPlugin(params), frame_(frame) {}
// WebPlugin overrides:
void Destroy() override {
frame_->ExecuteScript(WebScriptSource("console.log('done')"));
// Deletes this.
FakeWebPlugin::Destroy();
}
private:
WebLocalFrame* frame_; // Unowned
};
class PluginCreatingWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
// WebLocalFrameClient overrides:
WebPlugin* CreatePlugin(const WebPluginParams& params) override {
return new ScriptInDestroyPlugin(Frame(), params);
}
void DidAddMessageToConsole(const WebConsoleMessage& message,
const WebString& source_name,
unsigned source_line,
const WebString& stack_trace) override {
message_ = message.text;
}
const String& Message() const { return message_; }
private:
String message_;
};
PluginCreatingWebFrameClient frame_client;
WebViewImpl* web_view = web_view_helper_.Initialize(&frame_client);
WebURL base_url = url_test_helpers::ToKURL("https://example.com/");
frame_test_helpers::LoadHTMLString(
web_view->MainFrameImpl(),
"<!DOCTYPE html><html><body>"
"<object type='application/x-webkit-test-plugin'></object>"
"</body></html>",
base_url);
// Verify the plugin is loaded.
LocalFrame* main_frame = web_view->MainFrameImpl()->GetFrame();
auto* plugin_element =
To<HTMLObjectElement>(main_frame->GetDocument()->body()->firstChild());
EXPECT_TRUE(plugin_element->OwnedPlugin());
plugin_element->style()->setCSSText(main_frame->DomWindow(), "display: none",
ASSERT_NO_EXCEPTION);
EXPECT_TRUE(plugin_element->OwnedPlugin());
UpdateAllLifecyclePhases();
EXPECT_FALSE(plugin_element->OwnedPlugin());
EXPECT_EQ("done", frame_client.Message());
web_view_helper_.Reset(); // Remove dependency on locally scoped client.
}
// Check that first input delay is correctly reported to the document.
TEST_F(WebViewTest, FirstInputDelayReported) {
WebViewImpl* web_view = web_view_helper_.Initialize();
WebURL base_url = url_test_helpers::ToKURL("http://example.com/");
frame_test_helpers::LoadHTMLString(web_view->MainFrameImpl(),
"<html><body></body></html>", base_url);
LocalFrame* main_frame = web_view->MainFrameImpl()->GetFrame();
ASSERT_NE(nullptr, main_frame);
Document* document = main_frame->GetDocument();
ASSERT_NE(nullptr, document);
base::TimeTicks start_time = test_task_runner_->NowTicks();
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(70));
InteractiveDetector* interactive_detector =
GetTestInteractiveDetector(*document);
EXPECT_FALSE(interactive_detector->GetFirstInputDelay().has_value());
WebKeyboardEvent key_event1(WebInputEvent::Type::kRawKeyDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
key_event1.dom_key = ui::DomKey::FromCharacter(' ');
key_event1.windows_key_code = VKEY_SPACE;
key_event1.SetTimeStamp(test_task_runner_->NowTicks());
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(50));
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event1, ui::LatencyInfo()));
EXPECT_TRUE(interactive_detector->GetFirstInputDelay().has_value());
EXPECT_NEAR(50,
(*interactive_detector->GetFirstInputDelay()).InMillisecondsF(),
0.01);
EXPECT_EQ(70, (*interactive_detector->GetFirstInputTimestamp() - start_time)
.InMillisecondsF());
// Sending a second event won't change the FirstInputDelay.
WebKeyboardEvent key_event2(WebInputEvent::Type::kRawKeyDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
key_event2.dom_key = ui::DomKey::FromCharacter(' ');
key_event2.windows_key_code = VKEY_SPACE;
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(60));
key_event2.SetTimeStamp(test_task_runner_->NowTicks());
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event2, ui::LatencyInfo()));
EXPECT_NEAR(50,
(*interactive_detector->GetFirstInputDelay()).InMillisecondsF(),
0.01);
EXPECT_EQ(70, (*interactive_detector->GetFirstInputTimestamp() - start_time)
.InMillisecondsF());
}
// Check that longest input delay is correctly reported to the document.
TEST_F(WebViewTest, LongestInputDelayReported) {
WebViewImpl* web_view = web_view_helper_.Initialize();
WebURL base_url = url_test_helpers::ToKURL("http://example.com/");
frame_test_helpers::LoadHTMLString(web_view->MainFrameImpl(),
"<html><body></body></html>", base_url);
LocalFrame* main_frame = web_view->MainFrameImpl()->GetFrame();
ASSERT_NE(nullptr, main_frame);
Document* document = main_frame->GetDocument();
ASSERT_NE(nullptr, document);
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(70));
InteractiveDetector* interactive_detector =
GetTestInteractiveDetector(*document);
EXPECT_FALSE(interactive_detector->GetLongestInputDelay().has_value());
WebKeyboardEvent key_event1(WebInputEvent::Type::kRawKeyDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
key_event1.dom_key = ui::DomKey::FromCharacter(' ');
key_event1.windows_key_code = VKEY_SPACE;
key_event1.SetTimeStamp(test_task_runner_->NowTicks());
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(50));
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event1, ui::LatencyInfo()));
base::TimeTicks longest_input_timestamp = test_task_runner_->NowTicks();
WebKeyboardEvent key_event2(WebInputEvent::Type::kRawKeyDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
key_event2.dom_key = ui::DomKey::FromCharacter(' ');
key_event2.windows_key_code = VKEY_SPACE;
key_event2.SetTimeStamp(longest_input_timestamp);
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(100));
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event2, ui::LatencyInfo()));
WebKeyboardEvent key_event3(WebInputEvent::Type::kRawKeyDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
key_event3.dom_key = ui::DomKey::FromCharacter(' ');
key_event3.windows_key_code = VKEY_SPACE;
key_event3.SetTimeStamp(test_task_runner_->NowTicks());
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(70));
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event3, ui::LatencyInfo()));
EXPECT_NEAR(100,
(*interactive_detector->GetLongestInputDelay()).InMillisecondsF(),
0.01);
EXPECT_EQ(longest_input_timestamp,
interactive_detector->GetLongestInputTimestamp());
}
TEST_F(WebViewTest, InputDelayReported) {
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(50));
WebViewImpl* web_view = web_view_helper_.Initialize();
WebURL base_url = url_test_helpers::ToKURL("http://example.com/");
frame_test_helpers::LoadHTMLString(web_view->MainFrameImpl(),
"<html><body></body></html>", base_url,
test_task_runner_->GetMockTickClock());
LocalFrame* main_frame = web_view->MainFrameImpl()->GetFrame();
ASSERT_NE(nullptr, main_frame);
Document* document = main_frame->GetDocument();
ASSERT_NE(nullptr, document);
GetTestInteractiveDetector(*document);
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(70));
HistogramTester histogram_tester;
WebKeyboardEvent key_event1(WebInputEvent::Type::kRawKeyDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
key_event1.dom_key = ui::DomKey::FromCharacter(' ');
key_event1.windows_key_code = VKEY_SPACE;
key_event1.SetTimeStamp(test_task_runner_->NowTicks());
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(50));
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event1, ui::LatencyInfo()));
WebKeyboardEvent key_event2(WebInputEvent::Type::kRawKeyDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
key_event2.dom_key = ui::DomKey::FromCharacter(' ');
key_event2.windows_key_code = VKEY_SPACE;
key_event2.SetTimeStamp(test_task_runner_->NowTicks());
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(50));
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event2, ui::LatencyInfo()));
WebKeyboardEvent key_event3(WebInputEvent::Type::kRawKeyDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
key_event3.dom_key = ui::DomKey::FromCharacter(' ');
key_event3.windows_key_code = VKEY_SPACE;
key_event3.SetTimeStamp(test_task_runner_->NowTicks());
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(70));
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event3, ui::LatencyInfo()));
histogram_tester.ExpectTotalCount("PageLoad.InteractiveTiming.InputDelay3",
3);
histogram_tester.ExpectBucketCount("PageLoad.InteractiveTiming.InputDelay3",
50, 2);
histogram_tester.ExpectBucketCount("PageLoad.InteractiveTiming.InputDelay3",
70, 1);
histogram_tester.ExpectTotalCount(
"PageLoad.InteractiveTiming.InputTimestamp3", 3);
histogram_tester.ExpectBucketCount(
"PageLoad.InteractiveTiming.InputTimestamp3", 70, 1);
histogram_tester.ExpectBucketCount(
"PageLoad.InteractiveTiming.InputTimestamp3", 120, 1);
histogram_tester.ExpectBucketCount(
"PageLoad.InteractiveTiming.InputTimestamp3", 170, 1);
}
// Tests that if the page was backgrounded while an input event was queued,
// we do not count its delay to calculate longest input delay.
TEST_F(WebViewTest, LongestInputDelayPageBackgroundedDuringQueuing) {
WebViewImpl* web_view = web_view_helper_.Initialize();
WebURL base_url = url_test_helpers::ToKURL("http://example.com/");
frame_test_helpers::LoadHTMLString(web_view->MainFrameImpl(),
"<html><body></body></html>", base_url);
LocalFrame* main_frame = web_view->MainFrameImpl()->GetFrame();
ASSERT_NE(nullptr, main_frame);
Document* document = main_frame->GetDocument();
ASSERT_NE(nullptr, document);
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(70));
InteractiveDetector* interactive_detector =
GetTestInteractiveDetector(*document);
EXPECT_FALSE(interactive_detector->GetLongestInputDelay().has_value());
WebKeyboardEvent key_event1(WebInputEvent::Type::kRawKeyDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
key_event1.dom_key = ui::DomKey::FromCharacter(' ');
key_event1.windows_key_code = VKEY_SPACE;
base::TimeTicks key_event1_time = test_task_runner_->NowTicks();
key_event1.SetTimeStamp(key_event1_time);
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(50));
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event1, ui::LatencyInfo()));
WebKeyboardEvent key_event2(WebInputEvent::Type::kRawKeyDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
key_event2.dom_key = ui::DomKey::FromCharacter(' ');
key_event2.windows_key_code = VKEY_SPACE;
key_event2.SetTimeStamp(test_task_runner_->NowTicks());
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(100));
web_view->SetVisibilityState(mojom::blink::PageVisibilityState::kHidden,
/*initial_state=*/false);
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(100));
web_view->SetVisibilityState(mojom::blink::PageVisibilityState::kVisible,
/*initial_state=*/false);
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(100));
// Total input delay is >300ms.
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event2, ui::LatencyInfo()));
EXPECT_NEAR(50,
(*interactive_detector->GetLongestInputDelay()).InMillisecondsF(),
0.01);
EXPECT_EQ(key_event1_time, interactive_detector->GetLongestInputTimestamp());
}
// Tests that if the page was backgrounded at navigation start and an input
// event was queued before it was foregrounded, we do not count its delay to
// calculate longest input delay.
TEST_F(WebViewTest, LongestInputDelayPageBackgroundedAtNavStart) {
WebViewImpl* web_view = web_view_helper_.Initialize();
web_view->SetVisibilityState(mojom::blink::PageVisibilityState::kHidden,
/*initial_state=*/false);
WebURL base_url = url_test_helpers::ToKURL("http://example.com/");
frame_test_helpers::LoadHTMLString(web_view->MainFrameImpl(),
"<html><body></body></html>", base_url);
LocalFrame* main_frame = web_view->MainFrameImpl()->GetFrame();
ASSERT_NE(nullptr, main_frame);
Document* document = main_frame->GetDocument();
ASSERT_NE(nullptr, document);
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(70));
InteractiveDetector* interactive_detector =
GetTestInteractiveDetector(*document);
WebKeyboardEvent key_event(WebInputEvent::Type::kRawKeyDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
key_event.dom_key = ui::DomKey::FromCharacter(' ');
key_event.windows_key_code = VKEY_SPACE;
key_event.SetTimeStamp(test_task_runner_->NowTicks());
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(100));
web_view->SetVisibilityState(mojom::blink::PageVisibilityState::kVisible,
/*initial_state=*/false);
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo()));
EXPECT_FALSE(interactive_detector->GetLongestInputDelay().has_value());
}
// Tests page backgrounding outside of input queuing time does not affect
// longest input delay.
TEST_F(WebViewTest, LongestInputDelayPageBackgroundedNotDuringQueuing) {
WebViewImpl* web_view = web_view_helper_.Initialize();
WebURL base_url = url_test_helpers::ToKURL("http://example.com/");
frame_test_helpers::LoadHTMLString(web_view->MainFrameImpl(),
"<html><body></body></html>", base_url);
LocalFrame* main_frame = web_view->MainFrameImpl()->GetFrame();
ASSERT_NE(nullptr, main_frame);
Document* document = main_frame->GetDocument();
ASSERT_NE(nullptr, document);
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(70));
InteractiveDetector* interactive_detector =
GetTestInteractiveDetector(*document);
EXPECT_FALSE(interactive_detector->GetLongestInputDelay().has_value());
web_view->SetVisibilityState(mojom::blink::PageVisibilityState::kHidden,
/*initial_state=*/false);
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(100));
web_view->SetVisibilityState(mojom::blink::PageVisibilityState::kVisible,
/*initial_state=*/false);
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(1));
WebKeyboardEvent key_event(WebInputEvent::Type::kRawKeyDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
key_event.dom_key = ui::DomKey::FromCharacter(' ');
key_event.windows_key_code = VKEY_SPACE;
base::TimeTicks key_event_time = test_task_runner_->NowTicks();
key_event.SetTimeStamp(key_event_time);
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(50));
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo()));
EXPECT_NEAR(50,
(*interactive_detector->GetLongestInputDelay()).InMillisecondsF(),
0.01);
EXPECT_EQ(key_event_time, interactive_detector->GetLongestInputTimestamp());
}
// TODO(npm): Improve this test to receive real input sequences and avoid hacks.
// Check that first input delay is correctly reported to the document when the
// first input is a pointer down event, and we receive a pointer up event.
TEST_F(WebViewTest, PointerDownUpFirstInputDelay) {
WebViewImpl* web_view = web_view_helper_.Initialize();
WebURL base_url = url_test_helpers::ToKURL("http://example.com/");
frame_test_helpers::LoadHTMLString(web_view->MainFrameImpl(),
"<html><body></body></html>", base_url);
// Add an event listener for pointerdown to ensure it is not optimized out
// before reaching the EventDispatcher.
WebLocalFrame* frame = web_view_helper_.LocalMainFrame();
frame->ExecuteScript(
WebScriptSource("addEventListener('pointerdown', function() {});"));
LocalFrame* main_frame = web_view->MainFrameImpl()->GetFrame();
ASSERT_NE(nullptr, main_frame);
Document* document = main_frame->GetDocument();
ASSERT_NE(nullptr, document);
base::TimeTicks start_time = test_task_runner_->NowTicks();
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(70));
InteractiveDetector* interactive_detector =
GetTestInteractiveDetector(*document);
WebPointerEvent pointer_down(
WebInputEvent::Type::kPointerDown,
WebPointerProperties(1, WebPointerProperties::PointerType::kTouch), 5, 5);
pointer_down.SetTimeStamp(test_task_runner_->NowTicks());
// Set this to the left button, needed for testing to behave properly.
pointer_down.SetModifiers(WebInputEvent::kLeftButtonDown);
pointer_down.button = WebPointerProperties::Button::kLeft;
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(50));
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(pointer_down, ui::LatencyInfo()));
// We don't know if this pointer event will result in a scroll or not, so we
// can't report its delay. We don't consider a scroll to be meaningful input.
EXPECT_FALSE(interactive_detector->GetFirstInputDelay().has_value());
// When we receive a pointer up, we report the delay of the pointer down.
WebPointerEvent pointer_up(
WebInputEvent::Type::kPointerUp,
WebPointerProperties(1, WebPointerProperties::PointerType::kTouch), 5, 5);
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(60));
pointer_up.SetTimeStamp(test_task_runner_->NowTicks());
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(pointer_up, ui::LatencyInfo()));
EXPECT_NEAR(50,
(*interactive_detector->GetFirstInputDelay()).InMillisecondsF(),
0.01);
EXPECT_EQ(70, (*interactive_detector->GetFirstInputTimestamp() - start_time)
.InMillisecondsF());
}
// We need a way for JS to advance the mock clock. Hook into console.log, so
// that logging advances the clock by |event_handling_delay| seconds.
class MockClockAdvancingWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
MockClockAdvancingWebFrameClient(
scoped_refptr<base::TestMockTimeTaskRunner> task_runner,
base::TimeDelta event_handling_delay)
: task_runner_(std::move(task_runner)),
event_handling_delay_(event_handling_delay) {}
// WebLocalFrameClient overrides:
void DidAddMessageToConsole(const WebConsoleMessage& message,
const WebString& source_name,
unsigned source_line,
const WebString& stack_trace) override {
task_runner_->FastForwardBy(event_handling_delay_);
}
private:
scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
base::TimeDelta event_handling_delay_;
};
// Check that the input delay is correctly reported to the document.
TEST_F(WebViewTest, FirstInputDelayExcludesProcessingTime) {
// Page load timing logic depends on the time not being zero.
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(1));
MockClockAdvancingWebFrameClient frame_client(
test_task_runner_, base::TimeDelta::FromMilliseconds(6000));
WebViewImpl* web_view = web_view_helper_.Initialize(&frame_client);
WebURL base_url = url_test_helpers::ToKURL("http://example.com/");
frame_test_helpers::LoadHTMLString(web_view->MainFrameImpl(),
"<html><body></body></html>", base_url,
test_task_runner_->GetMockTickClock());
LocalFrame* main_frame = web_view->MainFrameImpl()->GetFrame();
ASSERT_NE(nullptr, main_frame);
Document* document = main_frame->GetDocument();
ASSERT_NE(nullptr, document);
WebLocalFrame* frame = web_view_helper_.LocalMainFrame();
// console.log will advance the mock clock.
frame->ExecuteScript(
WebScriptSource("document.addEventListener('keydown', "
"() => {console.log('advancing timer');})"));
InteractiveDetector* interactive_detector =
GetTestInteractiveDetector(*document);
WebKeyboardEvent key_event(WebInputEvent::Type::kRawKeyDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
key_event.dom_key = ui::DomKey::FromCharacter(' ');
key_event.windows_key_code = VKEY_SPACE;
key_event.SetTimeStamp(test_task_runner_->NowTicks());
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(5000));
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo()));
EXPECT_TRUE(interactive_detector->GetFirstInputDelay().has_value());
base::TimeDelta first_input_delay =
*interactive_detector->GetFirstInputDelay();
EXPECT_EQ(5000, first_input_delay.InMillisecondsF());
web_view_helper_.Reset(); // Remove dependency on locally scoped client.
}
// Check that the longest input delay is correctly reported to the document.
TEST_F(WebViewTest, LongestInputDelayExcludesProcessingTime) {
// Page load timing logic depends on the time not being zero.
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(1));
MockClockAdvancingWebFrameClient frame_client(
test_task_runner_, base::TimeDelta::FromMilliseconds(6000));
WebViewImpl* web_view = web_view_helper_.Initialize(&frame_client);
WebURL base_url = url_test_helpers::ToKURL("http://example.com/");
frame_test_helpers::LoadHTMLString(web_view->MainFrameImpl(),
"<html><body></body></html>", base_url);
LocalFrame* main_frame = web_view->MainFrameImpl()->GetFrame();
ASSERT_NE(nullptr, main_frame);
Document* document = main_frame->GetDocument();
ASSERT_NE(nullptr, document);
WebLocalFrame* frame = web_view_helper_.LocalMainFrame();
// console.log will advance the mock clock.
frame->ExecuteScript(
WebScriptSource("document.addEventListener('keydown', "
"() => {console.log('advancing timer');})"));
InteractiveDetector* interactive_detector =
GetTestInteractiveDetector(*document);
WebKeyboardEvent key_event(WebInputEvent::Type::kRawKeyDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
key_event.dom_key = ui::DomKey::FromCharacter(' ');
key_event.windows_key_code = VKEY_SPACE;
key_event.SetTimeStamp(test_task_runner_->NowTicks());
test_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(5000));
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event, ui::LatencyInfo()));
base::TimeDelta longest_input_delay =
*interactive_detector->GetLongestInputDelay();
EXPECT_EQ(5000, longest_input_delay.InMillisecondsF());
web_view_helper_.Reset(); // Remove dependency on locally scoped client.
}
TEST_F(WebViewTest, RootLayerAttachment) {
WebView* web_view = web_view_helper_.InitializeAndLoad("about:blank");
// Do a lifecycle update that includes compositing but not paint. Hit test
// events are an example of a real case where this occurs
// (see: WebViewTest::ClientTapHandling).
web_view->MainFrameWidget()->UpdateLifecycle(WebLifecycleUpdate::kPrePaint,
DocumentUpdateReason::kTest);
// Layers (including the root layer) should not be attached until the paint
// lifecycle phase.
cc::LayerTreeHost* layer_tree_host = web_view_helper_.GetLayerTreeHost();
EXPECT_FALSE(layer_tree_host->root_layer());
// Do a full lifecycle update and ensure that the root layer has been added.
web_view->MainFrameWidget()->UpdateLifecycle(WebLifecycleUpdate::kAll,
DocumentUpdateReason::kTest);
EXPECT_TRUE(layer_tree_host->root_layer());
}
// Verifies that we emit Blink.UseCounter.FeaturePolicy.PotentialAnimation for
// CSS and JS animations in a document.
// TODO(crbug.com/1066620): enable this test after metrics for document policy
// are added. Needs to update histogram name to document policy equivalent
// when re-enabled.
TEST_F(WebViewTest, DISABLED_PotentialViolationReportsForLayoutAnimations) {
const char* kHistogramName =
"Blink.UseCounter.FeaturePolicy.PotentialViolation";
WebViewImpl* web_view = web_view_helper_.Initialize();
// A page with non-violating animation does not generate report.
WebURL base_url_no_violation =
url_test_helpers::ToKURL("http://good-css.example.com/");
frame_test_helpers::LoadHTMLString(
web_view->MainFrameImpl(),
"<html><head><style>@keyframes foo {from "
"{color: blue;} to {color: red}}</style></head></html>",
base_url_no_violation);
HistogramTester histogram_tester;
histogram_tester.ExpectTotalCount(kHistogramName, 0);
// Page with 2 potential (CSS) layout-animation violations.
WebURL base_url_css_violations =
url_test_helpers::ToKURL("http://bad-css.example.com/");
frame_test_helpers::LoadHTMLString(
web_view->MainFrameImpl(),
"<html><head><style>@keyframes bar {"
"from{height: 100px;} to {height: 200px;}}"
"@keyframes baz {from{top: 100px;} to {top: 200px;}}"
"</style></head></html>",
base_url_css_violations);
histogram_tester.ExpectTotalCount(kHistogramName, 1);
// Page with a JS layout-animations violation.
WebURL base_url_js_violations =
url_test_helpers::ToKURL("http://js.example.com/");
frame_test_helpers::LoadHTMLString(
web_view->MainFrameImpl(),
"<html><body><div></div><script>document.body.firstChild.animate("
"{top: '100px'});</script></body></html>",
base_url_js_violations);
histogram_tester.ExpectTotalCount(kHistogramName, 2);
}
TEST_F(WebViewTest, ForceDarkModeInvalidatesPaint) {
WebViewImpl* web_view = web_view_helper_.Initialize();
web_view->MainFrameViewWidget()->Resize(gfx::Size(500, 500));
UpdateAllLifecyclePhases();
Document* document = web_view->MainFrameImpl()->GetFrame()->GetDocument();
ASSERT_TRUE(document);
web_view->GetSettings()->SetForceDarkModeEnabled(true);
EXPECT_TRUE(document->GetLayoutView()->ShouldDoFullPaintInvalidation());
}
// Regression test for https://crbug.com/1012068
TEST_F(WebViewTest, LongPressImageAndThenLongTapImage) {
RegisterMockedHttpURLLoad("long_press_image.html");
WebViewImpl* web_view =
web_view_helper_.InitializeAndLoad(base_url_ + "long_press_image.html");
web_view->SettingsImpl()->SetAlwaysShowContextMenuOnTouch(false);
web_view->MainFrameViewWidget()->Resize(gfx::Size(500, 300));
UpdateAllLifecyclePhases();
RunPendingTasks();
WebGestureEvent event(WebInputEvent::Type::kGestureLongPress,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
WebGestureDevice::kTouchscreen);
event.SetPositionInWidget(gfx::PointF(10, 10));
EXPECT_EQ(WebInputEventResult::kHandledSystem,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(event, ui::LatencyInfo())));
EXPECT_TRUE(
web_view->GetPage()->GetContextMenuController().ContextMenuNodeForFrame(
web_view->MainFrameImpl()->GetFrame()));
WebGestureEvent tap_event(WebInputEvent::Type::kGestureLongTap,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
WebGestureDevice::kTouchscreen);
tap_event.SetPositionInWidget(gfx::PointF(10, 10));
EXPECT_EQ(WebInputEventResult::kNotHandled,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(tap_event, ui::LatencyInfo())));
EXPECT_TRUE(
web_view->GetPage()->GetContextMenuController().ContextMenuNodeForFrame(
web_view->MainFrameImpl()->GetFrame()));
}
// Regression test for http://crbug.com/41562
TEST_F(WebViewTest, UpdateTargetURLWithInvalidURL) {
WebViewImpl* web_view = web_view_helper_.Initialize();
const KURL invalid_kurl("http://");
web_view->UpdateTargetURL(blink::WebURL(invalid_kurl),
/* fallback_url=*/blink::WebURL());
EXPECT_EQ(invalid_kurl, web_view->target_url_);
}
// Regression test for https://crbug.com/1112987
TEST_F(WebViewTest, LongPressAndThenLongTapLinkInIframeShouldShowContextMenu) {
RegisterMockedHttpURLLoad("long_press_link_in_iframe.html");
WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
base_url_ + "long_press_link_in_iframe.html");
web_view->SettingsImpl()->SetTouchDragDropEnabled(true);
web_view->MainFrameViewWidget()->Resize(gfx::Size(500, 300));
UpdateAllLifecyclePhases();
RunPendingTasks();
WebLocalFrameImpl* frame = web_view->MainFrameImpl();
Document* document = frame->GetFrame()->GetDocument();
Element* child_frame = document->getElementById("childframe");
DCHECK(child_frame);
Document* child_document =
To<HTMLIFrameElement>(child_frame)->contentDocument();
Element* anchor = child_document->getElementById("anchorTag");
IntPoint center =
To<WebLocalFrameImpl>(
web_view->MainFrame()->FirstChild()->ToWebLocalFrame())
->GetFrameView()
->FrameToScreen(anchor->GetLayoutObject()->AbsoluteBoundingBoxRect())
.Center();
WebGestureEvent event(WebInputEvent::Type::kGestureLongPress,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
WebGestureDevice::kTouchscreen);
event.SetPositionInWidget(gfx::PointF(center.X(), center.X()));
EXPECT_EQ(WebInputEventResult::kHandledSystem,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(event, ui::LatencyInfo())));
WebGestureEvent tap_event(WebInputEvent::Type::kGestureLongTap,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
WebGestureDevice::kTouchscreen);
tap_event.SetPositionInWidget(gfx::PointF(center.X(), center.X()));
EXPECT_EQ(WebInputEventResult::kNotHandled,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(tap_event, ui::LatencyInfo())));
EXPECT_EQ("anchor contextmenu",
web_view->MainFrameImpl()->GetDocument().Title());
}
} // namespace blink