blob: 7b99141df211e6ff44b6f0d3cfae5ee3aaecf1dd [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/bind_helpers.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/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_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/frame/frame_owner_element_type.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/page_zoom.h"
#include "third_party/blink/public/mojom/input/focus_type.mojom-blink.h"
#include "third_party/blink/public/mojom/manifest/display_mode.mojom-shared.h"
#include "third_party/blink/public/platform/web_coalesced_input_event.h"
#include "third_party/blink/public/platform/web_drag_data.h"
#include "third_party/blink/public/platform/web_drag_operation.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/web/web_autofill_client.h"
#include "third_party/blink/public/web/web_console_message.h"
#include "third_party/blink/public/web/web_device_emulation_params.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_frame_content_dumper.h"
#include "third_party/blink/public/web/web_frame_widget.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_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_tree_scope_type.h"
#include "third_party/blink/public/web/web_view_client.h"
#include "third_party/blink/public/web/web_widget.h"
#include "third_party/blink/public/web/web_widget_client.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_document.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/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_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/inspector/dev_tools_emulator.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/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/mojom/cursor_type.mojom-blink.h"
#include "ui/events/keycodes/dom/dom_key.h"
#include "v8/include/v8.h"
#if defined(OS_MACOSX)
#include "third_party/blink/public/web/mac/web_substring_util.h"
#endif
#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::url_test_helpers::ToKURL;
using blink::url_test_helpers::RegisterMockedURLLoad;
using blink::test::RunPendingTasks;
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 WebSize& 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:
WebSize size_;
WebViewImpl* web_view_;
};
class AutoResizeWebViewClient : public frame_test_helpers::TestWebViewClient {
public:
// WebViewClient methods
void DidAutoResize(const WebSize& new_size) override {
test_data_.SetSize(new_size);
}
// Local methods
TestData& GetTestData() { return test_data_; }
private:
TestData test_data_;
};
class TapHandlingWebWidgetClient
: public frame_test_helpers::TestWebWidgetClient {
public:
// WebWidgetClient overrides.
void DidHandleGestureEvent(const WebGestureEvent& event,
bool event_cancelled) override {
if (event.GetType() == WebInputEvent::kGestureTap) {
tap_x_ = event.PositionInWidget().x();
tap_y_ = event.PositionInWidget().y();
} else if (event.GetType() == WebInputEvent::kGestureLongPress) {
longpress_x_ = event.PositionInWidget().x();
longpress_y_ = event.PositionInWidget().y();
}
}
// Local methods
void Reset() {
tap_x_ = -1;
tap_y_ = -1;
longpress_x_ = -1;
longpress_y_ = -1;
}
int TapX() { return tap_x_; }
int TapY() { return tap_y_; }
int LongpressX() { return longpress_x_; }
int LongpressY() { return longpress_y_; }
private:
int tap_x_;
int tap_y_;
int longpress_x_;
int longpress_y_;
};
class WebViewTest : public testing::Test {
public:
WebViewTest() : base_url_("http://www.test.com/") {}
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 WebSize& 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_allocation_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 WebSize& min_auto_resize,
const WebSize& 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_;
frame_test_helpers::WebViewHelper web_view_helper_;
scoped_refptr<base::TestMockTimeTaskRunner> test_task_runner_;
};
static bool HitTestIsContentEditable(WebView* view, int x, int y) {
gfx::Point 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::Point hit_point(x, y);
WebHitTestResult hit_test_result =
view->MainFrameWidget()->HitTestResultAt(hit_point);
return hit_test_result.GetNode().To<WebElement>().GetAttribute("id").Utf8();
}
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");
WebView* web_view = web_view_helper_.InitializeAndLoad(url);
web_view->MainFrameWidget()->Resize(WebSize(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");
WebView* web_view = web_view_helper_.InitializeAndLoad(url);
web_view->MainFrameWidget()->Resize(WebSize(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::Point 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::Point 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");
WebView* web_view = web_view_helper_.InitializeAndLoad(url);
web_view->MainFrameWidget()->Resize(WebSize(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->MainFrameWidget()->Resize(WebSize(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->MainFrameWidget()->Resize(WebSize(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;
frame_test_helpers::TestWebWidgetClient web_widget_client;
WebViewImpl* web_view = static_cast<WebViewImpl*>(
WebView::Create(&web_view_client, false,
/*compositing_enabled=*/true, nullptr,
mojo::ScopedInterfaceEndpointHandle()));
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, nullptr);
web_frame_client.Bind(frame);
{
// Copy the steps done from WebViewHelper::InitializeWithOpener() to set up
// the appropriate pointers!
WebFrameWidget* widget =
blink::WebFrameWidget::CreateForMainFrame(&web_widget_client, frame);
widget->SetCompositorHosts(web_widget_client.layer_tree_host(),
web_widget_client.animation_host());
web_view->DidAttachLocalMainFrame();
}
// The color should be passed to the compositor.
cc::LayerTreeHost* host = web_widget_client.layer_tree_host();
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->MainFrameWidget()->Resize(WebSize(kWidth, kHeight));
UpdateAllLifecyclePhases();
// Set canvas background to red with alpha.
SkBitmap bitmap;
bitmap.allocN32Pixels(kWidth, kHeight);
SkCanvas canvas(bitmap);
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) {
ScopedCSSColorSchemeForTest enable_color_scheme(true);
WebViewImpl* web_view = web_view_helper_.Initialize();
ColorSchemeHelper color_scheme_helper(*(web_view->GetPage()));
color_scheme_helper.SetPreferredColorScheme(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(PreferredColorScheme::kDark);
UpdateAllLifecyclePhases();
EXPECT_EQ(Color::kBlack, 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::kBlack, frame_view->BaseBackgroundColor());
color_scheme_helper.SetPreferredColorScheme(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, 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"));
WebView* web_view = web_view_helper_.InitializeAndLoad(url);
web_view->MainFrameWidget()->Resize(WebSize(100, 100));
gfx::Point 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->MainFrameWidget()->Resize(WebSize(100, 100));
gfx::Point hit_point(75, 75);
// Image is at top left quandrant, so should not hit it.
WebHitTestResult negative_result = web_view->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->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->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");
WebView* web_view = web_view_helper_.InitializeAndLoad(url);
web_view->MainFrameWidget()->Resize(WebSize(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(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.
WebSize 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->MainFrameWidget()->Resize(WebSize(100, 100));
gfx::Point hit_point(55, 55);
// Image is at top left quandrant, so should not hit it.
WebHitTestResult negative_result = web_view->HitTestResultAt(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.
WebSize 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 WebSize& min_auto_resize,
const WebSize& 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) {
WebSize min_auto_resize(91, 56);
WebSize 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) {
WebSize min_auto_resize(90, 95);
WebSize 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) {
WebSize min_auto_resize(90, 100);
WebSize 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) {
WebSize min_auto_resize(90, 95);
WebSize 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) {
WebSize min_auto_resize(90, 95);
WebSize 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) {
WebSize min_auto_resize(90, 95);
WebSize 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->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->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->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->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->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->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->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->SetInitialFocus(false);
WebInputMethodController* active_input_method_controller =
web_view->MainFrameImpl()
->FrameWidget()
->GetActiveWebInputMethodController();
// The test requires non-empty composition.
std::string composition_text("hello");
WebVector<WebImeTextSpan> 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->SetInitialFocus(false);
web_view->MainFrameWidget()->Resize(WebSize(500, 300));
UpdateAllLifecyclePhases();
RunPendingTasks();
WebString input_id = WebString::FromUTF8("input");
// Focus in input.
EXPECT_TRUE(TapElementById(WebInputEvent::kGestureTap, input_id));
// Long press below input.
WebGestureEvent event(WebInputEvent::kGestureLongPress,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
WebGestureDevice::kTouchscreen);
event.SetPositionInWidget(gfx::PointF(100, 150));
EXPECT_EQ(WebInputEventResult::kHandledSystem,
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(event)));
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->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<WebImeTextSpan> 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->SetInitialFocus(false);
WebInputMethodController* active_input_method_controller =
web_view->MainFrameImpl()
->FrameWidget()
->GetActiveWebInputMethodController();
WebVector<WebImeTextSpan> 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->SetInitialFocus(false);
WebInputMethodController* active_input_method_controller =
web_view->MainFrameImpl()
->FrameWidget()
->GetActiveWebInputMethodController();
WebVector<WebImeTextSpan> 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->SetInitialFocus(false);
WebInputMethodController* active_input_method_controller =
web_view->MainFrameImpl()
->FrameWidget()
->GetActiveWebInputMethodController();
WebVector<WebImeTextSpan> 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->SetInitialFocus(false);
WebInputMethodController* active_input_method_controller =
web_view->MainFrameImpl()
->FrameWidget()
->GetActiveWebInputMethodController();
WebVector<WebImeTextSpan> 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->MainFrameWidget()->Resize(WebSize(800, 600));
web_view->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->SetInitialFocus(false);
WebVector<WebImeTextSpan> 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->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");
WebView* 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->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->SetInitialFocus(false);
WebVector<WebImeTextSpan> ime_text_spans(static_cast<size_t>(1));
ime_text_spans[0] =
WebImeTextSpan(WebImeTextSpan::Type::kComposition, 0, 4,
ui::mojom::ImeTextSpanThickness::kThin,
ui::mojom::ImeTextSpanUnderlineStyle::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<WebImeTextSpan> 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->SetInitialFocus(false);
WebVector<WebImeTextSpan> ime_text_spans(static_cast<size_t>(1));
ime_text_spans[0] =
WebImeTextSpan(WebImeTextSpan::Type::kComposition, 0, 4,
ui::mojom::ImeTextSpanThickness::kThin,
ui::mojom::ImeTextSpanUnderlineStyle::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<WebImeTextSpan> 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->SetInitialFocus(false);
WebVector<WebImeTextSpan> ime_text_spans(static_cast<size_t>(1));
ime_text_spans[0] =
WebImeTextSpan(WebImeTextSpan::Type::kComposition, 0, 4,
ui::mojom::ImeTextSpanThickness::kThin,
ui::mojom::ImeTextSpanUnderlineStyle::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->SetInitialFocus(false);
std::string composition_text_first("hello ");
std::string composition_text_second("world");
WebVector<WebImeTextSpan> 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) {
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->SetInitialFocus(false);
frame->SetEditableSelectionOffsets(4, 10);
EXPECT_TRUE(frame->IsSelectionAnchorFirst());
WebRect anchor;
WebRect focus;
web_view->MainFrameWidget()->SelectionBounds(anchor, focus);
frame->SelectRange(gfx::Point(focus.x, focus.y),
gfx::Point(anchor.x, anchor.y));
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->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->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->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->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->MainFrameWidget()->Resize(WebSize(200, 300));
float page_scale_expected = web_view_impl->PageScaleFactor();
WebDeviceEmulationParams params;
params.screen_position = WebDeviceEmulationParams::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->MainFrameWidget()->Resize(WebSize(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->MainFrameWidget()->Resize(WebSize(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->GetDocument(),
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, nullptr, false, nullptr);
main_frame_local->Loader().GetDocumentLoader()->CommitSameDocumentNavigation(
item2->Url(), WebFrameLoadType::kBackForward, item2.Get(),
ClientRedirectPolicy::kNotClientRedirect, nullptr, false, nullptr);
main_frame_local->Loader().GetDocumentLoader()->CommitSameDocumentNavigation(
item1->Url(), WebFrameLoadType::kBackForward, item1.Get(),
ClientRedirectPolicy::kNotClientRedirect, nullptr, false, nullptr);
web_view_impl->MainFrameWidget()->UpdateAllLifecyclePhases(
DocumentUpdateReason::kTest);
// Click a different anchor
FrameLoadRequest request_b(
main_frame_local->GetDocument(),
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, nullptr, false, nullptr);
main_frame_local->Loader().GetDocumentLoader()->CommitSameDocumentNavigation(
item3->Url(), WebFrameLoadType::kBackForward, item3.Get(),
ClientRedirectPolicy::kNotClientRedirect, nullptr, false, 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->MainFrameWidget()->Resize(WebSize(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);
Fullscreen::RequestFullscreen(*element);
web_view_impl->MainFrameWidget()->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->MainFrameWidget()->DidExitFullscreen();
UpdateAllLifecyclePhases();