blob: db616e79c2ce3e44517adaba9b099c5135607475 [file] [log] [blame]
/*
* Copyright (C) 2010 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_frame.h"
#include <stdarg.h>
#include <limits>
#include <map>
#include <memory>
#include <set>
#include "base/stl_util.h"
#include "build/build_config.h"
#include "cc/layers/picture_layer.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/frame/frame_owner_element_type.h"
#include "third_party/blink/public/common/page/launching_process_state.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-shared.h"
#include "third_party/blink/public/mojom/frame/find_in_page.mojom-shared.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/web_cache.h"
#include "third_party/blink/public/platform/web_coalesced_input_event.h"
#include "third_party/blink/public/platform/web_float_rect.h"
#include "third_party/blink/public/platform/web_keyboard_event.h"
#include "third_party/blink/public/platform/web_security_origin.h"
#include "third_party/blink/public/platform/web_url.h"
#include "third_party/blink/public/platform/web_url_loader_client.h"
#include "third_party/blink/public/platform/web_url_loader_mock_factory.h"
#include "third_party/blink/public/platform/web_url_response.h"
#include "third_party/blink/public/web/web_console_message.h"
#include "third_party/blink/public/web/web_context_menu_data.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_document_loader.h"
#include "third_party/blink/public/web/web_form_element.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_history_item.h"
#include "third_party/blink/public/web/web_local_frame_client.h"
#include "third_party/blink/public/web/web_navigation_timings.h"
#include "third_party/blink/public/web/web_print_params.h"
#include "third_party/blink/public/web/web_range.h"
#include "third_party/blink/public/web/web_remote_frame.h"
#include "third_party/blink/public/web/web_script_execution_callback.h"
#include "third_party/blink/public/web/web_script_source.h"
#include "third_party/blink/public/web/web_searchable_form_data.h"
#include "third_party/blink/public/web/web_security_policy.h"
#include "third_party/blink/public/web/web_settings.h"
#include "third_party/blink/public/web/web_text_check_client.h"
#include "third_party/blink/public/web/web_text_checking_completion.h"
#include "third_party/blink/public/web/web_text_checking_result.h"
#include "third_party/blink/public/web/web_view_client.h"
#include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value_factory.h"
#include "third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_serializer.h"
#include "third_party/blink/renderer/bindings/core/v8/usv_string_or_trusted_url.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_node.h"
#include "third_party/blink/renderer/core/clipboard/data_transfer.h"
#include "third_party/blink/renderer/core/clipboard/system_clipboard.h"
#include "third_party/blink/renderer/core/css/media_values.h"
#include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
#include "third_party/blink/renderer/core/css/resolver/viewport_style_resolver.h"
#include "third_party/blink/renderer/core/css/style_sheet_contents.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/dom/range.h"
#include "third_party/blink/renderer/core/dom/user_gesture_indicator.h"
#include "third_party/blink/renderer/core/editing/editor.h"
#include "third_party/blink/renderer/core/editing/ephemeral_range.h"
#include "third_party/blink/renderer/core/editing/finder/text_finder.h"
#include "third_party/blink/renderer/core/editing/frame_selection.h"
#include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h"
#include "third_party/blink/renderer/core/editing/spellcheck/idle_spell_check_controller.h"
#include "third_party/blink/renderer/core/editing/spellcheck/spell_checker.h"
#include "third_party/blink/renderer/core/events/mouse_event.h"
#include "third_party/blink/renderer/core/exported/web_remote_frame_impl.h"
#include "third_party/blink/renderer/core/exported/web_view_impl.h"
#include "third_party/blink/renderer/core/frame/browser_controls.h"
#include "third_party/blink/renderer/core/frame/event_handler_registry.h"
#include "third_party/blink/renderer/core/frame/find_in_page.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/remote_frame.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/frame/viewport_data.h"
#include "third_party/blink/renderer/core/frame/visual_viewport.h"
#include "third_party/blink/renderer/core/frame/web_frame_widget_base.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/geometry/dom_rect.h"
#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
#include "third_party/blink/renderer/core/html/html_body_element.h"
#include "third_party/blink/renderer/core/html/html_iframe_element.h"
#include "third_party/blink/renderer/core/html/image_document.h"
#include "third_party/blink/renderer/core/html/media/html_video_element.h"
#include "third_party/blink/renderer/core/input/event_handler.h"
#include "third_party/blink/renderer/core/inspector/dev_tools_emulator.h"
#include "third_party/blink/renderer/core/layout/hit_test_result.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/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/page/scoped_page_pauser.h"
#include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/scroll/scrollbar.h"
#include "third_party/blink/renderer/core/scroll/scrollbar_test_suite.h"
#include "third_party/blink/renderer/core/testing/null_execution_context.h"
#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
#include "third_party/blink/renderer/core/trustedtypes/trusted_url.h"
#include "third_party/blink/renderer/platform/bindings/microtask.h"
#include "third_party/blink/renderer/platform/cursor.h"
#include "third_party/blink/renderer/platform/drag_image.h"
#include "third_party/blink/renderer/platform/exported/wrapped_resource_request.h"
#include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
#include "third_party/blink/renderer/platform/keyboard_codes.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.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/scoped_fake_plugin_registry.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/blink/renderer/platform/weborigin/kurl_hash.h"
#include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/wtf/forward.h"
#include "v8/include/v8.h"
using blink::url_test_helpers::ToKURL;
using blink::mojom::SelectionMenuBehavior;
using blink::test::RunPendingTasks;
using testing::ElementsAre;
using testing::Mock;
using testing::_;
namespace blink {
::std::ostream& operator<<(::std::ostream& os, const WebFloatSize& size) {
return os << "WebFloatSize: [" << size.width << ", " << size.height << "]";
}
::std::ostream& operator<<(::std::ostream& os, const WebFloatPoint& point) {
return os << "WebFloatPoint: [" << point.x << ", " << point.y << "]";
}
const int kTouchPointPadding = 32;
class WebFrameTest : public testing::Test {
protected:
WebFrameTest()
: base_url_("http://internal.test/"),
not_base_url_("http://external.test/"),
chrome_url_("chrome://") {}
~WebFrameTest() override {
Platform::Current()
->GetURLLoaderMockFactory()
->UnregisterAllURLsAndClearMemoryCache();
}
void DisableRendererSchedulerThrottling() {
// Make sure that the RendererScheduler is foregrounded to avoid getting
// throttled.
if (kLaunchingProcessIsBackgrounded) {
ThreadScheduler::Current()
->GetWebMainThreadSchedulerForTest()
->SetRendererBackgrounded(false);
}
}
void RegisterMockedHttpURLLoad(const std::string& file_name) {
RegisterMockedURLLoadFromBase(base_url_, file_name);
}
void RegisterMockedChromeURLLoad(const std::string& file_name) {
RegisterMockedURLLoadFromBase(chrome_url_, file_name);
}
void RegisterMockedURLLoadFromBase(const std::string& base_url,
const std::string& file_name) {
url_test_helpers::RegisterMockedURLLoadFromBase(
WebString::FromUTF8(base_url), test::CoreTestDataPath(),
WebString::FromUTF8(file_name));
}
void RegisterMockedHttpURLLoadWithCSP(const std::string& file_name,
const std::string& csp,
bool report_only = false) {
WebURLResponse response;
response.SetMIMEType("text/html");
response.AddHTTPHeaderField(
report_only ? WebString("Content-Security-Policy-Report-Only")
: WebString("Content-Security-Policy"),
WebString::FromUTF8(csp));
std::string full_string = base_url_ + file_name;
url_test_helpers::RegisterMockedURLLoadWithCustomResponse(
ToKURL(full_string),
test::CoreTestDataPath(WebString::FromUTF8(file_name)), response);
}
void RegisterMockedHttpURLLoadWithMimeType(const std::string& file_name,
const std::string& mime_type) {
url_test_helpers::RegisterMockedURLLoadFromBase(
WebString::FromUTF8(base_url_), test::CoreTestDataPath(),
WebString::FromUTF8(file_name), WebString::FromUTF8(mime_type));
}
static void ConfigureCompositingWebView(WebSettings* settings) {
settings->SetPreferCompositingToLCDTextEnabled(true);
}
static void ConfigureAndroid(WebSettings* settings) {
settings->SetViewportMetaEnabled(true);
settings->SetViewportEnabled(true);
settings->SetMainFrameResizesAreOrientationChanges(true);
settings->SetShrinksViewportContentToFit(true);
settings->SetViewportStyle(WebViewportStyle::kMobile);
}
static void ConfigureLoadsImagesAutomatically(WebSettings* settings) {
settings->SetLoadsImagesAutomatically(true);
}
void InitializeTextSelectionWebView(
const std::string& url,
frame_test_helpers::WebViewHelper* web_view_helper) {
web_view_helper->InitializeAndLoad(url);
web_view_helper->GetWebView()->GetSettings()->SetDefaultFontSize(12);
web_view_helper->Resize(WebSize(640, 480));
}
std::unique_ptr<DragImage> NodeImageTestSetup(
frame_test_helpers::WebViewHelper* web_view_helper,
const std::string& testcase) {
RegisterMockedHttpURLLoad("nodeimage.html");
web_view_helper->InitializeAndLoad(base_url_ + "nodeimage.html");
web_view_helper->Resize(WebSize(640, 480));
LocalFrame* frame =
ToLocalFrame(web_view_helper->GetWebView()->GetPage()->MainFrame());
DCHECK(frame);
Element* element = frame->GetDocument()->getElementById(testcase.c_str());
return DataTransfer::NodeImage(*frame, *element);
}
void RemoveElementById(WebLocalFrameImpl* frame, const AtomicString& id) {
Element* element = frame->GetFrame()->GetDocument()->getElementById(id);
DCHECK(element);
element->remove();
}
// Both sets the inner html and runs the document lifecycle.
void InitializeWithHTML(LocalFrame& frame, const String& html_content) {
frame.GetDocument()->body()->SetInnerHTMLFromString(html_content);
frame.GetDocument()->View()->UpdateAllLifecyclePhases(
DocumentLifecycle::LifecycleUpdateReason::kTest);
}
WebFrame* LastChild(WebFrame* frame) { return frame->last_child_; }
WebFrame* PreviousSibling(WebFrame* frame) {
return frame->previous_sibling_;
}
void SwapAndVerifyFirstChildConsistency(const char* const message,
WebFrame* parent,
WebFrame* new_child);
void SwapAndVerifyMiddleChildConsistency(const char* const message,
WebFrame* parent,
WebFrame* new_child);
void SwapAndVerifyLastChildConsistency(const char* const message,
WebFrame* parent,
WebFrame* new_child);
void SwapAndVerifySubframeConsistency(const char* const message,
WebFrame* parent,
WebFrame* new_child);
int NumMarkersInRange(const Document* document,
const EphemeralRange& range,
DocumentMarker::MarkerTypes marker_types) {
Node* start_container = range.StartPosition().ComputeContainerNode();
unsigned start_offset = static_cast<unsigned>(
range.StartPosition().ComputeOffsetInContainerNode());
Node* end_container = range.EndPosition().ComputeContainerNode();
unsigned end_offset = static_cast<unsigned>(
range.EndPosition().ComputeOffsetInContainerNode());
int node_count = 0;
for (Node& node : range.Nodes()) {
const DocumentMarkerVector& markers_in_node =
document->Markers().MarkersFor(ToText(node), marker_types);
node_count += std::count_if(
markers_in_node.begin(), markers_in_node.end(),
[start_offset, end_offset, &node, &start_container,
&end_container](const DocumentMarker* marker) {
if (node == start_container && marker->EndOffset() <= start_offset)
return false;
if (node == end_container && marker->StartOffset() >= end_offset)
return false;
return true;
});
}
return node_count;
}
void UpdateAllLifecyclePhases(WebViewImpl* web_view) {
web_view->MainFrameWidget()->UpdateAllLifecyclePhases(
WebWidget::LifecycleUpdateReason::kTest);
}
static void GetElementAndCaretBoundsForFocusedEditableElement(
frame_test_helpers::WebViewHelper& helper,
IntRect& element_bounds,
IntRect& caret_bounds) {
Element* element = helper.GetWebView()->FocusedElement();
WebRect caret_in_viewport, unused;
helper.GetWebView()->MainFrameWidget()->SelectionBounds(caret_in_viewport,
unused);
caret_bounds =
helper.GetWebView()->GetPage()->GetVisualViewport().ViewportToRootFrame(
caret_in_viewport);
element_bounds = element->GetDocument().View()->ConvertToRootFrame(
PixelSnappedIntRect(element->Node::BoundingBox()));
}
std::string base_url_;
std::string not_base_url_;
std::string chrome_url_;
};
TEST_F(WebFrameTest, ContentText) {
RegisterMockedHttpURLLoad("iframes_test.html");
RegisterMockedHttpURLLoad("visible_iframe.html");
RegisterMockedHttpURLLoad("invisible_iframe.html");
RegisterMockedHttpURLLoad("zero_sized_iframe.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "iframes_test.html");
// Now retrieve the frames text and test it only includes visible elements.
std::string content = WebFrameContentDumper::DumpWebViewAsText(
web_view_helper.GetWebView(), 1024)
.Utf8();
EXPECT_NE(std::string::npos, content.find(" visible paragraph"));
EXPECT_NE(std::string::npos, content.find(" visible iframe"));
EXPECT_EQ(std::string::npos, content.find(" invisible pararaph"));
EXPECT_EQ(std::string::npos, content.find(" invisible iframe"));
EXPECT_EQ(std::string::npos, content.find("iframe with zero size"));
}
TEST_F(WebFrameTest, FrameForEnteredContext) {
RegisterMockedHttpURLLoad("iframes_test.html");
RegisterMockedHttpURLLoad("visible_iframe.html");
RegisterMockedHttpURLLoad("invisible_iframe.html");
RegisterMockedHttpURLLoad("zero_sized_iframe.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "iframes_test.html");
v8::HandleScope scope(v8::Isolate::GetCurrent());
EXPECT_EQ(web_view_helper.GetWebView()->MainFrame(),
WebLocalFrame::FrameForContext(web_view_helper.GetWebView()
->MainFrameImpl()
->MainWorldScriptContext()));
EXPECT_EQ(web_view_helper.GetWebView()->MainFrame()->FirstChild(),
WebLocalFrame::FrameForContext(web_view_helper.GetWebView()
->MainFrame()
->FirstChild()
->ToWebLocalFrame()
->MainWorldScriptContext()));
}
class ScriptExecutionCallbackHelper : public WebScriptExecutionCallback {
public:
explicit ScriptExecutionCallbackHelper(v8::Local<v8::Context> context)
: did_complete_(false), bool_value_(false), context_(context) {}
~ScriptExecutionCallbackHelper() override = default;
bool DidComplete() const { return did_complete_; }
const String& StringValue() const { return string_value_; }
bool BoolValue() { return bool_value_; }
private:
// WebScriptExecutionCallback:
void Completed(const WebVector<v8::Local<v8::Value>>& values) override {
did_complete_ = true;
if (!values.IsEmpty()) {
if (values[0]->IsString()) {
string_value_ =
ToCoreString(values[0]->ToString(context_).ToLocalChecked());
} else if (values[0]->IsBoolean()) {
bool_value_ = values[0].As<v8::Boolean>()->Value();
}
}
}
bool did_complete_;
String string_value_;
bool bool_value_;
v8::Local<v8::Context> context_;
};
TEST_F(WebFrameTest, RequestExecuteScript) {
RegisterMockedHttpURLLoad("foo.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "foo.html");
v8::HandleScope scope(v8::Isolate::GetCurrent());
ScriptExecutionCallbackHelper callback_helper(
web_view_helper.LocalMainFrame()->MainWorldScriptContext());
web_view_helper.GetWebView()
->MainFrameImpl()
->RequestExecuteScriptAndReturnValue(
WebScriptSource(WebString("'hello';")), false, &callback_helper);
RunPendingTasks();
EXPECT_TRUE(callback_helper.DidComplete());
EXPECT_EQ("hello", callback_helper.StringValue());
}
TEST_F(WebFrameTest, SuspendedRequestExecuteScript) {
RegisterMockedHttpURLLoad("foo.html");
RegisterMockedHttpURLLoad("bar.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "foo.html");
v8::HandleScope scope(v8::Isolate::GetCurrent());
ScriptExecutionCallbackHelper callback_helper(
web_view_helper.LocalMainFrame()->MainWorldScriptContext());
// Suspend scheduled tasks so the script doesn't run.
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrame()
->GetDocument()
->PauseScheduledTasks();
web_view_helper.GetWebView()
->MainFrameImpl()
->RequestExecuteScriptAndReturnValue(
WebScriptSource(WebString("'hello';")), false, &callback_helper);
RunPendingTasks();
EXPECT_FALSE(callback_helper.DidComplete());
// If the frame navigates, pending scripts should be removed, but the callback
// should always be ran.
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "bar.html");
EXPECT_TRUE(callback_helper.DidComplete());
EXPECT_EQ(String(), callback_helper.StringValue());
}
TEST_F(WebFrameTest, RequestExecuteV8Function) {
RegisterMockedHttpURLLoad("foo.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "foo.html");
auto callback = [](const v8::FunctionCallbackInfo<v8::Value>& info) {
info.GetReturnValue().Set(V8String(info.GetIsolate(), "hello"));
};
v8::HandleScope scope(v8::Isolate::GetCurrent());
v8::Local<v8::Context> context =
web_view_helper.LocalMainFrame()->MainWorldScriptContext();
ScriptExecutionCallbackHelper callback_helper(context);
v8::Local<v8::Function> function =
v8::Function::New(context, callback).ToLocalChecked();
web_view_helper.GetWebView()
->MainFrame()
->ToWebLocalFrame()
->RequestExecuteV8Function(context, function,
v8::Undefined(context->GetIsolate()), 0,
nullptr, &callback_helper);
RunPendingTasks();
EXPECT_TRUE(callback_helper.DidComplete());
EXPECT_EQ("hello", callback_helper.StringValue());
}
TEST_F(WebFrameTest, RequestExecuteV8FunctionWhileSuspended) {
DisableRendererSchedulerThrottling();
RegisterMockedHttpURLLoad("foo.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "foo.html");
auto callback = [](const v8::FunctionCallbackInfo<v8::Value>& info) {
info.GetReturnValue().Set(V8String(info.GetIsolate(), "hello"));
};
v8::HandleScope scope(v8::Isolate::GetCurrent());
v8::Local<v8::Context> context =
web_view_helper.LocalMainFrame()->MainWorldScriptContext();
// Suspend scheduled tasks so the script doesn't run.
WebLocalFrameImpl* main_frame = web_view_helper.LocalMainFrame();
main_frame->GetFrame()->GetDocument()->PauseScheduledTasks();
ScriptExecutionCallbackHelper callback_helper(context);
v8::Local<v8::Function> function =
v8::Function::New(context, callback).ToLocalChecked();
main_frame->RequestExecuteV8Function(context, function,
v8::Undefined(context->GetIsolate()), 0,
nullptr, &callback_helper);
RunPendingTasks();
EXPECT_FALSE(callback_helper.DidComplete());
main_frame->GetFrame()->GetDocument()->UnpauseScheduledTasks();
RunPendingTasks();
EXPECT_TRUE(callback_helper.DidComplete());
EXPECT_EQ("hello", callback_helper.StringValue());
}
TEST_F(WebFrameTest, RequestExecuteV8FunctionWhileSuspendedWithUserGesture) {
DisableRendererSchedulerThrottling();
RegisterMockedHttpURLLoad("foo.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "foo.html");
auto callback = [](const v8::FunctionCallbackInfo<v8::Value>& info) {
info.GetReturnValue().Set(v8::Boolean::New(
info.GetIsolate(), UserGestureIndicator::ProcessingUserGesture()));
};
// Suspend scheduled tasks so the script doesn't run.
WebLocalFrameImpl* main_frame = web_view_helper.LocalMainFrame();
main_frame->GetFrame()->GetDocument()->PauseScheduledTasks();
v8::HandleScope scope(v8::Isolate::GetCurrent());
v8::Local<v8::Context> context =
web_view_helper.LocalMainFrame()->MainWorldScriptContext();
std::unique_ptr<UserGestureIndicator> indicator =
LocalFrame::NotifyUserActivation(main_frame->GetFrame(),
UserGestureToken::kNewGesture);
ScriptExecutionCallbackHelper callback_helper(context);
v8::Local<v8::Function> function =
v8::Function::New(context, callback).ToLocalChecked();
main_frame->RequestExecuteV8Function(
main_frame->MainWorldScriptContext(), function,
v8::Undefined(context->GetIsolate()), 0, nullptr, &callback_helper);
RunPendingTasks();
EXPECT_FALSE(callback_helper.DidComplete());
main_frame->GetFrame()->GetDocument()->UnpauseScheduledTasks();
RunPendingTasks();
EXPECT_TRUE(callback_helper.DidComplete());
EXPECT_EQ(true, callback_helper.BoolValue());
}
class ScriptNotPausedCallbackHelper {
public:
ScriptNotPausedCallbackHelper() = default;
~ScriptNotPausedCallbackHelper() = default;
WebLocalFrame::PausableTaskCallback GetCallback() {
return WTF::Bind(&ScriptNotPausedCallbackHelper::Run,
WTF::Unretained(this));
}
void set_closure(base::OnceClosure closure) { closure_ = std::move(closure); }
const base::Optional<WebLocalFrame::PausableTaskResult>& result() const {
return result_;
}
private:
void Run(WebLocalFrame::PausableTaskResult result) {
ASSERT_FALSE(result_) << "Callback invoked multiple times!";
result_ = result;
if (closure_)
std::move(*closure_).Run();
}
base::Optional<WebLocalFrame::PausableTaskResult> result_;
base::Optional<base::OnceClosure> closure_;
};
TEST_F(WebFrameTest, CallingPostPausableTaskWhileNotPaused) {
RegisterMockedHttpURLLoad("foo.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "foo.html");
WebLocalFrameImpl* main_frame = web_view_helper.LocalMainFrame();
ScriptState::Scope scope(ToScriptStateForMainWorld(main_frame->GetFrame()));
ScriptNotPausedCallbackHelper callback_helper;
main_frame->PostPausableTask(callback_helper.GetCallback());
RunPendingTasks();
ASSERT_TRUE(callback_helper.result());
EXPECT_EQ(WebLocalFrame::PausableTaskResult::kReady,
*callback_helper.result());
}
// Flaky on Android: crbug.com/804892.
#if defined(OS_ANDROID)
TEST_F(WebFrameTest, DISABLED_CallingPostPausableTaskWhilePaused)
#else
TEST_F(WebFrameTest, CallingPostPausableTaskWhilePaused)
#endif
{
RegisterMockedHttpURLLoad("foo.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "foo.html");
WebLocalFrameImpl* main_frame = web_view_helper.LocalMainFrame();
ScriptState::Scope scope(ToScriptStateForMainWorld(main_frame->GetFrame()));
// Suspend scheduled tasks so the script doesn't run.
main_frame->GetFrame()->GetDocument()->PauseScheduledTasks();
ScriptNotPausedCallbackHelper callback_helper;
main_frame->PostPausableTask(callback_helper.GetCallback());
RunPendingTasks();
EXPECT_FALSE(callback_helper.result());
main_frame->GetFrame()->GetDocument()->UnpauseScheduledTasks();
RunPendingTasks();
ASSERT_TRUE(callback_helper.result());
EXPECT_EQ(WebLocalFrame::PausableTaskResult::kReady,
*callback_helper.result());
}
TEST_F(WebFrameTest, CallingPostPausableTaskAndNavigating) {
RegisterMockedHttpURLLoad("foo.html");
RegisterMockedHttpURLLoad("bar.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "foo.html");
WebLocalFrameImpl* main_frame = web_view_helper.LocalMainFrame();
ScriptState::Scope scope(ToScriptStateForMainWorld(main_frame->GetFrame()));
// Suspend scheduled tasks so the script doesn't run.
main_frame->GetFrame()->GetDocument()->PauseScheduledTasks();
ScriptNotPausedCallbackHelper callback_helper;
main_frame->PostPausableTask(callback_helper.GetCallback());
RunPendingTasks();
EXPECT_FALSE(callback_helper.result());
// If the frame navigates, pending scripts should be removed, but the callback
// should always be ran.
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "bar.html");
ASSERT_TRUE(callback_helper.result());
EXPECT_EQ(WebLocalFrame::PausableTaskResult::kContextInvalidOrDestroyed,
*callback_helper.result());
}
TEST_F(WebFrameTest, CallingPostPausableTaskAndDestroyingTheContext) {
RegisterMockedHttpURLLoad("foo.html");
RegisterMockedHttpURLLoad("bar.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "foo.html");
WebLocalFrameImpl* main_frame = web_view_helper.LocalMainFrame();
auto navigate_frame = [](frame_test_helpers::WebViewHelper* web_view_helper,
const std::string& url) {
frame_test_helpers::LoadFrame(
web_view_helper->GetWebView()->MainFrameImpl(), url);
};
ScriptNotPausedCallbackHelper callback_helper;
// Navigate the frame when the helper is notified that script can run. This
// will invalidate the context immediately.
callback_helper.set_closure(WTF::Bind(navigate_frame,
WTF::Unretained(&web_view_helper),
base_url_ + "bar.html"));
ScriptState::Scope scope(ToScriptStateForMainWorld(main_frame->GetFrame()));
main_frame->PostPausableTask(callback_helper.GetCallback());
RunPendingTasks();
ASSERT_TRUE(callback_helper.result());
EXPECT_EQ(WebLocalFrame::PausableTaskResult::kReady,
*callback_helper.result());
}
TEST_F(WebFrameTest, IframeScriptRemovesSelf) {
RegisterMockedHttpURLLoad("single_iframe.html");
RegisterMockedHttpURLLoad("visible_iframe.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "single_iframe.html");
v8::HandleScope scope(v8::Isolate::GetCurrent());
ScriptExecutionCallbackHelper callback_helper(
web_view_helper.LocalMainFrame()->MainWorldScriptContext());
web_view_helper.GetWebView()
->MainFrame()
->FirstChild()
->ToWebLocalFrame()
->RequestExecuteScriptAndReturnValue(
WebScriptSource(WebString(
"var iframe = "
"window.top.document.getElementsByTagName('iframe')[0]; "
"window.top.document.body.removeChild(iframe); 'hello';")),
false, &callback_helper);
RunPendingTasks();
EXPECT_TRUE(callback_helper.DidComplete());
EXPECT_EQ(String(), callback_helper.StringValue());
}
TEST_F(WebFrameTest, FormWithNullFrame) {
RegisterMockedHttpURLLoad("form.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "form.html");
WebVector<WebFormElement> forms;
web_view_helper.LocalMainFrame()->GetDocument().Forms(forms);
web_view_helper.Reset();
EXPECT_EQ(forms.size(), 1U);
// This test passes if this doesn't crash.
WebSearchableFormData searchable_data_form(forms[0]);
}
TEST_F(WebFrameTest, ChromePageJavascript) {
RegisterMockedChromeURLLoad("history.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(chrome_url_ + "history.html");
// Try to run JS against the chrome-style URL.
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
"javascript:document.body.appendChild(document."
"createTextNode('Clobbered'))");
// Now retrieve the frame's text and ensure it was modified by running
// javascript.
std::string content = WebFrameContentDumper::DumpWebViewAsText(
web_view_helper.GetWebView(), 1024)
.Utf8();
EXPECT_NE(std::string::npos, content.find("Clobbered"));
}
TEST_F(WebFrameTest, ChromePageNoJavascript) {
RegisterMockedChromeURLLoad("history.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(chrome_url_ + "history.html");
// Try to run JS against the chrome-style URL after prohibiting it.
WebSecurityPolicy::RegisterURLSchemeAsNotAllowingJavascriptURLs("chrome");
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
"javascript:document.body.appendChild(document."
"createTextNode('Clobbered'))");
// Now retrieve the frame's text and ensure it wasn't modified by running
// javascript.
std::string content = WebFrameContentDumper::DumpWebViewAsText(
web_view_helper.GetWebView(), 1024)
.Utf8();
EXPECT_EQ(std::string::npos, content.find("Clobbered"));
}
TEST_F(WebFrameTest, LocationSetHostWithMissingPort) {
std::string file_name = "print-location-href.html";
RegisterMockedHttpURLLoad(file_name);
RegisterMockedURLLoadFromBase("http://internal.test:0/", file_name);
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + file_name);
// Setting host to "hostname:" should be treated as "hostname:0".
frame_test_helpers::LoadFrame(
web_view_helper.GetWebView()->MainFrameImpl(),
"javascript:location.host = 'internal.test:'; void 0;");
frame_test_helpers::LoadFrame(
web_view_helper.GetWebView()->MainFrameImpl(),
"javascript:document.body.textContent = location.href; void 0;");
std::string content = WebFrameContentDumper::DumpWebViewAsText(
web_view_helper.GetWebView(), 1024)
.Utf8();
EXPECT_EQ("http://internal.test:0/" + file_name, content);
}
TEST_F(WebFrameTest, LocationSetEmptyPort) {
std::string file_name = "print-location-href.html";
RegisterMockedHttpURLLoad(file_name);
RegisterMockedURLLoadFromBase("http://internal.test:0/", file_name);
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + file_name);
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
"javascript:location.port = ''; void 0;");
frame_test_helpers::LoadFrame(
web_view_helper.GetWebView()->MainFrameImpl(),
"javascript:document.body.textContent = location.href; void 0;");
std::string content = WebFrameContentDumper::DumpWebViewAsText(
web_view_helper.GetWebView(), 1024)
.Utf8();
EXPECT_EQ("http://internal.test:0/" + file_name, content);
}
class EvaluateOnLoadWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
EvaluateOnLoadWebFrameClient() : executing_(false), was_executed_(false) {}
~EvaluateOnLoadWebFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
void DidClearWindowObject() override {
EXPECT_FALSE(executing_);
was_executed_ = true;
executing_ = true;
v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
Frame()->ExecuteScriptAndReturnValue(
WebScriptSource(WebString("window.someProperty = 42;")));
executing_ = false;
}
bool executing_;
bool was_executed_;
};
TEST_F(WebFrameTest, DidClearWindowObjectIsNotRecursive) {
EvaluateOnLoadWebFrameClient web_frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad("about:blank", &web_frame_client);
EXPECT_TRUE(web_frame_client.was_executed_);
}
class CSSCallbackWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
CSSCallbackWebFrameClient() : update_count_(0) {}
~CSSCallbackWebFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
void DidMatchCSS(
const WebVector<WebString>& newly_matching_selectors,
const WebVector<WebString>& stopped_matching_selectors) override;
std::map<WebLocalFrame*, std::set<std::string>> matched_selectors_;
int update_count_;
};
void CSSCallbackWebFrameClient::DidMatchCSS(
const WebVector<WebString>& newly_matching_selectors,
const WebVector<WebString>& stopped_matching_selectors) {
++update_count_;
std::set<std::string>& frame_selectors = matched_selectors_[Frame()];
for (size_t i = 0; i < newly_matching_selectors.size(); ++i) {
std::string selector = newly_matching_selectors[i].Utf8();
EXPECT_EQ(0U, frame_selectors.count(selector)) << selector;
frame_selectors.insert(selector);
}
for (size_t i = 0; i < stopped_matching_selectors.size(); ++i) {
std::string selector = stopped_matching_selectors[i].Utf8();
EXPECT_EQ(1U, frame_selectors.count(selector)) << selector;
frame_selectors.erase(selector);
}
}
class WebFrameCSSCallbackTest : public testing::Test {
protected:
WebFrameCSSCallbackTest() {
frame_ = helper_.InitializeAndLoad("about:blank", &client_)
->MainFrame()
->ToWebLocalFrame();
}
~WebFrameCSSCallbackTest() override {
EXPECT_EQ(1U, client_.matched_selectors_.size());
}
WebDocument Doc() const { return frame_->GetDocument(); }
int UpdateCount() const { return client_.update_count_; }
const std::set<std::string>& MatchedSelectors() {
return client_.matched_selectors_[frame_];
}
void LoadHTML(const std::string& html) {
frame_test_helpers::LoadHTMLString(frame_, html, ToKURL("about:blank"));
}
void ExecuteScript(const WebString& code) {
frame_->ExecuteScript(WebScriptSource(code));
frame_->View()->MainFrameWidget()->UpdateAllLifecyclePhases(
WebWidget::LifecycleUpdateReason::kTest);
RunPendingTasks();
}
CSSCallbackWebFrameClient client_;
frame_test_helpers::WebViewHelper helper_;
WebLocalFrame* frame_;
};
TEST_F(WebFrameCSSCallbackTest, AuthorStyleSheet) {
LoadHTML(
"<style>"
// This stylesheet checks that the internal property and value can't be
// set by a stylesheet, only WebDocument::watchCSSSelectors().
"div.initial_on { -internal-callback: none; }"
"div.initial_off { -internal-callback: -internal-presence; }"
"</style>"
"<div class=\"initial_on\"></div>"
"<div class=\"initial_off\"></div>");
Vector<WebString> selectors;
selectors.push_back(WebString::FromUTF8("div.initial_on"));
frame_->GetDocument().WatchCSSSelectors(WebVector<WebString>(selectors));
frame_->View()->MainFrameWidget()->UpdateAllLifecyclePhases(
WebWidget::LifecycleUpdateReason::kTest);
RunPendingTasks();
EXPECT_EQ(1, UpdateCount());
EXPECT_THAT(MatchedSelectors(), ElementsAre("div.initial_on"));
// Check that adding a watched selector calls back for already-present nodes.
selectors.push_back(WebString::FromUTF8("div.initial_off"));
Doc().WatchCSSSelectors(WebVector<WebString>(selectors));
frame_->View()->MainFrameWidget()->UpdateAllLifecyclePhases(
WebWidget::LifecycleUpdateReason::kTest);
RunPendingTasks();
EXPECT_EQ(2, UpdateCount());
EXPECT_THAT(MatchedSelectors(),
ElementsAre("div.initial_off", "div.initial_on"));
// Check that we can turn off callbacks for certain selectors.
Doc().WatchCSSSelectors(WebVector<WebString>());
frame_->View()->MainFrameWidget()->UpdateAllLifecyclePhases(
WebWidget::LifecycleUpdateReason::kTest);
RunPendingTasks();
EXPECT_EQ(3, UpdateCount());
EXPECT_THAT(MatchedSelectors(), ElementsAre());
}
TEST_F(WebFrameCSSCallbackTest, SharedComputedStyle) {
// Check that adding an element calls back when it matches an existing rule.
Vector<WebString> selectors;
selectors.push_back(WebString::FromUTF8("span"));
Doc().WatchCSSSelectors(WebVector<WebString>(selectors));
ExecuteScript(
"i1 = document.createElement('span');"
"i1.id = 'first_span';"
"document.body.appendChild(i1)");
EXPECT_EQ(1, UpdateCount());
EXPECT_THAT(MatchedSelectors(), ElementsAre("span"));
// Adding a second element that shares a ComputedStyle shouldn't call back.
// We use <span>s to avoid default style rules that can set
// ComputedStyle::unique().
ExecuteScript(
"i2 = document.createElement('span');"
"i2.id = 'second_span';"
"i1 = document.getElementById('first_span');"
"i1.parentNode.insertBefore(i2, i1.nextSibling);");
EXPECT_EQ(1, UpdateCount());
EXPECT_THAT(MatchedSelectors(), ElementsAre("span"));
// Removing the first element shouldn't call back.
ExecuteScript(
"i1 = document.getElementById('first_span');"
"i1.parentNode.removeChild(i1);");
EXPECT_EQ(1, UpdateCount());
EXPECT_THAT(MatchedSelectors(), ElementsAre("span"));
// But removing the second element *should* call back.
ExecuteScript(
"i2 = document.getElementById('second_span');"
"i2.parentNode.removeChild(i2);");
EXPECT_EQ(2, UpdateCount());
EXPECT_THAT(MatchedSelectors(), ElementsAre());
}
TEST_F(WebFrameCSSCallbackTest, CatchesAttributeChange) {
LoadHTML("<span></span>");
Vector<WebString> selectors;
selectors.push_back(WebString::FromUTF8("span[attr=\"value\"]"));
Doc().WatchCSSSelectors(WebVector<WebString>(selectors));
RunPendingTasks();
EXPECT_EQ(0, UpdateCount());
EXPECT_THAT(MatchedSelectors(), ElementsAre());
ExecuteScript(
"document.querySelector('span').setAttribute('attr', 'value');");
EXPECT_EQ(1, UpdateCount());
EXPECT_THAT(MatchedSelectors(), ElementsAre("span[attr=\"value\"]"));
}
TEST_F(WebFrameCSSCallbackTest, DisplayNone) {
LoadHTML("<div style='display:none'><span></span></div>");
Vector<WebString> selectors;
selectors.push_back(WebString::FromUTF8("span"));
Doc().WatchCSSSelectors(WebVector<WebString>(selectors));
RunPendingTasks();
EXPECT_EQ(0, UpdateCount()) << "Don't match elements in display:none trees.";
ExecuteScript(
"d = document.querySelector('div');"
"d.style.display = 'block';");
EXPECT_EQ(1, UpdateCount()) << "Match elements when they become displayed.";
EXPECT_THAT(MatchedSelectors(), ElementsAre("span"));
ExecuteScript(
"d = document.querySelector('div');"
"d.style.display = 'none';");
EXPECT_EQ(2, UpdateCount())
<< "Unmatch elements when they become undisplayed.";
EXPECT_THAT(MatchedSelectors(), ElementsAre());
ExecuteScript(
"s = document.querySelector('span');"
"s.style.display = 'none';");
EXPECT_EQ(2, UpdateCount())
<< "No effect from no-display'ing a span that's already undisplayed.";
ExecuteScript(
"d = document.querySelector('div');"
"d.style.display = 'block';");
EXPECT_EQ(2, UpdateCount())
<< "No effect from displaying a div whose span is display:none.";
ExecuteScript(
"s = document.querySelector('span');"
"s.style.display = 'inline';");
EXPECT_EQ(3, UpdateCount())
<< "Now the span is visible and produces a callback.";
EXPECT_THAT(MatchedSelectors(), ElementsAre("span"));
ExecuteScript(
"s = document.querySelector('span');"
"s.style.display = 'none';");
EXPECT_EQ(4, UpdateCount())
<< "Undisplaying the span directly should produce another callback.";
EXPECT_THAT(MatchedSelectors(), ElementsAre());
}
TEST_F(WebFrameCSSCallbackTest, DisplayContents) {
LoadHTML("<div style='display:contents'><span></span></div>");
Vector<WebString> selectors(1u, WebString::FromUTF8("span"));
Doc().WatchCSSSelectors(WebVector<WebString>(selectors));
frame_->View()->MainFrameWidget()->UpdateAllLifecyclePhases(
WebWidget::LifecycleUpdateReason::kTest);
RunPendingTasks();
EXPECT_EQ(1, UpdateCount()) << "Match elements in display:contents trees.";
EXPECT_THAT(MatchedSelectors(), ElementsAre("span"));
ExecuteScript(
"s = document.querySelector('span');"
"s.style.display = 'contents';");
EXPECT_EQ(1, UpdateCount()) << "Match elements which are display:contents.";
EXPECT_THAT(MatchedSelectors(), ElementsAre("span"));
ExecuteScript(
"d = document.querySelector('div');"
"d.style.display = 'block';");
EXPECT_EQ(1, UpdateCount())
<< "Still match display:contents after parent becomes display:block.";
EXPECT_THAT(MatchedSelectors(), ElementsAre("span"));
ExecuteScript(
"d = document.querySelector('div');"
"d.style.display = 'none';");
EXPECT_EQ(2, UpdateCount())
<< "No longer matched when parent becomes display:none.";
EXPECT_THAT(MatchedSelectors(), ElementsAre());
}
TEST_F(WebFrameCSSCallbackTest, Reparenting) {
LoadHTML(
"<div id='d1'><span></span></div>"
"<div id='d2'></div>");
Vector<WebString> selectors;
selectors.push_back(WebString::FromUTF8("span"));
Doc().WatchCSSSelectors(WebVector<WebString>(selectors));
frame_->View()->MainFrameWidget()->UpdateAllLifecyclePhases(
WebWidget::LifecycleUpdateReason::kTest);
RunPendingTasks();
EXPECT_EQ(1, UpdateCount());
EXPECT_THAT(MatchedSelectors(), ElementsAre("span"));
ExecuteScript(
"s = document.querySelector('span');"
"d2 = document.getElementById('d2');"
"d2.appendChild(s);");
EXPECT_EQ(1, UpdateCount()) << "Just moving an element that continues to "
"match shouldn't send a spurious callback.";
EXPECT_THAT(MatchedSelectors(), ElementsAre("span"));
}
TEST_F(WebFrameCSSCallbackTest, MultiSelector) {
LoadHTML("<span></span>");
// Check that selector lists match as the whole list, not as each element
// independently.
Vector<WebString> selectors;
selectors.push_back(WebString::FromUTF8("span"));
selectors.push_back(WebString::FromUTF8("span,p"));
Doc().WatchCSSSelectors(WebVector<WebString>(selectors));
frame_->View()->MainFrameWidget()->UpdateAllLifecyclePhases(
WebWidget::LifecycleUpdateReason::kTest);
RunPendingTasks();
EXPECT_EQ(1, UpdateCount());
EXPECT_THAT(MatchedSelectors(), ElementsAre("span", "span, p"));
}
TEST_F(WebFrameCSSCallbackTest, InvalidSelector) {
LoadHTML("<p><span></span></p>");
// Build a list with one valid selector and one invalid.
Vector<WebString> selectors;
selectors.push_back(WebString::FromUTF8("span"));
selectors.push_back(WebString::FromUTF8("[")); // Invalid.
selectors.push_back(WebString::FromUTF8("p span")); // Not compound.
Doc().WatchCSSSelectors(WebVector<WebString>(selectors));
frame_->View()->MainFrameWidget()->UpdateAllLifecyclePhases(
WebWidget::LifecycleUpdateReason::kTest);
RunPendingTasks();
EXPECT_EQ(1, UpdateCount());
EXPECT_THAT(MatchedSelectors(), ElementsAre("span"))
<< "An invalid selector shouldn't prevent other selectors from matching.";
}
TEST_F(WebFrameTest, DispatchMessageEventWithOriginCheck) {
RegisterMockedHttpURLLoad("postmessage_test.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "postmessage_test.html");
// Send a message with the correct origin.
WebSecurityOrigin correct_origin(
WebSecurityOrigin::Create(ToKURL(base_url_)));
WebDocument document = web_view_helper.LocalMainFrame()->GetDocument();
WebSerializedScriptValue data(WebSerializedScriptValue::CreateInvalid());
WebDOMMessageEvent message(data, "http://origin.com");
web_view_helper.GetWebView()
->MainFrameImpl()
->DispatchMessageEventWithOriginCheck(correct_origin, message,
false /* has_user_gesture */);
// Send another message with incorrect origin.
WebSecurityOrigin incorrect_origin(
WebSecurityOrigin::Create(ToKURL(chrome_url_)));
web_view_helper.GetWebView()
->MainFrameImpl()
->DispatchMessageEventWithOriginCheck(incorrect_origin, message,
false /* has_user_gesture */);
// Verify that only the first addition is in the body of the page.
std::string content = WebFrameContentDumper::DumpWebViewAsText(
web_view_helper.GetWebView(), 1024)
.Utf8();
EXPECT_NE(std::string::npos, content.find("Message 1."));
EXPECT_EQ(std::string::npos, content.find("Message 2."));
}
namespace {
scoped_refptr<SerializedScriptValue> SerializeString(
const StringView& message,
ScriptState* script_state) {
// This is inefficient, but avoids duplicating serialization logic for the
// sake of this test.
NonThrowableExceptionState exception_state;
ScriptState::Scope scope(script_state);
V8ScriptValueSerializer serializer(script_state);
return serializer.Serialize(V8String(script_state->GetIsolate(), message),
exception_state);
}
} // namespace
TEST_F(WebFrameTest, PostMessageThenDetach) {
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad("about:blank");
LocalFrame* frame =
ToLocalFrame(web_view_helper.GetWebView()->GetPage()->MainFrame());
NonThrowableExceptionState exception_state;
scoped_refptr<SerializedScriptValue> message =
SerializeString("message", ToScriptStateForMainWorld(frame));
MessagePortArray message_ports;
frame->DomWindow()->PostMessageForTesting(
message, message_ports, "*", frame->DomWindow(), exception_state);
web_view_helper.Reset();
EXPECT_FALSE(exception_state.HadException());
// Success is not crashing.
RunPendingTasks();
}
namespace {
class FixedLayoutTestWebViewClient
: public frame_test_helpers::TestWebViewClient {
public:
FixedLayoutTestWebViewClient() = default;
~FixedLayoutTestWebViewClient() override = default;
// frame_test_helpers::TestWebViewClient:
WebScreenInfo GetScreenInfo() override { return screen_info_; }
WebScreenInfo screen_info_;
};
class FakeCompositingWebViewClient : public FixedLayoutTestWebViewClient {};
// Helper function to set autosizing multipliers on a document.
bool SetTextAutosizingMultiplier(Document* document, float multiplier) {
bool multiplier_set = false;
for (LayoutObject* layout_object = document->GetLayoutView(); layout_object;
layout_object = layout_object->NextInPreOrder()) {
if (layout_object->Style()) {
scoped_refptr<ComputedStyle> modified_style =
ComputedStyle::Clone(layout_object->StyleRef());
modified_style->SetTextAutosizingMultiplier(multiplier);
EXPECT_EQ(multiplier, modified_style->TextAutosizingMultiplier());
layout_object->SetStyleInternal(std::move(modified_style));
multiplier_set = true;
}
}
return multiplier_set;
}
// Helper function to check autosizing multipliers on a document.
bool CheckTextAutosizingMultiplier(Document* document, float multiplier) {
bool multiplier_checked = false;
for (LayoutObject* layout_object = document->GetLayoutView(); layout_object;
layout_object = layout_object->NextInPreOrder()) {
if (layout_object->Style() && layout_object->IsText()) {
EXPECT_EQ(multiplier, layout_object->Style()->TextAutosizingMultiplier());
multiplier_checked = true;
}
}
return multiplier_checked;
}
} // namespace
TEST_F(WebFrameTest, ChangeInFixedLayoutResetsTextAutosizingMultipliers) {
RegisterMockedHttpURLLoad("fixed_layout.html");
FixedLayoutTestWebViewClient client;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "fixed_layout.html", nullptr,
&client, ConfigureAndroid);
Document* document =
ToLocalFrame(web_view_helper.GetWebView()->GetPage()->MainFrame())
->GetDocument();
document->GetSettings()->SetTextAutosizingEnabled(true);
EXPECT_TRUE(document->GetSettings()->TextAutosizingEnabled());
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_TRUE(SetTextAutosizingMultiplier(document, 2));
ViewportDescription description =
document->GetViewportData().GetViewportDescription();
// Choose a width that's not going match the viewport width of the loaded
// document.
description.min_width = Length(100, blink::kFixed);
description.max_width = Length(100, blink::kFixed);
web_view_helper.GetWebView()->UpdatePageDefinedViewportConstraints(
description);
EXPECT_TRUE(CheckTextAutosizingMultiplier(document, 1));
}
TEST_F(WebFrameTest, WorkingTextAutosizingMultipliers_VirtualViewport) {
const std::string html_file = "fixed_layout.html";
RegisterMockedHttpURLLoad(html_file);
FixedLayoutTestWebViewClient client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + html_file, nullptr, &client,
ConfigureAndroid);
Document* document =
ToLocalFrame(web_view_helper.GetWebView()->GetPage()->MainFrame())
->GetDocument();
document->GetSettings()->SetTextAutosizingEnabled(true);
EXPECT_TRUE(document->GetSettings()->TextAutosizingEnabled());
web_view_helper.Resize(WebSize(490, 800));
// Multiplier: 980 / 490 = 2.0
EXPECT_TRUE(CheckTextAutosizingMultiplier(document, 2.0));
}
TEST_F(WebFrameTest,
VisualViewportSetSizeInvalidatesTextAutosizingMultipliers) {
RegisterMockedHttpURLLoad("iframe_reload.html");
RegisterMockedHttpURLLoad("visible_iframe.html");
FixedLayoutTestWebViewClient client;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "iframe_reload.html", nullptr,
&client, ConfigureAndroid);
LocalFrame* main_frame =
ToLocalFrame(web_view_helper.GetWebView()->GetPage()->MainFrame());
Document* document = main_frame->GetDocument();
LocalFrameView* frame_view = web_view_helper.LocalMainFrame()->GetFrameView();
document->GetSettings()->SetTextAutosizingEnabled(true);
EXPECT_TRUE(document->GetSettings()->TextAutosizingEnabled());
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
for (Frame* frame = main_frame; frame; frame = frame->Tree().TraverseNext()) {
if (!frame->IsLocalFrame())
continue;
EXPECT_TRUE(
SetTextAutosizingMultiplier(ToLocalFrame(frame)->GetDocument(), 2));
for (LayoutObject* layout_object =
ToLocalFrame(frame)->GetDocument()->GetLayoutView();
layout_object; layout_object = layout_object->NextInPreOrder()) {
if (layout_object->IsText())
EXPECT_FALSE(layout_object->NeedsLayout());
}
}
frame_view->GetPage()->GetVisualViewport().SetSize(IntSize(200, 200));
for (Frame* frame = main_frame; frame; frame = frame->Tree().TraverseNext()) {
if (!frame->IsLocalFrame())
continue;
for (LayoutObject* layout_object =
ToLocalFrame(frame)->GetDocument()->GetLayoutView();
!layout_object; layout_object = layout_object->NextInPreOrder()) {
if (layout_object->IsText())
EXPECT_TRUE(layout_object->NeedsLayout());
}
}
}
TEST_F(WebFrameTest, ZeroHeightPositiveWidthNotIgnored) {
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 1280;
int viewport_height = 0;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(nullptr, &client, ConfigureAndroid);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_EQ(viewport_width, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width());
EXPECT_EQ(viewport_height, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height());
}
TEST_F(WebFrameTest, DeviceScaleFactorUsesDefaultWithoutViewportTag) {
RegisterMockedHttpURLLoad("no_viewport_tag.html");
int viewport_width = 640;
int viewport_height = 480;
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 2;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "no_viewport_tag.html", nullptr,
&client, ConfigureAndroid);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_EQ(
2,
web_view_helper.GetWebView()->GetPage()->DeviceScaleFactorDeprecated());
// Device scale factor should be independent of page scale.
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(1, 2);
web_view_helper.GetWebView()->SetPageScaleFactor(0.5);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
EXPECT_EQ(1, web_view_helper.GetWebView()->PageScaleFactor());
// Force the layout to happen before leaving the test.
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
}
TEST_F(WebFrameTest, FixedLayoutInitializeAtMinimumScale) {
RegisterMockedHttpURLLoad("fixed_layout.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
// Make sure we initialize to minimum scale, even if the window size
// only becomes available after the load begins.
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(nullptr, &client, ConfigureAndroid);
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.25f, 5);
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "fixed_layout.html");
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
int default_fixed_layout_width = 980;
float minimum_page_scale_factor =
viewport_width / (float)default_fixed_layout_width;
EXPECT_EQ(minimum_page_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor());
EXPECT_EQ(minimum_page_scale_factor,
web_view_helper.GetWebView()->MinimumPageScaleFactor());
// Assume the user has pinch zoomed to page scale factor 2.
float user_pinch_page_scale_factor = 2;
web_view_helper.GetWebView()->SetPageScaleFactor(
user_pinch_page_scale_factor);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
// Make sure we don't reset to initial scale if the page continues to load.
web_view_helper.GetWebView()->DidCommitLoad(false, false);
web_view_helper.GetWebView()->DidChangeContentsSize();
EXPECT_EQ(user_pinch_page_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor());
// Make sure we don't reset to initial scale if the viewport size changes.
web_view_helper.Resize(WebSize(viewport_width, viewport_height + 100));
EXPECT_EQ(user_pinch_page_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor());
}
TEST_F(WebFrameTest, WideDocumentInitializeAtMinimumScale) {
RegisterMockedHttpURLLoad("wide_document.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
// Make sure we initialize to minimum scale, even if the window size
// only becomes available after the load begins.
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(nullptr, &client, ConfigureAndroid);
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.25f, 5);
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "wide_document.html");
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
int wide_document_width = 1500;
float minimum_page_scale_factor = viewport_width / (float)wide_document_width;
EXPECT_EQ(minimum_page_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor());
EXPECT_EQ(minimum_page_scale_factor,
web_view_helper.GetWebView()->MinimumPageScaleFactor());
// Assume the user has pinch zoomed to page scale factor 2.
float user_pinch_page_scale_factor = 2;
web_view_helper.GetWebView()->SetPageScaleFactor(
user_pinch_page_scale_factor);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
// Make sure we don't reset to initial scale if the page continues to load.
web_view_helper.GetWebView()->DidCommitLoad(false, false);
web_view_helper.GetWebView()->DidChangeContentsSize();
EXPECT_EQ(user_pinch_page_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor());
// Make sure we don't reset to initial scale if the viewport size changes.
web_view_helper.Resize(WebSize(viewport_width, viewport_height + 100));
EXPECT_EQ(user_pinch_page_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor());
}
TEST_F(WebFrameTest, DelayedViewportInitialScale) {
RegisterMockedHttpURLLoad("viewport-auto-initial-scale.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "viewport-auto-initial-scale.html", nullptr, &client,
ConfigureAndroid);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_EQ(0.25f, web_view_helper.GetWebView()->PageScaleFactor());
ViewportData& viewport =
ToLocalFrame(web_view_helper.GetWebView()->GetPage()->MainFrame())
->GetDocument()
->GetViewportData();
ViewportDescription description = viewport.GetViewportDescription();
description.zoom = 2;
viewport.SetViewportDescription(description);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
EXPECT_EQ(2, web_view_helper.GetWebView()->PageScaleFactor());
}
TEST_F(WebFrameTest, setLoadWithOverviewModeToFalse) {
RegisterMockedHttpURLLoad("viewport-auto-initial-scale.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "viewport-auto-initial-scale.html", nullptr, &client,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetLoadWithOverviewMode(false);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
// The page must be displayed at 100% zoom.
EXPECT_EQ(1.0f, web_view_helper.GetWebView()->PageScaleFactor());
}
TEST_F(WebFrameTest, SetLoadWithOverviewModeToFalseAndNoWideViewport) {
RegisterMockedHttpURLLoad("large-div.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "large-div.html", nullptr,
&client, ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetLoadWithOverviewMode(false);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(false);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
// The page must be displayed at 100% zoom, despite that it hosts a wide div
// element.
EXPECT_EQ(1.0f, web_view_helper.GetWebView()->PageScaleFactor());
}
TEST_F(WebFrameTest, NoWideViewportIgnoresPageViewportWidth) {
RegisterMockedHttpURLLoad("viewport-auto-initial-scale.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "viewport-auto-initial-scale.html", nullptr, &client,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(false);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
// The page sets viewport width to 3000, but with UseWideViewport == false is
// must be ignored.
EXPECT_EQ(viewport_width, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->Size()
.Width());
EXPECT_EQ(viewport_height, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->Size()
.Height());
}
TEST_F(WebFrameTest, NoWideViewportIgnoresPageViewportWidthButAccountsScale) {
RegisterMockedHttpURLLoad("viewport-wide-2x-initial-scale.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "viewport-wide-2x-initial-scale.html", nullptr, &client,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(false);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
// The page sets viewport width to 3000, but with UseWideViewport == false it
// must be ignored while the initial scale specified by the page must be
// accounted.
EXPECT_EQ(viewport_width / 2, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->Size()
.Width());
EXPECT_EQ(viewport_height / 2, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->Size()
.Height());
}
TEST_F(WebFrameTest, WideViewportSetsTo980WithoutViewportTag) {
RegisterMockedHttpURLLoad("no_viewport_tag.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "no_viewport_tag.html", nullptr,
&client, ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(true);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_EQ(980, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->LayoutViewport()
->ContentsSize()
.Width());
EXPECT_EQ(980.0 / viewport_width * viewport_height,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->LayoutViewport()
->ContentsSize()
.Height());
}
TEST_F(WebFrameTest, WideViewportSetsTo980WithXhtmlMp) {
RegisterMockedHttpURLLoad("viewport/viewport-legacy-xhtmlmp.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(nullptr, &client, ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(true);
frame_test_helpers::LoadFrame(
web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "viewport/viewport-legacy-xhtmlmp.html");
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_EQ(viewport_width, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->Size()
.Width());
EXPECT_EQ(viewport_height, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->Size()
.Height());
}
TEST_F(WebFrameTest, NoWideViewportAndHeightInMeta) {
RegisterMockedHttpURLLoad("viewport-height-1000.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "viewport-height-1000.html",
nullptr, &client, ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(false);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_EQ(viewport_width, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->Size()
.Width());
}
TEST_F(WebFrameTest, WideViewportSetsTo980WithAutoWidth) {
RegisterMockedHttpURLLoad("viewport-2x-initial-scale.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "viewport-2x-initial-scale.html", nullptr, &client,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(true);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_EQ(980, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->Size()
.Width());
EXPECT_EQ(980.0 / viewport_width * viewport_height,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->Size()
.Height());
}
TEST_F(WebFrameTest, PageViewportInitialScaleOverridesLoadWithOverviewMode) {
RegisterMockedHttpURLLoad("viewport-wide-2x-initial-scale.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "viewport-wide-2x-initial-scale.html", nullptr, &client,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetLoadWithOverviewMode(false);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
// The page must be displayed at 200% zoom, as specified in its viewport meta
// tag.
EXPECT_EQ(2.0f, web_view_helper.GetWebView()->PageScaleFactor());
}
TEST_F(WebFrameTest, setInitialPageScaleFactorPermanently) {
RegisterMockedHttpURLLoad("fixed_layout.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
float enforced_page_scale_factor = 2.0f;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "fixed_layout.html", nullptr,
&client, ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetLoadWithOverviewMode(false);
web_view_helper.GetWebView()->SetInitialPageScaleOverride(
enforced_page_scale_factor);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
EXPECT_EQ(enforced_page_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor());
int viewport_width = 640;
int viewport_height = 480;
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_EQ(enforced_page_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor());
web_view_helper.GetWebView()->SetInitialPageScaleOverride(-1);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
EXPECT_EQ(1.0, web_view_helper.GetWebView()->PageScaleFactor());
}
TEST_F(WebFrameTest,
PermanentInitialPageScaleFactorOverridesLoadWithOverviewMode) {
RegisterMockedHttpURLLoad("viewport-auto-initial-scale.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
float enforced_page_scale_factor = 0.5f;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "viewport-auto-initial-scale.html", nullptr, &client,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetLoadWithOverviewMode(false);
web_view_helper.GetWebView()->SetInitialPageScaleOverride(
enforced_page_scale_factor);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_EQ(enforced_page_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor());
}
TEST_F(WebFrameTest,
PermanentInitialPageScaleFactorOverridesPageViewportInitialScale) {
RegisterMockedHttpURLLoad("viewport-wide-2x-initial-scale.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
float enforced_page_scale_factor = 0.5f;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "viewport-wide-2x-initial-scale.html", nullptr, &client,
ConfigureAndroid);
web_view_helper.GetWebView()->SetInitialPageScaleOverride(
enforced_page_scale_factor);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_EQ(enforced_page_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor());
}
TEST_F(WebFrameTest, SmallPermanentInitialPageScaleFactorIsClobbered) {
const char* pages[] = {
// These pages trigger the clobbering condition. There must be a matching
// item in "pageScaleFactors" array.
"viewport-device-0.5x-initial-scale.html",
"viewport-initial-scale-1.html",
// These ones do not.
"viewport-auto-initial-scale.html",
"viewport-target-densitydpi-device-and-fixed-width.html"};
float page_scale_factors[] = {0.5f, 1.0f};
for (size_t i = 0; i < arraysize(pages); ++i)
RegisterMockedHttpURLLoad(pages[i]);
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 400;
int viewport_height = 300;
float enforced_page_scale_factor = 0.75f;
for (size_t i = 0; i < arraysize(pages); ++i) {
for (int quirk_enabled = 0; quirk_enabled <= 1; ++quirk_enabled) {
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + pages[i], nullptr, &client,
ConfigureAndroid);
web_view_helper.GetWebView()
->GetSettings()
->SetClobberUserAgentInitialScaleQuirk(quirk_enabled);
web_view_helper.GetWebView()->SetInitialPageScaleOverride(
enforced_page_scale_factor);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
float expected_page_scale_factor =
quirk_enabled && i < arraysize(page_scale_factors)
? page_scale_factors[i]
: enforced_page_scale_factor;
EXPECT_EQ(expected_page_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor());
}
}
}
TEST_F(WebFrameTest, PermanentInitialPageScaleFactorAffectsLayoutWidth) {
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
float enforced_page_scale_factor = 0.5;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad("about:blank", nullptr, &client,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(false);
web_view_helper.GetWebView()->GetSettings()->SetLoadWithOverviewMode(false);
web_view_helper.GetWebView()->SetInitialPageScaleOverride(
enforced_page_scale_factor);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_EQ(viewport_width / enforced_page_scale_factor,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->Size()
.Width());
EXPECT_EQ(enforced_page_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor());
}
TEST_F(WebFrameTest, DocumentElementClientHeightWorksWithWrapContentMode) {
RegisterMockedHttpURLLoad("0-by-0.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "0-by-0.html", nullptr, &client,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetForceZeroLayoutHeight(true);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
LocalFrame* frame = web_view_helper.LocalMainFrame()->GetFrame();
Document* document = frame->GetDocument();
EXPECT_EQ(viewport_height, document->documentElement()->clientHeight());
EXPECT_EQ(viewport_width, document->documentElement()->clientWidth());
}
TEST_F(WebFrameTest, SetForceZeroLayoutHeightWorksWithWrapContentMode) {
RegisterMockedHttpURLLoad("0-by-0.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "0-by-0.html", nullptr, &client,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetForceZeroLayoutHeight(true);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
PaintLayerCompositor* compositor = web_view_helper.GetWebView()->Compositor();
GraphicsLayer* scroll_container = compositor->RootGraphicsLayer();
EXPECT_EQ(0, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width());
EXPECT_EQ(0, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height());
EXPECT_EQ(0.0, scroll_container->Size().width());
EXPECT_EQ(0.0, scroll_container->Size().height());
web_view_helper.Resize(WebSize(viewport_width, 0));
EXPECT_EQ(viewport_width, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width());
EXPECT_EQ(0, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height());
EXPECT_EQ(viewport_width, scroll_container->Size().width());
EXPECT_EQ(0.0, scroll_container->Size().height());
// The flag ForceZeroLayoutHeight will cause the following resize of viewport
// height to be ignored by the outer viewport (the container layer of
// LayerCompositor). The height of the visualViewport, however, is not
// affected.
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_FALSE(web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->NeedsLayout());
EXPECT_EQ(viewport_width, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width());
EXPECT_EQ(0, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height());
EXPECT_EQ(viewport_width, scroll_container->Size().width());
EXPECT_EQ(viewport_height, scroll_container->Size().height());
LocalFrame* frame = web_view_helper.LocalMainFrame()->GetFrame();
VisualViewport& visual_viewport = frame->GetPage()->GetVisualViewport();
EXPECT_EQ(viewport_height, visual_viewport.ContainerLayer()->Size().height());
EXPECT_TRUE(visual_viewport.ContainerLayer()->CcLayer()->masks_to_bounds());
EXPECT_FALSE(scroll_container->CcLayer()->masks_to_bounds());
}
TEST_F(WebFrameTest, SetForceZeroLayoutHeight) {
RegisterMockedHttpURLLoad("200-by-300.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "200-by-300.html", nullptr,
&client, ConfigureAndroid);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_LE(viewport_height, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height());
web_view_helper.GetWebView()->GetSettings()->SetForceZeroLayoutHeight(true);
EXPECT_TRUE(web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->NeedsLayout());
EXPECT_EQ(0, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height());
web_view_helper.Resize(WebSize(viewport_width, viewport_height * 2));
EXPECT_FALSE(web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->NeedsLayout());
EXPECT_EQ(0, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height());
web_view_helper.Resize(WebSize(viewport_width * 2, viewport_height));
EXPECT_EQ(0, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height());
web_view_helper.GetWebView()->GetSettings()->SetForceZeroLayoutHeight(false);
EXPECT_LE(viewport_height, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height());
}
TEST_F(WebFrameTest, ToggleViewportMetaOnOff) {
RegisterMockedHttpURLLoad("viewport-device-width.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "viewport-device-width.html",
nullptr, &client);
WebSettings* settings = web_view_helper.GetWebView()->GetSettings();
settings->SetViewportMetaEnabled(false);
settings->SetViewportEnabled(true);
settings->SetMainFrameResizesAreOrientationChanges(true);
settings->SetShrinksViewportContentToFit(true);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
ViewportData& viewport =
ToLocalFrame(web_view_helper.GetWebView()->GetPage()->MainFrame())
->GetDocument()
->GetViewportData();
EXPECT_FALSE(viewport.GetViewportDescription().IsLegacyViewportType());
settings->SetViewportMetaEnabled(true);
EXPECT_TRUE(viewport.GetViewportDescription().IsLegacyViewportType());
settings->SetViewportMetaEnabled(false);
EXPECT_FALSE(viewport.GetViewportDescription().IsLegacyViewportType());
}
TEST_F(WebFrameTest,
SetForceZeroLayoutHeightWorksWithRelayoutsWhenHeightChanged) {
// this unit test is an attempt to target a real world case where an app could
// 1. call resize(width, 0) and setForceZeroLayoutHeight(true)
// 2. load content (hoping that the viewport height would increase
// as more content is added)
// 3. fail to register touch events aimed at the loaded content
// because the layout is only updated if either width or height is changed
RegisterMockedHttpURLLoad("button.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "button.html", nullptr, &client,
ConfigureAndroid);
// set view height to zero so that if the height of the view is not
// successfully updated during later resizes touch events will fail
// (as in not hit content included in the view)
web_view_helper.Resize(WebSize(viewport_width, 0));
web_view_helper.GetWebView()->GetSettings()->SetForceZeroLayoutHeight(true);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
FloatPoint hit_point = FloatPoint(30, 30); // button size is 100x100
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
Document* document = frame->GetFrame()->GetDocument();
Element* element = document->getElementById("tap_button");
ASSERT_NE(nullptr, element);
EXPECT_EQ(String("oldValue"), element->innerText());
WebGestureEvent gesture_event(WebInputEvent::kGestureTap,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
kWebGestureDeviceTouchscreen);
gesture_event.SetFrameScale(1);
gesture_event.SetPositionInWidget(hit_point);
gesture_event.SetPositionInScreen(hit_point);
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrame()
->GetEventHandler()
.HandleGestureEvent(gesture_event);
// when pressed, the button changes its own text to "updatedValue"
EXPECT_EQ(String("updatedValue"), element->innerText());
}
TEST_F(WebFrameTest, FrameOwnerPropertiesMargin) {
frame_test_helpers::WebViewHelper helper;
helper.InitializeRemote();
WebFrameOwnerProperties properties;
properties.margin_width = 11;
properties.margin_height = 22;
WebLocalFrameImpl* local_frame = frame_test_helpers::CreateLocalChild(
*helper.RemoteMainFrame(), "frameName", properties);
RegisterMockedHttpURLLoad("frame_owner_properties.html");
frame_test_helpers::LoadFrame(local_frame,
base_url_ + "frame_owner_properties.html");
// Check if the LocalFrame has seen the marginwidth and marginheight
// properties.
Document* child_document = local_frame->GetFrame()->GetDocument();
EXPECT_EQ(11, child_document->FirstBodyElement()->GetIntegralAttribute(
html_names::kMarginwidthAttr));
EXPECT_EQ(22, child_document->FirstBodyElement()->GetIntegralAttribute(
html_names::kMarginheightAttr));
LocalFrameView* frame_view = local_frame->GetFrameView();
frame_view->Resize(800, 600);
frame_view->SetNeedsLayout();
frame_view->UpdateAllLifecyclePhases(
DocumentLifecycle::LifecycleUpdateReason::kTest);
// Expect scrollbars to be enabled by default.
EXPECT_NE(nullptr, frame_view->LayoutViewport()->HorizontalScrollbar());
EXPECT_NE(nullptr, frame_view->LayoutViewport()->VerticalScrollbar());
}
TEST_F(WebFrameTest, FrameOwnerPropertiesScrolling) {
frame_test_helpers::WebViewHelper helper;
helper.InitializeRemote();
WebFrameOwnerProperties properties;
// Turn off scrolling in the subframe.
properties.scrolling_mode =
WebFrameOwnerProperties::ScrollingMode::kAlwaysOff;
WebLocalFrameImpl* local_frame = frame_test_helpers::CreateLocalChild(
*helper.RemoteMainFrame(), "frameName", properties);
RegisterMockedHttpURLLoad("frame_owner_properties.html");
frame_test_helpers::LoadFrame(local_frame,
base_url_ + "frame_owner_properties.html");
Document* child_document = local_frame->GetFrame()->GetDocument();
EXPECT_EQ(0, child_document->FirstBodyElement()->GetIntegralAttribute(
html_names::kMarginwidthAttr));
EXPECT_EQ(0, child_document->FirstBodyElement()->GetIntegralAttribute(
html_names::kMarginheightAttr));
LocalFrameView* frame_view = local_frame->GetFrameView();
EXPECT_EQ(nullptr, frame_view->LayoutViewport()->HorizontalScrollbar());
EXPECT_EQ(nullptr, frame_view->LayoutViewport()->VerticalScrollbar());
}
TEST_F(WebFrameTest, SetForceZeroLayoutHeightWorksAcrossNavigations) {
RegisterMockedHttpURLLoad("200-by-300.html");
RegisterMockedHttpURLLoad("large-div.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "200-by-300.html", nullptr,
&client, ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetForceZeroLayoutHeight(true);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "large-div.html");
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
EXPECT_EQ(0, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height());
}
TEST_F(WebFrameTest, SetForceZeroLayoutHeightWithWideViewportQuirk) {
RegisterMockedHttpURLLoad("200-by-300.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "200-by-300.html", nullptr,
&client, ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(true);
web_view_helper.GetWebView()->GetSettings()->SetForceZeroLayoutHeight(true);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_EQ(0, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height());
}
TEST_F(WebFrameTest, WideViewportAndWideContentWithInitialScale) {
RegisterMockedHttpURLLoad("wide_document_width_viewport.html");
RegisterMockedHttpURLLoad("white-1x1.png");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 600;
int viewport_height = 800;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad("about:blank", nullptr, &client,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(true);
web_view_helper.GetWebView()->GetSettings()->SetViewportMetaLayoutSizeQuirk(
true);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
frame_test_helpers::LoadFrame(
web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "wide_document_width_viewport.html");
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
int wide_document_width = 800;
float minimum_page_scale_factor = viewport_width / (float)wide_document_width;
EXPECT_EQ(minimum_page_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor());
EXPECT_EQ(minimum_page_scale_factor,
web_view_helper.GetWebView()->MinimumPageScaleFactor());
}
TEST_F(WebFrameTest, WideViewportQuirkClobbersHeight) {
RegisterMockedHttpURLLoad("viewport-height-1000.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 600;
int viewport_height = 800;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad("about:blank", nullptr, &client,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(false);
web_view_helper.GetWebView()->GetSettings()->SetViewportMetaLayoutSizeQuirk(
true);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "viewport-height-1000.html");
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_EQ(800, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height());
EXPECT_EQ(1, web_view_helper.GetWebView()->PageScaleFactor());
}
TEST_F(WebFrameTest, LayoutSize320Quirk) {
RegisterMockedHttpURLLoad("viewport/viewport-30.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 600;
int viewport_height = 800;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad("about:blank", nullptr, &client,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(true);
web_view_helper.GetWebView()->GetSettings()->SetViewportMetaLayoutSizeQuirk(
true);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "viewport/viewport-30.html");
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_EQ(600, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width());
EXPECT_EQ(800, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height());
EXPECT_EQ(1, web_view_helper.GetWebView()->PageScaleFactor());
// The magic number to snap to device-width is 320, so test that 321 is
// respected.
ViewportData& viewport =
ToLocalFrame(web_view_helper.GetWebView()->GetPage()->MainFrame())
->GetDocument()
->GetViewportData();
ViewportDescription description = viewport.GetViewportDescription();
description.min_width = Length(321, blink::kFixed);
description.max_width = Length(321, blink::kFixed);
viewport.SetViewportDescription(description);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
EXPECT_EQ(321, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width());
description.min_width = Length(320, blink::kFixed);
description.max_width = Length(320, blink::kFixed);
viewport.SetViewportDescription(description);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
EXPECT_EQ(600, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width());
description = viewport.GetViewportDescription();
description.max_height = Length(1000, blink::kFixed);
viewport.SetViewportDescription(description);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
EXPECT_EQ(1000, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height());
description.max_height = Length(320, blink::kFixed);
viewport.SetViewportDescription(description);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
EXPECT_EQ(800, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height());
}
TEST_F(WebFrameTest, ZeroValuesQuirk) {
RegisterMockedHttpURLLoad("viewport-zero-values.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(nullptr, &client, ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetViewportMetaZeroValuesQuirk(
true);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetViewportMetaLayoutSizeQuirk(
true);
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "viewport-zero-values.html");
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_EQ(viewport_width, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width());
EXPECT_EQ(1.0f, web_view_helper.GetWebView()->PageScaleFactor());
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(true);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
EXPECT_EQ(viewport_width, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width());
EXPECT_EQ(1.0f, web_view_helper.GetWebView()->PageScaleFactor());
}
TEST_F(WebFrameTest, OverflowHiddenDisablesScrolling) {
RegisterMockedHttpURLLoad("body-overflow-hidden.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(nullptr, &client);
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "body-overflow-hidden.html");
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
LocalFrameView* view = web_view_helper.LocalMainFrame()->GetFrameView();
EXPECT_FALSE(view->LayoutViewport()->UserInputScrollable(kVerticalScrollbar));
EXPECT_FALSE(
view->LayoutViewport()->UserInputScrollable(kHorizontalScrollbar));
}
TEST_F(WebFrameTest, OverflowHiddenDisablesScrollingWithSetCanHaveScrollbars) {
RegisterMockedHttpURLLoad("body-overflow-hidden-short.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(nullptr, &client);
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "body-overflow-hidden-short.html");
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
LocalFrameView* view = web_view_helper.LocalMainFrame()->GetFrameView();
EXPECT_FALSE(view->LayoutViewport()->UserInputScrollable(kVerticalScrollbar));
EXPECT_FALSE(
view->LayoutViewport()->UserInputScrollable(kHorizontalScrollbar));
web_view_helper.LocalMainFrame()->GetFrameView()->SetCanHaveScrollbars(true);
EXPECT_FALSE(view->LayoutViewport()->UserInputScrollable(kVerticalScrollbar));
EXPECT_FALSE(
view->LayoutViewport()->UserInputScrollable(kHorizontalScrollbar));
}
TEST_F(WebFrameTest, IgnoreOverflowHiddenQuirk) {
RegisterMockedHttpURLLoad("body-overflow-hidden.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(nullptr, &client);
web_view_helper.GetWebView()
->GetSettings()
->SetIgnoreMainFrameOverflowHiddenQuirk(true);
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "body-overflow-hidden.html");
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
LocalFrameView* view = web_view_helper.LocalMainFrame()->GetFrameView();
EXPECT_TRUE(view->LayoutViewport()->UserInputScrollable(kVerticalScrollbar));
}
TEST_F(WebFrameTest, NonZeroValuesNoQuirk) {
RegisterMockedHttpURLLoad("viewport-nonzero-values.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
float expected_page_scale_factor = 0.5f;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(nullptr, &client, ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetViewportMetaZeroValuesQuirk(
true);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "viewport-nonzero-values.html");
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_EQ(viewport_width / expected_page_scale_factor,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width());
EXPECT_EQ(expected_page_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor());
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(true);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
EXPECT_EQ(viewport_width / expected_page_scale_factor,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width());
EXPECT_EQ(expected_page_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor());
}
TEST_F(WebFrameTest, setPageScaleFactorDoesNotLayout) {
RegisterMockedHttpURLLoad("fixed_layout.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
// Small viewport to ensure there are always scrollbars.
int viewport_width = 64;
int viewport_height = 48;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "fixed_layout.html", nullptr,
&client, ConfigureAndroid);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
unsigned prev_layout_count =
web_view_helper.LocalMainFrame()->GetFrameView()->LayoutCountForTesting();
web_view_helper.GetWebView()->SetPageScaleFactor(3);
EXPECT_FALSE(web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->NeedsLayout());
EXPECT_EQ(prev_layout_count, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->LayoutCountForTesting());
}
TEST_F(WebFrameTest, setPageScaleFactorWithOverlayScrollbarsDoesNotLayout) {
RegisterMockedHttpURLLoad("fixed_layout.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "fixed_layout.html", nullptr,
&client, ConfigureAndroid);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
unsigned prev_layout_count =
web_view_helper.LocalMainFrame()->GetFrameView()->LayoutCountForTesting();
web_view_helper.GetWebView()->SetPageScaleFactor(30);
EXPECT_FALSE(web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->NeedsLayout());
EXPECT_EQ(prev_layout_count, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->LayoutCountForTesting());
}
TEST_F(WebFrameTest, pageScaleFactorWrittenToHistoryItem) {
RegisterMockedHttpURLLoad("fixed_layout.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "fixed_layout.html", nullptr,
&client, ConfigureAndroid);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
web_view_helper.GetWebView()->SetPageScaleFactor(3);
EXPECT_EQ(3,
ToLocalFrame(web_view_helper.GetWebView()->GetPage()->MainFrame())
->Loader()
.GetDocumentLoader()
->GetHistoryItem()
->GetViewState()
->page_scale_factor_);
}
TEST_F(WebFrameTest, initialScaleWrittenToHistoryItem) {
RegisterMockedHttpURLLoad("fixed_layout.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(nullptr, &client, ConfigureAndroid);
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.25f, 5);
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "fixed_layout.html");
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
int default_fixed_layout_width = 980;
float minimum_page_scale_factor =
viewport_width / (float)default_fixed_layout_width;
EXPECT_EQ(minimum_page_scale_factor,
ToLocalFrame(web_view_helper.GetWebView()->GetPage()->MainFrame())
->Loader()
.GetDocumentLoader()
->GetHistoryItem()
->GetViewState()
->page_scale_factor_);
}
TEST_F(WebFrameTest, pageScaleFactorDoesntShrinkFrameView) {
RegisterMockedHttpURLLoad("large-div.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
// Small viewport to ensure there are always scrollbars.
int viewport_width = 64;
int viewport_height = 48;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "large-div.html", nullptr,
&client, ConfigureAndroid);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
LocalFrameView* view = web_view_helper.LocalMainFrame()->GetFrameView();
int viewport_width_minus_scrollbar = viewport_width;
int viewport_height_minus_scrollbar = viewport_height;
if (view->LayoutViewport()->VerticalScrollbar() &&
!view->LayoutViewport()->VerticalScrollbar()->IsOverlayScrollbar())
viewport_width_minus_scrollbar -= 15;
if (view->LayoutViewport()->HorizontalScrollbar() &&
!view->LayoutViewport()->HorizontalScrollbar()->IsOverlayScrollbar())
viewport_height_minus_scrollbar -= 15;
web_view_helper.GetWebView()->SetPageScaleFactor(2);
IntSize unscaled_size = view->Size();
EXPECT_EQ(viewport_width, unscaled_size.Width());
EXPECT_EQ(viewport_height, unscaled_size.Height());
IntSize unscaled_size_minus_scrollbar = view->Size();
EXPECT_EQ(viewport_width_minus_scrollbar,
unscaled_size_minus_scrollbar.Width());
EXPECT_EQ(viewport_height_minus_scrollbar,
unscaled_size_minus_scrollbar.Height());
IntSize frame_view_size = view->Size();
EXPECT_EQ(viewport_width_minus_scrollbar, frame_view_size.Width());
EXPECT_EQ(viewport_height_minus_scrollbar, frame_view_size.Height());
}
TEST_F(WebFrameTest, pageScaleFactorDoesNotApplyCssTransform) {
RegisterMockedHttpURLLoad("fixed_layout.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "fixed_layout.html", nullptr,
&client, ConfigureAndroid);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
web_view_helper.GetWebView()->SetPageScaleFactor(2);
EXPECT_EQ(980,
ToLocalFrame(web_view_helper.GetWebView()->GetPage()->MainFrame())
->ContentLayoutObject()
->DocumentRect()
.Width());
EXPECT_EQ(980, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->LayoutViewport()
->ContentsSize()
.Width());
}
TEST_F(WebFrameTest, targetDensityDpiHigh) {
RegisterMockedHttpURLLoad("viewport-target-densitydpi-high.html");
FixedLayoutTestWebViewClient client;
// high-dpi = 240
float target_dpi = 240.0f;
float device_scale_factors[] = {1.0f, 4.0f / 3.0f, 2.0f};
int viewport_width = 640;
int viewport_height = 480;
for (size_t i = 0; i < arraysize(device_scale_factors); ++i) {
float device_scale_factor = device_scale_factors[i];
float device_dpi = device_scale_factor * 160.0f;
client.screen_info_.device_scale_factor = device_scale_factor;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "viewport-target-densitydpi-high.html", nullptr, &client,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()
->GetSettings()
->SetSupportDeprecatedTargetDensityDPI(true);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
// We need to account for the fact that logical pixels are unconditionally
// multiplied by deviceScaleFactor to produce physical pixels.
float density_dpi_scale_ratio =
device_scale_factor * target_dpi / device_dpi;
EXPECT_NEAR(viewport_width * density_dpi_scale_ratio,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width(),
1.0f);
EXPECT_NEAR(viewport_height * density_dpi_scale_ratio,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height(),
1.0f);
EXPECT_NEAR(1.0f / density_dpi_scale_ratio,
web_view_helper.GetWebView()->PageScaleFactor(), 0.01f);
}
}
TEST_F(WebFrameTest, targetDensityDpiDevice) {
RegisterMockedHttpURLLoad("viewport-target-densitydpi-device.html");
float device_scale_factors[] = {1.0f, 4.0f / 3.0f, 2.0f};
FixedLayoutTestWebViewClient client;
int viewport_width = 640;
int viewport_height = 480;
for (size_t i = 0; i < arraysize(device_scale_factors); ++i) {
client.screen_info_.device_scale_factor = device_scale_factors[i];
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "viewport-target-densitydpi-device.html", nullptr, &client,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()
->GetSettings()
->SetSupportDeprecatedTargetDensityDPI(true);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_NEAR(viewport_width * client.screen_info_.device_scale_factor,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width(),
1.0f);
EXPECT_NEAR(viewport_height * client.screen_info_.device_scale_factor,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height(),
1.0f);
EXPECT_NEAR(1.0f / client.screen_info_.device_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor(), 0.01f);
}
}
TEST_F(WebFrameTest, targetDensityDpiDeviceAndFixedWidth) {
RegisterMockedHttpURLLoad(
"viewport-target-densitydpi-device-and-fixed-width.html");
float device_scale_factors[] = {1.0f, 4.0f / 3.0f, 2.0f};
FixedLayoutTestWebViewClient client;
int viewport_width = 640;
int viewport_height = 480;
for (size_t i = 0; i < arraysize(device_scale_factors); ++i) {
client.screen_info_.device_scale_factor = device_scale_factors[i];
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "viewport-target-densitydpi-device-and-fixed-width.html",
nullptr, &client, ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()
->GetSettings()
->SetSupportDeprecatedTargetDensityDPI(true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(true);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_NEAR(viewport_width,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width(),
1.0f);
EXPECT_NEAR(viewport_height,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height(),
1.0f);
EXPECT_NEAR(1.0f, web_view_helper.GetWebView()->PageScaleFactor(), 0.01f);
}
}
TEST_F(WebFrameTest, NoWideViewportAndScaleLessThanOne) {
RegisterMockedHttpURLLoad("viewport-initial-scale-less-than-1.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1.33f;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "viewport-initial-scale-less-than-1.html", nullptr, &client,
ConfigureAndroid);
web_view_helper.GetWebView()
->GetSettings()
->SetSupportDeprecatedTargetDensityDPI(true);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(false);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_NEAR(viewport_width * client.screen_info_.device_scale_factor,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width(),
1.0f);
EXPECT_NEAR(viewport_height * client.screen_info_.device_scale_factor,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height(),
1.0f);
EXPECT_NEAR(1.0f / client.screen_info_.device_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor(), 0.01f);
}
TEST_F(WebFrameTest, NoWideViewportAndScaleLessThanOneWithDeviceWidth) {
RegisterMockedHttpURLLoad(
"viewport-initial-scale-less-than-1-device-width.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1.33f;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "viewport-initial-scale-less-than-1-device-width.html",
nullptr, &client, ConfigureAndroid);
web_view_helper.GetWebView()
->GetSettings()
->SetSupportDeprecatedTargetDensityDPI(true);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(false);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
const float kPageZoom = 0.25f;
EXPECT_NEAR(
viewport_width * client.screen_info_.device_scale_factor / kPageZoom,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width(),
1.0f);
EXPECT_NEAR(
viewport_height * client.screen_info_.device_scale_factor / kPageZoom,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height(),
1.0f);
EXPECT_NEAR(1.0f / client.screen_info_.device_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor(), 0.01f);
}
TEST_F(WebFrameTest, NoWideViewportAndNoViewportWithInitialPageScaleOverride) {
RegisterMockedHttpURLLoad("large-div.html");
FixedLayoutTestWebViewClient client;
int viewport_width = 640;
int viewport_height = 480;
float enforced_page_scale_factor = 5.0f;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "large-div.html", nullptr,
&client, ConfigureAndroid);
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.25f, 5);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(false);
web_view_helper.GetWebView()->SetInitialPageScaleOverride(
enforced_page_scale_factor);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_NEAR(viewport_width / enforced_page_scale_factor,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width(),
1.0f);
EXPECT_NEAR(viewport_height / enforced_page_scale_factor,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height(),
1.0f);
EXPECT_NEAR(enforced_page_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor(), 0.01f);
}
TEST_F(WebFrameTest, NoUserScalableQuirkIgnoresViewportScale) {
RegisterMockedHttpURLLoad("viewport-initial-scale-and-user-scalable-no.html");
FixedLayoutTestWebViewClient client;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "viewport-initial-scale-and-user-scalable-no.html", nullptr,
&client, ConfigureAndroid);
web_view_helper.GetWebView()
->GetSettings()
->SetViewportMetaNonUserScalableQuirk(true);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_NEAR(viewport_width,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width(),
1.0f);
EXPECT_NEAR(viewport_height,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height(),
1.0f);
EXPECT_NEAR(1.0f, web_view_helper.GetWebView()->PageScaleFactor(), 0.01f);
}
TEST_F(WebFrameTest,
NoUserScalableQuirkIgnoresViewportScaleForNonWideViewport) {
RegisterMockedHttpURLLoad("viewport-initial-scale-and-user-scalable-no.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1.33f;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "viewport-initial-scale-and-user-scalable-no.html", nullptr,
&client, ConfigureAndroid);
web_view_helper.GetWebView()
->GetSettings()
->SetSupportDeprecatedTargetDensityDPI(true);
web_view_helper.GetWebView()
->GetSettings()
->SetViewportMetaNonUserScalableQuirk(true);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(false);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_NEAR(viewport_width * client.screen_info_.device_scale_factor,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width(),
1.0f);
EXPECT_NEAR(viewport_height * client.screen_info_.device_scale_factor,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height(),
1.0f);
EXPECT_NEAR(1.0f / client.screen_info_.device_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor(), 0.01f);
}
TEST_F(WebFrameTest, NoUserScalableQuirkIgnoresViewportScaleForWideViewport) {
RegisterMockedHttpURLLoad("viewport-2x-initial-scale-non-user-scalable.html");
FixedLayoutTestWebViewClient client;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "viewport-2x-initial-scale-non-user-scalable.html", nullptr,
&client, ConfigureAndroid);
web_view_helper.GetWebView()
->GetSettings()
->SetViewportMetaNonUserScalableQuirk(true);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(true);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_NEAR(viewport_width,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width(),
1.0f);
EXPECT_NEAR(viewport_height,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height(),
1.0f);
EXPECT_NEAR(1.0f, web_view_helper.GetWebView()->PageScaleFactor(), 0.01f);
}
TEST_F(WebFrameTest, DesktopPageCanBeZoomedInWhenWideViewportIsTurnedOff) {
RegisterMockedHttpURLLoad("no_viewport_tag.html");
FixedLayoutTestWebViewClient client;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "no_viewport_tag.html", nullptr,
&client, ConfigureAndroid);
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.25f, 5);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(false);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_NEAR(1.0f, web_view_helper.GetWebView()->PageScaleFactor(), 0.01f);
EXPECT_NEAR(1.0f, web_view_helper.GetWebView()->MinimumPageScaleFactor(),
0.01f);
EXPECT_NEAR(5.0f, web_view_helper.GetWebView()->MaximumPageScaleFactor(),
0.01f);
}
TEST_F(WebFrameTest, AtViewportInsideAtMediaInitialViewport) {
RegisterMockedHttpURLLoad("viewport-inside-media.html");
FixedLayoutTestWebViewClient client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "viewport-inside-media.html",
nullptr, &client, ConfigureAndroid);
web_view_helper.Resize(WebSize(640, 480));
EXPECT_EQ(2000, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width());
web_view_helper.Resize(WebSize(1200, 480));
EXPECT_EQ(1200, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width());
}
TEST_F(WebFrameTest, AtViewportAffectingAtMediaRecalcCount) {
RegisterMockedHttpURLLoad("viewport-and-media.html");
FixedLayoutTestWebViewClient client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(nullptr, &client, ConfigureAndroid);
web_view_helper.Resize(WebSize(640, 480));
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "viewport-and-media.html");
Document* document =
web_view_helper.LocalMainFrame()->GetFrame()->GetDocument();
EXPECT_EQ(2000, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width());
// The styleForElementCount() should match the number of elements for a single
// pass of computed styles construction for the document.
EXPECT_EQ(8u, document->GetStyleEngine().StyleForElementCount());
EXPECT_EQ(Color(0, 128, 0),
document->body()->GetComputedStyle()->VisitedDependentColor(
GetCSSPropertyColor()));
}
TEST_F(WebFrameTest, AtViewportWithViewportLengths) {
RegisterMockedHttpURLLoad("viewport-lengths.html");
FixedLayoutTestWebViewClient client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(nullptr, &client, ConfigureAndroid);
web_view_helper.Resize(WebSize(800, 600));
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "viewport-lengths.html");
LocalFrameView* view = web_view_helper.LocalMainFrame()->GetFrameView();
EXPECT_EQ(400, view->GetLayoutSize().Width());
EXPECT_EQ(300, view->GetLayoutSize().Height());
web_view_helper.Resize(WebSize(1000, 400));
EXPECT_EQ(500, view->GetLayoutSize().Width());
EXPECT_EQ(200, view->GetLayoutSize().Height());
}
class WebFrameResizeTest : public WebFrameTest {
protected:
static FloatSize ComputeRelativeOffset(const IntPoint& absolute_offset,
const LayoutRect& rect) {
FloatSize relative_offset =
FloatPoint(absolute_offset) - FloatPoint(rect.Location());
relative_offset.Scale(1.f / rect.Width(), 1.f / rect.Height());
return relative_offset;
}
void TestResizeYieldsCorrectScrollAndScale(
const char* url,
const float initial_page_scale_factor,
const WebSize scroll_offset,
const WebSize viewport_size,
const bool should_scale_relative_to_viewport_width) {
RegisterMockedHttpURLLoad(url);
const float aspect_ratio =
static_cast<float>(viewport_size.width) / viewport_size.height;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + url, nullptr, nullptr,
ConfigureAndroid);
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.25f, 5);
// Origin scrollOffsets preserved under resize.
{
web_view_helper.Resize(
WebSize(viewport_size.width, viewport_size.height));
web_view_helper.GetWebView()->SetPageScaleFactor(
initial_page_scale_factor);
ASSERT_EQ(viewport_size,
web_view_helper.GetWebView()->MainFrameWidget()->Size());
ASSERT_EQ(initial_page_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor());
web_view_helper.Resize(
WebSize(viewport_size.height, viewport_size.width));
float expected_page_scale_factor =
initial_page_scale_factor *
(should_scale_relative_to_viewport_width ? 1 / aspect_ratio : 1);
EXPECT_NEAR(expected_page_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor(), 0.05f);
EXPECT_EQ(WebSize(), web_view_helper.LocalMainFrame()->GetScrollOffset());
}
// Resizing just the height should not affect pageScaleFactor or
// scrollOffset.
{
web_view_helper.Resize(
WebSize(viewport_size.width, viewport_size.height));
web_view_helper.GetWebView()->SetPageScaleFactor(
initial_page_scale_factor);
web_view_helper.LocalMainFrame()->SetScrollOffset(scroll_offset);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
const WebSize expected_scroll_offset =
web_view_helper.LocalMainFrame()->GetScrollOffset();
web_view_helper.Resize(
WebSize(viewport_size.width, viewport_size.height * 0.8f));
EXPECT_EQ(initial_page_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor());
EXPECT_EQ(expected_scroll_offset,
web_view_helper.LocalMainFrame()->GetScrollOffset());
web_view_helper.Resize(
WebSize(viewport_size.width, viewport_size.height * 0.8f));
EXPECT_EQ(initial_page_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor());
EXPECT_EQ(expected_scroll_offset,
web_view_helper.LocalMainFrame()->GetScrollOffset());
}
}
};
TEST_F(WebFrameResizeTest,
ResizeYieldsCorrectScrollAndScaleForWidthEqualsDeviceWidth) {
// With width=device-width, pageScaleFactor is preserved across resizes as
// long as the content adjusts according to the device-width.
const char* url = "resize_scroll_mobile.html";
const float kInitialPageScaleFactor = 1;
const WebSize scroll_offset(0, 50);
const WebSize viewport_size(120, 160);
const bool kShouldScaleRelativeToViewportWidth = true;
TestResizeYieldsCorrectScrollAndScale(url, kInitialPageScaleFactor,
scroll_offset, viewport_size,
kShouldScaleRelativeToViewportWidth);
}
TEST_F(WebFrameResizeTest, ResizeYieldsCorrectScrollAndScaleForMinimumScale) {
// This tests a scenario where minimum-scale is set to 1.0, but some element
// on the page is slightly larger than the portrait width, so our "natural"
// minimum-scale would be lower. In that case, we should stick to 1.0 scale
// on rotation and not do anything strange.
const char* url = "resize_scroll_minimum_scale.html";
const float kInitialPageScaleFactor = 1;
const WebSize scroll_offset(0, 0);
const WebSize viewport_size(240, 320);
const bool kShouldScaleRelativeToViewportWidth = false;
TestResizeYieldsCorrectScrollAndScale(url, kInitialPageScaleFactor,
scroll_offset, viewport_size,
kShouldScaleRelativeToViewportWidth);
}
TEST_F(WebFrameResizeTest, ResizeYieldsCorrectScrollAndScaleForFixedWidth) {
// With a fixed width, pageScaleFactor scales by the relative change in
// viewport width.
const char* url = "resize_scroll_fixed_width.html";
const float kInitialPageScaleFactor = 2;
const WebSize scroll_offset(0, 200);
const WebSize viewport_size(240, 320);
const bool kShouldScaleRelativeToViewportWidth = true;
TestResizeYieldsCorrectScrollAndScale(url, kInitialPageScaleFactor,
scroll_offset, viewport_size,
kShouldScaleRelativeToViewportWidth);
}
TEST_F(WebFrameResizeTest, ResizeYieldsCorrectScrollAndScaleForFixedLayout) {
// With a fixed layout, pageScaleFactor scales by the relative change in
// viewport width.
const char* url = "resize_scroll_fixed_layout.html";
const float kInitialPageScaleFactor = 2;
const WebSize scroll_offset(200, 400);
const WebSize viewport_size(320, 240);
const bool kShouldScaleRelativeToViewportWidth = true;
TestResizeYieldsCorrectScrollAndScale(url, kInitialPageScaleFactor,
scroll_offset, viewport_size,
kShouldScaleRelativeToViewportWidth);
}
TEST_F(WebFrameTest, pageScaleFactorUpdatesScrollbars) {
RegisterMockedHttpURLLoad("fixed_layout.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "fixed_layout.html", nullptr,
&client, ConfigureAndroid);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
LocalFrameView* view = web_view_helper.LocalMainFrame()->GetFrameView();
ScrollableArea* scrollable_area = view->LayoutViewport();
EXPECT_EQ(scrollable_area->ScrollSize(kHorizontalScrollbar),
scrollable_area->ContentsSize().Width() - view->Width());
EXPECT_EQ(scrollable_area->ScrollSize(kVerticalScrollbar),
scrollable_area->ContentsSize().Height() - view->Height());
web_view_helper.GetWebView()->SetPageScaleFactor(10);
EXPECT_EQ(scrollable_area->ScrollSize(kHorizontalScrollbar),
scrollable_area->ContentsSize().Width() - view->Width());
EXPECT_EQ(scrollable_area->ScrollSize(kVerticalScrollbar),
scrollable_area->ContentsSize().Height() - view->Height());
}
TEST_F(WebFrameTest, CanOverrideScaleLimits) {
RegisterMockedHttpURLLoad("no_scale_for_you.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "no_scale_for_you.html",
nullptr, &client, ConfigureAndroid);
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.25f, 5);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
EXPECT_EQ(2.0f, web_view_helper.GetWebView()->MinimumPageScaleFactor());
EXPECT_EQ(2.0f, web_view_helper.GetWebView()->MaximumPageScaleFactor());
web_view_helper.GetWebView()->SetIgnoreViewportTagScaleLimits(true);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
EXPECT_EQ(1.0f, web_view_helper.GetWebView()->MinimumPageScaleFactor());
EXPECT_EQ(5.0f, web_view_helper.GetWebView()->MaximumPageScaleFactor());
web_view_helper.GetWebView()->SetIgnoreViewportTagScaleLimits(false);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
EXPECT_EQ(2.0f, web_view_helper.GetWebView()->MinimumPageScaleFactor());
EXPECT_EQ(2.0f, web_view_helper.GetWebView()->MaximumPageScaleFactor());
}
// Android doesn't have scrollbars on the main LocalFrameView
#if defined(OS_ANDROID)
TEST_F(WebFrameTest, DISABLED_updateOverlayScrollbarLayers)
#else
TEST_F(WebFrameTest, updateOverlayScrollbarLayers)
#endif
{
RegisterMockedHttpURLLoad("large-div.html");
int view_width = 500;
int view_height = 500;
FakeCompositingWebViewClient fake_compositing_web_view_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(nullptr, &fake_compositing_web_view_client,
&ConfigureCompositingWebView);
web_view_helper.Resize(WebSize(view_width, view_height));
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "large-div.html");
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
LocalFrameView* view = web_view_helper.LocalMainFrame()->GetFrameView();
EXPECT_TRUE(view->LayoutViewport()->LayerForHorizontalScrollbar());
EXPECT_TRUE(view->LayoutViewport()->LayerForVerticalScrollbar());
web_view_helper.Resize(WebSize(view_width * 10, view_height * 10));
EXPECT_FALSE(view->LayoutViewport()->LayerForHorizontalScrollbar());
EXPECT_FALSE(view->LayoutViewport()->LayerForVerticalScrollbar());
}
void SetScaleAndScrollAndLayout(WebViewImpl* web_view,
WebPoint scroll,
float scale) {
web_view->SetPageScaleFactor(scale);
web_view->MainFrameImpl()->SetScrollOffset(WebSize(scroll.x, scroll.y));
web_view->MainFrameWidget()->UpdateAllLifecyclePhases(
WebWidget::LifecycleUpdateReason::kTest);
}
void SimulatePageScale(WebViewImpl* web_view_impl, float& scale) {
float scale_delta =
web_view_impl->FakePageScaleAnimationPageScaleForTesting() /
web_view_impl->PageScaleFactor();
web_view_impl->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), scale_delta, 0,
cc::BrowserControlsState::kBoth});
scale = web_view_impl->PageScaleFactor();
}
WebRect ComputeBlockBoundHelper(WebViewImpl* web_view_impl,
const gfx::Point& point,
bool ignore_clipping) {
DCHECK(web_view_impl->MainFrameImpl());
WebFrameWidgetBase* widget =
web_view_impl->MainFrameImpl()->FrameWidgetImpl();
DCHECK(widget);
return widget->ComputeBlockBound(point, ignore_clipping);
}
void SimulateDoubleTap(WebViewImpl* web_view_impl,
gfx::Point& point,
float& scale) {
web_view_impl->AnimateDoubleTapZoom(
IntPoint(point), ComputeBlockBoundHelper(web_view_impl, point, false));
EXPECT_TRUE(web_view_impl->FakeDoubleTapAnimationPendingForTesting());
SimulatePageScale(web_view_impl, scale);
}
TEST_F(WebFrameTest, DivAutoZoomParamsTest) {
RegisterMockedHttpURLLoad("get_scale_for_auto_zoom_into_div_test.html");
const float kDeviceScaleFactor = 2.0f;
int viewport_width = 640 / kDeviceScaleFactor;
int viewport_height = 1280 / kDeviceScaleFactor;
float double_tap_zoom_already_legible_ratio = 1.2f;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "get_scale_for_auto_zoom_into_div_test.html", nullptr,
nullptr, ConfigureAndroid);
web_view_helper.GetWebView()->SetDeviceScaleFactor(kDeviceScaleFactor);
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.01f, 4);
web_view_helper.GetWebView()->SetPageScaleFactor(0.5f);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
WebRect wide_div(200, 100, 400, 150);
WebRect tall_div(200, 300, 400, 800);
gfx::Point double_tap_point_wide(wide_div.x + 50, wide_div.y + 50);
gfx::Point double_tap_point_tall(tall_div.x + 50, tall_div.y + 50);
float scale;
IntPoint scroll;
float double_tap_zoom_already_legible_scale =
web_view_helper.GetWebView()->MinimumPageScaleFactor() *
double_tap_zoom_already_legible_ratio;
// Test double-tap zooming into wide div.
WebRect wide_block_bound = ComputeBlockBoundHelper(
web_view_helper.GetWebView(), double_tap_point_wide, false);
web_view_helper.GetWebView()->ComputeScaleAndScrollForBlockRect(
double_tap_point_wide, wide_block_bound, kTouchPointPadding,
double_tap_zoom_already_legible_scale, scale, scroll);
// The div should horizontally fill the screen (modulo margins), and
// vertically centered (modulo integer rounding).
EXPECT_NEAR(viewport_width / (float)wide_div.width, scale, 0.1);
EXPECT_NEAR(wide_div.x, scroll.X(), 20);
EXPECT_EQ(0, scroll.Y());
SetScaleAndScrollAndLayout(web_view_helper.GetWebView(), scroll, scale);
// Test zoom out back to minimum scale.
wide_block_bound = ComputeBlockBoundHelper(web_view_helper.GetWebView(),
double_tap_point_wide, false);
web_view_helper.GetWebView()->ComputeScaleAndScrollForBlockRect(
double_tap_point_wide, wide_block_bound, kTouchPointPadding,
double_tap_zoom_already_legible_scale, scale, scroll);
// FIXME: Looks like we are missing EXPECTs here.
scale = web_view_helper.GetWebView()->MinimumPageScaleFactor();
SetScaleAndScrollAndLayout(web_view_helper.GetWebView(), WebPoint(0, 0),
scale);
// Test double-tap zooming into tall div.
WebRect tall_block_bound = ComputeBlockBoundHelper(
web_view_helper.GetWebView(), double_tap_point_tall, false);
web_view_helper.GetWebView()->ComputeScaleAndScrollForBlockRect(
double_tap_point_tall, tall_block_bound, kTouchPointPadding,
double_tap_zoom_already_legible_scale, scale, scroll);
// The div should start at the top left of the viewport.
EXPECT_NEAR(viewport_width / (float)tall_div.width, scale, 0.1);
EXPECT_NEAR(tall_div.x, scroll.X(), 20);
EXPECT_NEAR(tall_div.y, scroll.Y(), 20);
}
TEST_F(WebFrameTest, DivAutoZoomWideDivTest) {
RegisterMockedHttpURLLoad("get_wide_div_for_auto_zoom_test.html");
const float kDeviceScaleFactor = 2.0f;
int viewport_width = 640 / kDeviceScaleFactor;
int viewport_height = 1280 / kDeviceScaleFactor;
float double_tap_zoom_already_legible_ratio = 1.2f;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "get_wide_div_for_auto_zoom_test.html", nullptr, nullptr,
ConfigureAndroid);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
web_view_helper.GetWebView()->SetDeviceScaleFactor(kDeviceScaleFactor);
web_view_helper.GetWebView()->SetPageScaleFactor(1.0f);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
web_view_helper.GetWebView()->EnableFakePageScaleAnimationForTesting(true);
float double_tap_zoom_already_legible_scale =
web_view_helper.GetWebView()->MinimumPageScaleFactor() *
double_tap_zoom_already_legible_ratio;
WebRect div(0, 100, viewport_width, 150);
gfx::Point point(div.x + 50, div.y + 50);
float scale;
SetScaleAndScrollAndLayout(
web_view_helper.GetWebView(), WebPoint(0, 0),
(web_view_helper.GetWebView()->MinimumPageScaleFactor()) *
(1 + double_tap_zoom_already_legible_ratio) / 2);
SimulateDoubleTap(web_view_helper.GetWebView(), point, scale);
EXPECT_FLOAT_EQ(double_tap_zoom_already_legible_scale, scale);
SimulateDoubleTap(web_view_helper.GetWebView(), point, scale);
EXPECT_FLOAT_EQ(web_view_helper.GetWebView()->MinimumPageScaleFactor(),
scale);
}
TEST_F(WebFrameTest, DivAutoZoomVeryTallTest) {
// When a block is taller than the viewport and a zoom targets a lower part
// of it, then we should keep the target point onscreen instead of snapping
// back up the top of the block.
RegisterMockedHttpURLLoad("very_tall_div.html");
const float kDeviceScaleFactor = 2.0f;
int viewport_width = 640 / kDeviceScaleFactor;
int viewport_height = 1280 / kDeviceScaleFactor;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "very_tall_div.html", nullptr,
nullptr, ConfigureAndroid);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
web_view_helper.GetWebView()->SetDeviceScaleFactor(kDeviceScaleFactor);
web_view_helper.GetWebView()->SetPageScaleFactor(1.0f);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
WebRect div(200, 300, 400, 5000);
gfx::Point point(div.x + 50, div.y + 3000);
float scale;
IntPoint scroll;
WebRect block_bound =
ComputeBlockBoundHelper(web_view_helper.GetWebView(), point, true);
web_view_helper.GetWebView()->ComputeScaleAndScrollForBlockRect(
point, block_bound, 0, 1.0f, scale, scroll);
EXPECT_EQ(scale, 1.0f);
EXPECT_EQ(scroll.Y(), 2660);
}
TEST_F(WebFrameTest, DivAutoZoomMultipleDivsTest) {
RegisterMockedHttpURLLoad("get_multiple_divs_for_auto_zoom_test.html");
const float kDeviceScaleFactor = 2.0f;
int viewport_width = 640 / kDeviceScaleFactor;
int viewport_height = 1280 / kDeviceScaleFactor;
float double_tap_zoom_already_legible_ratio = 1.2f;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "get_multiple_divs_for_auto_zoom_test.html", nullptr, nullptr,
ConfigureAndroid);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.5f, 4);
web_view_helper.GetWebView()->SetDeviceScaleFactor(kDeviceScaleFactor);
web_view_helper.GetWebView()->SetPageScaleFactor(0.5f);
web_view_helper.GetWebView()->SetMaximumLegibleScale(1.f);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
web_view_helper.GetWebView()->EnableFakePageScaleAnimationForTesting(true);
WebRect top_div(200, 100, 200, 150);
WebRect bottom_div(200, 300, 200, 150);
gfx::Point top_point(top_div.x + 50, top_div.y + 50);
gfx::Point bottom_point(bottom_div.x + 50, bottom_div.y + 50);
float scale;
SetScaleAndScrollAndLayout(
web_view_helper.GetWebView(), WebPoint(0, 0),
(web_view_helper.GetWebView()->MinimumPageScaleFactor()) *
(1 + double_tap_zoom_already_legible_ratio) / 2);
// Test double tap on two different divs. After first zoom, we should go back
// to minimum page scale with a second double tap.
SimulateDoubleTap(web_view_helper.GetWebView(), top_point, scale);
EXPECT_FLOAT_EQ(1, scale);
SimulateDoubleTap(web_view_helper.GetWebView(), bottom_point, scale);
EXPECT_FLOAT_EQ(web_view_helper.GetWebView()->MinimumPageScaleFactor(),
scale);
// If the user pinch zooms after double tap, a second double tap should zoom
// back to the div.
SimulateDoubleTap(web_view_helper.GetWebView(), top_point, scale);
EXPECT_FLOAT_EQ(1, scale);
web_view_helper.GetWebView()->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 0.6f, 0,
cc::BrowserControlsState::kBoth});
SimulateDoubleTap(web_view_helper.GetWebView(), bottom_point, scale);
EXPECT_FLOAT_EQ(1, scale);
SimulateDoubleTap(web_view_helper.GetWebView(), bottom_point, scale);
EXPECT_FLOAT_EQ(web_view_helper.GetWebView()->MinimumPageScaleFactor(),
scale);
// If we didn't yet get an auto-zoom update and a second double-tap arrives,
// should go back to minimum scale.
web_view_helper.GetWebView()->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 1.1f, 0,
cc::BrowserControlsState::kBoth});
WebRect block_bounds =
ComputeBlockBoundHelper(web_view_helper.GetWebView(), top_point, false);
web_view_helper.GetWebView()->AnimateDoubleTapZoom(IntPoint(top_point),
block_bounds);
EXPECT_TRUE(
web_view_helper.GetWebView()->FakeDoubleTapAnimationPendingForTesting());
SimulateDoubleTap(web_view_helper.GetWebView(), bottom_point, scale);
EXPECT_FLOAT_EQ(web_view_helper.GetWebView()->MinimumPageScaleFactor(),
scale);
}
TEST_F(WebFrameTest, DivAutoZoomScaleBoundsTest) {
RegisterMockedHttpURLLoad("get_scale_bounds_check_for_auto_zoom_test.html");
int viewport_width = 320;
int viewport_height = 480;
float double_tap_zoom_already_legible_ratio = 1.2f;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "get_scale_bounds_check_for_auto_zoom_test.html", nullptr,
nullptr, ConfigureAndroid);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
web_view_helper.GetWebView()->SetDeviceScaleFactor(1.5f);
web_view_helper.GetWebView()->SetMaximumLegibleScale(1.f);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
web_view_helper.GetWebView()->EnableFakePageScaleAnimationForTesting(true);
WebRect div(200, 100, 200, 150);
gfx::Point double_tap_point(div.x + 50, div.y + 50);
float scale;
// Test double tap scale bounds.
// minimumPageScale < doubleTapZoomAlreadyLegibleScale < 1
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.5f, 4);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
float double_tap_zoom_already_legible_scale =
web_view_helper.GetWebView()->MinimumPageScaleFactor() *
double_tap_zoom_already_legible_ratio;
SetScaleAndScrollAndLayout(
web_view_helper.GetWebView(), WebPoint(0, 0),
(web_view_helper.GetWebView()->MinimumPageScaleFactor()) *
(1 + double_tap_zoom_already_legible_ratio) / 2);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(1, scale);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(web_view_helper.GetWebView()->MinimumPageScaleFactor(),
scale);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(1, scale);
// Zoom in to reset double_tap_zoom_in_effect flag.
web_view_helper.GetWebView()->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 1.1f, 0,
cc::BrowserControlsState::kBoth});
// 1 < minimumPageScale < doubleTapZoomAlreadyLegibleScale
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(1.1f, 4);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
double_tap_zoom_already_legible_scale =
web_view_helper.GetWebView()->MinimumPageScaleFactor() *
double_tap_zoom_already_legible_ratio;
SetScaleAndScrollAndLayout(
web_view_helper.GetWebView(), WebPoint(0, 0),
(web_view_helper.GetWebView()->MinimumPageScaleFactor()) *
(1 + double_tap_zoom_already_legible_ratio) / 2);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(double_tap_zoom_already_legible_scale, scale);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(web_view_helper.GetWebView()->MinimumPageScaleFactor(),
scale);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(double_tap_zoom_already_legible_scale, scale);
// Zoom in to reset double_tap_zoom_in_effect flag.
web_view_helper.GetWebView()->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 1.1f, 0,
cc::BrowserControlsState::kBoth});
// minimumPageScale < 1 < doubleTapZoomAlreadyLegibleScale
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.95f, 4);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
double_tap_zoom_already_legible_scale =
web_view_helper.GetWebView()->MinimumPageScaleFactor() *
double_tap_zoom_already_legible_ratio;
SetScaleAndScrollAndLayout(
web_view_helper.GetWebView(), WebPoint(0, 0),
(web_view_helper.GetWebView()->MinimumPageScaleFactor()) *
(1 + double_tap_zoom_already_legible_ratio) / 2);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(double_tap_zoom_already_legible_scale, scale);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(web_view_helper.GetWebView()->MinimumPageScaleFactor(),
scale);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(double_tap_zoom_already_legible_scale, scale);
}
TEST_F(WebFrameTest, DivAutoZoomScaleLegibleScaleTest) {
RegisterMockedHttpURLLoad("get_scale_bounds_check_for_auto_zoom_test.html");
int viewport_width = 320;
int viewport_height = 480;
float double_tap_zoom_already_legible_ratio = 1.2f;
float maximum_legible_scale_factor = 1.13f;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "get_scale_bounds_check_for_auto_zoom_test.html", nullptr,
nullptr, ConfigureAndroid);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
web_view_helper.GetWebView()->SetMaximumLegibleScale(
maximum_legible_scale_factor);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
web_view_helper.GetWebView()->EnableFakePageScaleAnimationForTesting(true);
web_view_helper.GetWebView()
->GetPage()
->GetSettings()
.SetTextAutosizingEnabled(true);
WebRect div(200, 100, 200, 150);
gfx::Point double_tap_point(div.x + 50, div.y + 50);
float scale;
// Test double tap scale bounds.
// minimumPageScale < doubleTapZoomAlreadyLegibleScale < 1 <
// maximumLegibleScaleFactor
float legible_scale = maximum_legible_scale_factor;
SetScaleAndScrollAndLayout(
web_view_helper.GetWebView(), WebPoint(0, 0),
(web_view_helper.GetWebView()->MinimumPageScaleFactor()) *
(1 + double_tap_zoom_already_legible_ratio) / 2);
float double_tap_zoom_already_legible_scale =
web_view_helper.GetWebView()->MinimumPageScaleFactor() *
double_tap_zoom_already_legible_ratio;
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.5f, 4);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(legible_scale, scale);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(web_view_helper.GetWebView()->MinimumPageScaleFactor(),
scale);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(legible_scale, scale);
// Zoom in to reset double_tap_zoom_in_effect flag.
web_view_helper.GetWebView()->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 1.1f, 0,
cc::BrowserControlsState::kBoth});
// 1 < maximumLegibleScaleFactor < minimumPageScale <
// doubleTapZoomAlreadyLegibleScale
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(1.0f, 4);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
double_tap_zoom_already_legible_scale =
web_view_helper.GetWebView()->MinimumPageScaleFactor() *
double_tap_zoom_already_legible_ratio;
SetScaleAndScrollAndLayout(
web_view_helper.GetWebView(), WebPoint(0, 0),
(web_view_helper.GetWebView()->MinimumPageScaleFactor()) *
(1 + double_tap_zoom_already_legible_ratio) / 2);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(double_tap_zoom_already_legible_scale, scale);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(web_view_helper.GetWebView()->MinimumPageScaleFactor(),
scale);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(double_tap_zoom_already_legible_scale, scale);
// Zoom in to reset double_tap_zoom_in_effect flag.
web_view_helper.GetWebView()->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 1.1f, 0,
cc::BrowserControlsState::kBoth});
// minimumPageScale < 1 < maximumLegibleScaleFactor <
// doubleTapZoomAlreadyLegibleScale
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.95f, 4);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
double_tap_zoom_already_legible_scale =
web_view_helper.GetWebView()->MinimumPageScaleFactor() *
double_tap_zoom_already_legible_ratio;
SetScaleAndScrollAndLayout(
web_view_helper.GetWebView(), WebPoint(0, 0),
(web_view_helper.GetWebView()->MinimumPageScaleFactor()) *
(1 + double_tap_zoom_already_legible_ratio) / 2);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(double_tap_zoom_already_legible_scale, scale);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(web_view_helper.GetWebView()->MinimumPageScaleFactor(),
scale);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(double_tap_zoom_already_legible_scale, scale);
// Zoom in to reset double_tap_zoom_in_effect flag.
web_view_helper.GetWebView()->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 1.1f, 0,
cc::BrowserControlsState::kBoth});
// minimumPageScale < 1 < doubleTapZoomAlreadyLegibleScale <
// maximumLegibleScaleFactor
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.9f, 4);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
double_tap_zoom_already_legible_scale =
web_view_helper.GetWebView()->MinimumPageScaleFactor() *
double_tap_zoom_already_legible_ratio;
SetScaleAndScrollAndLayout(
web_view_helper.GetWebView(), WebPoint(0, 0),
(web_view_helper.GetWebView()->MinimumPageScaleFactor()) *
(1 + double_tap_zoom_already_legible_ratio) / 2);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(legible_scale, scale);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(web_view_helper.GetWebView()->MinimumPageScaleFactor(),
scale);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(legible_scale, scale);
}
TEST_F(WebFrameTest, DivAutoZoomScaleFontScaleFactorTest) {
RegisterMockedHttpURLLoad("get_scale_bounds_check_for_auto_zoom_test.html");
int viewport_width = 320;
int viewport_height = 480;
float double_tap_zoom_already_legible_ratio = 1.2f;
float accessibility_font_scale_factor = 1.13f;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "get_scale_bounds_check_for_auto_zoom_test.html", nullptr,
nullptr, ConfigureAndroid);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
web_view_helper.GetWebView()->SetMaximumLegibleScale(1.f);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
web_view_helper.GetWebView()->EnableFakePageScaleAnimationForTesting(true);
web_view_helper.GetWebView()
->GetPage()
->GetSettings()
.SetTextAutosizingEnabled(true);
web_view_helper.GetWebView()
->GetPage()
->GetSettings()
.SetAccessibilityFontScaleFactor(accessibility_font_scale_factor);
WebRect div(200, 100, 200, 150);
gfx::Point double_tap_point(div.x + 50, div.y + 50);
float scale;
// Test double tap scale bounds.
// minimumPageScale < doubleTapZoomAlreadyLegibleScale < 1 <
// accessibilityFontScaleFactor
float legible_scale = accessibility_font_scale_factor;
SetScaleAndScrollAndLayout(
web_view_helper.GetWebView(), WebPoint(0, 0),
(web_view_helper.GetWebView()->MinimumPageScaleFactor()) *
(1 + double_tap_zoom_already_legible_ratio) / 2);
float double_tap_zoom_already_legible_scale =
web_view_helper.GetWebView()->MinimumPageScaleFactor() *
double_tap_zoom_already_legible_ratio;
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.5f, 4);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(legible_scale, scale);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(web_view_helper.GetWebView()->MinimumPageScaleFactor(),
scale);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(legible_scale, scale);
// Zoom in to reset double_tap_zoom_in_effect flag.
web_view_helper.GetWebView()->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 1.1f, 0,
cc::BrowserControlsState::kBoth});
// 1 < accessibilityFontScaleFactor < minimumPageScale <
// doubleTapZoomAlreadyLegibleScale
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(1.0f, 4);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
double_tap_zoom_already_legible_scale =
web_view_helper.GetWebView()->MinimumPageScaleFactor() *
double_tap_zoom_already_legible_ratio;
SetScaleAndScrollAndLayout(
web_view_helper.GetWebView(), WebPoint(0, 0),
(web_view_helper.GetWebView()->MinimumPageScaleFactor()) *
(1 + double_tap_zoom_already_legible_ratio) / 2);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(double_tap_zoom_already_legible_scale, scale);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(web_view_helper.GetWebView()->MinimumPageScaleFactor(),
scale);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(double_tap_zoom_already_legible_scale, scale);
// Zoom in to reset double_tap_zoom_in_effect flag.
web_view_helper.GetWebView()->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 1.1f, 0,
cc::BrowserControlsState::kBoth});
// minimumPageScale < 1 < accessibilityFontScaleFactor <
// doubleTapZoomAlreadyLegibleScale
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.95f, 4);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
double_tap_zoom_already_legible_scale =
web_view_helper.GetWebView()->MinimumPageScaleFactor() *
double_tap_zoom_already_legible_ratio;
SetScaleAndScrollAndLayout(
web_view_helper.GetWebView(), WebPoint(0, 0),
(web_view_helper.GetWebView()->MinimumPageScaleFactor()) *
(1 + double_tap_zoom_already_legible_ratio) / 2);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(double_tap_zoom_already_legible_scale, scale);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(web_view_helper.GetWebView()->MinimumPageScaleFactor(),
scale);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(double_tap_zoom_already_legible_scale, scale);
// Zoom in to reset double_tap_zoom_in_effect flag.
web_view_helper.GetWebView()->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 1.1f, 0,
cc::BrowserControlsState::kBoth});
// minimumPageScale < 1 < doubleTapZoomAlreadyLegibleScale <
// accessibilityFontScaleFactor
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.9f, 4);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
double_tap_zoom_already_legible_scale =
web_view_helper.GetWebView()->MinimumPageScaleFactor() *
double_tap_zoom_already_legible_ratio;
SetScaleAndScrollAndLayout(
web_view_helper.GetWebView(), WebPoint(0, 0),
(web_view_helper.GetWebView()->MinimumPageScaleFactor()) *
(1 + double_tap_zoom_already_legible_ratio) / 2);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(legible_scale, scale);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(web_view_helper.GetWebView()->MinimumPageScaleFactor(),
scale);
SimulateDoubleTap(web_view_helper.GetWebView(), double_tap_point, scale);
EXPECT_FLOAT_EQ(legible_scale, scale);
}
TEST_F(WebFrameTest, BlockBoundTest) {
RegisterMockedHttpURLLoad("block_bound.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "block_bound.html", nullptr,
nullptr, ConfigureAndroid);
web_view_helper.Resize(WebSize(300, 300));
IntRect rect_back = IntRect(0, 0, 200, 200);
IntRect rect_left_top = IntRect(10, 10, 80, 80);
IntRect rect_right_bottom = IntRect(110, 110, 80, 80);
IntRect block_bound;
block_bound = IntRect(ComputeBlockBoundHelper(web_view_helper.GetWebView(),
gfx::Point(9, 9), true));
EXPECT_EQ(rect_back, block_bound);
block_bound = IntRect(ComputeBlockBoundHelper(web_view_helper.GetWebView(),
gfx::Point(10, 10), true));
EXPECT_EQ(rect_left_top, block_bound);
block_bound = IntRect(ComputeBlockBoundHelper(web_view_helper.GetWebView(),
gfx::Point(50, 50), true));
EXPECT_EQ(rect_left_top, block_bound);
block_bound = IntRect(ComputeBlockBoundHelper(web_view_helper.GetWebView(),
gfx::Point(89, 89), true));
EXPECT_EQ(rect_left_top, block_bound);
block_bound = IntRect(ComputeBlockBoundHelper(web_view_helper.GetWebView(),
gfx::Point(90, 90), true));
EXPECT_EQ(rect_back, block_bound);
block_bound = IntRect(ComputeBlockBoundHelper(web_view_helper.GetWebView(),
gfx::Point(109, 109), true));
EXPECT_EQ(rect_back, block_bound);
block_bound = IntRect(ComputeBlockBoundHelper(web_view_helper.GetWebView(),
gfx::Point(110, 110), true));
EXPECT_EQ(rect_right_bottom, block_bound);
}
TEST_F(WebFrameTest, DontZoomInOnFocusedInTouchAction) {
RegisterMockedHttpURLLoad("textbox_in_touch_action.html");
int viewport_width = 600;
int viewport_height = 1000;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "textbox_in_touch_action.html");
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.25f, 4);
web_view_helper.GetWebView()->EnableFakePageScaleAnimationForTesting(true);
web_view_helper.GetWebView()
->GetPage()
->GetSettings()
.SetTextAutosizingEnabled(false);
web_view_helper.GetWebView()
->GetSettings()
->SetAutoZoomFocusedNodeToLegibleScale(true);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
float initial_scale = web_view_helper.GetWebView()->PageScaleFactor();
// Focus the first textbox that's in a touch-action: pan-x ancestor, this
// shouldn't cause an autozoom since pan-x disables pinch-zoom.
web_view_helper.GetWebView()->AdvanceFocus(false);
web_view_helper.GetWebView()
->MainFrameImpl()
->FrameWidget()
->ScrollFocusedEditableElementIntoView();
EXPECT_EQ(
web_view_helper.GetWebView()->FakePageScaleAnimationPageScaleForTesting(),
0);
SetScaleAndScrollAndLayout(web_view_helper.GetWebView(), WebPoint(0, 0),
initial_scale);
ASSERT_EQ(initial_scale, web_view_helper.GetWebView()->PageScaleFactor());
// Focus the second textbox that's in a touch-action: manipulation ancestor,
// this should cause an autozoom since it allows pinch-zoom.
web_view_helper.GetWebView()->AdvanceFocus(false);
web_view_helper.GetWebView()
->MainFrameImpl()
->FrameWidget()
->ScrollFocusedEditableElementIntoView();
EXPECT_GT(
web_view_helper.GetWebView()->FakePageScaleAnimationPageScaleForTesting(),
initial_scale);
SetScaleAndScrollAndLayout(web_view_helper.GetWebView(), WebPoint(0, 0),
initial_scale);
ASSERT_EQ(initial_scale, web_view_helper.GetWebView()->PageScaleFactor());
// Focus the third textbox that has a touch-action: pan-x ancestor, this
// should cause an autozoom since it's seperated from the node with the
// touch-action by an overflow:scroll element.
web_view_helper.GetWebView()->AdvanceFocus(false);
web_view_helper.GetWebView()
->MainFrameImpl()
->FrameWidget()
->ScrollFocusedEditableElementIntoView();
EXPECT_GT(
web_view_helper.GetWebView()->FakePageScaleAnimationPageScaleForTesting(),
initial_scale);
}
TEST_F(WebFrameTest, DivScrollIntoEditableTest) {
RegisterMockedHttpURLLoad("get_scale_for_zoom_into_editable_test.html");
const bool kAutoZoomToLegibleScale = true;
int viewport_width = 450;
int viewport_height = 300;
float left_box_ratio = 0.3f;
int caret_padding = 10;
float min_readable_caret_height = 16.0f;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "get_scale_for_zoom_into_editable_test.html");
web_view_helper.GetWebView()
->GetPage()
->GetSettings()
.SetTextAutosizingEnabled(false);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.25f, 4);
web_view_helper.GetWebView()->EnableFakePageScaleAnimationForTesting(true);
WebRect edit_box_with_text(200, 200, 250, 20);
WebRect edit_box_with_no_text(200, 250, 250, 20);
// Test scrolling the focused node
// The edit box is shorter and narrower than the viewport when legible.
web_view_helper.GetWebView()->AdvanceFocus(false);
// Set the caret to the end of the input box.
web_view_helper.GetWebView()
->MainFrameImpl()
->GetDocument()
.GetElementById("EditBoxWithText")
.To<WebInputElement>()
.SetSelectionRange(1000, 1000);
SetScaleAndScrollAndLayout(web_view_helper.GetWebView(), WebPoint(0, 0), 1);
WebRect rect, caret;
web_view_helper.GetWebView()->SelectionBounds(caret, rect);
// Set the page scale to be smaller than the minimal readable scale.
float initial_scale = min_readable_caret_height / caret.height * 0.5f;
SetScaleAndScrollAndLayout(web_view_helper.GetWebView(), WebPoint(0, 0),
initial_scale);
float scale;
IntPoint scroll;
bool need_animation;
IntRect element_bounds, caret_bounds;
GetElementAndCaretBoundsForFocusedEditableElement(
web_view_helper, element_bounds, caret_bounds);
web_view_helper.GetWebView()->ComputeScaleAndScrollForEditableElementRects(
element_bounds, caret_bounds, kAutoZoomToLegibleScale, scale, scroll,
need_animation);
EXPECT_TRUE(need_animation);
// The edit box should be left aligned with a margin for possible label.
int h_scroll = edit_box_with_text.x - left_box_ratio * viewport_width / scale;
EXPECT_NEAR(h_scroll, scroll.X(), 2);
int v_scroll = edit_box_with_text.y -
(viewport_height / scale - edit_box_with_text.height) / 2;
EXPECT_NEAR(v_scroll, scroll.Y(), 2);
EXPECT_NEAR(min_readable_caret_height / caret.height, scale, 0.1);
// The edit box is wider than the viewport when legible.
viewport_width = 200;
viewport_height = 150;
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
SetScaleAndScrollAndLayout(web_view_helper.GetWebView(), WebPoint(0, 0),
initial_scale);
GetElementAndCaretBoundsForFocusedEditableElement(
web_view_helper, element_bounds, caret_bounds);
web_view_helper.GetWebView()->ComputeScaleAndScrollForEditableElementRects(
element_bounds, caret_bounds, kAutoZoomToLegibleScale, scale, scroll,
need_animation);
EXPECT_TRUE(need_animation);
// The caret should be right aligned since the caret would be offscreen when
// the edit box is left aligned.
h_scroll = caret.x + caret.width + caret_padding - viewport_width / scale;
EXPECT_NEAR(h_scroll, scroll.X(), 2);
EXPECT_NEAR(min_readable_caret_height / caret.height, scale, 0.1);
SetScaleAndScrollAndLayout(web_view_helper.GetWebView(), WebPoint(0, 0),
initial_scale);
// Move focus to edit box with text.
web_view_helper.GetWebView()->AdvanceFocus(false);
GetElementAndCaretBoundsForFocusedEditableElement(
web_view_helper, element_bounds, caret_bounds);
web_view_helper.GetWebView()->ComputeScaleAndScrollForEditableElementRects(
element_bounds, caret_bounds, kAutoZoomToLegibleScale, scale, scroll,
need_animation);
EXPECT_TRUE(need_animation);
// The edit box should be left aligned.
h_scroll = edit_box_with_no_text.x;
EXPECT_NEAR(h_scroll, scroll.X(), 2);
v_scroll = edit_box_with_no_text.y -
(viewport_height / scale - edit_box_with_no_text.height) / 2;
EXPECT_NEAR(v_scroll, scroll.Y(), 2);
EXPECT_NEAR(min_readable_caret_height / caret.height, scale, 0.1);
// Move focus back to the first edit box.
web_view_helper.GetWebView()->AdvanceFocus(true);
// Zoom out slightly.
const float within_tolerance_scale = scale * 0.9f;
SetScaleAndScrollAndLayout(web_view_helper.GetWebView(), scroll,
within_tolerance_scale);
// Move focus back to the second edit box.
web_view_helper.GetWebView()->AdvanceFocus(false);
GetElementAndCaretBoundsForFocusedEditableElement(
web_view_helper, element_bounds, caret_bounds);
web_view_helper.GetWebView()->ComputeScaleAndScrollForEditableElementRects(
element_bounds, caret_bounds, kAutoZoomToLegibleScale, scale, scroll,
need_animation);
// The scale should not be adjusted as the zoomed out scale was sufficiently
// close to the previously focused scale.
EXPECT_FALSE(need_animation);
}
TEST_F(WebFrameTest, DivScrollIntoEditablePreservePageScaleTest) {
RegisterMockedHttpURLLoad("get_scale_for_zoom_into_editable_test.html");
const bool kAutoZoomToLegibleScale = true;
const int kViewportWidth = 450;
const int kViewportHeight = 300;
const float kMinReadableCaretHeight = 16.0f;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "get_scale_for_zoom_into_editable_test.html");
web_view_helper.GetWebView()
->GetPage()
->GetSettings()
.SetTextAutosizingEnabled(false);
web_view_helper.Resize(WebSize(kViewportWidth, kViewportHeight));
web_view_helper.GetWebView()->EnableFakePageScaleAnimationForTesting(true);
const WebRect edit_box_with_text(200, 200, 250, 20);
web_view_helper.GetWebView()->AdvanceFocus(false);
// Set the caret to the begining of the input box.
web_view_helper.GetWebView()
->MainFrameImpl()
->GetDocument()
.GetElementById("EditBoxWithText")
.To<WebInputElement>()
.SetSelectionRange(0, 0);
SetScaleAndScrollAndLayout(web_view_helper.GetWebView(), WebPoint(0, 0), 1);
WebRect rect, caret;
web_view_helper.GetWebView()->SelectionBounds(caret, rect);
// Set the page scale to be twice as large as the minimal readable scale.
float new_scale = kMinReadableCaretHeight / caret.height * 2.0;
SetScaleAndScrollAndLayout(web_view_helper.GetWebView(), WebPoint(0, 0),
new_scale);
float scale;
IntPoint scroll;
bool need_animation;
IntRect element_bounds, caret_bounds;
GetElementAndCaretBoundsForFocusedEditableElement(
web_view_helper, element_bounds, caret_bounds);
web_view_helper.GetWebView()->ComputeScaleAndScrollForEditableElementRects(
element_bounds, caret_bounds, kAutoZoomToLegibleScale, scale, scroll,
need_animation);
EXPECT_TRUE(need_animation);
// Edit box and caret should be left alinged
int h_scroll = edit_box_with_text.x;
EXPECT_NEAR(h_scroll, scroll.X(), 1);
int v_scroll = edit_box_with_text.y -
(kViewportHeight / scale - edit_box_with_text.height) / 2;
EXPECT_NEAR(v_scroll, scroll.Y(), 1);
// Page scale have to be unchanged
EXPECT_EQ(new_scale, scale);
// Set page scale and scroll such that edit box will be under the screen
new_scale = 3.0;
h_scroll = 200;
SetScaleAndScrollAndLayout(web_view_helper.GetWebView(),
WebPoint(h_scroll, 0), new_scale);
GetElementAndCaretBoundsForFocusedEditableElement(
web_view_helper, element_bounds, caret_bounds);
web_view_helper.GetWebView()->ComputeScaleAndScrollForEditableElementRects(
element_bounds, caret_bounds, kAutoZoomToLegibleScale, scale, scroll,
need_animation);
EXPECT_TRUE(need_animation);
// Horizontal scroll have to be the same
EXPECT_NEAR(h_scroll, scroll.X(), 1);
v_scroll = edit_box_with_text.y -
(kViewportHeight / scale - edit_box_with_text.height) / 2;
EXPECT_NEAR(v_scroll, scroll.Y(), 1);
// Page scale have to be unchanged
EXPECT_EQ(new_scale, scale);
}
// Tests the scroll into view functionality when
// autoZoomeFocusedNodeToLegibleScale set to false. i.e. The path non-Android
// platforms take.
TEST_F(WebFrameTest, DivScrollIntoEditableTestZoomToLegibleScaleDisabled) {
RegisterMockedHttpURLLoad("get_scale_for_zoom_into_editable_test.html");
const bool kAutoZoomToLegibleScale = false;
int viewport_width = 100;
int viewport_height = 100;
float left_box_ratio = 0.3f;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "get_scale_for_zoom_into_editable_test.html");
web_view_helper.GetWebView()
->GetPage()
->GetSettings()
.SetTextAutosizingEnabled(false);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.25f, 4);
web_view_helper.GetWebView()->EnableFakePageScaleAnimationForTesting(true);
WebRect edit_box_with_text(200, 200, 250, 20);
WebRect edit_box_with_no_text(200, 250, 250, 20);
// Test scrolling the focused node
// Since we're zoomed out, the caret is considered too small to be legible and
// so we'd normally zoom in. Make sure we don't change scale since the
// auto-zoom setting is off.
// Focus the second empty textbox.
web_view_helper.GetWebView()->AdvanceFocus(false);
web_view_helper.GetWebView()->AdvanceFocus(false);
// Set the page scale to be smaller than the minimal readable scale.
float initial_scale = 0.25f;
SetScaleAndScrollAndLayout(web_view_helper.GetWebView(), WebPoint(0, 0),
initial_scale);
float scale;
IntPoint scroll;
bool need_animation;
IntRect element_bounds, caret_bounds;
GetElementAndCaretBoundsForFocusedEditableElement(
web_view_helper, element_bounds, caret_bounds);
web_view_helper.GetWebView()->ComputeScaleAndScrollForEditableElementRects(
element_bounds, caret_bounds, kAutoZoomToLegibleScale, scale, scroll,
need_animation);
// There should be no change in page scale.
EXPECT_EQ(initial_scale, scale);
// The edit box should be left aligned with a margin for possible label.
EXPECT_TRUE(need_animation);
int h_scroll =
edit_box_with_no_text.x - left_box_ratio * viewport_width / scale;
EXPECT_NEAR(h_scroll, scroll.X(), 2);
int v_scroll = edit_box_with_no_text.y -
(viewport_height / scale - edit_box_with_no_text.height) / 2;
EXPECT_NEAR(v_scroll, scroll.Y(), 2);
SetScaleAndScrollAndLayout(web_view_helper.GetWebView(), scroll, scale);
// Select the first textbox.
web_view_helper.GetWebView()->AdvanceFocus(true);
WebRect rect, caret;
web_view_helper.GetWebView()->SelectionBounds(caret, rect);
GetElementAndCaretBoundsForFocusedEditableElement(
web_view_helper, element_bounds, caret_bounds);
web_view_helper.GetWebView()->ComputeScaleAndScrollForEditableElementRects(
element_bounds, caret_bounds, kAutoZoomToLegibleScale, scale, scroll,
need_animation);
// There should be no change at all since the textbox is fully visible
// already.
EXPECT_EQ(initial_scale, scale);
EXPECT_FALSE(need_animation);
}
// Tests zoom into editable zoom and scroll correctly when zoom-for-dsf enabled.
TEST_F(WebFrameTest, DivScrollIntoEditableTestWithDeviceScaleFactor) {
RegisterMockedHttpURLLoad("get_scale_for_zoom_into_editable_test.html");
bool kAutoZoomToLegibleScale = true;
const float kDeviceScaleFactor = 2.f;
int viewport_width = 200 * kDeviceScaleFactor;
int viewport_height = 150 * kDeviceScaleFactor;
float min_readable_caret_height = 16.0f * kDeviceScaleFactor;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "get_scale_for_zoom_into_editable_test.html", nullptr,
nullptr, ConfigureAndroid);
web_view_helper.GetWebView()
->GetPage()
->GetSettings()
.SetTextAutosizingEnabled(false);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
web_view_helper.GetWebView()->SetZoomFactorForDeviceScaleFactor(
kDeviceScaleFactor);
web_view_helper.GetWebView()->SetDefaultPageScaleLimits(0.25f, 4);
web_view_helper.GetWebView()->EnableFakePageScaleAnimationForTesting(true);
WebRect edit_box_with_text(200 * kDeviceScaleFactor, 200 * kDeviceScaleFactor,
250 * kDeviceScaleFactor, 20 * kDeviceScaleFactor);
web_view_helper.GetWebView()->AdvanceFocus(false);
// Set the page scale to be smaller than the minimal readable scale.
float initial_scale = 0.5f;
SetScaleAndScrollAndLayout(web_view_helper.GetWebView(), WebPoint(0, 0),
initial_scale);
ASSERT_EQ(web_view_helper.GetWebView()->PageScaleFactor(), initial_scale);
float scale;
IntPoint scroll;
bool need_animation;
IntRect element_bounds, caret_bounds;
GetElementAndCaretBoundsForFocusedEditableElement(
web_view_helper, element_bounds, caret_bounds);
web_view_helper.GetWebView()->ComputeScaleAndScrollForEditableElementRects(
element_bounds, caret_bounds, kAutoZoomToLegibleScale, scale, scroll,
need_animation);
EXPECT_TRUE(need_animation);
// The edit box wider than the viewport when legible should be left aligned.
int h_scroll = edit_box_with_text.x;
EXPECT_NEAR(h_scroll, scroll.X(), 2);
int v_scroll = edit_box_with_text.y -
(viewport_height / scale - edit_box_with_text.height) / 2;
EXPECT_NEAR(v_scroll, scroll.Y(), 2);
EXPECT_NEAR(min_readable_caret_height / caret_bounds.Height(), scale, 0.1);
}
TEST_F(WebFrameTest, CharacterIndexAtPointWithPinchZoom) {
RegisterMockedHttpURLLoad("sometext.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "sometext.html");
web_view_helper.LoadAhem();
web_view_helper.Resize(WebSize(640, 480));
// Move the visual viewport to the start of the target div containing the
// text.
web_view_helper.GetWebView()->SetPageScaleFactor(2);
web_view_helper.GetWebView()->SetVisualViewportOffset(WebFloatPoint(100, 50));
WebRect base_rect;
WebRect extent_rect;
WebLocalFrame* main_frame =
web_view_helper.GetWebView()->MainFrame()->ToWebLocalFrame();
// Since we're zoomed in to 2X, each char of Ahem is 20px wide/tall in
// viewport space. We expect to hit the fifth char on the first line.
size_t ix = main_frame->CharacterIndexForPoint(WebPoint(100, 15));
EXPECT_EQ(5ul, ix);
}
TEST_F(WebFrameTest, FirstRectForCharacterRangeWithPinchZoom) {
RegisterMockedHttpURLLoad("textbox.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "textbox.html");
web_view_helper.Resize(WebSize(640, 480));
WebLocalFrame* main_frame = web_view_helper.LocalMainFrame();
main_frame->ExecuteScript(WebScriptSource("selectRange();"));
WebRect old_rect;
main_frame->FirstRectForCharacterRange(0, 5, old_rect);
WebFloatPoint visual_offset(100, 130);
float scale = 2;
web_view_helper.GetWebView()->SetPageScaleFactor(scale);
web_view_helper.GetWebView()->SetVisualViewportOffset(visual_offset);
WebRect base_rect;
WebRect extent_rect;
WebRect rect;
main_frame->FirstRectForCharacterRange(0, 5, rect);
EXPECT_EQ((old_rect.x - visual_offset.x) * scale, rect.x);
EXPECT_EQ((old_rect.y - visual_offset.y) * scale, rect.y);
EXPECT_EQ(old_rect.width * scale, rect.width);
EXPECT_EQ(old_rect.height * scale, rect.height);
}
class TestReloadDoesntRedirectWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
TestReloadDoesntRedirectWebFrameClient() = default;
~TestReloadDoesntRedirectWebFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
void BeginNavigation(std::unique_ptr<WebNavigationInfo> info) override {
EXPECT_FALSE(info->is_client_redirect);
TestWebFrameClient::BeginNavigation(std::move(info));
}
};
TEST_F(WebFrameTest, ReloadDoesntSetRedirect) {
// Test for case in http://crbug.com/73104. Reloading a frame very quickly
// would sometimes call BeginNavigation with isRedirect=true
RegisterMockedHttpURLLoad("form.html");
TestReloadDoesntRedirectWebFrameClient web_frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "form.html", &web_frame_client);
web_view_helper.GetWebView()->MainFrameImpl()->StartReload(
WebFrameLoadType::kReloadBypassingCache);
// start another reload before request is delivered.
frame_test_helpers::ReloadFrameBypassingCache(
web_view_helper.GetWebView()->MainFrameImpl());
}
class ClearScrollStateOnCommitWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
ClearScrollStateOnCommitWebFrameClient() = default;
~ClearScrollStateOnCommitWebFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
void DidCommitProvisionalLoad(const WebHistoryItem&,
WebHistoryCommitType,
WebGlobalObjectReusePolicy) override {
Frame()->View()->ResetScrollAndScaleState();
}
};
TEST_F(WebFrameTest, ReloadPreservesState) {
const std::string url = "200-by-300.html";
const float kPageScaleFactor = 1.1684f;
const int kPageWidth = 120;
const int kPageHeight = 100;
RegisterMockedHttpURLLoad(url);
ClearScrollStateOnCommitWebFrameClient client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + url, &client);
web_view_helper.Resize(WebSize(kPageWidth, kPageHeight));
web_view_helper.LocalMainFrame()->SetScrollOffset(
WebSize(kPageWidth / 4, kPageHeight / 4));
web_view_helper.GetWebView()->SetPageScaleFactor(kPageScaleFactor);
// Reload the page and end up at the same url. State should not be propagated.
web_view_helper.GetWebView()->MainFrameImpl()->StartReload(
WebFrameLoadType::kReload);
frame_test_helpers::PumpPendingRequestsForFrameToLoad(
web_view_helper.LocalMainFrame());
EXPECT_EQ(0, web_view_helper.LocalMainFrame()->GetScrollOffset().width);
EXPECT_EQ(0, web_view_helper.LocalMainFrame()->GetScrollOffset().height);
EXPECT_EQ(1.0f, web_view_helper.GetWebView()->PageScaleFactor());
}
TEST_F(WebFrameTest, ReloadWhileProvisional) {
// Test that reloading while the previous load is still pending does not cause
// the initial request to get lost.
RegisterMockedHttpURLLoad("fixed_layout.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize();
WebURLRequest request(ToKURL(base_url_ + "fixed_layout.html"));
web_view_helper.GetWebView()->MainFrameImpl()->StartNavigation(request);
// start reload before first request is delivered.
frame_test_helpers::ReloadFrameBypassingCache(
web_view_helper.GetWebView()->MainFrameImpl());
WebDocumentLoader* document_loader =
web_view_helper.LocalMainFrame()->GetDocumentLoader();
ASSERT_TRUE(document_loader);
EXPECT_EQ(ToKURL(base_url_ + "fixed_layout.html"),
KURL(document_loader->GetUrl()));
}
TEST_F(WebFrameTest, AppendRedirects) {
const std::string first_url = "about:blank";
const std::string second_url = "http://internal.test";
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(first_url);
WebDocumentLoader* document_loader =
web_view_helper.LocalMainFrame()->GetDocumentLoader();
ASSERT_TRUE(document_loader);
document_loader->AppendRedirect(ToKURL(second_url));
WebVector<WebURL> redirects;
document_loader->RedirectChain(redirects);
ASSERT_EQ(2U, redirects.size());
EXPECT_EQ(ToKURL(first_url), KURL(redirects[0]));
EXPECT_EQ(ToKURL(second_url), KURL(redirects[1]));
}
TEST_F(WebFrameTest, IframeRedirect) {
RegisterMockedHttpURLLoad("iframe_redirect.html");
RegisterMockedHttpURLLoad("visible_iframe.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "iframe_redirect.html");
// Pump pending requests one more time. The test page loads script that
// navigates.
frame_test_helpers::PumpPendingRequestsForFrameToLoad(
web_view_helper.LocalMainFrame());
WebFrame* iframe = web_view_helper.LocalMainFrame()->FindFrameByName(
WebString::FromUTF8("ifr"));
ASSERT_TRUE(iframe && iframe->IsWebLocalFrame());
WebDocumentLoader* iframe_document_loader =
iframe->ToWebLocalFrame()->GetDocumentLoader();
ASSERT_TRUE(iframe_document_loader);
WebVector<WebURL> redirects;
iframe_document_loader->RedirectChain(redirects);
ASSERT_EQ(2U, redirects.size());
EXPECT_EQ(ToKURL("about:blank"), KURL(redirects[0]));
EXPECT_EQ(ToKURL("http://internal.test/visible_iframe.html"),
KURL(redirects[1]));
}
TEST_F(WebFrameTest, ClearFocusedNodeTest) {
RegisterMockedHttpURLLoad("iframe_clear_focused_node_test.html");
RegisterMockedHttpURLLoad("autofocus_input_field_iframe.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ +
"iframe_clear_focused_node_test.html");
// Clear the focused node.
web_view_helper.GetWebView()->ClearFocusedElement();
// Now retrieve the FocusedNode and test it should be null.
EXPECT_EQ(nullptr, web_view_helper.GetWebView()->FocusedElement());
}
class ChangedSelectionCounter : public frame_test_helpers::TestWebFrameClient {
public:
ChangedSelectionCounter() : call_count_(0) {}
void DidChangeSelection(bool isSelectionEmpty) override { ++call_count_; }
int Count() const { return call_count_; }
void Reset() { call_count_ = 0; }
private:
int call_count_;
};
TEST_F(WebFrameTest, TabKeyCursorMoveTriggersOneSelectionChange) {
ChangedSelectionCounter counter;
frame_test_helpers::WebViewHelper web_view_helper;
RegisterMockedHttpURLLoad("editable_elements.html");
WebViewImpl* web_view = web_view_helper.InitializeAndLoad(
base_url_ + "editable_elements.html", &counter);
WebKeyboardEvent tab_down(WebInputEvent::kKeyDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
WebKeyboardEvent tab_up(WebInputEvent::kKeyUp, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
tab_down.dom_key = Platform::Current()->DomKeyEnumFromString("\t");
tab_up.dom_key = Platform::Current()->DomKeyEnumFromString("\t");
tab_down.windows_key_code = VKEY_TAB;
tab_up.windows_key_code = VKEY_TAB;
// Move to the next text-field: 1 cursor change.
counter.Reset();
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(tab_down));
web_view->MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(tab_up));
EXPECT_EQ(1, counter.Count());
// Move to another text-field: 1 cursor change.
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(tab_down));
web_view->MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(tab_up));
EXPECT_EQ(2, counter.Count());
// Move to a number-field: 1 cursor change.
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(tab_down));
web_view->MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(tab_up));
EXPECT_EQ(3, counter.Count());
// Move to an editable element: 1 cursor change.
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(tab_down));
web_view->MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(tab_up));
EXPECT_EQ(4, counter.Count());
// Move to a non-editable element: 0 cursor changes.
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(tab_down));
web_view->MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(tab_up));
EXPECT_EQ(4, counter.Count());
}
// Implementation of WebLocalFrameClient that tracks the v8 contexts that are
// created and destroyed for verification.
class ContextLifetimeTestWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
struct Notification {
public:
Notification(WebLocalFrame* frame,
v8::Local<v8::Context> context,
int world_id)
: frame(frame),
context(context->GetIsolate(), context),
world_id(world_id) {}
~Notification() { context.Reset(); }
bool Equals(Notification* other) {
return other && frame == other->frame && context == other->context &&
world_id == other->world_id;
}
WebLocalFrame* frame;
v8::Persistent<v8::Context> context;
int world_id;
};
ContextLifetimeTestWebFrameClient(
Vector<std::unique_ptr<Notification>>& create_notifications,
Vector<std::unique_ptr<Notification>>& release_notifications)
: create_notifications_(create_notifications),
release_notifications_(release_notifications) {}
~ContextLifetimeTestWebFrameClient() override = default;
void Reset() {
create_notifications_.clear();
release_notifications_.clear();
}
// WebLocalFrameClient:
WebLocalFrame* CreateChildFrame(WebLocalFrame* parent,
WebTreeScopeType scope,
const WebString& name,
const WebString& fallback_name,
WebSandboxFlags sandbox_flags,
const ParsedFeaturePolicy& container_policy,
const WebFrameOwnerProperties&,
FrameOwnerElementType) override {
return CreateLocalChild(*parent, scope,
std::make_unique<ContextLifetimeTestWebFrameClient>(
create_notifications_, release_notifications_));
}
void DidCreateScriptContext(v8::Local<v8::Context> context,
int world_id) override {
create_notifications_.push_back(
std::make_unique<Notification>(Frame(), context, world_id));
}
void WillReleaseScriptContext(v8::Local<v8::Context> context,
int world_id) override {
release_notifications_.push_back(
std::make_unique<Notification>(Frame(), context, world_id));
}
private:
Vector<std::unique_ptr<Notification>>& create_notifications_;
Vector<std::unique_ptr<Notification>>& release_notifications_;
};
TEST_F(WebFrameTest, ContextNotificationsLoadUnload) {
v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
RegisterMockedHttpURLLoad("context_notifications_test.html");
RegisterMockedHttpURLLoad("context_notifications_test_frame.html");
// Load a frame with an iframe, make sure we get the right create
// notifications.
Vector<std::unique_ptr<ContextLifetimeTestWebFrameClient::Notification>>
create_notifications;
Vector<std::unique_ptr<ContextLifetimeTestWebFrameClient::Notification>>
release_notifications;
ContextLifetimeTestWebFrameClient web_frame_client(create_notifications,
release_notifications);
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "context_notifications_test.html", &web_frame_client);
WebLocalFrameImpl* main_frame = web_view_helper.LocalMainFrame();
WebFrame* child_frame = main_frame->FirstChild();
ASSERT_EQ(2u, create_notifications.size());
EXPECT_EQ(0u, release_notifications.size());
auto& first_create_notification = create_notifications[0];
auto& second_create_notification = create_notifications[1];
EXPECT_EQ(main_frame, first_create_notification->frame);
EXPECT_EQ(main_frame->MainWorldScriptContext(),
first_create_notification->context);
EXPECT_EQ(0, first_create_notification->world_id);
EXPECT_EQ(child_frame, second_create_notification->frame);
EXPECT_EQ(child_frame->ToWebLocalFrame()->MainWorldScriptContext(),
second_create_notification->context);
EXPECT_EQ(0, second_create_notification->world_id);
// Close the view. We should get two release notifications that are exactly
// the same as the create ones, in reverse order.
web_view_helper.Reset();
ASSERT_EQ(2u, release_notifications.size());
auto& first_release_notification = release_notifications[0];
auto& second_release_notification = release_notifications[1];
ASSERT_TRUE(
first_create_notification->Equals(second_release_notification.get()));
ASSERT_TRUE(
second_create_notification->Equals(first_release_notification.get()));
}
TEST_F(WebFrameTest, ContextNotificationsReload) {
v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
RegisterMockedHttpURLLoad("context_notifications_test.html");
RegisterMockedHttpURLLoad("context_notifications_test_frame.html");
Vector<std::unique_ptr<ContextLifetimeTestWebFrameClient::Notification>>
create_notifications;
Vector<std::unique_ptr<ContextLifetimeTestWebFrameClient::Notification>>
release_notifications;
ContextLifetimeTestWebFrameClient web_frame_client(create_notifications,
release_notifications);
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "context_notifications_test.html", &web_frame_client);
// Refresh, we should get two release notifications and two more create
// notifications.
frame_test_helpers::ReloadFrame(
web_view_helper.GetWebView()->MainFrameImpl());
ASSERT_EQ(4u, create_notifications.size());
ASSERT_EQ(2u, release_notifications.size());
// The two release notifications we got should be exactly the same as the
// first two create notifications.
for (size_t i = 0; i < release_notifications.size(); ++i) {
EXPECT_TRUE(release_notifications[i]->Equals(
create_notifications[create_notifications.size() - 3 - i].get()));
}
// The last two create notifications should be for the current frames and
// context.
WebLocalFrameImpl* main_frame = web_view_helper.LocalMainFrame();
WebFrame* child_frame = main_frame->FirstChild();
auto& first_refresh_notification = create_notifications[2];
auto& second_refresh_notification = create_notifications[3];
EXPECT_EQ(main_frame, first_refresh_notification->frame);
EXPECT_EQ(main_frame->MainWorldScriptContext(),
first_refresh_notification->context);
EXPECT_EQ(0, first_refresh_notification->world_id);
EXPECT_EQ(child_frame, second_refresh_notification->frame);
EXPECT_EQ(child_frame->ToWebLocalFrame()->MainWorldScriptContext(),
second_refresh_notification->context);
EXPECT_EQ(0, second_refresh_notification->world_id);
}
TEST_F(WebFrameTest, ContextNotificationsIsolatedWorlds) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::HandleScope handle_scope(isolate);
RegisterMockedHttpURLLoad("context_notifications_test.html");
RegisterMockedHttpURLLoad("context_notifications_test_frame.html");
Vector<std::unique_ptr<ContextLifetimeTestWebFrameClient::Notification>>
create_notifications;
Vector<std::unique_ptr<ContextLifetimeTestWebFrameClient::Notification>>
release_notifications;
ContextLifetimeTestWebFrameClient web_frame_client(create_notifications,
release_notifications);
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "context_notifications_test.html", &web_frame_client);
// Add an isolated world.
web_frame_client.Reset();
int isolated_world_id = 42;
WebScriptSource script_source("hi!");
web_view_helper.LocalMainFrame()->ExecuteScriptInIsolatedWorld(
isolated_world_id, script_source);
// We should now have a new create notification.
ASSERT_EQ(1u, create_notifications.size());
auto& notification = create_notifications[0];
ASSERT_EQ(isolated_world_id, notification->world_id);
ASSERT_EQ(web_view_helper.GetWebView()->MainFrame(), notification->frame);
// We don't have an API to enumarate isolated worlds for a frame, but we can
// at least assert that the context we got is *not* the main world's context.
ASSERT_NE(web_view_helper.LocalMainFrame()->MainWorldScriptContext(),
v8::Local<v8::Context>::New(isolate, notification->context));
web_view_helper.Reset();
// We should have gotten three release notifications (one for each of the
// frames, plus one for the isolated context).
ASSERT_EQ(3u, release_notifications.size());
// And one of them should be exactly the same as the create notification for
// the isolated context.
int match_count = 0;
for (size_t i = 0; i < release_notifications.size(); ++i) {
if (release_notifications[i]->Equals(create_notifications[0].get()))
++match_count;
}
EXPECT_EQ(1, match_count);
}
TEST_F(WebFrameTest, FindInPage) {
RegisterMockedHttpURLLoad("find.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "find.html");
ASSERT_TRUE(web_view_helper.LocalMainFrame());
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
const int kFindIdentifier = 12345;
auto options = mojom::blink::FindOptions::New();
// Find in a <div> element.
EXPECT_TRUE(frame->GetFindInPage()->FindInternal(
kFindIdentifier, WebString::FromUTF8("bar1"), *options, false));
frame->GetFindInPage()->StopFinding(
blink::mojom::StopFindAction::kStopFindActionKeepSelection);
WebRange range = frame->SelectionRange();
EXPECT_EQ(5, range.StartOffset());
EXPECT_EQ(9, range.EndOffset());
EXPECT_TRUE(frame->GetDocument().FocusedElement().IsNull());
// Find in an <input> value.
EXPECT_TRUE(frame->GetFindInPage()->FindInternal(
kFindIdentifier, WebString::FromUTF8("bar2"), *options, false));
// Confirm stopFinding(WebLocalFrame::StopFindActionKeepSelection) sets the
// selection on the found text.
frame->GetFindInPage()->StopFinding(
blink::mojom::StopFindAction::kStopFindActionKeepSelection);
range = frame->SelectionRange();
ASSERT_FALSE(range.IsNull());
EXPECT_EQ(5, range.StartOffset());
EXPECT_EQ(9, range.EndOffset());
EXPECT_TRUE(frame->GetDocument().FocusedElement().HasHTMLTagName("input"));
// Find in a <textarea> content.
EXPECT_TRUE(frame->GetFindInPage()->FindInternal(
kFindIdentifier, WebString::FromUTF8("bar3"), *options, false));
// Confirm stopFinding(WebLocalFrame::StopFindActionKeepSelection) sets the
// selection on the found text.
frame->GetFindInPage()->StopFinding(
blink::mojom::StopFindAction::kStopFindActionKeepSelection);
range = frame->SelectionRange();
ASSERT_FALSE(range.IsNull());
EXPECT_EQ(5, range.StartOffset());
EXPECT_EQ(9, range.EndOffset());
EXPECT_TRUE(frame->GetDocument().FocusedElement().HasHTMLTagName("textarea"));
// Find in a contentEditable element.
EXPECT_TRUE(frame->GetFindInPage()->FindInternal(
kFindIdentifier, WebString::FromUTF8("bar4"), *options, false));
// Confirm stopFinding(WebLocalFrame::StopFindActionKeepSelection) sets the
// selection on the found text.
frame->GetFindInPage()->StopFinding(
blink::mojom::StopFindAction::kStopFindActionKeepSelection);
range = frame->SelectionRange();
ASSERT_FALSE(range.IsNull());
EXPECT_EQ(0, range.StartOffset());
EXPECT_EQ(4, range.EndOffset());
// "bar4" is surrounded by <span>, but the focusable node should be the parent
// <div>.
EXPECT_TRUE(frame->GetDocument().FocusedElement().HasHTMLTagName("div"));
// Find in <select> content.
EXPECT_FALSE(frame->GetFindInPage()->FindInternal(
kFindIdentifier, WebString::FromUTF8("bar5"), *options, false));
// If there are any matches, stopFinding will set the selection on the found
// text. However, we do not expect any matches, so check that the selection
// is null.
frame->GetFindInPage()->StopFinding(
blink::mojom::StopFindAction::kStopFindActionKeepSelection);
range = frame->SelectionRange();
ASSERT_TRUE(range.IsNull());
}
TEST_F(WebFrameTest, GetContentAsPlainText) {
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad("about:blank");
// We set the size because it impacts line wrapping, which changes the
// resulting text value.
web_view_helper.Resize(WebSize(640, 480));
WebLocalFrame* frame = web_view_helper.LocalMainFrame();
// Generate a simple test case.
const char kSimpleSource[] = "<div>Foo bar</div><div></div>baz";
KURL test_url = ToKURL("about:blank");
frame_test_helpers::LoadHTMLString(frame, kSimpleSource, test_url);
// Make sure it comes out OK.
const std::string expected("Foo bar\nbaz");
WebString text = WebFrameContentDumper::DumpWebViewAsText(
web_view_helper.GetWebView(), std::numeric_limits<size_t>::max());
EXPECT_EQ(expected, text.Utf8());
// Try reading the same one with clipping of the text.
const int kLength = 5;
text = WebFrameContentDumper::DumpWebViewAsText(web_view_helper.GetWebView(),
kLength);
EXPECT_EQ(expected.substr(0, kLength), text.Utf8());
// Now do a new test with a subframe.
const char kOuterFrameSource[] = "Hello<iframe></iframe> world";
frame_test_helpers::LoadHTMLString(frame, kOuterFrameSource, test_url);
// Load something into the subframe.
WebLocalFrame* subframe = frame->FirstChild()->ToWebLocalFrame();
ASSERT_TRUE(subframe);
frame_test_helpers::LoadHTMLString(subframe, "sub<p>text", test_url);
text = WebFrameContentDumper::DumpWebViewAsText(
web_view_helper.GetWebView(), std::numeric_limits<size_t>::max());
EXPECT_EQ("Hello world\n\nsub\n\ntext", text.Utf8());
// Get the frame text where the subframe separator falls on the boundary of
// what we'll take. There used to be a crash in this case.
text = WebFrameContentDumper::DumpWebViewAsText(web_view_helper.GetWebView(),
12);
EXPECT_EQ("Hello world", text.Utf8());
}
TEST_F(WebFrameTest, GetFullHtmlOfPage) {
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad("about:blank");
WebLocalFrame* frame = web_view_helper.LocalMainFrame();
// Generate a simple test case.
const char kSimpleSource[] = "<p>Hello</p><p>World</p>";
KURL test_url = ToKURL("about:blank");
frame_test_helpers::LoadHTMLString(frame, kSimpleSource, test_url);
WebString text = WebFrameContentDumper::DumpWebViewAsText(
web_view_helper.GetWebView(), std::numeric_limits<size_t>::max());
EXPECT_EQ("Hello\n\nWorld", text.Utf8());
const std::string html = WebFrameContentDumper::DumpAsMarkup(frame).Utf8();
// Load again with the output html.
frame_test_helpers::LoadHTMLString(frame, html, test_url);
EXPECT_EQ(html, WebFrameContentDumper::DumpAsMarkup(frame).Utf8());
text = WebFrameContentDumper::DumpWebViewAsText(
web_view_helper.GetWebView(), std::numeric_limits<size_t>::max());
EXPECT_EQ("Hello\n\nWorld", text.Utf8());
// Test selection check
EXPECT_FALSE(frame->HasSelection());
frame->ExecuteCommand(WebString::FromUTF8("SelectAll"));
EXPECT_TRUE(frame->HasSelection());
frame->ExecuteCommand(WebString::FromUTF8("Unselect"));
EXPECT_FALSE(frame->HasSelection());
WebString selection_html = frame->SelectionAsMarkup();
EXPECT_TRUE(selection_html.IsEmpty());
}
class TestExecuteScriptDuringDidCreateScriptContext
: public frame_test_helpers::TestWebFrameClient {
public:
TestExecuteScriptDuringDidCreateScriptContext() = default;
~TestExecuteScriptDuringDidCreateScriptContext() override = default;
// frame_test_helpers::TestWebFrameClient:
void DidCreateScriptContext(v8::Local<v8::Context> context,
int world_id) override {
Frame()->ExecuteScript(WebScriptSource("window.history = 'replaced';"));
}
};
TEST_F(WebFrameTest, ExecuteScriptDuringDidCreateScriptContext) {
RegisterMockedHttpURLLoad("hello_world.html");
TestExecuteScriptDuringDidCreateScriptContext web_frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "hello_world.html",
&web_frame_client);
frame_test_helpers::ReloadFrame(
web_view_helper.GetWebView()->MainFrameImpl());
}
class TestFindInPageClient : public mojom::blink::FindInPageClient {
public:
TestFindInPageClient()
: find_results_are_ready_(false),
count_(-1),
active_index_(-1),
binding_(this) {}
~TestFindInPageClient() override = default;
void SetFrame(WebLocalFrameImpl* frame) {
mojom::blink::FindInPageClientPtr client;
binding_.Bind(MakeRequest(&client));
frame->GetFindInPage()->SetClient(std::move(client));
}
void SetNumberOfMatches(
int request_id,
unsigned int current_number_of_matches,
mojom::blink::FindMatchUpdateType final_update) final {
count_ = current_number_of_matches;
find_results_are_ready_ =
(final_update == mojom::blink::FindMatchUpdateType::kFinalUpdate);
}
void SetActiveMatch(int request_id,
const WebRect& active_match_rect,
int active_match_ordinal,
mojom::blink::FindMatchUpdateType final_update) final {
active_index_ = active_match_ordinal;
find_results_are_ready_ =
(final_update == mojom::blink::FindMatchUpdateType::kFinalUpdate);
}
bool FindResultsAreReady() const { return find_results_are_ready_; }
int Count() const { return count_; }
int ActiveIndex() const { return active_index_; }
private:
bool find_results_are_ready_;
int count_;
int active_index_;
mojo::Binding<mojom::blink::FindInPageClient> binding_;
};
TEST_F(WebFrameTest, FindInPageMatchRects) {
RegisterMockedHttpURLLoad("find_in_page_frame.html");
frame_test_helpers::TestWebFrameClient frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "find_in_page_frame.html",
&frame_client);
web_view_helper.Resize(WebSize(640, 480));
web_view_helper.GetWebView()->SetMaximumLegibleScale(1.f);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
RunPendingTasks();
// Note that the 'result 19' in the <select> element is not expected to
// produce a match. Also, results 00 and 01 are in a different frame that is
// not included in this test.
const char kFindString[] = "result";
const int kFindIdentifier = 12345;
const int kNumResults = 17;
auto options = mojom::blink::FindOptions::New();
options->run_synchronously_for_testing = true;
WebString search_text = WebString::FromUTF8(kFindString);
WebLocalFrameImpl* main_frame = web_view_helper.LocalMainFrame();
TestFindInPageClient find_in_page_client;
find_in_page_client.SetFrame(main_frame);
EXPECT_TRUE(main_frame->GetFindInPage()->FindInternal(
kFindIdentifier, search_text, *options, false));
main_frame->EnsureTextFinder().ResetMatchCount();
for (WebLocalFrameImpl* frame = main_frame; frame;
frame = ToWebLocalFrameImpl(frame->TraverseNext())) {
frame->EnsureTextFinder().StartScopingStringMatches(kFindIdentifier,
search_text, *options);
}
RunPendingTasks();
EXPECT_TRUE(find_in_page_client.FindResultsAreReady());
WebVector<WebFloatRect> web_match_rects =
main_frame->EnsureTextFinder().FindMatchRects();
ASSERT_EQ(static_cast<size_t>(kNumResults), web_match_rects.size());
int rects_version = main_frame->GetFindInPage()->FindMatchMarkersVersion();
for (int result_index = 0; result_index < kNumResults; ++result_index) {
FloatRect result_rect =
static_cast<FloatRect>(web_match_rects[result_index]);
// Select the match by the center of its rect.
EXPECT_EQ(main_frame->EnsureTextFinder().SelectNearestFindMatch(
result_rect.Center(), nullptr),
result_index + 1);
// Check that the find result ordering matches with our expectations.
Range* result = main_frame->GetTextFinder()->ActiveMatch();
ASSERT_TRUE(result);
result->setEnd(result->endContainer(), result->endOffset() + 3);
EXPECT_EQ(result->GetText(),
String::Format("%s %02d", kFindString, result_index + 2));
// Verify that the expected match rect also matches the currently active
// match. Compare the enclosing rects to prevent precision issues caused by
// CSS transforms.
FloatRect active_match = main_frame->GetFindInPage()->ActiveFindMatchRect();
EXPECT_EQ(EnclosingIntRect(active_match), EnclosingIntRect(result_rect));
// The rects version should not have changed.
EXPECT_EQ(main_frame->GetFindInPage()->FindMatchMarkersVersion(),
rects_version);
}
// Resizing should update the rects version.
web_view_helper.Resize(WebSize(800, 600));
RunPendingTasks();
EXPECT_TRUE(main_frame->GetFindInPage()->FindMatchMarkersVersion() !=
rects_version);
}
TEST_F(WebFrameTest, FindInPageActiveIndex) {
RegisterMockedHttpURLLoad("find_match_count.html");
frame_test_helpers::TestWebFrameClient frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "find_match_count.html",
&frame_client);
web_view_helper.GetWebView()->MainFrameWidget()->Resize(WebSize(640, 480));
RunPendingTasks();
const char* kFindString = "a";
const int kFindIdentifier = 7777;
const int kActiveIndex = 1;
auto options = mojom::blink::FindOptions::New();
options->run_synchronously_for_testing = true;
WebString search_text = WebString::FromUTF8(kFindString);
WebLocalFrameImpl* main_frame = web_view_helper.LocalMainFrame();
TestFindInPageClient find_in_page_client;
find_in_page_client.SetFrame(main_frame);
EXPECT_TRUE(main_frame->GetFindInPage()->FindInternal(
kFindIdentifier, search_text, *options, false));
main_frame->EnsureTextFinder().ResetMatchCount();
for (WebLocalFrameImpl* frame = main_frame; frame;
frame = ToWebLocalFrameImpl(frame->TraverseNext())) {
frame->EnsureTextFinder().StartScopingStringMatches(kFindIdentifier,
search_text, *options);
}
RunPendingTasks();
EXPECT_TRUE(main_frame->GetFindInPage()->FindInternal(
kFindIdentifier, search_text, *options, false));
main_frame->GetFindInPage()->StopFinding(
mojom::StopFindAction::kStopFindActionClearSelection);
for (WebLocalFrameImpl* frame = main_frame; frame;
frame = ToWebLocalFrameImpl(frame->TraverseNext())) {
frame->EnsureTextFinder().StartScopingStringMatches(kFindIdentifier,
search_text, *options);
}
RunPendingTasks();
EXPECT_TRUE(find_in_page_client.FindResultsAreReady());
EXPECT_EQ(kActiveIndex, find_in_page_client.ActiveIndex());
const char* kFindStringNew = "e";
WebString search_text_new = WebString::FromUTF8(kFindStringNew);
EXPECT_TRUE(main_frame->GetFindInPage()->FindInternal(
kFindIdentifier, search_text_new, *options, false));
main_frame->EnsureTextFinder().ResetMatchCount();
for (WebLocalFrameImpl* frame = main_frame; frame;
frame = ToWebLocalFrameImpl(frame->TraverseNext())) {
frame->EnsureTextFinder().StartScopingStringMatches(
kFindIdentifier, search_text_new, *options);
}
RunPendingTasks();
EXPECT_TRUE(find_in_page_client.FindResultsAreReady());
EXPECT_EQ(kActiveIndex, find_in_page_client.ActiveIndex());
}
TEST_F(WebFrameTest, FindOnDetachedFrame) {
RegisterMockedHttpURLLoad("find_in_page.html");
RegisterMockedHttpURLLoad("find_in_page_frame.html");
frame_test_helpers::TestWebFrameClient frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "find_in_page.html",
&frame_client);
web_view_helper.Resize(WebSize(640, 480));
RunPendingTasks();
const char kFindString[] = "result";
const int kFindIdentifier = 12345;
auto options = mojom::blink::FindOptions::New();
options->run_synchronously_for_testing = true;
WebString search_text = WebString::FromUTF8(kFindString);
WebLocalFrameImpl* main_frame = web_view_helper.LocalMainFrame();
TestFindInPageClient main_find_in_page_client;
main_find_in_page_client.SetFrame(main_frame);
WebLocalFrameImpl* second_frame =
ToWebLocalFrameImpl(main_frame->TraverseNext());
// Detach the frame before finding.
RemoveElementById(main_frame, "frame");
EXPECT_TRUE(main_frame->GetFindInPage()->FindInternal(
kFindIdentifier, search_text, *options, false));
EXPECT_FALSE(second_frame->GetFindInPage()->FindInternal(
kFindIdentifier, search_text, *options, false));
RunPendingTasks();
EXPECT_FALSE(main_find_in_page_client.FindResultsAreReady());
main_frame->EnsureTextFinder().ResetMatchCount();
for (WebLocalFrameImpl* frame = main_frame; frame;
frame = ToWebLocalFrameImpl(frame->TraverseNext())) {
frame->EnsureTextFinder().StartScopingStringMatches(kFindIdentifier,
search_text, *options);
}
RunPendingTasks();
EXPECT_TRUE(main_find_in_page_client.FindResultsAreReady());
}
TEST_F(WebFrameTest, FindDetachFrameBeforeScopeStrings) {
RegisterMockedHttpURLLoad("find_in_page.html");
RegisterMockedHttpURLLoad("find_in_page_frame.html");
frame_test_helpers::TestWebFrameClient frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "find_in_page.html",
&frame_client);
web_view_helper.Resize(WebSize(640, 480));
RunPendingTasks();
const char kFindString[] = "result";
const int kFindIdentifier = 12345;
auto options = mojom::blink::FindOptions::New();
options->run_synchronously_for_testing = true;
WebString search_text = WebString::FromUTF8(kFindString);
WebLocalFrameImpl* main_frame = web_view_helper.LocalMainFrame();
TestFindInPageClient find_in_page_client;
find_in_page_client.SetFrame(main_frame);
for (WebLocalFrameImpl* frame = main_frame; frame;
frame = ToWebLocalFrameImpl(frame->TraverseNext())) {
EXPECT_TRUE(frame->GetFindInPage()->FindInternal(
kFindIdentifier, search_text, *options, false));
}
RunPendingTasks();
EXPECT_FALSE(find_in_page_client.FindResultsAreReady());
// Detach the frame between finding and scoping.
RemoveElementById(main_frame, "frame");
main_frame->EnsureTextFinder().ResetMatchCount();
for (WebLocalFrameImpl* frame = main_frame; frame;
frame = ToWebLocalFrameImpl(frame->TraverseNext())) {
frame->EnsureTextFinder().StartScopingStringMatches(kFindIdentifier,
search_text, *options);
}
RunPendingTasks();
EXPECT_TRUE(find_in_page_client.FindResultsAreReady());
}
TEST_F(WebFrameTest, FindDetachFrameWhileScopingStrings) {
RegisterMockedHttpURLLoad("find_in_page.html");
RegisterMockedHttpURLLoad("find_in_page_frame.html");
frame_test_helpers::TestWebFrameClient frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "find_in_page.html",
&frame_client);
web_view_helper.Resize(WebSize(640, 480));
RunPendingTasks();
const char kFindString[] = "result";
const int kFindIdentifier = 12345;
auto options = mojom::blink::FindOptions::New();
options->run_synchronously_for_testing = true;
WebString search_text = WebString::FromUTF8(kFindString);
WebLocalFrameImpl* main_frame = web_view_helper.LocalMainFrame();
TestFindInPageClient find_in_page_client;
find_in_page_client.SetFrame(main_frame);
for (WebLocalFrameImpl* frame = main_frame; frame;
frame = ToWebLocalFrameImpl(frame->TraverseNext())) {
EXPECT_TRUE(frame->GetFindInPage()->FindInternal(
kFindIdentifier, search_text, *options, false));
}
RunPendingTasks();
EXPECT_FALSE(find_in_page_client.FindResultsAreReady());
main_frame->EnsureTextFinder().ResetMatchCount();
for (WebLocalFrameImpl* frame = main_frame; frame;
frame = ToWebLocalFrameImpl(frame->TraverseNext())) {
frame->EnsureTextFinder().StartScopingStringMatches(kFindIdentifier,
search_text, *options);
}
// The first startScopingStringMatches will have reset the state. Detach
// before it actually scopes.
RemoveElementById(main_frame, "frame");
for (WebLocalFrameImpl* frame = main_frame; frame;
frame = ToWebLocalFrameImpl(frame->TraverseNext())) {
frame->EnsureTextFinder().StartScopingStringMatches(kFindIdentifier,
search_text, *options);
}
RunPendingTasks();
EXPECT_TRUE(find_in_page_client.FindResultsAreReady());
}
TEST_F(WebFrameTest, ResetMatchCount) {
RegisterMockedHttpURLLoad("find_in_generated_frame.html");
frame_test_helpers::TestWebFrameClient frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "find_in_generated_frame.html",
&frame_client);
web_view_helper.Resize(WebSize(640, 480));
RunPendingTasks();
const char kFindString[] = "result";
const int kFindIdentifier = 12345;
auto options = mojom::blink::FindOptions::New();
options->run_synchronously_for_testing = true;
WebString search_text = WebString::FromUTF8(kFindString);
WebLocalFrameImpl* main_frame = web_view_helper.LocalMainFrame();
TestFindInPageClient find_in_page_client;
find_in_page_client.SetFrame(main_frame);
// Check that child frame exists.
EXPECT_TRUE(!!main_frame->TraverseNext());
for (WebLocalFrameImpl* frame = main_frame; frame;
frame = ToWebLocalFrameImpl(frame->TraverseNext())) {
EXPECT_FALSE(frame->GetFindInPage()->FindInternal(
kFindIdentifier, search_text, *options, false));
}
RunPendingTasks();
EXPECT_FALSE(find_in_page_client.FindResultsAreReady());
main_frame->EnsureTextFinder().ResetMatchCount();
}
TEST_F(WebFrameTest, SetTickmarks) {
RegisterMockedHttpURLLoad("find.html");
frame_test_helpers::TestWebFrameClient frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "find.html", &frame_client);
web_view_helper.Resize(WebSize(640, 480));
RunPendingTasks();
const char kFindString[] = "foo";
const int kFindIdentifier = 12345;
auto options = mojom::blink::FindOptions::New();
options->run_synchronously_for_testing = true;
WebString search_text = WebString::FromUTF8(kFindString);
WebLocalFrameImpl* main_frame = web_view_helper.LocalMainFrame();
TestFindInPageClient find_in_page_client;
find_in_page_client.SetFrame(main_frame);
EXPECT_TRUE(main_frame->GetFindInPage()->FindInternal(
kFindIdentifier, search_text, *options, false));
main_frame->EnsureTextFinder().ResetMatchCount();
main_frame->EnsureTextFinder().StartScopingStringMatches(
kFindIdentifier, search_text, *options);
RunPendingTasks();
EXPECT_TRUE(find_in_page_client.FindResultsAreReady());
// Get the tickmarks for the original find request.
LocalFrameView* frame_view = web_view_helper.LocalMainFrame()->GetFrameView();
ScrollableArea* layout_viewport = frame_view->LayoutViewport();
Vector<IntRect> original_tickmarks;
layout_viewport->GetTickmarks(original_tickmarks);
EXPECT_EQ(4u, original_tickmarks.size());
// Override the tickmarks.
Vector<IntRect> overriding_tickmarks_expected;
overriding_tickmarks_expected.push_back(IntRect(0, 0, 100, 100));
overriding_tickmarks_expected.push_back(IntRect(0, 20, 100, 100));
overriding_tickmarks_expected.push_back(IntRect(0, 30, 100, 100));
main_frame->SetTickmarks(overriding_tickmarks_expected);
// Check the tickmarks are overriden correctly.
Vector<IntRect> overriding_tickmarks_actual;
layout_viewport->GetTickmarks(overriding_tickmarks_actual);
EXPECT_EQ(overriding_tickmarks_expected, overriding_tickmarks_actual);
// Reset the tickmark behavior.
Vector<IntRect> reset_tickmarks;
main_frame->SetTickmarks(reset_tickmarks);
// Check that the original tickmarks are returned
Vector<IntRect> original_tickmarks_after_reset;
layout_viewport->GetTickmarks(original_tickmarks_after_reset);
EXPECT_EQ(original_tickmarks, original_tickmarks_after_reset);
}
TEST_F(WebFrameTest, FindInPageJavaScriptUpdatesDOM) {
RegisterMockedHttpURLLoad("find.html");
frame_test_helpers::TestWebFrameClient frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "find.html", &frame_client);
web_view_helper.Resize(WebSize(640, 480));
RunPendingTasks();
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
TestFindInPageClient find_in_page_client;
find_in_page_client.SetFrame(frame);
const int kFindIdentifier = 12345;
static const char* kFindString = "foo";
WebString search_text = WebString::FromUTF8(kFindString);
auto options = mojom::blink::FindOptions::New();
options->run_synchronously_for_testing = true;
bool active_now;
frame->EnsureTextFinder().ResetMatchCount();
frame->EnsureTextFinder().StartScopingStringMatches(kFindIdentifier,
search_text, *options);
RunPendingTasks();
EXPECT_TRUE(find_in_page_client.FindResultsAreReady());
// Find in a <div> element.
options->find_next = true;
EXPECT_TRUE(frame->GetFindInPage()->FindInternal(
kFindIdentifier, search_text, *options, false, &active_now));
EXPECT_TRUE(active_now);
// Insert new text, which contains occurence of |searchText|.
frame->ExecuteScript(WebScriptSource(
"var newTextNode = document.createTextNode('bar5 foo5');"
"var textArea = document.getElementsByTagName('textarea')[0];"
"document.body.insertBefore(newTextNode, textArea);"));
// Find in a <input> element.
EXPECT_TRUE(frame->GetFindInPage()->FindInternal(
kFindIdentifier, search_text, *options, false, &active_now));
EXPECT_TRUE(active_now);
// Find in the inserted text node.
EXPECT_TRUE(frame->GetFindInPage()->FindInternal(
kFindIdentifier, search_text, *options, false, &active_now));
frame->GetFindInPage()->StopFinding(
blink::mojom::StopFindAction::kStopFindActionKeepSelection);
WebRange range = frame->SelectionRange();
EXPECT_EQ(5, range.StartOffset());
EXPECT_EQ(8, range.EndOffset());
EXPECT_TRUE(frame->GetDocument().FocusedElement().IsNull());
EXPECT_FALSE(active_now);
}
struct FakeTimerSetter {
FakeTimerSetter() {
time_elapsed_ = 1.0;
original_time_function_ = SetTimeFunctionsForTesting(ReturnMockTime);
}
~FakeTimerSetter() { SetTimeFunctionsForTesting(original_time_function_); }
static double ReturnMockTime() {
time_elapsed_ += 1.0;
return time_elapsed_;
}
private:
TimeFunction original_time_function_;
static double time_elapsed_;
};
double FakeTimerSetter::time_elapsed_ = 0.;
TEST_F(WebFrameTest, FindInPageJavaScriptUpdatesDOMProperOrdinal) {
FakeTimerSetter fake_timer;
const WebString search_pattern = WebString::FromUTF8("abc");
// We have 2 occurrences of the pattern in our text.
const char* html =
"foo bar foo bar foo abc bar foo bar foo bar foo bar foo bar foo bar foo "
"bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo "
"bar foo bar foo abc bar <div id='new_text'></div>";
frame_test_helpers::TestWebFrameClient frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(&frame_client);
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
frame_test_helpers::LoadHTMLString(frame, html,
url_test_helpers::ToKURL(base_url_));
web_view_helper.Resize(WebSize(640, 480));
web_view_helper.GetWebView()->MainFrameWidget()->SetFocus(true);
RunPendingTasks();
TestFindInPageClient find_in_page_client;
find_in_page_client.SetFrame(frame);
const int kFindIdentifier = 12345;
auto options = mojom::blink::FindOptions::New();
options->run_synchronously_for_testing = true;
options->find_next = false;
options->forward = true;
// The first search that will start the scoping process.
frame->GetFindInPage()->Find(kFindIdentifier, search_pattern,
options->Clone());
EXPECT_FALSE(find_in_page_client.FindResultsAreReady());
RunPendingTasks();
EXPECT_EQ(2, find_in_page_client.Count());
EXPECT_EQ(1, find_in_page_client.ActiveIndex());
options->find_next = true;
// The second search will jump to the next match without any scoping.
frame->GetFindInPage()->Find(kFindIdentifier, search_pattern,
options->Clone());
// Run pending tasks to make sure IncreaseMatchCount calls passes.
RunPendingTasks();
EXPECT_EQ(2, find_in_page_client.Count());
EXPECT_EQ(2, find_in_page_client.ActiveIndex());
EXPECT_FALSE(frame->EnsureTextFinder().ScopingInProgress());
// Insert new text, which contains occurence of |searchText|.
frame->ExecuteScript(
WebScriptSource("var textDiv = document.getElementById('new_text');"
"textDiv.innerHTML = 'foo abc';"));
// The third search will find a new match and initiate a new scoping.
frame->GetFindInPage()->Find(kFindIdentifier, search_pattern,
options->Clone());
RunPendingTasks();
EXPECT_EQ(3, find_in_page_client.Count());
EXPECT_EQ(3, find_in_page_client.ActiveIndex());
}
TEST_F(WebFrameTest, FindInPageStopFindActionKeepSelectionInAnotherDocument) {
RegisterMockedHttpURLLoad("find.html");
RegisterMockedHttpURLLoad("hello_world.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "find.html");
ASSERT_TRUE(web_view_helper.LocalMainFrame());
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
const int kFindIdentifier = 12345;
auto options = mojom::blink::FindOptions::New();
// Set active match
ASSERT_TRUE(frame->GetFindInPage()->FindInternal(
kFindIdentifier, WebString::FromUTF8("foo"), *options, false));
// Move to another page.
frame_test_helpers::LoadFrame(frame, base_url_ + "hello_world.html");
// Stop Find-In-Page. |TextFinder::active_match_| still hold a |Range| in
// "find.html".
frame->GetFindInPage()->StopFinding(
blink::mojom::StopFindAction::kStopFindActionKeepSelection);
// Pass if not crash. See http://crbug.com/719880 for details.
}
TEST_F(WebFrameTest, FindInPageForcedRedoOfFindInPage) {
const WebString search_pattern = WebString::FromUTF8("bar");
const char* html = "foo bar foo foo bar";
frame_test_helpers::TestWebFrameClient frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(&frame_client);
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
frame_test_helpers::LoadHTMLString(frame, html,
url_test_helpers::ToKURL(base_url_));
web_view_helper.Resize(WebSize(640, 480));
web_view_helper.GetWebView()->MainFrameWidget()->SetFocus(true);
RunPendingTasks();
TestFindInPageClient find_in_page_client;
find_in_page_client.SetFrame(frame);
const int kFindIdentifier = 12345;
auto options = mojom::blink::FindOptions::New();
options->run_synchronously_for_testing = true;
options->find_next = false;
options->forward = true;
// First run.
frame->GetFindInPage()->Find(kFindIdentifier, search_pattern,
options->Clone());
RunPendingTasks();
EXPECT_EQ(2, find_in_page_client.Count());
EXPECT_EQ(1, find_in_page_client.ActiveIndex());
options->force = true;
frame->GetFindInPage()->Find(kFindIdentifier, search_pattern,
options->Clone());
RunPendingTasks();
EXPECT_EQ(2, find_in_page_client.Count());
EXPECT_EQ(1, find_in_page_client.ActiveIndex());
options->find_next = true;
options->force = false;
frame->GetFindInPage()->Find(kFindIdentifier, search_pattern,
options->Clone());
RunPendingTasks();
EXPECT_EQ(2, find_in_page_client.Count());
EXPECT_EQ(2, find_in_page_client.ActiveIndex());
options->find_next = false;
options->force = true;
frame->GetFindInPage()->Find(kFindIdentifier, search_pattern,
options->Clone());
RunPendingTasks();
EXPECT_EQ(2, find_in_page_client.Count());
EXPECT_EQ(2, find_in_page_client.ActiveIndex());
}
static WebPoint TopLeft(const WebRect& rect) {
return WebPoint(rect.x, rect.y);
}
static WebPoint BottomRightMinusOne(const WebRect& rect) {
// FIXME: If we don't subtract 1 from the x- and y-coordinates of the
// selection bounds, selectRange() will select the *next* element. That's
// strictly correct, as hit-testing checks the pixel to the lower-right of
// the input coordinate, but it's a wart on the API.
return WebPoint(rect.x + rect.width - 1, rect.y + rect.height - 1);
}
static WebRect ElementBounds(WebLocalFrame* frame, const WebString& id) {
return frame->GetDocument().GetElementById(id).BoundsInViewport();
}
static std::string SelectionAsString(WebFrame* frame) {
return frame->ToWebLocalFrame()->SelectionAsText().Utf8();
}
TEST_F(WebFrameTest, SelectRange) {
WebLocalFrame* frame;
WebRect start_web_rect;
WebRect end_web_rect;
RegisterMockedHttpURLLoad("select_range_basic.html");
RegisterMockedHttpURLLoad("select_range_scroll.html");
frame_test_helpers::WebViewHelper web_view_helper;
InitializeTextSelectionWebView(base_url_ + "select_range_basic.html",
&web_view_helper);
frame = web_view_helper.LocalMainFrame();
EXPECT_EQ("Some test text for testing.", SelectionAsString(frame));
web_view_helper.GetWebView()->MainFrameWidget()->SelectionBounds(
start_web_rect, end_web_rect);
frame->ExecuteCommand(WebString::FromUTF8("Unselect"));
EXPECT_EQ("", SelectionAsString(frame));
frame->SelectRange(TopLeft(start_web_rect),
BottomRightMinusOne(end_web_rect));
// On some devices, the above bottomRightMinusOne() causes the ending '.' not
// selected.
std::string selection_string = SelectionAsString(frame);
EXPECT_TRUE(selection_string == "Some test text for testing." ||
selection_string == "Some test text for testing");
InitializeTextSelectionWebView(base_url_ + "select_range_scroll.html",
&web_view_helper);
frame = web_view_helper.LocalMainFrame();
EXPECT_EQ("Some offscreen test text for testing.", SelectionAsString(frame));
web_view_helper.GetWebView()->MainFrameWidget()->SelectionBounds(
start_web_rect, end_web_rect);
frame->ExecuteCommand(WebString::FromUTF8("Unselect"));
EXPECT_EQ("", SelectionAsString(frame));
frame->SelectRange(TopLeft(start_web_rect),
BottomRightMinusOne(end_web_rect));
// On some devices, the above bottomRightMinusOne() causes the ending '.' not
// selected.
selection_string = SelectionAsString(frame);
EXPECT_TRUE(selection_string == "Some offscreen test text for testing." ||
selection_string == "Some offscreen test text for testing");
}
TEST_F(WebFrameTest, SelectRangeDefaultHandleVisibility) {
RegisterMockedHttpURLLoad("select_range_basic.html");
frame_test_helpers::WebViewHelper web_view_helper;
InitializeTextSelectionWebView(base_url_ + "select_range_basic.html",
&web_view_helper);
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
frame->SelectRange(WebRange(0, 5), WebLocalFrame::kHideSelectionHandle,
SelectionMenuBehavior::kHide);
EXPECT_FALSE(frame->SelectionRange().IsNull());
EXPECT_FALSE(frame->GetFrame()->Selection().IsHandleVisible())
<< "By default selection handles should not be visible";
}
TEST_F(WebFrameTest, SelectRangeHideHandle) {
RegisterMockedHttpURLLoad("select_range_basic.html");
frame_test_helpers::WebViewHelper web_view_helper;
InitializeTextSelectionWebView(base_url_ + "select_range_basic.html",
&web_view_helper);
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
frame->SelectRange(WebRange(0, 5), WebLocalFrame::kHideSelectionHandle,
SelectionMenuBehavior::kHide);
EXPECT_FALSE(frame->GetFrame()->Selection().IsHandleVisible())
<< "Selection handle should not be visible with kHideSelectionHandle";
}
TEST_F(WebFrameTest, SelectRangeShowHandle) {
RegisterMockedHttpURLLoad("select_range_basic.html");
frame_test_helpers::WebViewHelper web_view_helper;
InitializeTextSelectionWebView(base_url_ + "select_range_basic.html",
&web_view_helper);
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
frame->SelectRange(WebRange(0, 5), WebLocalFrame::kShowSelectionHandle,
SelectionMenuBehavior::kHide);
EXPECT_TRUE(frame->GetFrame()->Selection().IsHandleVisible())
<< "Selection handle should be visible with kShowSelectionHandle";
}
TEST_F(WebFrameTest, SelectRangePreserveHandleVisibility) {
RegisterMockedHttpURLLoad("select_range_basic.html");
frame_test_helpers::WebViewHelper web_view_helper;
InitializeTextSelectionWebView(base_url_ + "select_range_basic.html",
&web_view_helper);
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
frame->SelectRange(WebRange(0, 5), WebLocalFrame::kHideSelectionHandle,
SelectionMenuBehavior::kHide);
frame->SelectRange(WebRange(0, 6), WebLocalFrame::kPreserveHandleVisibility,
SelectionMenuBehavior::kHide);
EXPECT_FALSE(frame->GetFrame()->Selection().IsHandleVisible())
<< "kPreserveHandleVisibility should keep handles invisible";
frame->SelectRange(WebRange(0, 5), WebLocalFrame::kShowSelectionHandle,
SelectionMenuBehavior::kHide);
frame->SelectRange(WebRange(0, 6), WebLocalFrame::kPreserveHandleVisibility,
SelectionMenuBehavior::kHide);
EXPECT_TRUE(frame->GetFrame()->Selection().IsHandleVisible())
<< "kPreserveHandleVisibility should keep handles visible";
}
TEST_F(WebFrameTest, SelectRangeInIframe) {
WebFrame* frame;
WebRect start_web_rect;
WebRect end_web_rect;
RegisterMockedHttpURLLoad("select_range_iframe.html");
RegisterMockedHttpURLLoad("select_range_basic.html");
frame_test_helpers::WebViewHelper web_view_helper;
InitializeTextSelectionWebView(base_url_ + "select_range_iframe.html",
&web_view_helper);
frame = web_view_helper.GetWebView()->MainFrame();
WebLocalFrame* subframe = frame->FirstChild()->ToWebLocalFrame();
EXPECT_EQ("Some test text for testing.", SelectionAsString(subframe));
web_view_helper.GetWebView()->MainFrameWidget()->SelectionBounds(
start_web_rect, end_web_rect);
subframe->ExecuteCommand(WebString::FromUTF8("Unselect"));
EXPECT_EQ("", SelectionAsString(subframe));
subframe->SelectRange(TopLeft(start_web_rect),
BottomRightMinusOne(end_web_rect));
// On some devices, the above bottomRightMinusOne() causes the ending '.' not
// selected.
std::string selection_string = SelectionAsString(subframe);
EXPECT_TRUE(selection_string == "Some test text for testing." ||
selection_string == "Some test text for testing");
}
TEST_F(WebFrameTest, SelectRangeDivContentEditable) {
WebLocalFrame* frame;
WebRect start_web_rect;
WebRect end_web_rect;
RegisterMockedHttpURLLoad("select_range_div_editable.html");
// Select the middle of an editable element, then try to extend the selection
// to the top of the document. The selection range should be clipped to the
// bounds of the editable element.
frame_test_helpers::WebViewHelper web_view_helper;
InitializeTextSelectionWebView(base_url_ + "select_range_div_editable.html",
&web_view_helper);
frame = web_view_helper.LocalMainFrame();
EXPECT_EQ("This text is initially selected.", SelectionAsString(frame));
web_view_helper.GetWebView()->MainFrameWidget()->SelectionBounds(
start_web_rect, end_web_rect);
frame->SelectRange(BottomRightMinusOne(end_web_rect), WebPoint(0, 0));
EXPECT_EQ("16-char header. This text is initially selected.",
SelectionAsString(frame));
// As above, but extending the selection to the bottom of the document.
InitializeTextSelectionWebView(base_url_ + "select_range_div_editable.html",
&web_view_helper);
frame = web_view_helper.LocalMainFrame();
web_view_helper.GetWebView()->MainFrameWidget()->SelectionBounds(
start_web_rect, end_web_rect);
frame->SelectRange(TopLeft(start_web_rect),
BottomRightMinusOne(end_web_rect));
EXPECT_EQ("This text is initially selected.", SelectionAsString(frame));
web_view_helper.GetWebView()->MainFrameWidget()->SelectionBounds(
start_web_rect, end_web_rect);
web_view_helper.GetWebView()->MainFrameWidget()->SelectionBounds(
start_web_rect, end_web_rect);
frame->SelectRange(TopLeft(start_web_rect), WebPoint(640, 480));
EXPECT_EQ("This text is initially selected. 16-char footer.",
SelectionAsString(frame));
}
// positionForPoint returns the wrong values for contenteditable spans. See
// http://crbug.com/238334.
TEST_F(WebFrameTest, DISABLED_SelectRangeSpanContentEditable) {
WebLocalFrame* frame;
WebRect start_web_rect;
WebRect end_web_rect;
RegisterMockedHttpURLLoad("select_range_span_editable.html");
// Select the middle of an editable element, then try to extend the selection
// to the top of the document.
// The selection range should be clipped to the bounds of the editable
// element.
frame_test_helpers::WebViewHelper web_view_helper;
InitializeTextSelectionWebView(base_url_ + "select_range_span_editable.html",
&web_view_helper);
frame = web_view_helper.LocalMainFrame();
EXPECT_EQ("This text is initially selected.", SelectionAsString(frame));
web_view_helper.GetWebView()->MainFrameWidget()->SelectionBounds(
start_web_rect, end_web_rect);
frame->SelectRange(BottomRightMinusOne(end_web_rect), WebPoint(0, 0));
EXPECT_EQ("16-char header. This text is initially selected.",
SelectionAsString(frame));
// As above, but extending the selection to the bottom of the document.
InitializeTextSelectionWebView(base_url_ + "select_range_span_editable.html",
&web_view_helper);
frame = web_view_helper.LocalMainFrame();
web_view_helper.GetWebView()->MainFrameWidget()->SelectionBounds(
start_web_rect, end_web_rect);
frame->SelectRange(TopLeft(start_web_rect),
BottomRightMinusOne(end_web_rect));
EXPECT_EQ("This text is initially selected.", SelectionAsString(frame));
web_view_helper.GetWebView()->MainFrameWidget()->SelectionBounds(
start_web_rect, end_web_rect);
EXPECT_EQ("This text is initially selected.", SelectionAsString(frame));
web_view_helper.GetWebView()->MainFrameWidget()->SelectionBounds(
start_web_rect, end_web_rect);
frame->SelectRange(TopLeft(start_web_rect), WebPoint(640, 480));
EXPECT_EQ("This text is initially selected. 16-char footer.",
SelectionAsString(frame));
}
TEST_F(WebFrameTest, SelectRangeCanMoveSelectionStart) {
RegisterMockedHttpURLLoad("text_selection.html");
frame_test_helpers::WebViewHelper web_view_helper;
InitializeTextSelectionWebView(base_url_ + "text_selection.html",
&web_view_helper);
WebLocalFrame* frame = web_view_helper.LocalMainFrame();
// Select second span. We can move the start to include the first span.
frame->ExecuteScript(WebScriptSource("selectElement('header_2');"));
EXPECT_EQ("Header 2.", SelectionAsString(frame));
frame->SelectRange(BottomRightMinusOne(ElementBounds(frame, "header_2")),
TopLeft(ElementBounds(frame, "header_1")));
EXPECT_EQ("Header 1. Header 2.", SelectionAsString(frame));
// We can move the start and end together.
frame->ExecuteScript(WebScriptSource("selectElement('header_1');"));
EXPECT_EQ("Header 1.", SelectionAsString(frame));
frame->SelectRange(BottomRightMinusOne(ElementBounds(frame, "header_1")),
BottomRightMinusOne(ElementBounds(frame, "header_1")));
EXPECT_EQ("", SelectionAsString(frame));
// Selection is a caret, not empty.
EXPECT_FALSE(frame->SelectionRange().IsNull());
// We can move the start across the end.
frame->ExecuteScript(WebScriptSource("selectElement('header_1');"));
EXPECT_EQ("Header 1.", SelectionAsString(frame));
frame->SelectRange(BottomRightMinusOne(ElementBounds(frame, "header_1")),
BottomRightMinusOne(ElementBounds(frame, "header_2")));
EXPECT_EQ(" Header 2.", SelectionAsString(frame));
// Can't extend the selection part-way into an editable element.
frame->ExecuteScript(WebScriptSource("selectElement('footer_2');"));
EXPECT_EQ("Footer 2.", SelectionAsString(frame));
frame->SelectRange(BottomRightMinusOne(ElementBounds(frame, "footer_2")),
TopLeft(ElementBounds(frame, "editable_2")));
EXPECT_EQ(" [ Footer 1. Footer 2.", SelectionAsString(frame));
// Can extend the selection completely across editable elements.
frame->ExecuteScript(WebScriptSource("selectElement('footer_2');"));
EXPECT_EQ("Footer 2.", SelectionAsString(frame));
frame->SelectRange(BottomRightMinusOne(ElementBounds(frame, "footer_2")),
TopLeft(ElementBounds(frame, "header_2")));
EXPECT_EQ("Header 2. ] [ Editable 1. Editable 2. ] [ Footer 1. Footer 2.",
SelectionAsString(frame));
// If the selection is editable text, we can't extend it into non-editable
// text.
frame->ExecuteScript(WebScriptSource("selectElement('editable_2');"));
EXPECT_EQ("Editable 2.", SelectionAsString(frame));
frame->SelectRange(BottomRightMinusOne(ElementBounds(frame, "editable_2")),
TopLeft(ElementBounds(frame, "header_2")));
EXPECT_EQ("[ Editable 1. Editable 2.", SelectionAsString(frame));
}
TEST_F(WebFrameTest, SelectRangeCanMoveSelectionEnd) {
RegisterMockedHttpURLLoad("text_selection.html");
frame_test_helpers::WebViewHelper web_view_helper;
InitializeTextSelectionWebView(base_url_ + "text_selection.html",
&web_view_helper);
WebLocalFrame* frame = web_view_helper.LocalMainFrame();
// Select first span. We can move the end to include the second span.
frame->ExecuteScript(WebScriptSource("selectElement('header_1');"));
EXPECT_EQ("Header 1.", SelectionAsString(frame));
frame->SelectRange(TopLeft(ElementBounds(frame, "header_1")),
BottomRightMinusOne(ElementBounds(frame, "header_2")));
EXPECT_EQ("Header 1. Header 2.", SelectionAsString(frame));
// We can move the start and end together.
frame->ExecuteScript(WebScriptSource("selectElement('header_2');"));
EXPECT_EQ("Header 2.", SelectionAsString(frame));
frame->SelectRange(TopLeft(ElementBounds(frame, "header_2")),
TopLeft(ElementBounds(frame, "header_2")));
EXPECT_EQ("", SelectionAsString(frame));
// Selection is a caret, not empty.
EXPECT_FALSE(frame->SelectionRange().IsNull());
// We can move the end across the start.
frame->ExecuteScript(WebScriptSource("selectElement('header_2');"));
EXPECT_EQ("Header 2.", SelectionAsString(frame));
frame->SelectRange(TopLeft(ElementBounds(frame, "header_2")),
TopLeft(ElementBounds(frame, "header_1")));
EXPECT_EQ("Header 1. ", SelectionAsString(frame));
// Can't extend the selection part-way into an editable element.
frame->ExecuteScript(WebScriptSource("selectElement('header_1');"));
EXPECT_EQ("Header 1.", SelectionAsString(frame));
frame->SelectRange(TopLeft(ElementBounds(frame, "header_1")),
BottomRightMinusOne(ElementBounds(frame, "editable_1")));
EXPECT_EQ("Header 1. Header 2. ] ", SelectionAsString(frame));
// Can extend the selection completely across editable elements.
frame->ExecuteScript(WebScriptSource("selectElement('header_1');"));
EXPECT_EQ("Header 1.", SelectionAsString(frame));
frame->SelectRange(TopLeft(ElementBounds(frame, "header_1")),
BottomRightMinusOne(ElementBounds(frame, "footer_1")));
EXPECT_EQ("Header 1. Header 2. ] [ Editable 1. Editable 2. ] [ Footer 1.",
SelectionAsString(frame));
// If the selection is editable text, we can't extend it into non-editable
// text.
frame->ExecuteScript(WebScriptSource("selectElement('editable_1');"));
EXPECT_EQ("Editable 1.", SelectionAsString(frame));
frame->SelectRange(TopLeft(ElementBounds(frame, "editable_1")),
BottomRightMinusOne(ElementBounds(frame, "footer_1")));
EXPECT_EQ("Editable 1. Editable 2. ]", SelectionAsString(frame));
}
TEST_F(WebFrameTest, MoveRangeSelectionExtent) {
WebLocalFrameImpl* frame;
WebRect start_web_rect;
WebRect end_web_rect;
RegisterMockedHttpURLLoad("move_range_selection_extent.html");
frame_test_helpers::WebViewHelper web_view_helper;
InitializeTextSelectionWebView(base_url_ + "move_range_selection_extent.html",
&web_view_helper);
frame = web_view_helper.LocalMainFrame();
EXPECT_EQ("This text is initially selected.", SelectionAsString(frame));
web_view_helper.GetWebView()->MainFrameWidget()->SelectionBounds(
start_web_rect, end_web_rect);
frame->MoveRangeSelectionExtent(WebPoint(640, 480));
EXPECT_EQ("This text is initially selected. 16-char footer.",
SelectionAsString(frame));
frame->MoveRangeSelectionExtent(WebPoint(0, 0));
EXPECT_EQ("16-char header. ", SelectionAsString(frame));
// Reset with swapped base and extent.
frame->SelectRange(TopLeft(end_web_rect),
BottomRightMinusOne(start_web_rect));
EXPECT_EQ("This text is initially selected.", SelectionAsString(frame));
frame->MoveRangeSelectionExtent(WebPoint(640, 480));
EXPECT_EQ(" 16-char footer.", SelectionAsString(frame));
frame->MoveRangeSelectionExtent(WebPoint(0, 0));
EXPECT_EQ("16-char header. This text is initially selected.",
SelectionAsString(frame));
frame->ExecuteCommand(WebString::FromUTF8("Unselect"));
EXPECT_EQ("", SelectionAsString(frame));
}
TEST_F(WebFrameTest, MoveRangeSelectionExtentCannotCollapse) {
WebLocalFrameImpl* frame;
WebRect start_web_rect;
WebRect end_web_rect;
RegisterMockedHttpURLLoad("move_range_selection_extent.html");
frame_test_helpers::WebViewHelper web_view_helper;
InitializeTextSelectionWebView(base_url_ + "move_range_selection_extent.html",
&web_view_helper);
frame = web_view_helper.LocalMainFrame();
EXPECT_EQ("This text is initially selected.", SelectionAsString(frame));
web_view_helper.GetWebView()->MainFrameWidget()->SelectionBounds(
start_web_rect, end_web_rect);
frame->MoveRangeSelectionExtent(BottomRightMinusOne(start_web_rect));
EXPECT_EQ("This text is initially selected.", SelectionAsString(frame));
// Reset with swapped base and extent.
frame->SelectRange(TopLeft(end_web_rect),
BottomRightMinusOne(start_web_rect));
EXPECT_EQ("This text is initially selected.", SelectionAsString(frame));
frame->MoveRangeSelectionExtent(BottomRightMinusOne(end_web_rect));
EXPECT_EQ("This text is initially selected.", SelectionAsString(frame));
}
TEST_F(WebFrameTest, MoveRangeSelectionExtentScollsInputField) {
WebLocalFrameImpl* frame;
WebRect start_web_rect;
WebRect end_web_rect;
RegisterMockedHttpURLLoad("move_range_selection_extent_input_field.html");
frame_test_helpers::WebViewHelper web_view_helper;
InitializeTextSelectionWebView(
base_url_ + "move_range_selection_extent_input_field.html",
&web_view_helper);
frame = web_view_helper.LocalMainFrame();
EXPECT_EQ("Length", SelectionAsString(frame));
web_view_helper.GetWebView()->MainFrameWidget()->SelectionBounds(
start_web_rect, end_web_rect);
EXPECT_EQ(0, frame->GetFrame()
->Selection()
.ComputeVisibleSelectionInDOMTree()
.RootEditableElement()
->scrollLeft());
frame->MoveRangeSelectionExtent(
WebPoint(end_web_rect.x + 500, end_web_rect.y));
EXPECT_GE(frame->GetFrame()
->Selection()
.ComputeVisibleSelectionInDOMTree()
.RootEditableElement()
->scrollLeft(),
1);
EXPECT_EQ("Lengthy text goes here.", SelectionAsString(frame));
}
TEST_F(WebFrameTest, SmartClipData) {
static const char kExpectedClipText[] = "\nPrice 10,000,000won";
static const char kExpectedClipHtml[] =
"<div id=\"div4\" style=\"padding: 10px; margin: 10px; border: 2px "
"solid skyblue; float: left; width: 190px; height: 30px; "
"color: rgb(0, 0, 0); font-family: myahem; font-size: 8px; font-style: "
"normal; font-variant-ligatures: normal; font-variant-caps: normal; "
"font-weight: 400; letter-spacing: "
"normal; orphans: 2; text-align: start; "
"text-indent: 0px; text-transform: none; white-space: normal; widows: "
"2; word-spacing: 0px; -webkit-text-stroke-width: 0px; "
"text-decoration-style: initial; text-decoration-color: initial;\">Air "
"conditioner</div><div id=\"div5\" style=\"padding: 10px; margin: 10px; "
"border: 2px solid skyblue; float: left; width: 190px; height: 30px; "
"color: rgb(0, 0, 0); font-family: myahem; font-size: 8px; font-style: "
"normal; font-variant-ligatures: normal; font-variant-caps: normal; "
"font-weight: 400; letter-spacing: normal; orphans: 2; text-align: "
"start; text-indent: 0px; text-transform: none; white-space: normal; "
"widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; "
"text-decoration-style: initial; text-decoration-color: initial;\">Price "
"10,000,000won</div>";
WebString clip_text;
WebString clip_html;
WebRect clip_rect;
RegisterMockedHttpURLLoad("Ahem.ttf");
RegisterMockedHttpURLLoad("smartclip.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "smartclip.html");
WebLocalFrame* frame = web_view_helper.LocalMainFrame();
web_view_helper.Resize(WebSize(500, 500));
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
WebRect crop_rect(300, 125, 152, 50);
frame->ExtractSmartClipData(crop_rect, clip_text, clip_html, clip_rect);
EXPECT_STREQ(kExpectedClipText, clip_text.Utf8().c_str());
EXPECT_STREQ(kExpectedClipHtml, clip_html.Utf8().c_str());
}
TEST_F(WebFrameTest, SmartClipDataWithPinchZoom) {
static const char kExpectedClipText[] = "\nPrice 10,000,000won";
static const char kExpectedClipHtml[] =
"<div id=\"div4\" style=\"padding: 10px; margin: 10px; border: 2px "
"solid skyblue; float: left; width: 190px; height: 30px; "
"color: rgb(0, 0, 0); font-family: myahem; font-size: 8px; font-style: "
"normal; font-variant-ligatures: normal; font-variant-caps: normal; "
"font-weight: 400; letter-spacing: "
"normal; orphans: 2; text-align: start; "
"text-indent: 0px; text-transform: none; white-space: normal; widows: "
"2; word-spacing: 0px; -webkit-text-stroke-width: 0px; "
"text-decoration-style: initial; text-decoration-color: initial;\">Air "
"conditioner</div><div id=\"div5\" style=\"padding: 10px; margin: 10px; "
"border: 2px solid skyblue; float: left; width: 190px; height: 30px; "
"color: rgb(0, 0, 0); font-family: myahem; font-size: 8px; font-style: "
"normal; font-variant-ligatures: normal; font-variant-caps: normal; "
"font-weight: 400; letter-spacing: normal; orphans: 2; text-align: "
"start; text-indent: 0px; text-transform: none; white-space: normal; "
"widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; "
"text-decoration-style: initial; text-decoration-color: initial;\">Price "
"10,000,000won</div>";
WebString clip_text;
WebString clip_html;
WebRect clip_rect;
RegisterMockedHttpURLLoad("Ahem.ttf");
RegisterMockedHttpURLLoad("smartclip.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "smartclip.html");
WebLocalFrame* frame = web_view_helper.LocalMainFrame();
web_view_helper.Resize(WebSize(500, 500));
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
web_view_helper.GetWebView()->SetPageScaleFactor(1.5);
web_view_helper.GetWebView()->SetVisualViewportOffset(
WebFloatPoint(167, 100));
WebRect crop_rect(200, 38, 228, 75);
frame->ExtractSmartClipData(crop_rect, clip_text, clip_html, clip_rect);
EXPECT_STREQ(kExpectedClipText, clip_text.Utf8().c_str());
EXPECT_STREQ(kExpectedClipHtml, clip_html.Utf8().c_str());
}
TEST_F(WebFrameTest, SmartClipReturnsEmptyStringsWhenUserSelectIsNone) {
WebString clip_text;
WebString clip_html;
WebRect clip_rect;
RegisterMockedHttpURLLoad("Ahem.ttf");
RegisterMockedHttpURLLoad("smartclip_user_select_none.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ +
"smartclip_user_select_none.html");
WebLocalFrame* frame = web_view_helper.LocalMainFrame();
web_view_helper.Resize(WebSize(500, 500));
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
WebRect crop_rect(0, 0, 100, 100);
frame->ExtractSmartClipData(crop_rect, clip_text, clip_html, clip_rect);
EXPECT_STREQ("", clip_text.Utf8().c_str());
EXPECT_STREQ("", clip_html.Utf8().c_str());
}
TEST_F(WebFrameTest, SmartClipDoesNotCrashPositionReversed) {
WebString clip_text;
WebString clip_html;
WebRect clip_rect;
RegisterMockedHttpURLLoad("Ahem.ttf");
RegisterMockedHttpURLLoad("smartclip_reversed_positions.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ +
"smartclip_reversed_positions.html");
WebLocalFrame* frame = web_view_helper.LocalMainFrame();
web_view_helper.Resize(WebSize(500, 500));
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
// Left upper corner of the rect will be end position in the DOM hierarchy.
WebRect crop_rect(30, 110, 400, 250);
// This should not still crash. See crbug.com/589082 for more details.
frame->ExtractSmartClipData(crop_rect, clip_text, clip_html, clip_rect);
}
static int ComputeOffset(LayoutObject* layout_object, int x, int y) {
return layout_object->PositionForPoint(LayoutPoint(x, y))
.GetPosition()
.ComputeOffsetInContainerNode();
}
// positionForPoint returns the wrong values for contenteditable spans. See
// http://crbug.com/238334.
TEST_F(WebFrameTest, DISABLED_PositionForPointTest) {
RegisterMockedHttpURLLoad("select_range_span_editable.html");
frame_test_helpers::WebViewHelper web_view_helper;
InitializeTextSelectionWebView(base_url_ + "select_range_span_editable.html",
&web_view_helper);
WebLocalFrameImpl* main_frame = web_view_helper.LocalMainFrame();
LayoutObject* layout_object = main_frame->GetFrame()
->Selection()
.ComputeVisibleSelectionInDOMTree()
.RootEditableElement()
->GetLayoutObject();
EXPECT_EQ(0, ComputeOffset(layout_object, -1, -1));
EXPECT_EQ(64, ComputeOffset(layout_object, 1000, 1000));
RegisterMockedHttpURLLoad("select_range_div_editable.html");
InitializeTextSelectionWebView(base_url_ + "select_range_div_editable.html",
&web_view_helper);
main_frame = web_view_helper.LocalMainFrame();
layout_object = main_frame->GetFrame()
->Selection()
.ComputeVisibleSelectionInDOMTree()
.RootEditableElement()
->GetLayoutObject();
EXPECT_EQ(0, ComputeOffset(layout_object, -1, -1));
EXPECT_EQ(64, ComputeOffset(layout_object, 1000, 1000));
}
#if !defined(OS_MACOSX) && !defined(OS_LINUX)
TEST_F(WebFrameTest, SelectRangeStaysHorizontallyAlignedWhenMoved) {
RegisterMockedHttpURLLoad("move_caret.html");
frame_test_helpers::WebViewHelper web_view_helper;
InitializeTextSelectionWebView(base_url_ + "move_caret.html",
&web_view_helper);
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
WebRect initial_start_rect;
WebRect initial_end_rect;
WebRect start_rect;
WebRect end_rect;
frame->ExecuteScript(WebScriptSource("selectRange();"));
web_view_helper.GetWebView()->MainFrameWidget()->SelectionBounds(
initial_start_rect, initial_end_rect);
WebPoint moved_start(TopLeft(initial_start_rect));
moved_start.y += 40;
frame->SelectRange(moved_start, BottomRightMinusOne(initial_end_rect));
web_view_helper.GetWebView()->MainFrameWidget()->SelectionBounds(start_rect,
end_rect);
EXPECT_EQ(start_rect, initial_start_rect);
EXPECT_EQ(end_rect, initial_end_rect);
moved_start.y -= 80;
frame->SelectRange(moved_start, BottomRightMinusOne(initial_end_rect));
web_view_helper.GetWebView()->MainFrameWidget()->SelectionBounds(start_rect,
end_rect);
EXPECT_EQ(start_rect, initial_start_rect);
EXPECT_EQ(end_rect, initial_end_rect);
WebPoint moved_end(BottomRightMinusOne(initial_end_rect));
moved_end.y += 40;
frame->SelectRange(TopLeft(initial_start_rect), moved_end);
web_view_helper.GetWebView()->MainFrameWidget()->SelectionBounds(start_rect,
end_rect);
EXPECT_EQ(start_rect, initial_start_rect);
EXPECT_EQ(end_rect, initial_end_rect);
moved_end.y -= 80;
frame->SelectRange(TopLeft(initial_start_rect), moved_end);
web_view_helper.GetWebView()->MainFrameWidget()->SelectionBounds(start_rect,
end_rect);
EXPECT_EQ(start_rect, initial_start_rect);
EXPECT_EQ(end_rect, initial_end_rect);
}
TEST_F(WebFrameTest, MoveCaretStaysHorizontallyAlignedWhenMoved) {
WebLocalFrameImpl* frame;
RegisterMockedHttpURLLoad("move_caret.html");
frame_test_helpers::WebViewHelper web_view_helper;
InitializeTextSelectionWebView(base_url_ + "move_caret.html",
&web_view_helper);
frame = (WebLocalFrameImpl*)web_view_helper.GetWebView()->MainFrame();
WebRect initial_start_rect;
WebRect initial_end_rect;
WebRect start_rect;
WebRect end_rect;
frame->ExecuteScript(WebScriptSource("selectCaret();"));
web_view_helper.GetWebView()->MainFrameWidget()->SelectionBounds(
initial_start_rect, initial_end_rect);
WebPoint move_to(TopLeft(initial_start_rect));
move_to.y += 40;
frame->MoveCaretSelection(move_to);
web_view_helper.GetWebView()->MainFrameWidget()->SelectionBounds(start_rect,
end_rect);
EXPECT_EQ(start_rect, initial_start_rect);
EXPECT_EQ(end_rect, initial_end_rect);
move_to.y -= 80;
frame->MoveCaretSelection(move_to);
web_view_helper.GetWebView()->MainFrameWidget()->SelectionBounds(start_rect,
end_rect);
EXPECT_EQ(start_rect, initial_start_rect);
EXPECT_EQ(end_rect, initial_end_rect);
}
#endif
class CompositedSelectionBoundsTest
: public WebFrameTest,
private ScopedCompositedSelectionUpdateForTest {
protected:
CompositedSelectionBoundsTest()
: ScopedCompositedSelectionUpdateForTest(true) {
RegisterMockedHttpURLLoad("Ahem.ttf");
web_view_helper_.Initialize(nullptr, &web_view_client_);
web_view_helper_.GetWebView()->GetSettings()->SetDefaultFontSize(12);
web_view_helper_.GetWebView()->SetDefaultPageScaleLimits(1, 1);
web_view_helper_.Resize(WebSize(640, 480));
}
void RunTestWithNoSelection(const char* test_file) {
RegisterMockedHttpURLLoad(test_file);
web_view_helper_.GetWebView()->MainFrameWidget()->SetFocus(true);
frame_test_helpers::LoadFrame(
web_view_helper_.GetWebView()->MainFrameImpl(), base_url_ + test_file);
UpdateAllLifecyclePhases(web_view_helper_.GetWebView());
cc::LayerTreeHost* layer_tree_host =
web_view_client_.layer_tree_view()->layer_tree_host();
const cc::LayerSelection& selection = layer_tree_host->selection();
ASSERT_EQ(selection, cc::LayerSelection());
ASSERT_EQ(selection.start, cc::LayerSelectionBound());
ASSERT_EQ(selection.end, cc::LayerSelectionBound());
}
void RunTest(const char* test_file) {
RegisterMockedHttpURLLoad(test_file);
web_view_helper_.GetWebView()->MainFrameWidget()->SetFocus(true);
frame_test_helpers::LoadFrame(
web_view_helper_.GetWebView()->MainFrameImpl(), base_url_ + test_file);
UpdateAllLifecyclePhases(web_view_helper_.GetWebView());
v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
v8::Local<v8::Value> result =
web_view_helper_.GetWebView()
->MainFrameImpl()
->ExecuteScriptAndReturnValue(WebScriptSource("expectedResult"));
ASSERT_FALSE(result.IsEmpty() || (*result)->IsUndefined());
ASSERT_TRUE((*result)->IsArray());
v8::Array& expected_result = *v8::Array::Cast(*result);
ASSERT_GE(expected_result.Length(), 10u);
v8::Local<v8::Context> context =
v8::Isolate::GetCurrent()->GetCurrentContext();
const int start_edge_top_in_layer_x = expected_result.Get(context, 1)
.ToLocalChecked()
.As<v8::Int32>()
->Value();
const int start_edge_top_in_layer_y = expected_result.Get(context, 2)
.ToLocalChecked()
.As<v8::Int32>()
->Value();
const int start_edge_bottom_in_layer_x = expected_result.Get(context, 3)
.ToLocalChecked()
.As<v8::Int32>()
->Value();
const int start_edge_bottom_in_layer_y = expected_result.Get(context, 4)
.ToLocalChecked()
.As<v8::Int32>()
->Value();
const int end_edge_top_in_layer_x = expected_result.Get(context, 6)
.ToLocalChecked()
.As<v8::Int32>()
->Value();
const int end_edge_top_in_layer_y = expected_result.Get(context, 7)
.ToLocalChecked()
.As<v8::Int32>()
->Value();
const int end_edge_bottom_in_layer_x = expected_result.Get(context, 8)
.ToLocalChecked()
.As<v8::Int32>()
->Value();
const int end_edge_bottom_in_layer_y = expected_result.Get(context, 9)
.ToLocalChecked()
.As<v8::Int32>()
->Value();
FloatPoint hit_point;
if (expected_result.Length() >= 17) {
hit_point = FloatPoint(expected_result.Get(context, 15)
.ToLocalChecked()
.As<v8::Int32>()
->Value(),
expected_result.Get(context, 16)
.ToLocalChecked()
.As<v8::Int32>()
->Value());
} else {
hit_point =
FloatPoint((start_edge_top_in_layer_x + start_edge_bottom_in_layer_x +
end_edge_top_in_layer_x + end_edge_bottom_in_layer_x) /
4,
(start_edge_top_in_layer_y + start_edge_bottom_in_layer_y +
end_edge_top_in_layer_y + end_edge_bottom_in_layer_y) /
4 +
3);
}
WebGestureEvent gesture_event(WebInputEvent::kGestureTap,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
kWebGestureDeviceTouchscreen);
gesture_event.SetFrameScale(1);
gesture_event.SetPositionInWidget(hit_point);
gesture_event.SetPositionInScreen(hit_point);
web_view_helper_.GetWebView()
->MainFrameImpl()
->GetFrame()
->GetEventHandler()
.HandleGestureEvent(gesture_event);
cc::LayerTreeHost* layer_tree_host =
web_view_client_.layer_tree_view()->layer_tree_host();
const cc::LayerSelection& selection = layer_tree_host->selection();
ASSERT_NE(selection, cc::LayerSelection());
ASSERT_NE(selection.start, cc::LayerSelectionBound());
ASSERT_NE(selection.end, cc::LayerSelectionBound());
blink::Node* layer_owner_node_for_start = V8Node::ToImplWithTypeCheck(
v8::Isolate::GetCurrent(), expected_result.Get(0));
ASSERT_TRUE(layer_owner_node_for_start);
EXPECT_EQ(GetExpectedLayerForSelection(layer_owner_node_for_start)
->CcLayer()
->id(),
selection.start.layer_id);
EXPECT_EQ(start_edge_top_in_layer_x, selection.start.edge_top.x());
EXPECT_EQ(start_edge_top_in_layer_y, selection.start.edge_top.y());
EXPECT_EQ(start_edge_bottom_in_layer_x, selection.start.edge_bottom.x());
blink::Node* layer_owner_node_for_end = V8Node::ToImplWithTypeCheck(
v8::Isolate::GetCurrent(),
expected_result.Get(context, 5).ToLocalChecked());
ASSERT_TRUE(layer_owner_node_for_end);
EXPECT_EQ(
GetExpectedLayerForSelection(layer_owner_node_for_end)->CcLayer()->id(),
selection.end.layer_id);
EXPECT_EQ(end_edge_top_in_layer_x, selection.end.edge_top.x());
EXPECT_EQ(end_edge_top_in_layer_y, selection.end.edge_top.y());
EXPECT_EQ(end_edge_bottom_in_layer_x, selection.end.edge_bottom.x());
// Platform differences can introduce small stylistic deviations in
// y-axis positioning, the details of which aren't relevant to
// selection behavior. However, such deviations from the expected value
// should be consistent for the corresponding y coordinates.
int y_bottom_epsilon = 0;
if (expected_result.Length() == 13) {
y_bottom_epsilon = expected_result.Get(context, 12)
.ToLocalChecked()
.As<v8::Int32>()
->Value();
}
int y_bottom_deviation =
start_edge_bottom_in_layer_y - selection.start.edge_bottom.y();
EXPECT_GE(y_bottom_epsilon, std::abs(y_bottom_deviation));
EXPECT_EQ(y_bottom_deviation,
end_edge_bottom_in_layer_y - selection.end.edge_bottom.y());
if (expected_result.Length() >= 15) {
bool start_hidden = expected_result.Get(context, 13)
.ToLocalChecked()
.As<v8::Boolean>()
->Value();
bool end_hidden = expected_result.Get(context, 14)
.ToLocalChecked()
.As<v8::Boolean>()
->Value();
EXPECT_EQ(start_hidden, selection.start.hidden);
EXPECT_EQ(end_hidden, selection.end.hidden);
}
}
void RunTestWithMultipleFiles(const char* test_file, ...) {
va_list aux_files;
va_start(aux_files, test_file);
while (const char* aux_file = va_arg(aux_files, const char*))
RegisterMockedHttpURLLoad(aux_file);
va_end(aux_files);
RunTest(test_file);
}
GraphicsLayer* GetExpectedLayerForSelection(blink::Node* node) const {
CompositedLayerMapping* clm = node->GetLayoutObject()
->EnclosingLayer()
->EnclosingLayerForPaintInvalidation()
->GetCompositedLayerMapping();
// If the Node is a scroller, the selection will be relative to its
// scrolling contents layer.
return clm->ScrollingContentsLayer() ? clm->ScrollingContentsLayer()
: clm->MainGraphicsLayer();
}
frame_test_helpers::TestWebViewClient web_view_client_;
frame_test_helpers::WebViewHelper web_view_helper_;
};
TEST_F(CompositedSelectionBoundsTest, None) {
RunTestWithNoSelection("composited_selection_bounds_none.html");
}
TEST_F(CompositedSelectionBoundsTest, NoneReadonlyCaret) {
RunTestWithNoSelection(
"composited_selection_bounds_none_readonly_caret.html");
}
TEST_F(CompositedSelectionBoundsTest, DetachedFrame) {
RunTestWithNoSelection("composited_selection_bounds_detached_frame.html");
}
TEST_F(CompositedSelectionBoundsTest, Basic) {
RunTest("composited_selection_bounds_basic.html");
}
TEST_F(CompositedSelectionBoundsTest, Transformed) {
RunTest("composited_selection_bounds_transformed.html");
}
TEST_F(CompositedSelectionBoundsTest, VerticalRightToLeft) {
RunTest("composited_selection_bounds_vertical_rl.html");
}
TEST_F(CompositedSelectionBoundsTest, VerticalLeftToRight) {
RunTest("composited_selection_bounds_vertical_lr.html");
}
TEST_F(CompositedSelectionBoundsTest, SplitLayer) {
RunTest("composited_selection_bounds_split_layer.html");
}
TEST_F(CompositedSelectionBoundsTest, Iframe) {
RunTestWithMultipleFiles("composited_selection_bounds_iframe.html",
"composited_selection_bounds_basic.html", nullptr);
}
TEST_F(CompositedSelectionBoundsTest, Editable) {
RunTest("composited_selection_bounds_editable.html");
}
TEST_F(CompositedSelectionBoundsTest, EditableDiv) {
RunTest("composited_selection_bounds_editable_div.html");
}
#if defined(OS_LINUX)
#if !defined(OS_ANDROID)
TEST_F(CompositedSelectionBoundsTest, Input) {
RunTest("composited_selection_bounds_input.html");
}
TEST_F(CompositedSelectionBoundsTest, InputScrolled) {
RunTest("composited_selection_bounds_input_scrolled.html");
}
#endif
#endif
class TestSubstituteDataWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
TestSubstituteDataWebFrameClient() : commit_called_(false) {}
~TestSubstituteDataWebFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
void DidFailProvisionalLoad(const WebURLError& error,
WebHistoryCommitType) override {
const char kData[] = "This should appear";
auto navigation_params = WebNavigationParams::CreateWithHTMLString(
kData, KURL("chrome-error://chromewebdata/"));
navigation_params->unreachable_url = error.url();
navigation_params->frame_load_type = WebFrameLoadType::kReplaceCurrentItem;
Frame()->CommitNavigation(std::move(navigation_params),
nullptr /* extra_data */);
}
void DidCommitProvisionalLoad(const WebHistoryItem&,
WebHistoryCommitType,
WebGlobalObjectReusePolicy) override {
if (Frame()->GetDocumentLoader()->GetResponse().CurrentRequestUrl() !=
WebURL(url_test_helpers::ToKURL("about:blank")))
commit_called_ = true;
}
bool CommitCalled() const { return commit_called_; }
private:
bool commit_called_;
};
TEST_F(WebFrameTest, ReplaceNavigationAfterHistoryNavigation) {
TestSubstituteDataWebFrameClient web_frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad("about:blank", &web_frame_client);
WebLocalFrame* frame = web_view_helper.GetWebView()->MainFrameImpl();
// Load a url as a history navigation that will return an error.
// TestSubstituteDataWebFrameClient will start a SubstituteData load in
// response to the load failure, which should get fully committed. Due to
// https://bugs.webkit.org/show_bug.cgi?id=91685,
// FrameLoader::didReceiveData() wasn't getting called in this case, which
// resulted in the SubstituteData document not getting displayed.
std::string error_url = "http://0.0.0.0";
ResourceError error = ResourceError::Failure(ToKURL(error_url));
WebURLResponse response;
response.SetCurrentRequestUrl(url_test_helpers::ToKURL(error_url));
response.SetMIMEType("text/html");
response.SetHTTPStatusCode(500);
WebHistoryItem error_history_item;
error_history_item.Initialize();
error_history_item.SetURLString(
WebString::FromUTF8(error_url.c_str(), error_url.length()));
Platform::Current()->GetURLLoaderMockFactory()->RegisterErrorURL(
url_test_helpers::ToKURL(error_url), response, error);
frame_test_helpers::LoadHistoryItem(frame, error_history_item,
mojom::FetchCacheMode::kDefault);
WebString text = WebFrameContentDumper::DumpWebViewAsText(
web_view_helper.GetWebView(), std::numeric_limits<size_t>::max());
EXPECT_EQ("This should appear", text.Utf8());
EXPECT_TRUE(web_frame_client.CommitCalled());
}
class TestWillInsertBodyWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
TestWillInsertBodyWebFrameClient() : did_load_(false) {}
~TestWillInsertBodyWebFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
void DidCommitProvisionalLoad(const WebHistoryItem&,
WebHistoryCommitType,
WebGlobalObjectReusePolicy) override {
did_load_ = true;
}
bool did_load_;
};
TEST_F(WebFrameTest, HTMLDocument) {
RegisterMockedHttpURLLoad("clipped-body.html");
TestWillInsertBodyWebFrameClient web_frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "clipped-body.html",
&web_frame_client);
EXPECT_TRUE(web_frame_client.did_load_);
}
TEST_F(WebFrameTest, EmptyDocument) {
RegisterMockedHttpURLLoad("frameserializer/svg/green_rectangle.svg");
TestWillInsertBodyWebFrameClient web_frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(&web_frame_client);
EXPECT_FALSE(web_frame_client.did_load_);
}
TEST_F(WebFrameTest, MoveCaretSelectionTowardsWindowPointWithNoSelection) {
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad("about:blank");
WebFrame* frame = web_view_helper.GetWebView()->MainFrame();
// This test passes if this doesn't crash.
frame->ToWebLocalFrame()->MoveCaretSelection(WebPoint(0, 0));
}
class TextCheckClient : public WebTextCheckClient {
public:
TextCheckClient() : number_of_times_checked_(0) {}
~TextCheckClient() override = default;
// WebTextCheckClient:
bool IsSpellCheckingEnabled() const override { return true; }
void RequestCheckingOfText(const WebString&,
WebTextCheckingCompletion* completion) override {
++number_of_times_checked_;
Vector<WebTextCheckingResult> results;
const int kMisspellingStartOffset = 1;
const int kMisspellingLength = 8;
results.push_back(WebTextCheckingResult(
kWebTextDecorationTypeSpelling, kMisspellingStartOffset,
kMisspellingLength, WebVector<WebString>()));
completion->DidFinishCheckingText(results);
}
int NumberOfTimesChecked() const { return number_of_times_checked_; }
private:
int number_of_times_checked_;
};
TEST_F(WebFrameTest, ReplaceMisspelledRange) {
RegisterMockedHttpURLLoad("spell.html");
frame_test_helpers::WebViewHelper web_view_helper;
InitializeTextSelectionWebView(base_url_ + "spell.html", &web_view_helper);
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
TextCheckClient textcheck;
frame->SetTextCheckClient(&textcheck);
Document* document = frame->GetFrame()->GetDocument();
Element* element = document->getElementById("data");
web_view_helper.GetWebView()->GetSettings()->SetEditingBehavior(
WebSettings::EditingBehavior::kWin);
element->focus();
NonThrowableExceptionState exception_state;
document->execCommand("InsertText", false, "_wellcome_.", exception_state);
EXPECT_FALSE(exception_state.HadException());
document->GetFrame()
->GetSpellChecker()
.GetIdleSpellCheckController()
.ForceInvocationForTesting();
const int kAllTextBeginOffset = 0;
const int kAllTextLength = 11;
frame->SelectRange(WebRange(kAllTextBeginOffset, kAllTextLength),
WebLocalFrame::kHideSelectionHandle,
SelectionMenuBehavior::kHide);
EphemeralRange selection_range = frame->GetFrame()
->Selection()
.ComputeVisibleSelectionInDOMTree()
.ToNormalizedEphemeralRange();
EXPECT_EQ(1, textcheck.NumberOfTimesChecked());
EXPECT_EQ(1, NumMarkersInRange(document, selection_range,
DocumentMarker::MarkerTypes::Spelling()));
frame->ReplaceMisspelledRange("welcome");
EXPECT_EQ("_welcome_.", WebFrameContentDumper::DumpWebViewAsText(
web_view_helper.GetWebView(),
std::numeric_limits<size_t>::max())
.Utf8());
}
TEST_F(WebFrameTest, RemoveSpellingMarkers) {
RegisterMockedHttpURLLoad("spell.html");
frame_test_helpers::WebViewHelper web_view_helper;
InitializeTextSelectionWebView(base_url_ + "spell.html", &web_view_helper);
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
TextCheckClient textcheck;
frame->SetTextCheckClient(&textcheck);
Document* document = frame->GetFrame()->GetDocument();
Element* element = document->getElementById("data");
web_view_helper.GetWebView()->GetSettings()->SetEditingBehavior(
WebSettings::EditingBehavior::kWin);
element->focus();
NonThrowableExceptionState exception_state;
document->execCommand("InsertText", false, "_wellcome_.", exception_state);
EXPECT_FALSE(exception_state.HadException());
document->GetFrame()
->GetSpellChecker()
.GetIdleSpellCheckController()
.ForceInvocationForTesting();
frame->RemoveSpellingMarkers();
const int kAllTextBeginOffset = 0;
const int kAllTextLength = 11;
frame->SelectRange(WebRange(kAllTextBeginOffset, kAllTextLength),
WebLocalFrame::kHideSelectionHandle,
SelectionMenuBehavior::kHide);
EphemeralRange selection_range = frame->GetFrame()
->Selection()
.ComputeVisibleSelectionInDOMTree()
.ToNormalizedEphemeralRange();
EXPECT_EQ(0, NumMarkersInRange(document, selection_range,
DocumentMarker::MarkerTypes::Spelling()));
}
static void GetSpellingMarkerOffsets(WebVector<unsigned>* offsets,
const Document& document) {
Vector<unsigned> result;
const DocumentMarkerVector& document_markers = document.Markers().Markers();
for (size_t i = 0; i < document_markers.size(); ++i)
result.push_back(document_markers[i]->StartOffset());
offsets->Assign(result);
}
TEST_F(WebFrameTest, RemoveSpellingMarkersUnderWords) {
RegisterMockedHttpURLLoad("spell.html");
frame_test_helpers::WebViewHelper web_view_helper;
InitializeTextSelectionWebView(base_url_ + "spell.html", &web_view_helper);
WebLocalFrameImpl* web_frame = web_view_helper.LocalMainFrame();
TextCheckClient textcheck;
web_frame->SetTextCheckClient(&textcheck);
LocalFrame* frame = web_frame->GetFrame();
Document* document = frame->GetDocument();
Element* element = document->getElementById("data");
web_view_helper.GetWebView()->GetSettings()->SetEditingBehavior(
WebSettings::EditingBehavior::kWin);
element->focus();
NonThrowableExceptionState exception_state;
document->execCommand("InsertText", false, " wellcome ", exception_state);
EXPECT_FALSE(exception_state.HadException());
frame->GetSpellChecker()
.GetIdleSpellCheckController()
.ForceInvocationForTesting();
WebVector<unsigned> offsets1;
GetSpellingMarkerOffsets(&offsets1, *frame->GetDocument());
EXPECT_EQ(1U, offsets1.size());
Vector<String> words;
words.push_back("wellcome");
frame->RemoveSpellingMarkersUnderWords(words);
WebVector<unsigned> offsets2;
GetSpellingMarkerOffsets(&offsets2, *frame->GetDocument());
EXPECT_EQ(0U, offsets2.size());
}
class StubbornTextCheckClient : public WebTextCheckClient {
public:
StubbornTextCheckClient() : completion_(nullptr) {}
~StubbornTextCheckClient() override = default;
// WebTextCheckClient:
bool IsSpellCheckingEnabled() const override { return true; }
void RequestCheckingOfText(const WebString&,
WebTextCheckingCompletion* completion) override {
completion_ = completion;
}
void CancelAllPendingRequests() override {
if (!completion_)
return;
completion_->DidCancelCheckingText();
completion_ = nullptr;
}
void KickNoResults() { Kick(-1, -1, kWebTextDecorationTypeSpelling); }
void Kick() { Kick(1, 8, kWebTextDecorationTypeSpelling); }
void KickGrammar() { Kick(1, 8, kWebTextDecorationTypeGrammar); }
private:
void Kick(int misspelling_start_offset,
int misspelling_length,
WebTextDecorationType type) {
if (!completion_)
return;
Vector<WebTextCheckingResult> results;
if (misspelling_start_offset >= 0 && misspelling_length > 0) {
results.push_back(WebTextCheckingResult(type, misspelling_start_offset,
misspelling_length));
}
completion_->DidFinishCheckingText(results);
completion_ = nullptr;
}
WebTextCheckingCompletion* completion_;
};
TEST_F(WebFrameTest, SlowSpellcheckMarkerPosition) {
RegisterMockedHttpURLLoad("spell.html");
frame_test_helpers::WebViewHelper web_view_helper;
InitializeTextSelectionWebView(base_url_ + "spell.html", &web_view_helper);
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
StubbornTextCheckClient textcheck;
frame->SetTextCheckClient(&textcheck);
Document* document = frame->GetFrame()->GetDocument();
Element* element = document->getElementById("data");
web_view_helper.GetWebView()->GetSettings()->SetEditingBehavior(
WebSettings::EditingBehavior::kWin);
element->focus();
NonThrowableExceptionState exception_state;
document->execCommand("InsertText", false, "wellcome ", exception_state);
EXPECT_FALSE(exception_state.HadException());
document->execCommand("InsertText", false, "he", exception_state);
EXPECT_FALSE(exception_state.HadException());
document->GetFrame()
->GetSpellChecker()
.GetIdleSpellCheckController()
.ForceInvocationForTesting();
textcheck.Kick();
WebVector<unsigned> offsets;
GetSpellingMarkerOffsets(&offsets, *frame->GetFrame()->GetDocument());
EXPECT_EQ(0U, offsets.size());
}
TEST_F(WebFrameTest, SpellcheckResultErasesMarkers) {
RegisterMockedHttpURLLoad("spell.html");
frame_test_helpers::WebViewHelper web_view_helper;
InitializeTextSelectionWebView(base_url_ + "spell.html", &web_view_helper);
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
StubbornTextCheckClient textcheck;
frame->SetTextCheckClient(&textcheck);
Document* document = frame->GetFrame()->GetDocument();
Element* element = document->getElementById("data");
web_view_helper.GetWebView()->GetSettings()->SetEditingBehavior(
WebSettings::EditingBehavior::kWin);
element->focus();
NonThrowableExceptionState exception_state;
document->execCommand("InsertText", false, "welcome ", exception_state);
document->GetFrame()
->GetSpellChecker()
.GetIdleSpellCheckController()
.ForceInvocationForTesting();
document->UpdateStyleAndLayout();
EXPECT_FALSE(exception_state.HadException());
auto range = EphemeralRange::RangeOfContents(*element);
document->Markers().AddSpellingMarker(range);
document->Markers().AddGrammarMarker(range);
EXPECT_EQ(2U, document->Markers().Markers().size());
textcheck.KickNoResults();
EXPECT_EQ(0U, document->Markers().Markers().size());
}
TEST_F(WebFrameTest, SpellcheckResultsSavedInDocument) {
RegisterMockedHttpURLLoad("spell.html");
frame_test_helpers::WebViewHelper web_view_helper;
InitializeTextSelectionWebView(base_url_ + "spell.html", &web_view_helper);
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
StubbornTextCheckClient textcheck;
frame->SetTextCheckClient(&textcheck);
Document* document = frame->GetFrame()->GetDocument();
Element* element = document->getElementById("data");
web_view_helper.GetWebView()->GetSettings()->SetEditingBehavior(
WebSettings::EditingBehavior::kWin);
element->focus();
NonThrowableExceptionState exception_state;
document->execCommand("InsertText", false, "wellcome ", exception_state);
EXPECT_FALSE(exception_state.HadException());
document->GetFrame()
->GetSpellChecker()
.GetIdleSpellCheckController()
.ForceInvocationForTesting();
textcheck.Kick();
ASSERT_EQ(1U, document->Markers().Markers().size());
ASSERT_NE(static_cast<DocumentMarker*>(nullptr),
document->Markers().Markers()[0]);
EXPECT_EQ(DocumentMarker::kSpelling,
document->Markers().Markers()[0]->GetType());
document->execCommand("InsertText", false, "wellcome ", exception_state);
EXPECT_FALSE(exception_state.HadException());
document->GetFrame()
->GetSpellChecker()
.GetIdleSpellCheckController()
.ForceInvocationForTesting();
textcheck.KickGrammar();
ASSERT_EQ(1U, document->Markers().Markers().size());
ASSERT_NE(static_cast<DocumentMarker*>(nullptr),
document->Markers().Markers()[0]);
EXPECT_EQ(DocumentMarker::kGrammar,
document->Markers().Markers()[0]->GetType());
}
class TestAccessInitialDocumentWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
TestAccessInitialDocumentWebFrameClient() = default;
~TestAccessInitialDocumentWebFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
void DidAccessInitialDocument() override { ++did_access_initial_document_; }
// !!!!!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!!!!!!
// If the actual counts in the tests below increase, this could be an
// indicator of a bug that causes DidAccessInitialDocument() to always be
// invoked, regardless of whether or not the initial document is accessed.
// Please do not simply increment the expected counts in the below tests
// without understanding what's causing the increased count.
int did_access_initial_document_ = 0;
};
TEST_F(WebFrameTest, DidAccessInitialDocumentBody) {
TestAccessInitialDocumentWebFrameClient web_frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(&web_frame_client);
RunPendingTasks();
EXPECT_EQ(0, web_frame_client.did_access_initial_document_);
// Create another window that will try to access it.
frame_test_helpers::WebViewHelper new_web_view_helper;
WebViewImpl* new_view = new_web_view_helper.InitializeWithOpener(
web_view_helper.GetWebView()->MainFrame());
RunPendingTasks();
EXPECT_EQ(0, web_frame_client.did_access_initial_document_);
// Access the initial document by modifying the body.
new_view->MainFrameImpl()->ExecuteScript(
WebScriptSource("window.opener.document.body.innerHTML += 'Modified';"));
RunPendingTasks();
EXPECT_EQ(1, web_frame_client.did_access_initial_document_);
web_view_helper.Reset();
}
TEST_F(WebFrameTest, DidAccessInitialDocumentOpen) {
TestAccessInitialDocumentWebFrameClient web_frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(&web_frame_client);
RunPendingTasks();
EXPECT_EQ(0, web_frame_client.did_access_initial_document_);
// Create another window that will try to access it.
frame_test_helpers::WebViewHelper new_web_view_helper;
WebViewImpl* new_view = new_web_view_helper.InitializeWithOpener(
web_view_helper.GetWebView()->MainFrame());
RunPendingTasks();
EXPECT_EQ(0, web_frame_client.did_access_initial_document_);
// Access the initial document by calling document.open(), which allows
// arbitrary modification of the initial document.
new_view->MainFrameImpl()->ExecuteScript(
WebScriptSource("window.opener.document.open();"));
RunPendingTasks();
EXPECT_EQ(1, web_frame_client.did_access_initial_document_);
web_view_helper.Reset();
}
TEST_F(WebFrameTest, DidAccessInitialDocumentNavigator) {
TestAccessInitialDocumentWebFrameClient web_frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(&web_frame_client);
RunPendingTasks();
EXPECT_EQ(0, web_frame_client.did_access_initial_document_);
// Create another window that will try to access it.
frame_test_helpers::WebViewHelper new_web_view_helper;
WebViewImpl* new_view = new_web_view_helper.InitializeWithOpener(
web_view_helper.GetWebView()->MainFrame());
RunPendingTasks();
EXPECT_EQ(0, web_frame_client.did_access_initial_document_);
// Access the initial document to get to the navigator object.
new_view->MainFrameImpl()->ExecuteScript(
WebScriptSource("console.log(window.opener.navigator);"));
RunPendingTasks();
EXPECT_EQ(1, web_frame_client.did_access_initial_document_);
web_view_helper.Reset();
}
TEST_F(WebFrameTest, DidAccessInitialDocumentViaJavascriptUrl) {
TestAccessInitialDocumentWebFrameClient web_frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(&web_frame_client);
RunPendingTasks();
EXPECT_EQ(0, web_frame_client.did_access_initial_document_);
// Access the initial document from a javascript: URL.
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
"javascript:document.body.appendChild(document."
"createTextNode('Modified'))");
EXPECT_EQ(1, web_frame_client.did_access_initial_document_);
web_view_helper.Reset();
}
TEST_F(WebFrameTest, DidAccessInitialDocumentBodyBeforeModalDialog) {
TestAccessInitialDocumentWebFrameClient web_frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(&web_frame_client);
RunPendingTasks();
EXPECT_EQ(0, web_frame_client.did_access_initial_document_);
// Create another window that will try to access it.
frame_test_helpers::WebViewHelper new_web_view_helper;
WebViewImpl* new_view = new_web_view_helper.InitializeWithOpener(
web_view_helper.GetWebView()->MainFrame());
RunPendingTasks();
EXPECT_EQ(0, web_frame_client.did_access_initial_document_);
// Access the initial document by modifying the body.
new_view->MainFrameImpl()->ExecuteScript(
WebScriptSource("window.opener.document.body.innerHTML += 'Modified';"));
EXPECT_EQ(1, web_frame_client.did_access_initial_document_);
// Run a modal dialog, which used to run a nested run loop and require
// a special case for notifying about the access.
new_view->MainFrameImpl()->ExecuteScript(
WebScriptSource("window.opener.confirm('Modal');"));
EXPECT_EQ(2, web_frame_client.did_access_initial_document_);
// Ensure that we don't notify again later.
RunPendingTasks();
EXPECT_EQ(2, web_frame_client.did_access_initial_document_);
web_view_helper.Reset();
}
TEST_F(WebFrameTest, DidWriteToInitialDocumentBeforeModalDialog) {
TestAccessInitialDocumentWebFrameClient web_frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(&web_frame_client);
RunPendingTasks();
EXPECT_EQ(0, web_frame_client.did_access_initial_document_);
// Create another window that will try to access it.
frame_test_helpers::WebViewHelper new_web_view_helper;
WebViewImpl* new_view = new_web_view_helper.InitializeWithOpener(
web_view_helper.GetWebView()->MainFrame());
RunPendingTasks();
EXPECT_EQ(0, web_frame_client.did_access_initial_document_);
// Access the initial document with document.write, which moves us past the
// initial empty document state of the state machine.
new_view->MainFrameImpl()->ExecuteScript(
WebScriptSource("window.opener.document.write('Modified'); "
"window.opener.document.close();"));
EXPECT_EQ(1, web_frame_client.did_access_initial_document_);
// Run a modal dialog, which used to run a nested run loop and require
// a special case for notifying about the access.
new_view->MainFrameImpl()->ExecuteScript(
WebScriptSource("window.opener.confirm('Modal');"));
EXPECT_EQ(1, web_frame_client.did_access_initial_document_);
// Ensure that we don't notify again later.
RunPendingTasks();
EXPECT_EQ(1, web_frame_client.did_access_initial_document_);
web_view_helper.Reset();
}
class TestScrolledFrameClient : public frame_test_helpers::TestWebFrameClient {
public:
TestScrolledFrameClient() { Reset(); }
~TestScrolledFrameClient() override = default;
void Reset() { did_scroll_frame_ = false; }
bool WasFrameScrolled() const { return did_scroll_frame_; }
// WebLocalFrameClient:
void DidChangeScrollOffset() override {
if (Frame()->Parent())
return;
EXPECT_FALSE(did_scroll_frame_);
LocalFrameView* view = ToWebLocalFrameImpl(Frame())->GetFrameView();
// LocalFrameView can be scrolled in
// LocalFrameView::SetFixedVisibleContentRect which is called from
// LocalFrame::CreateView (before the frame is associated with the the
// view).
if (view)
did_scroll_frame_ = true;
}
private:
bool did_scroll_frame_;
};
TEST_F(WebFrameTest, CompositorScrollIsUserScrollLongPage) {
RegisterMockedHttpURLLoad("long_scroll.html");
TestScrolledFrameClient client;
// Make sure we initialize to minimum scale, even if the window size
// only becomes available after the load begins.
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "long_scroll.html", &client);
web_view_helper.Resize(WebSize(1000, 1000));
WebLocalFrameImpl* frame_impl = web_view_helper.LocalMainFrame();
DocumentLoader::InitialScrollState& initial_scroll_state =
frame_impl->GetFrame()
->Loader()
.GetDocumentLoader()
->GetInitialScrollState();
EXPECT_FALSE(client.WasFrameScrolled());
EXPECT_FALSE(initial_scroll_state.was_scrolled_by_user);
auto* scrollable_area = frame_impl->GetFrameView()->LayoutViewport();
// Do a compositor scroll, verify that this is counted as a user scroll.
scrollable_area->DidScroll(FloatPoint(0, 1));
web_view_helper.GetWebView()->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 1.7f, 0,
cc::BrowserControlsState::kBoth});
EXPECT_TRUE(client.WasFrameScrolled());
EXPECT_TRUE(initial_scroll_state.was_scrolled_by_user);
client.Reset();
initial_scroll_state.was_scrolled_by_user = false;
// The page scale 1.0f and scroll.
scrollable_area->DidScroll(FloatPoint(0, 2));
web_view_helper.GetWebView()->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 1.0f, 0,
cc::BrowserControlsState::kBoth});
EXPECT_TRUE(client.WasFrameScrolled());
EXPECT_TRUE(initial_scroll_state.was_scrolled_by_user);
client.Reset();
initial_scroll_state.was_scrolled_by_user = false;
// No scroll event if there is no scroll delta.
scrollable_area->DidScroll(FloatPoint(0, 2));
web_view_helper.GetWebView()->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 1.0f, 0,
cc::BrowserControlsState::kBoth});
EXPECT_FALSE(client.WasFrameScrolled());
EXPECT_FALSE(initial_scroll_state.was_scrolled_by_user);
client.Reset();
// Non zero page scale and scroll.
scrollable_area->DidScroll(FloatPoint(9, 15));
web_view_helper.GetWebView()->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 0.6f, 0,
cc::BrowserControlsState::kBoth});
EXPECT_TRUE(client.WasFrameScrolled());
EXPECT_TRUE(initial_scroll_state.was_scrolled_by_user);
client.Reset();
initial_scroll_state.was_scrolled_by_user = false;
// Programmatic scroll.
frame_impl->ExecuteScript(WebScriptSource("window.scrollTo(0, 20);"));
EXPECT_TRUE(client.WasFrameScrolled());
EXPECT_FALSE(initial_scroll_state.was_scrolled_by_user);
client.Reset();
// Programmatic scroll to same offset. No scroll event should be generated.
frame_impl->ExecuteScript(WebScriptSource("window.scrollTo(0, 20);"));
EXPECT_FALSE(client.WasFrameScrolled());
EXPECT_FALSE(initial_scroll_state.was_scrolled_by_user);
client.Reset();
}
TEST_F(WebFrameTest, SiteForCookiesForRedirect) {
String file_path = test::CoreTestDataPath("first_party.html");
WebURL test_url(ToKURL("http://internal.test/first_party_redirect.html"));
char redirect[] = "http://internal.test/first_party.html";
WebURL redirect_url(ToKURL(redirect));
WebURLResponse redirect_response;
redirect_response.SetMIMEType("text/html");
redirect_response.SetHTTPStatusCode(302);
redirect_response.SetHTTPHeaderField("Location", redirect);
Platform::Current()->GetURLLoaderMockFactory()->RegisterURL(
test_url, redirect_response, file_path);
WebURLResponse final_response;
final_response.SetMIMEType("text/html");
Platform::Current()->GetURLLoaderMockFactory()->RegisterURL(
redirect_url, final_response, file_path);
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "first_party_redirect.html");
EXPECT_TRUE(web_view_helper.GetWebView()
->MainFrameImpl()
->GetDocument()
.SiteForCookies() == redirect_url);
}
class TestNewWindowWebViewClient
: public frame_test_helpers::TestWebViewClient {
public:
TestNewWindowWebViewClient() = default;
~TestNewWindowWebViewClient() override = default;
// frame_test_helpers::TestWebFrameClient:
WebView* CreateView(WebLocalFrame*,
const WebURLRequest&,
const WebWindowFeatures&,
const WebString&,
WebNavigationPolicy,
bool,
WebSandboxFlags,
const SessionStorageNamespaceId&) override {
EXPECT_TRUE(false);
return nullptr;
}
};
class TestNewWindowWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
TestNewWindowWebFrameClient() : begin_navigation_call_count_(0) {}
~TestNewWindowWebFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
void BeginNavigation(std::unique_ptr<WebNavigationInfo> info) override {
if (ignore_navigations_) {
begin_navigation_call_count_++;
return;
}
TestWebFrameClient::BeginNavigation(std::move(info));
}
int BeginNavigationCallCount() const { return begin_navigation_call_count_; }
void IgnoreNavigations() { ignore_navigations_ = true; }
private:
bool ignore_navigations_ = false;
int begin_navigation_call_count_;
};
TEST_F(WebFrameTest, ModifiedClickNewWindow) {
// This test checks that ctrl+click does not just open a new window,
// but instead goes to client to decide the navigation policy.
RegisterMockedHttpURLLoad("ctrl_click.html");
RegisterMockedHttpURLLoad("hello_world.html");
TestNewWindowWebViewClient web_view_client;
TestNewWindowWebFrameClient web_frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "ctrl_click.html",
&web_frame_client, &web_view_client);
LocalFrame* frame =
ToLocalFrame(web_view_helper.GetWebView()->GetPage()->MainFrame());
Document* document = frame->GetDocument();
KURL destination = ToKURL(base_url_ + "hello_world.html");
// ctrl+click event
MouseEventInit* mouse_initializer = MouseEventInit::Create();
mouse_initializer->setView(document->domWindow());
mouse_initializer->setButton(1);
mouse_initializer->setCtrlKey(true);
Event* event =
MouseEvent::Create(nullptr, event_type_names::kClick, mouse_initializer);
FrameLoadRequest frame_request(document, ResourceRequest(destination));
frame_request.SetTriggeringEventInfo(
WebTriggeringEventInfo::kFromTrustedEvent);
std::unique_ptr<UserGestureIndicator> gesture =
LocalFrame::NotifyUserActivation(frame);
web_frame_client.IgnoreNavigations();
ToLocalFrame(web_view_helper.GetWebView()->GetPage()->MainFrame())
->Loader()
.StartNavigation(frame_request, WebFrameLoadType::kStandard,
NavigationPolicyFromEvent(event));
frame_test_helpers::PumpPendingRequestsForFrameToLoad(
web_view_helper.LocalMainFrame());
// BeginNavigation should be called for the ctrl+click.
EXPECT_EQ(1, web_frame_client.BeginNavigationCallCount());
}
TEST_F(WebFrameTest, BackToReload) {
RegisterMockedHttpURLLoad("fragment_middle_click.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "fragment_middle_click.html");
WebLocalFrame* frame = web_view_helper.LocalMainFrame();
const FrameLoader& main_frame_loader =
web_view_helper.LocalMainFrame()->GetFrame()->Loader();
Persistent<HistoryItem> first_item =
main_frame_loader.GetDocumentLoader()->GetHistoryItem();
EXPECT_TRUE(first_item);
RegisterMockedHttpURLLoad("white-1x1.png");
frame_test_helpers::LoadFrame(frame, base_url_ + "white-1x1.png");
EXPECT_NE(first_item.Get(),
main_frame_loader.GetDocumentLoader()->GetHistoryItem());
frame_test_helpers::LoadHistoryItem(frame, WebHistoryItem(first_item.Get()),
mojom::FetchCacheMode::kDefault);
EXPECT_EQ(first_item.Get(),
main_frame_loader.GetDocumentLoader()->GetHistoryItem());
frame_test_helpers::ReloadFrame(frame);
EXPECT_EQ(mojom::FetchCacheMode::kValidateCache,
frame->GetDocumentLoader()->GetRequest().GetCacheMode());
}
TEST_F(WebFrameTest, BackDuringChildFrameReload) {
RegisterMockedHttpURLLoad("page_with_blank_iframe.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "page_with_blank_iframe.html");
WebLocalFrameImpl* main_frame = web_view_helper.LocalMainFrame();
const FrameLoader& main_frame_loader = main_frame->GetFrame()->Loader();
WebLocalFrame* child_frame = main_frame->FirstChild()->ToWebLocalFrame();
ASSERT_TRUE(child_frame);
// Start a history navigation, then have a different frame commit a
// navigation. In this case, reload an about:blank frame, which will commit
// synchronously. After the history navigation completes, both the
// appropriate document url and the current history item should reflect the
// history navigation.
RegisterMockedHttpURLLoad("white-1x1.png");
WebHistoryItem item;
item.Initialize();
WebURL history_url(ToKURL(base_url_ + "white-1x1.png"));
item.SetURLString(history_url.GetString());
HistoryItem* history_item = item;
auto params = std::make_unique<WebNavigationParams>();
params->request = WrappedResourceRequest(
history_item->GenerateResourceRequest(mojom::FetchCacheMode::kDefault));
params->frame_load_type = WebFrameLoadType::kBackForward;
params->history_item = item;
main_frame->CommitNavigation(std::move(params), nullptr /* extra_data */);
frame_test_helpers::ReloadFrame(child_frame);
EXPECT_EQ(item.UrlString(), main_frame->GetDocument().Url().GetString());
EXPECT_EQ(item.UrlString(), WebString(main_frame_loader.GetDocumentLoader()
->GetHistoryItem()
->UrlString()));
}
TEST_F(WebFrameTest, ReloadPost) {
RegisterMockedHttpURLLoad("reload_post.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "reload_post.html");
WebLocalFrame* frame = web_view_helper.LocalMainFrame();
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
"javascript:document.forms[0].submit()");
// Pump requests one more time after the javascript URL has executed to
// trigger the actual POST load request.
frame_test_helpers::PumpPendingRequestsForFrameToLoad(
web_view_helper.LocalMainFrame());
EXPECT_EQ(WebString::FromUTF8("POST"),
frame->GetDocumentLoader()->GetRequest().HttpMethod());
frame_test_helpers::ReloadFrame(frame);
EXPECT_EQ(mojom::FetchCacheMode::kValidateCache,
frame->GetDocumentLoader()->GetRequest().GetCacheMode());
EXPECT_EQ(kWebNavigationTypeFormResubmitted,
frame->GetDocumentLoader()->GetNavigationType());
}
TEST_F(WebFrameTest, LoadHistoryItemReload) {
RegisterMockedHttpURLLoad("fragment_middle_click.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "fragment_middle_click.html");
WebLocalFrame* frame = web_view_helper.LocalMainFrame();
const FrameLoader& main_frame_loader =
web_view_helper.LocalMainFrame()->GetFrame()->Loader();
Persistent<HistoryItem> first_item =
main_frame_loader.GetDocumentLoader()->GetHistoryItem();
EXPECT_TRUE(first_item);
RegisterMockedHttpURLLoad("white-1x1.png");
frame_test_helpers::LoadFrame(frame, base_url_ + "white-1x1.png");
EXPECT_NE(first_item.Get(),
main_frame_loader.GetDocumentLoader()->GetHistoryItem());
// Cache policy overrides should take.
frame_test_helpers::LoadHistoryItem(frame, WebHistoryItem(first_item),
mojom::FetchCacheMode::kValidateCache);
EXPECT_EQ(first_item.Get(),
main_frame_loader.GetDocumentLoader()->GetHistoryItem());
EXPECT_EQ(mojom::FetchCacheMode::kValidateCache,
frame->GetDocumentLoader()->GetRequest().GetCacheMode());
}
class TestCachePolicyWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
TestCachePolicyWebFrameClient()
: cache_mode_(mojom::FetchCacheMode::kDefault),
will_send_request_call_count_(0) {}
~TestCachePolicyWebFrameClient() override = default;
mojom::FetchCacheMode GetCacheMode() const { return cache_mode_; }
int WillSendRequestCallCount() const { return will_send_request_call_count_; }
TestCachePolicyWebFrameClient& ChildClient(size_t i) {
return *child_clients_[i].get();
}
size_t ChildFrameCreationCount() const { return child_clients_.size(); }
// frame_test_helpers::TestWebFrameClient:
WebLocalFrame* CreateChildFrame(
WebLocalFrame* parent,
WebTreeScopeType scope,
const WebString&,
const WebString&,
WebSandboxFlags,
const ParsedFeaturePolicy&,
const WebFrameOwnerProperties& frame_owner_properties,
FrameOwnerElementType) override {
auto child = std::make_unique<TestCachePolicyWebFrameClient>();
auto* child_ptr = child.get();
child_clients_.push_back(std::move(child));
return CreateLocalChild(*parent, scope, child_ptr);
}
void WillSendRequest(WebURLRequest& request) override {
cache_mode_ = request.GetCacheMode();
will_send_request_call_count_++;
}
private:
mojom::FetchCacheMode cache_mode_;
Vector<std::unique_ptr<TestCachePolicyWebFrameClient>> child_clients_;
int will_send_request_call_count_;
};
TEST_F(WebFrameTest, ReloadIframe) {
RegisterMockedHttpURLLoad("iframe_reload.html");
RegisterMockedHttpURLLoad("visible_iframe.html");
TestCachePolicyWebFrameClient main_frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "iframe_reload.html",
&main_frame_client);
WebLocalFrameImpl* main_frame = web_view_helper.LocalMainFrame();
ASSERT_EQ(1U, main_frame_client.ChildFrameCreationCount());
TestCachePolicyWebFrameClient* child_client =
&main_frame_client.ChildClient(0);
WebLocalFrameImpl* child_frame =
ToWebLocalFrameImpl(main_frame->FirstChild());
EXPECT_EQ(child_client, child_frame->Client());
EXPECT_EQ(1u, main_frame->GetFrame()->Tree().ScopedChildCount());
EXPECT_EQ(1, child_client->WillSendRequestCallCount());
EXPECT_EQ(mojom::FetchCacheMode::kDefault, child_client->GetCacheMode());
frame_test_helpers::ReloadFrame(main_frame);
// A new child WebLocalFrame should have been created with a new client.
ASSERT_EQ(2U, main_frame_client.ChildFrameCreationCount());
TestCachePolicyWebFrameClient* new_child_client =
&main_frame_client.ChildClient(1);
WebLocalFrameImpl* new_child_frame =
ToWebLocalFrameImpl(main_frame->FirstChild());
EXPECT_EQ(new_child_client, new_child_frame->Client());
ASSERT_NE(child_client, new_child_client);
ASSERT_NE(child_frame, new_child_frame);
// But there should still only be one subframe.
EXPECT_EQ(1u, main_frame->GetFrame()->Tree().ScopedChildCount());
EXPECT_EQ(1, new_child_client->WillSendRequestCallCount());
// Sub-frames should not be forcibly revalidated.
// TODO(toyoshim): Will consider to revalidate main resources in sub-frames
// on reloads. Or will do only for bypassingCache.
EXPECT_EQ(mojom::FetchCacheMode::kDefault, new_child_client->GetCacheMode());
}
class TestSameDocumentWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
TestSameDocumentWebFrameClient() : frame_load_type_reload_seen_(false) {}
~TestSameDocumentWebFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
void WillSendRequest(WebURLRequest&) override {
FrameLoader& frame_loader =
ToWebLocalFrameImpl(Frame())->GetFrame()->Loader();
if (frame_loader.GetProvisionalDocumentLoader()->LoadType() ==
WebFrameLoadType::kReload)
frame_load_type_reload_seen_ = true;
}
bool FrameLoadTypeReloadSeen() const { return frame_load_type_reload_seen_; }
private:
bool frame_load_type_reload_seen_;
};
TEST_F(WebFrameTest, NavigateToSame) {
RegisterMockedHttpURLLoad("navigate_to_same.html");
TestSameDocumentWebFrameClient client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "navigate_to_same.html",
&client);
EXPECT_FALSE(client.FrameLoadTypeReloadSeen());
FrameLoadRequest frame_request(
nullptr,
ResourceRequest(
ToLocalFrame(web_view_helper.GetWebView()->GetPage()->MainFrame())
->GetDocument()
->Url()));
ToLocalFrame(web_view_helper.GetWebView()->GetPage()->MainFrame())
->Loader()
.StartNavigation(frame_request);
frame_test_helpers::PumpPendingRequestsForFrameToLoad(
web_view_helper.LocalMainFrame());
EXPECT_TRUE(client.FrameLoadTypeReloadSeen());
}
class TestSameDocumentWithImageWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
TestSameDocumentWithImageWebFrameClient() : num_of_image_requests_(0) {}
~TestSameDocumentWithImageWebFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
void WillSendRequest(WebURLRequest& request) override {
if (request.GetRequestContext() == mojom::RequestContextType::IMAGE) {
num_of_image_requests_++;
EXPECT_EQ(mojom::FetchCacheMode::kDefault, request.GetCacheMode());
}
}
int NumOfImageRequests() const { return num_of_image_requests_; }
private:
int num_of_image_requests_;
};
TEST_F(WebFrameTest, NavigateToSameNoConditionalRequestForSubresource) {
RegisterMockedHttpURLLoad("foo_with_image.html");
RegisterMockedHttpURLLoad("white-1x1.png");
TestSameDocumentWithImageWebFrameClient client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "foo_with_image.html", &client,
nullptr,
&ConfigureLoadsImagesAutomatically);
WebCache::Clear();
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "foo_with_image.html");
// 2 images are requested, and each triggers 2 willSendRequest() calls,
// once for preloading and once for the real request.
EXPECT_EQ(client.NumOfImageRequests(), 4);
}
TEST_F(WebFrameTest, WebNodeImageContents) {
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad("about:blank");
WebLocalFrame* frame = web_view_helper.LocalMainFrame();
static const char kBluePNG[] =
"<img "
"src=\"data:image/"
"png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+"
"9AAAAGElEQVQYV2NkYPj/n4EIwDiqEF8oUT94AFIQE/cCn90IAAAAAElFTkSuQmCC\">";
// Load up the image and test that we can extract the contents.
KURL test_url = ToKURL("about:blank");
frame_test_helpers::LoadHTMLString(frame, kBluePNG, test_url);
WebNode node = frame->GetDocument().Body().FirstChild();
EXPECT_TRUE(node.IsElementNode());
WebElement element = node.To<WebElement>();
SkBitmap image = element.ImageContents();
ASSERT_FALSE(image.isNull());
EXPECT_EQ(image.width(), 10);
EXPECT_EQ(image.height(), 10);
EXPECT_EQ(image.getColor(0, 0), SK_ColorBLUE);
}
TEST_F(WebFrameTest, WebNodeImageContentsWithOrientation) {
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad("about:blank");
WebLocalFrame* frame = web_view_helper.LocalMainFrame();
// 4x8 jpg with orientation = 6 ( 90 degree CW rotation ).
// w - white, b - blue.
// raw => oriented
// w w w w b b b b w w w w
// w w w w b b b b w w w w
// w w w w b b b b w w w w
// w w w w b b b b w w w w
// b b b b
// b b b b
// b b b b
// b b b b
static const char kBlueJPGWithOrientation[] =
"<img "
"src=\"data:image/"
"jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/4QBiRXhpZgAATU0AKgAAAAgABQESAAM"
"AAAABAAYAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAAITAAMAAAABAA"
"EAAAAAAAAAAABgAAAAAQAAAGAAAAAB/9sAQwACAQECAQECAgICAgICAgMFAwMDAwMGBAQDB"
"QcGBwcHBgcHCAkLCQgICggHBwoNCgoLDAwMDAcJDg8NDA4LDAwM/9sAQwECAgIDAwMGAwMG"
"DAgHCAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw"
"M/8AAEQgACAAEAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC/"
"/EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJ"
"DNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3"
"eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tf"
"Y2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCA"
"kKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM"
"1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpz"
"dHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytL"
"T1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A7j/iMz/6tv8A/Mgf/e"
"2iiiv9ff8AiVzwx/6Fn/lbEf8Ay0+A/tvG/wA/4L/I/9k=\">";
// Load up the image and test that we can extract the contents.
KURL test_url = ToKURL("about:blank");
frame_test_helpers::LoadHTMLString(frame, kBlueJPGWithOrientation, test_url);
WebNode node = frame->GetDocument().Body().FirstChild();
EXPECT_TRUE(node.IsElementNode());
WebElement element = node.To<WebElement>();
SkBitmap image_with_orientation = element.ImageContents();
ASSERT_FALSE(image_with_orientation.isNull());
EXPECT_EQ(image_with_orientation.width(), 8);
EXPECT_EQ(image_with_orientation.height(), 4);
// Should be almost blue.
SkColor oriented_color = image_with_orientation.getColor(0, 0);
EXPECT_NEAR(SkColorGetR(oriented_color), SkColorGetR(SK_ColorBLUE), 5);
EXPECT_NEAR(SkColorGetG(oriented_color), SkColorGetG(SK_ColorBLUE), 5);
EXPECT_NEAR(SkColorGetB(oriented_color), SkColorGetB(SK_ColorBLUE), 5);
EXPECT_NEAR(SkColorGetA(oriented_color), SkColorGetA(SK_ColorBLUE), 5);
}
class TestStartStopCallbackWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
TestStartStopCallbackWebFrameClient()
: start_loading_count_(0), stop_loading_count_(0) {}
~TestStartStopCallbackWebFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
void DidStartLoading() override {
TestWebFrameClient::DidStartLoading();
start_loading_count_++;
}
void DidStopLoading() override {
TestWebFrameClient::DidStopLoading();
stop_loading_count_++;
}
int StartLoadingCount() const { return start_loading_count_; }
int StopLoadingCount() const { return stop_loading_count_; }
private:
int start_loading_count_;
int stop_loading_count_;
};
TEST_F(WebFrameTest, PushStateStartsAndStops) {
RegisterMockedHttpURLLoad("push_state.html");
TestStartStopCallbackWebFrameClient client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "push_state.html", &client);
EXPECT_EQ(client.StartLoadingCount(), 2);
EXPECT_EQ(client.StopLoadingCount(), 2);
}
class TestDidNavigateCommitTypeWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
TestDidNavigateCommitTypeWebFrameClient()
: last_commit_type_(kWebHistoryInertCommit) {}
~TestDidNavigateCommitTypeWebFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
void DidFinishSameDocumentNavigation(const WebHistoryItem&,
WebHistoryCommitType type,
bool content_initiated) override {
last_commit_type_ = type;
}
WebHistoryCommitType LastCommitType() const { return last_commit_type_; }
private:
WebHistoryCommitType last_commit_type_;
};
TEST_F(WebFrameTest, SameDocumentHistoryNavigationCommitType) {
RegisterMockedHttpURLLoad("push_state.html");
TestDidNavigateCommitTypeWebFrameClient client;
frame_test_helpers::WebViewHelper web_view_helper;
WebViewImpl* web_view_impl =
web_view_helper.InitializeAndLoad(base_url_ + "push_state.html", &client);
Persistent<HistoryItem> item =
ToLocalFrame(web_view_impl->GetPage()->MainFrame())
->Loader()
.GetDocumentLoader()
->GetHistoryItem();
RunPendingTasks();
ToLocalFrame(web_view_impl->GetPage()->MainFrame())
->Loader()
.CommitSameDocumentNavigation(item->Url(), WebFrameLoadType::kBackForward,
item.Get(),
ClientRedirectPolicy::kNotClientRedirect,
nullptr, /* origin_document */
false, /* has_event */
nullptr /* extra_data */);
EXPECT_EQ(kWebBackForwardCommit, client.LastCommitType());
}
class TestHistoryChildWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
TestHistoryChildWebFrameClient() = default;
~TestHistoryChildWebFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
void DidStartProvisionalLoad(WebDocumentLoader* document_loader) override {
replaces_current_history_item_ =
document_loader->ReplacesCurrentHistoryItem();
}
bool ReplacesCurrentHistoryItem() { return replaces_current_history_item_; }
private:
bool replaces_current_history_item_ = false;
};
class TestHistoryWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
TestHistoryWebFrameClient() = default;
~TestHistoryWebFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
WebLocalFrame* CreateChildFrame(WebLocalFrame* parent,
WebTreeScopeType scope,
const WebString& name,
const WebString& fallback_name,
WebSandboxFlags,
const ParsedFeaturePolicy&,
const WebFrameOwnerProperties&,
FrameOwnerElementType) override {
return CreateLocalChild(*parent, scope, &child_client_);
}
TestHistoryChildWebFrameClient& ChildClient() { return child_client_; }
private:
TestHistoryChildWebFrameClient child_client_;
};
// Tests that the first navigation in an initially blank subframe will result in
// a history entry being replaced and not a new one being added.
TEST_F(WebFrameTest, FirstBlankSubframeNavigation) {
RegisterMockedHttpURLLoad("history.html");
RegisterMockedHttpURLLoad("find.html");
TestHistoryWebFrameClient client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad("about:blank", &client);
WebLocalFrame* frame = web_view_helper.LocalMainFrame();
frame->ExecuteScript(WebScriptSource(WebString::FromUTF8(
"document.body.appendChild(document.createElement('iframe'))")));
WebLocalFrameImpl* iframe = ToWebLocalFrameImpl(frame->FirstChild());
ASSERT_EQ(&client.ChildClient(), iframe->Client());
std::string url1 = base_url_ + "history.html";
frame_test_helpers::LoadFrame(iframe, url1);
EXPECT_EQ(url1, iframe->GetDocument().Url().GetString().Utf8());
EXPECT_TRUE(client.ChildClient().ReplacesCurrentHistoryItem());
std::string url2 = base_url_ + "find.html";
frame_test_helpers::LoadFrame(iframe, url2);
EXPECT_EQ(url2, iframe->GetDocument().Url().GetString().Utf8());
EXPECT_FALSE(client.ChildClient().ReplacesCurrentHistoryItem());
}
// Tests that a navigation in a frame with a non-blank initial URL will create
// a new history item, unlike the case above.
TEST_F(WebFrameTest, FirstNonBlankSubframeNavigation) {
RegisterMockedHttpURLLoad("history.html");
RegisterMockedHttpURLLoad("find.html");
TestHistoryWebFrameClient client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad("about:blank", &client);
WebLocalFrame* frame = web_view_helper.LocalMainFrame();
std::string url1 = base_url_ + "history.html";
std::string load_frame_js =
"javascript:var f = document.createElement('iframe'); "
"f.src = '";
load_frame_js += url1 + "';" + "document.body.appendChild(f)";
frame_test_helpers::LoadFrame(frame, load_frame_js);
WebLocalFrame* iframe = frame->FirstChild()->ToWebLocalFrame();
EXPECT_EQ(url1, iframe->GetDocument().Url().GetString().Utf8());
std::string url2 = base_url_ + "find.html";
frame_test_helpers::LoadFrame(iframe, url2);
EXPECT_EQ(url2, iframe->GetDocument().Url().GetString().Utf8());
EXPECT_FALSE(client.ChildClient().ReplacesCurrentHistoryItem());
}
// Test verifies that layout will change a layer's scrollable attibutes
TEST_F(WebFrameTest, overflowHiddenRewrite) {
RegisterMockedHttpURLLoad("non-scrollable.html");
std::unique_ptr<FakeCompositingWebViewClient>
fake_compositing_web_view_client =
std::make_unique<FakeCompositingWebViewClient>();
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(nullptr, fake_compositing_web_view_client.get(),
&ConfigureCompositingWebView);
web_view_helper.Resize(WebSize(100, 100));
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "non-scrollable.html");
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
PaintLayerCompositor* compositor = web_view_helper.GetWebView()->Compositor();
GraphicsLayer* scroll_layer = compositor->ScrollLayer();
ASSERT_TRUE(scroll_layer);
cc::Layer* cc_scroll_layer = scroll_layer->CcLayer();
// Verify that the cc::Layer is not scrollable initially.
ASSERT_FALSE(cc_scroll_layer->GetUserScrollableHorizontal());
ASSERT_FALSE(cc_scroll_layer->GetUserScrollableVertical());
// Call javascript to make the layer scrollable, and verify it.
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
frame->ExecuteScript(WebScriptSource("allowScroll();"));
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
scroll_layer = compositor->ScrollLayer();
cc_scroll_layer = scroll_layer->CcLayer();
ASSERT_TRUE(cc_scroll_layer->GetUserScrollableHorizontal());
ASSERT_TRUE(cc_scroll_layer->GetUserScrollableVertical());
}
// Test that currentHistoryItem reflects the current page, not the provisional
// load.
TEST_F(WebFrameTest, CurrentHistoryItem) {
RegisterMockedHttpURLLoad("fixed_layout.html");
std::string url = base_url_ + "fixed_layout.html";
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize();
WebLocalFrame* frame = web_view_helper.GetWebView()->MainFrameImpl();
const FrameLoader& main_frame_loader =
web_view_helper.LocalMainFrame()->GetFrame()->Loader();
WebURLRequest request(ToKURL(url));
frame->StartNavigation(request);
// Before commit, there is no history item.
EXPECT_FALSE(main_frame_loader.GetDocumentLoader()->GetHistoryItem());
frame_test_helpers::PumpPendingRequestsForFrameToLoad(
web_view_helper.LocalMainFrame());
// After commit, there is.
HistoryItem* item = main_frame_loader.GetDocumentLoader()->GetHistoryItem();
ASSERT_TRUE(item);
EXPECT_EQ(WTF::String(url.data()), item->UrlString());
}
class FailCreateChildFrame : public frame_test_helpers::TestWebFrameClient {
public:
FailCreateChildFrame() : call_count_(0) {}
~FailCreateChildFrame() override = default;
// frame_test_helpers::TestWebFrameClient:
WebLocalFrame* CreateChildFrame(
WebLocalFrame* parent,
WebTreeScopeType scope,
const WebString& name,
const WebString& fallback_name,
WebSandboxFlags sandbox_flags,
const ParsedFeaturePolicy& container_policy,
const WebFrameOwnerProperties& frame_owner_properties,
FrameOwnerElementType) override {
++call_count_;
return nullptr;
}
int CallCount() const { return call_count_; }
private:
int call_count_;
};
// Test that we don't crash if WebLocalFrameClient::createChildFrame() fails.
TEST_F(WebFrameTest, CreateChildFrameFailure) {
RegisterMockedHttpURLLoad("create_child_frame_fail.html");
FailCreateChildFrame client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "create_child_frame_fail.html",
&client);
EXPECT_EQ(1, client.CallCount());
}
TEST_F(WebFrameTest, fixedPositionInFixedViewport) {
RegisterMockedHttpURLLoad("fixed-position-in-fixed-viewport.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "fixed-position-in-fixed-viewport.html", nullptr, nullptr,
ConfigureAndroid);
WebViewImpl* web_view = web_view_helper.GetWebView();
web_view_helper.Resize(WebSize(100, 100));
Document* document = web_view->MainFrameImpl()->GetFrame()->GetDocument();
Element* bottom_fixed = document->getElementById("bottom-fixed");
Element* top_bottom_fixed = document->getElementById("top-bottom-fixed");
Element* right_fixed = document->getElementById("right-fixed");
Element* left_right_fixed = document->getElementById("left-right-fixed");
// The layout viewport will hit the min-scale limit of 0.25, so it'll be
// 400x800.
web_view_helper.Resize(WebSize(100, 200));
EXPECT_EQ(800, bottom_fixed->OffsetTop() + bottom_fixed->OffsetHeight());
EXPECT_EQ(800, top_bottom_fixed->OffsetHeight());
// Now the layout viewport hits the content width limit of 500px so it'll be
// 500x500.
web_view_helper.Resize(WebSize(200, 200));
EXPECT_EQ(500, right_fixed->OffsetLeft() + right_fixed->OffsetWidth());
EXPECT_EQ(500, left_right_fixed->OffsetWidth());
}
TEST_F(WebFrameTest, FrameViewMoveWithSetFrameRect) {
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad("about:blank");
web_view_helper.Resize(WebSize(200, 200));
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
LocalFrameView* frame_view = web_view_helper.LocalMainFrame()->GetFrameView();
EXPECT_EQ(IntRect(0, 0, 200, 200), frame_view->FrameRect());
frame_view->SetFrameRect(IntRect(100, 100, 200, 200));
EXPECT_EQ(IntRect(100, 100, 200, 200), frame_view->FrameRect());
}
TEST_F(WebFrameTest, FrameViewScrollAccountsForBrowserControls) {
FakeCompositingWebViewClient client;
RegisterMockedHttpURLLoad("long_scroll.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "long_scroll.html", nullptr,
&client, ConfigureAndroid);
WebViewImpl* web_view = web_view_helper.GetWebView();
LocalFrameView* frame_view = web_view_helper.LocalMainFrame()->GetFrameView();
float browser_controls_height = 40;
web_view->ResizeWithBrowserControls(WebSize(100, 100),
browser_controls_height, 0, false);
web_view->SetPageScaleFactor(2.0f);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
web_view->MainFrameImpl()->SetScrollOffset(WebSize(0, 2000));
EXPECT_EQ(ScrollOffset(0, 1900),
frame_view->LayoutViewport()->GetScrollOffset());
// Simulate the browser controls showing by 20px, thus shrinking the viewport
// and allowing it to scroll an additional 20px.
web_view->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 1.0f,
20.0f / browser_controls_height, cc::BrowserControlsState::kBoth});
EXPECT_EQ(ScrollOffset(0, 1920),
frame_view->LayoutViewport()->MaximumScrollOffset());
// Show more, make sure the scroll actually gets clamped.
web_view->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 1.0f,
20.0f / browser_controls_height, cc::BrowserControlsState::kBoth});
web_view->MainFrameImpl()->SetScrollOffset(WebSize(0, 2000));
EXPECT_EQ(ScrollOffset(0, 1940),
frame_view->LayoutViewport()->GetScrollOffset());
// Hide until there's 10px showing.
web_view->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 1.0f,
-30.0f / browser_controls_height, cc::BrowserControlsState::kBoth});
EXPECT_EQ(ScrollOffset(0, 1910),
frame_view->LayoutViewport()->MaximumScrollOffset());
// Simulate a LayoutEmbeddedContent::resize. The frame is resized to
// accomodate the browser controls and Blink's view of the browser controls
// matches that of the CC
web_view->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 1.0f,
30.0f / browser_controls_height, cc::BrowserControlsState::kBoth});
web_view->ResizeWithBrowserControls(WebSize(100, 60), 40.0f, 0, true);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
EXPECT_EQ(ScrollOffset(0, 1940),
frame_view->LayoutViewport()->MaximumScrollOffset());
// Now simulate hiding.
web_view->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 1.0f,
-10.0f / browser_controls_height, cc::BrowserControlsState::kBoth});
EXPECT_EQ(ScrollOffset(0, 1930),
frame_view->LayoutViewport()->MaximumScrollOffset());
// Reset to original state: 100px widget height, browser controls fully
// hidden.
web_view->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 1.0f,
-30.0f / browser_controls_height, cc::BrowserControlsState::kBoth});
web_view->ResizeWithBrowserControls(WebSize(100, 100),
browser_controls_height, 0, false);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
EXPECT_EQ(ScrollOffset(0, 1900),
frame_view->LayoutViewport()->MaximumScrollOffset());
// Show the browser controls by just 1px, since we're zoomed in to 2X, that
// should allow an extra 0.5px of scrolling in the visual viewport. Make
// sure we're not losing any pixels when applying the adjustment on the
// main frame.
web_view->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 1.0f,
1.0f / browser_controls_height, cc::BrowserControlsState::kBoth});
EXPECT_EQ(ScrollOffset(0, 1901),
frame_view->LayoutViewport()->MaximumScrollOffset());
web_view->MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 1.0f,
2.0f / browser_controls_height, cc::BrowserControlsState::kBoth});
EXPECT_EQ(ScrollOffset(0, 1903),
frame_view->LayoutViewport()->MaximumScrollOffset());
}
TEST_F(WebFrameTest, MaximumScrollPositionCanBeNegative) {
RegisterMockedHttpURLLoad("rtl-overview-mode.html");
FixedLayoutTestWebViewClient client;
client.screen_info_.device_scale_factor = 1;
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "rtl-overview-mode.html",
nullptr, &client, ConfigureAndroid);
web_view_helper.GetWebView()->SetInitialPageScaleOverride(-1);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetLoadWithOverviewMode(true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(true);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
LocalFrameView* frame_view = web_view_helper.LocalMainFrame()->GetFrameView();
ScrollableArea* layout_viewport = frame_view->LayoutViewport();
EXPECT_LT(layout_viewport->MaximumScrollOffset().Width(), 0);
}
TEST_F(WebFrameTest, FullscreenLayerSize) {
FakeCompositingWebViewClient client;
RegisterMockedHttpURLLoad("fullscreen_div.html");
frame_test_helpers::WebViewHelper web_view_helper;
int viewport_width = 640;
int viewport_height = 480;
client.screen_info_.rect.width = viewport_width;
client.screen_info_.rect.height = viewport_height;
WebViewImpl* web_view_impl = web_view_helper.InitializeAndLoad(
base_url_ + "fullscreen_div.html", nullptr, &client, ConfigureAndroid);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
UpdateAllLifecyclePhases(web_view_impl);
LocalFrame* frame = web_view_impl->MainFrameImpl()->GetFrame();
Document* document = frame->GetDocument();
std::unique_ptr<UserGestureIndicator> gesture =
LocalFrame::NotifyUserActivation(frame);
Element* div_fullscreen = document->getElementById("div1");
Fullscreen::RequestFullscreen(*div_fullscreen);
EXPECT_EQ(nullptr, Fullscreen::FullscreenElementFrom(*document));
web_view_impl->MainFrameWidget()->DidEnterFullscreen();
EXPECT_EQ(div_fullscreen, Fullscreen::FullscreenElementFrom(*document));
UpdateAllLifecyclePhases(web_view_impl);
EXPECT_EQ(div_fullscreen, Fullscreen::FullscreenElementFrom(*document));
// Verify that the element is sized to the viewport.
LayoutBox* fullscreen_layout_object =
ToLayoutBox(div_fullscreen->GetLayoutObject());
EXPECT_EQ(viewport_width, fullscreen_layout_object->LogicalWidth().ToInt());
EXPECT_EQ(viewport_height, fullscreen_layout_object->LogicalHeight().ToInt());
// Verify it's updated after a device rotation.
client.screen_info_.rect.width = viewport_height;
client.screen_info_.rect.height = viewport_width;
web_view_helper.Resize(WebSize(viewport_height, viewport_width));
UpdateAllLifecyclePhases(web_view_impl);
EXPECT_EQ(viewport_height, fullscreen_layout_object->LogicalWidth().ToInt());
EXPECT_EQ(viewport_width, fullscreen_layout_object->LogicalHeight().ToInt());
}
TEST_F(WebFrameTest, FullscreenLayerNonScrollable) {
FakeCompositingWebViewClient client;
RegisterMockedHttpURLLoad("fullscreen_div.html");
frame_test_helpers::WebViewHelper web_view_helper;
int viewport_width = 640;
int viewport_height = 480;
WebViewImpl* web_view_impl = web_view_helper.InitializeAndLoad(
base_url_ + "fullscreen_div.html", nullptr, &client, ConfigureAndroid);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
UpdateAllLifecyclePhases(web_view_impl);
LocalFrame* frame = web_view_impl->MainFrameImpl()->GetFrame();
Document* document = frame->GetDocument();
std::unique_ptr<UserGestureIndicator> gesture =
LocalFrame::NotifyUserActivation(frame);
Element* div_fullscreen = document->getElementById("div1");
Fullscreen::RequestFullscreen(*div_fullscreen);
EXPECT_EQ(nullptr, Fullscreen::FullscreenElementFrom(*document));
web_view_impl->MainFrameWidget()->DidEnterFullscreen();
EXPECT_EQ(div_fullscreen, Fullscreen::FullscreenElementFrom(*document));
UpdateAllLifecyclePhases(web_view_impl);
EXPECT_EQ(div_fullscreen, Fullscreen::FullscreenElementFrom(*document));
// Verify that the viewports are nonscrollable.
LocalFrameView* frame_view = web_view_helper.LocalMainFrame()->GetFrameView();
GraphicsLayer* layout_viewport_scroll_layer =
web_view_impl->Compositor()->ScrollLayer();
GraphicsLayer* visual_viewport_scroll_layer =
frame_view->GetPage()->GetVisualViewport().ScrollLayer();
ASSERT_FALSE(
layout_viewport_scroll_layer->CcLayer()->GetUserScrollableHorizontal());
ASSERT_FALSE(
layout_viewport_scroll_layer->CcLayer()->GetUserScrollableVertical());
ASSERT_FALSE(
visual_viewport_scroll_layer->CcLayer()->GetUserScrollableHorizontal());
ASSERT_FALSE(
visual_viewport_scroll_layer->CcLayer()->GetUserScrollableVertical());
// Verify that the viewports are scrollable upon exiting fullscreen.
EXPECT_EQ(div_fullscreen, Fullscreen::FullscreenElementFrom(*document));
web_view_impl->MainFrameWidget()->DidExitFullscreen();
EXPECT_EQ(nullptr, Fullscreen::FullscreenElementFrom(*document));
UpdateAllLifecyclePhases(web_view_impl);
EXPECT_EQ(nullptr, Fullscreen::FullscreenElementFrom(*document));
layout_viewport_scroll_layer = web_view_impl->Compositor()->ScrollLayer();
visual_viewport_scroll_layer =
frame_view->GetPage()->GetVisualViewport().ScrollLayer();
ASSERT_TRUE(
layout_viewport_scroll_layer->CcLayer()->GetUserScrollableHorizontal());
ASSERT_TRUE(
layout_viewport_scroll_layer->CcLayer()->GetUserScrollableVertical());
ASSERT_TRUE(
visual_viewport_scroll_layer->CcLayer()->GetUserScrollableHorizontal());
ASSERT_TRUE(
visual_viewport_scroll_layer->CcLayer()->GetUserScrollableVertical());
}
TEST_F(WebFrameTest, FullscreenMainFrame) {
FakeCompositingWebViewClient client;
RegisterMockedHttpURLLoad("fullscreen_div.html");
frame_test_helpers::WebViewHelper web_view_helper;
int viewport_width = 640;
int viewport_height = 480;
WebViewImpl* web_view_impl = web_view_helper.InitializeAndLoad(
base_url_ + "fullscreen_div.html", nullptr, &client, ConfigureAndroid);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
UpdateAllLifecyclePhases(web_view_impl);
cc::Layer* cc_scroll_layer = web_view_impl->MainFrameImpl()
->GetFrame()
->View()
->LayoutViewport()
->LayerForScrolling()
->CcLayer();
ASSERT_TRUE(cc_scroll_layer->scrollable());
ASSERT_TRUE(cc_scroll_layer->GetUserScrollableHorizontal());
ASSERT_TRUE(cc_scroll_layer->GetUserScrollableVertical());
LocalFrame* frame = web_view_impl->MainFrameImpl()->GetFrame();
Document* document = frame->GetDocument();
std::unique_ptr<UserGestureIndicator> gesture =
LocalFrame::NotifyUserActivation(frame);
Fullscreen::RequestFullscreen(*document->documentElement());
EXPECT_EQ(nullptr, Fullscreen::FullscreenElementFrom(*document));
web_view_impl->MainFrameWidget()->DidEnterFullscreen();
EXPECT_EQ(document->documentElement(),
Fullscreen::FullscreenElementFrom(*document));
UpdateAllLifecyclePhases(web_view_impl);
EXPECT_EQ(document->documentElement(),
Fullscreen::FullscreenElementFrom(*document));
// Verify that the main frame is still scrollable.
cc_scroll_layer = web_view_impl->MainFrameImpl()
->GetFrame()
->View()
->LayoutViewport()
->LayerForScrolling()
->CcLayer();
ASSERT_TRUE(cc_scroll_layer->scrollable());
ASSERT_TRUE(cc_scroll_layer->GetUserScrollableHorizontal());
ASSERT_TRUE(cc_scroll_layer->GetUserScrollableVertical());
// Verify the main frame still behaves correctly after a resize.
web_view_helper.Resize(WebSize(viewport_height, viewport_width));
ASSERT_TRUE(cc_scroll_layer->scrollable());
ASSERT_TRUE(cc_scroll_layer->GetUserScrollableHorizontal());
ASSERT_TRUE(cc_scroll_layer->GetUserScrollableVertical());
}
TEST_F(WebFrameTest, FullscreenSubframe) {
FakeCompositingWebViewClient client;
RegisterMockedHttpURLLoad("fullscreen_iframe.html");
RegisterMockedHttpURLLoad("fullscreen_div.html");
frame_test_helpers::WebViewHelper web_view_helper;
WebViewImpl* web_view_impl = web_view_helper.InitializeAndLoad(
base_url_ + "fullscreen_iframe.html", nullptr, &client, ConfigureAndroid);
int viewport_width = 640;
int viewport_height = 480;
client.screen_info_.rect.width = viewport_width;
client.screen_info_.rect.height = viewport_height;
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
UpdateAllLifecyclePhases(web_view_impl);
LocalFrame* frame =
ToWebLocalFrameImpl(
web_view_helper.GetWebView()->MainFrame()->FirstChild())
->GetFrame();
Document* document = frame->GetDocument();
std::unique_ptr<UserGestureIndicator> gesture =
LocalFrame::NotifyUserActivation(frame);
Element* div_fullscreen = document->getElementById("div1");
Fullscreen::RequestFullscreen(*div_fullscreen);
web_view_impl->MainFrameWidget()->DidEnterFullscreen();
UpdateAllLifecyclePhases(web_view_impl);
// Verify that the element is sized to the viewport.
LayoutBox* fullscreen_layout_object =
ToLayoutBox(div_fullscreen->GetLayoutObject());
EXPECT_EQ(viewport_width, fullscreen_layout_object->LogicalWidth().ToInt());
EXPECT_EQ(viewport_height, fullscreen_layout_object->LogicalHeight().ToInt());
// Verify it's updated after a device rotation.
client.screen_info_.rect.width = viewport_height;
client.screen_info_.rect.height = viewport_width;
web_view_helper.Resize(WebSize(viewport_height, viewport_width));
UpdateAllLifecyclePhases(web_view_impl);
EXPECT_EQ(viewport_height, fullscreen_layout_object->LogicalWidth().ToInt());
EXPECT_EQ(viewport_width, fullscreen_layout_object->LogicalHeight().ToInt());
}
// Tests entering nested fullscreen and then exiting via the same code path
// that's used when the browser process exits fullscreen.
TEST_F(WebFrameTest, FullscreenNestedExit) {
RegisterMockedHttpURLLoad("fullscreen_iframe.html");
RegisterMockedHttpURLLoad("fullscreen_div.html");
frame_test_helpers::WebViewHelper web_view_helper;
WebViewImpl* web_view_impl =
web_view_helper.InitializeAndLoad(base_url_ + "fullscreen_iframe.html");
UpdateAllLifecyclePhases(web_view_impl);
Document* top_doc = web_view_impl->MainFrameImpl()->GetFrame()->GetDocument();
Element* top_body = top_doc->body();
HTMLIFrameElement* iframe =
ToHTMLIFrameElement(top_doc->QuerySelector("iframe"));
Document* iframe_doc = iframe->contentDocument();
Element* iframe_body = iframe_doc->body();
{
std::unique_ptr<UserGestureIndicator> gesture =
LocalFrame::NotifyUserActivation(top_doc->GetFrame());
Fullscreen::RequestFullscreen(*top_body);
}
web_view_impl->MainFrameWidget()->DidEnterFullscreen();
UpdateAllLifecyclePhases(web_view_impl);
{
std::unique_ptr<UserGestureIndicator> gesture =
LocalFrame::NotifyUserActivation(iframe_doc->GetFrame());
Fullscreen::RequestFullscreen(*iframe_body);
}
web_view_impl->MainFrameWidget()->DidEnterFullscreen();
Microtask::PerformCheckpoint(V8PerIsolateData::MainThreadIsolate());
UpdateAllLifecyclePhases(web_view_impl);
// We are now in nested fullscreen, with both documents having a non-empty
// fullscreen element stack.
EXPECT_EQ(iframe, Fullscreen::FullscreenElementFrom(*top_doc));
EXPECT_EQ(iframe_body, Fullscreen::FullscreenElementFrom(*iframe_doc));
web_view_impl->MainFrameWidget()->DidExitFullscreen();
UpdateAllLifecyclePhases(web_view_impl);
// We should now have fully exited fullscreen.
EXPECT_EQ(nullptr, Fullscreen::FullscreenElementFrom(*top_doc));
EXPECT_EQ(nullptr, Fullscreen::FullscreenElementFrom(*iframe_doc));
}
TEST_F(WebFrameTest, FullscreenWithTinyViewport) {
FakeCompositingWebViewClient client;
RegisterMockedHttpURLLoad("viewport-tiny.html");
frame_test_helpers::WebViewHelper web_view_helper;
WebViewImpl* web_view_impl = web_view_helper.InitializeAndLoad(
base_url_ + "viewport-tiny.html", nullptr, &client, ConfigureAndroid);
int viewport_width = 384;
int viewport_height = 640;
client.screen_info_.rect.width = viewport_width;
client.screen_info_.rect.height = viewport_height;
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
UpdateAllLifecyclePhases(web_view_impl);
auto* layout_view = web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutView();
EXPECT_EQ(320, layout_view->LogicalWidth().Floor());
EXPECT_EQ(533, layout_view->LogicalHeight().Floor());
EXPECT_FLOAT_EQ(1.2, web_view_impl->PageScaleFactor());
EXPECT_FLOAT_EQ(1.2, web_view_impl->MinimumPageScaleFactor());
EXPECT_FLOAT_EQ(5.0, web_view_impl->MaximumPageScaleFactor());
LocalFrame* frame = web_view_impl->MainFrameImpl()->GetFrame();
std::unique_ptr<UserGestureIndicator> gesture =
LocalFrame::NotifyUserActivation(frame);
Fullscreen::RequestFullscreen(*frame->GetDocument()->documentElement());
web_view_impl->MainFrameWidget()->DidEnterFullscreen();
UpdateAllLifecyclePhases(web_view_impl);
EXPECT_EQ(384, layout_view->LogicalWidth().Floor());
EXPECT_EQ(640, layout_view->LogicalHeight().Floor());
EXPECT_FLOAT_EQ(1.0, web_view_impl->PageScaleFactor());
EXPECT_FLOAT_EQ(1.0, web_view_impl->MinimumPageScaleFactor());
EXPECT_FLOAT_EQ(1.0, web_view_impl->MaximumPageScaleFactor());
web_view_impl->MainFrameWidget()->DidExitFullscreen();
UpdateAllLifecyclePhases(web_view_impl);
EXPECT_EQ(320, layout_view->LogicalWidth().Floor());
EXPECT_EQ(533, layout_view->LogicalHeight().Floor());
EXPECT_FLOAT_EQ(1.2, web_view_impl->PageScaleFactor());
EXPECT_FLOAT_EQ(1.2, web_view_impl->MinimumPageScaleFactor());
EXPECT_FLOAT_EQ(5.0, web_view_impl->MaximumPageScaleFactor());
}
TEST_F(WebFrameTest, FullscreenResizeWithTinyViewport) {
FakeCompositingWebViewClient client;
RegisterMockedHttpURLLoad("viewport-tiny.html");
frame_test_helpers::WebViewHelper web_view_helper;
WebViewImpl* web_view_impl = web_view_helper.InitializeAndLoad(
base_url_ + "viewport-tiny.html", nullptr, &client, ConfigureAndroid);
int viewport_width = 384;
int viewport_height = 640;
client.screen_info_.rect.width = viewport_width;
client.screen_info_.rect.height = viewport_height;
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
UpdateAllLifecyclePhases(web_view_impl);
auto* layout_view = web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutView();
LocalFrame* frame = web_view_impl->MainFrameImpl()->GetFrame();
std::unique_ptr<UserGestureIndicator> gesture =
LocalFrame::NotifyUserActivation(frame);
Fullscreen::RequestFullscreen(*frame->GetDocument()->documentElement());
web_view_impl->MainFrameWidget()->DidEnterFullscreen();
UpdateAllLifecyclePhases(web_view_impl);
EXPECT_EQ(384, layout_view->LogicalWidth().Floor());
EXPECT_EQ(640, layout_view->LogicalHeight().Floor());
EXPECT_FLOAT_EQ(1.0, web_view_impl->PageScaleFactor());
EXPECT_FLOAT_EQ(1.0, web_view_impl->MinimumPageScaleFactor());
EXPECT_FLOAT_EQ(1.0, web_view_impl->MaximumPageScaleFactor());
viewport_width = 640;
viewport_height = 384;
client.screen_info_.rect.width = viewport_width;
client.screen_info_.rect.height = viewport_height;
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
UpdateAllLifecyclePhases(web_view_impl);
EXPECT_EQ(640, layout_view->LogicalWidth().Floor());
EXPECT_EQ(384, layout_view->LogicalHeight().Floor());
EXPECT_FLOAT_EQ(1.0, web_view_impl->PageScaleFactor());
EXPECT_FLOAT_EQ(1.0, web_view_impl->MinimumPageScaleFactor());
EXPECT_FLOAT_EQ(1.0, web_view_impl->MaximumPageScaleFactor());
web_view_impl->MainFrameWidget()->DidExitFullscreen();
UpdateAllLifecyclePhases(web_view_impl);
EXPECT_EQ(320, layout_view->LogicalWidth().Floor());
EXPECT_EQ(192, layout_view->LogicalHeight().Floor());
EXPECT_FLOAT_EQ(2, web_view_impl->PageScaleFactor());
EXPECT_FLOAT_EQ(2, web_view_impl->MinimumPageScaleFactor());
EXPECT_FLOAT_EQ(5.0, web_view_impl->MaximumPageScaleFactor());
}
TEST_F(WebFrameTest, FullscreenRestoreScaleFactorUponExiting) {
// The purpose of this test is to more precisely simulate the sequence of
// resize and switching fullscreen state operations on WebView, with the
// interference from Android status bars like a real device does.
// This verifies we handle the transition and restore states correctly.
WebSize screen_size_minus_status_bars_minus_url_bar(598, 303);
WebSize screen_size_minus_status_bars(598, 359);
WebSize screen_size(640, 384);
FakeCompositingWebViewClient client;
RegisterMockedHttpURLLoad("fullscreen_restore_scale_factor.html");
frame_test_helpers::WebViewHelper web_view_helper;
WebViewImpl* web_view_impl = web_view_helper.InitializeAndLoad(
base_url_ + "fullscreen_restore_scale_factor.html", nullptr, &client,
&ConfigureAndroid);
client.screen_info_.rect.width =
screen_size_minus_status_bars_minus_url_bar.width;
client.screen_info_.rect.height =
screen_size_minus_status_bars_minus_url_bar.height;
web_view_helper.Resize(screen_size_minus_status_bars_minus_url_bar);
auto* layout_view = web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutView();
EXPECT_EQ(screen_size_minus_status_bars_minus_url_bar.width,
layout_view->LogicalWidth().Floor());
EXPECT_EQ(screen_size_minus_status_bars_minus_url_bar.height,
layout_view->LogicalHeight().Floor());
EXPECT_FLOAT_EQ(1.0, web_view_impl->PageScaleFactor());
EXPECT_FLOAT_EQ(1.0, web_view_impl->MinimumPageScaleFactor());
EXPECT_FLOAT_EQ(5.0, web_view_impl->MaximumPageScaleFactor());
{
LocalFrame* frame = web_view_impl->MainFrameImpl()->GetFrame();
std::unique_ptr<UserGestureIndicator> gesture =
LocalFrame::NotifyUserActivation(frame);
Fullscreen::RequestFullscreen(*frame->GetDocument()->body());
}
web_view_impl->MainFrameWidget()->DidEnterFullscreen();
UpdateAllLifecyclePhases(web_view_impl);
client.screen_info_.rect.width = screen_size_minus_status_bars.width;
client.screen_info_.rect.height = screen_size_minus_status_bars.height;
web_view_helper.Resize(screen_size_minus_status_bars);
client.screen_info_.rect.width = screen_size.width;
client.screen_info_.rect.height = screen_size.height;
web_view_helper.Resize(screen_size);
EXPECT_EQ(screen_size.width, layout_view->LogicalWidth().Floor());
EXPECT_EQ(screen_size.height, layout_view->LogicalHeight().Floor());
EXPECT_FLOAT_EQ(1.0, web_view_impl->PageScaleFactor());
EXPECT_FLOAT_EQ(1.0, web_view_impl->MinimumPageScaleFactor());
EXPECT_FLOAT_EQ(1.0, web_view_impl->MaximumPageScaleFactor());
web_view_impl->MainFrameWidget()->DidExitFullscreen();
UpdateAllLifecyclePhases(web_view_impl);
client.screen_info_.rect.width = screen_size_minus_status_bars.width;
client.screen_info_.rect.height = screen_size_minus_status_bars.height;
web_view_helper.Resize(screen_size_minus_status_bars);
client.screen_info_.rect.width =
screen_size_minus_status_bars_minus_url_bar.width;
client.screen_info_.rect.height =
screen_size_minus_status_bars_minus_url_bar.height;
web_view_helper.Resize(screen_size_minus_status_bars_minus_url_bar);
EXPECT_EQ(screen_size_minus_status_bars_minus_url_bar.width,
layout_view->LogicalWidth().Floor());
EXPECT_EQ(screen_size_minus_status_bars_minus_url_bar.height,
layout_view->LogicalHeight().Floor());
EXPECT_FLOAT_EQ(1.0, web_view_impl->PageScaleFactor());
EXPECT_FLOAT_EQ(1.0, web_view_impl->MinimumPageScaleFactor());
EXPECT_FLOAT_EQ(5.0, web_view_impl->MaximumPageScaleFactor());
}
// Tests that leaving fullscreen by navigating to a new page resets the
// fullscreen page scale constraints.
TEST_F(WebFrameTest, ClearFullscreenConstraintsOnNavigation) {
RegisterMockedHttpURLLoad("viewport-tiny.html");
frame_test_helpers::WebViewHelper web_view_helper;
int viewport_width = 100;
int viewport_height = 200;
WebViewImpl* web_view_impl = web_view_helper.InitializeAndLoad(
base_url_ + "viewport-tiny.html", nullptr, nullptr, ConfigureAndroid);
web_view_helper.Resize(WebSize(viewport_width, viewport_height));
UpdateAllLifecyclePhases(web_view_impl);
// viewport-tiny.html specifies a 320px layout width.
auto* layout_view =
web_view_impl->MainFrameImpl()->GetFrameView()->GetLayoutView();
EXPECT_EQ(320, layout_view->LogicalWidth().Floor());
EXPECT_EQ(640, layout_view->LogicalHeight().Floor());
EXPECT_FLOAT_EQ(0.3125, web_view_impl->PageScaleFactor());
EXPECT_FLOAT_EQ(0.3125, web_view_impl->MinimumPageScaleFactor());
EXPECT_FLOAT_EQ(5.0, web_view_impl->MaximumPageScaleFactor());
LocalFrame* frame = web_view_impl->MainFrameImpl()->GetFrame();
std::unique_ptr<UserGestureIndicator> gesture =
LocalFrame::NotifyUserActivation(frame, UserGestureToken::kNewGesture);
Fullscreen::RequestFullscreen(*frame->GetDocument()->documentElement());
web_view_impl->MainFrameWidget()->DidEnterFullscreen();
UpdateAllLifecyclePhases(web_view_impl);
// Entering fullscreen causes layout size and page scale limits to be
// overridden.
EXPECT_EQ(100, layout_view->LogicalWidth().Floor());
EXPECT_EQ(200, layout_view->LogicalHeight().Floor());
EXPECT_FLOAT_EQ(1.0, web_view_impl->PageScaleFactor());
EXPECT_FLOAT_EQ(1.0, web_view_impl->MinimumPageScaleFactor());
EXPECT_FLOAT_EQ(1.0, web_view_impl->MaximumPageScaleFactor());
const char kSource[] = "<meta name=\"viewport\" content=\"width=200\">";
// Load a new page before exiting fullscreen.
KURL test_url = ToKURL("about:blank");
WebLocalFrame* web_frame = web_view_helper.LocalMainFrame();
frame_test_helpers::LoadHTMLString(web_frame, kSource, test_url);
web_view_impl->MainFrameWidget()->DidExitFullscreen();
UpdateAllLifecyclePhases(web_view_impl);
// Make sure the new page's layout size and scale factor limits aren't
// overridden.
layout_view = web_view_impl->MainFrameImpl()->GetFrameView()->GetLayoutView();
EXPECT_EQ(200, layout_view->LogicalWidth().Floor());
EXPECT_EQ(400, layout_view->LogicalHeight().Floor());
EXPECT_FLOAT_EQ(0.5, web_view_impl->MinimumPageScaleFactor());
EXPECT_FLOAT_EQ(5.0, web_view_impl->MaximumPageScaleFactor());
}
TEST_F(WebFrameTest, OverlayFullscreenVideo) {
ScopedForceOverlayFullscreenVideoForTest force_overlay_fullscreen_video(true);
RegisterMockedHttpURLLoad("fullscreen_video.html");
frame_test_helpers::TestWebViewClient web_view_client;
frame_test_helpers::WebViewHelper web_view_helper;
WebViewImpl* web_view_impl = web_view_helper.InitializeAndLoad(
base_url_ + "fullscreen_video.html", nullptr, &web_view_client);
const cc::LayerTreeHost* layer_tree_host =
web_view_client.layer_tree_view()->layer_tree_host();
LocalFrame* frame = web_view_impl->MainFrameImpl()->GetFrame();
std::unique_ptr<UserGestureIndicator> gesture =
LocalFrame::NotifyUserActivation(frame);
HTMLVideoElement* video =
ToHTMLVideoElement(frame->GetDocument()->getElementById("video"));
EXPECT_TRUE(video->UsesOverlayFullscreenVideo());
EXPECT_FALSE(video->IsFullscreen());
EXPECT_EQ(SkColorGetA(layer_tree_host->background_color()), SK_AlphaOPAQUE);
video->webkitEnterFullscreen();
web_view_impl->MainFrameWidget()->DidEnterFullscreen();
UpdateAllLifecyclePhases(web_view_impl);
EXPECT_TRUE(video->IsFullscreen());
EXPECT_LT(SkColorGetA(layer_tree_host->background_color()), SK_AlphaOPAQUE);
web_view_impl->MainFrameWidget()->DidExitFullscreen();
UpdateAllLifecyclePhases(web_view_impl);
EXPECT_FALSE(video->IsFullscreen());
EXPECT_EQ(SkColorGetA(layer_tree_host->background_color()), SK_AlphaOPAQUE);
}
TEST_F(WebFrameTest, LayoutBlockPercentHeightDescendants) {
RegisterMockedHttpURLLoad("percent-height-descendants.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ +
"percent-height-descendants.html");
WebViewImpl* web_view = web_view_helper.GetWebView();
web_view_helper.Resize(WebSize(800, 800));
UpdateAllLifecyclePhases(web_view);
Document* document = web_view->MainFrameImpl()->GetFrame()->GetDocument();
LayoutBlock* container =
ToLayoutBlock(document->getElementById("container")->GetLayoutObject());
LayoutBox* percent_height_in_anonymous =
ToLayoutBox(document->getElementById("percent-height-in-anonymous")
->GetLayoutObject());
LayoutBox* percent_height_direct_child =
ToLayoutBox(document->getElementById("percent-height-direct-child")
->GetLayoutObject());
EXPECT_TRUE(
container->HasPercentHeightDescendant(percent_height_in_anonymous));
EXPECT_TRUE(
container->HasPercentHeightDescendant(percent_height_direct_child));
ASSERT_TRUE(container->PercentHeightDescendants());
ASSERT_TRUE(container->HasPercentHeightDescendants());
EXPECT_EQ(2U, container->PercentHeightDescendants()->size());
EXPECT_TRUE(container->PercentHeightDescendants()->Contains(
percent_height_in_anonymous));
EXPECT_TRUE(container->PercentHeightDescendants()->Contains(
percent_height_direct_child));
LayoutBlock* anonymous_block = percent_height_in_anonymous->ContainingBlock();
EXPECT_TRUE(anonymous_block->IsAnonymous());
EXPECT_FALSE(anonymous_block->HasPercentHeightDescendants());
}
TEST_F(WebFrameTest, HasVisibleContentOnVisibleFrames) {
RegisterMockedHttpURLLoad("visible_frames.html");
frame_test_helpers::WebViewHelper web_view_helper;
WebViewImpl* web_view_impl =
web_view_helper.InitializeAndLoad(base_url_ + "visible_frames.html");
for (WebFrame* frame = web_view_impl->MainFrameImpl()->TraverseNext(); frame;
frame = frame->TraverseNext()) {
EXPECT_TRUE(frame->ToWebLocalFrame()->HasVisibleContent());
}
}
TEST_F(WebFrameTest, HasVisibleContentOnHiddenFrames) {
RegisterMockedHttpURLLoad("hidden_frames.html");
frame_test_helpers::WebViewHelper web_view_helper;
WebViewImpl* web_view_impl =
web_view_helper.InitializeAndLoad(base_url_ + "hidden_frames.html");
for (WebFrame* frame = web_view_impl->MainFrameImpl()->TraverseNext(); frame;
frame = frame->TraverseNext()) {
EXPECT_FALSE(frame->ToWebLocalFrame()->HasVisibleContent());
}
}
class ManifestChangeWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
ManifestChangeWebFrameClient() : manifest_change_count_(0) {}
~ManifestChangeWebFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
void DidChangeManifest() override { ++manifest_change_count_; }
int ManifestChangeCount() { return manifest_change_count_; }
private:
int manifest_change_count_;
};
TEST_F(WebFrameTest, NotifyManifestChange) {
RegisterMockedHttpURLLoad("link-manifest-change.html");
ManifestChangeWebFrameClient web_frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "link-manifest-change.html",
&web_frame_client);
EXPECT_EQ(14, web_frame_client.ManifestChangeCount());
}
static Resource* FetchManifest(Document* document, const KURL& url) {
FetchParameters fetch_parameters{ResourceRequest(url)};
fetch_parameters.SetRequestContext(mojom::RequestContextType::MANIFEST);
return RawResource::FetchSynchronously(fetch_parameters, document->Fetcher());
}
TEST_F(WebFrameTest, ManifestFetch) {
RegisterMockedHttpURLLoad("foo.html");
RegisterMockedHttpURLLoad("link-manifest-fetch.json");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "foo.html");
Document* document =
web_view_helper.LocalMainFrame()->GetFrame()->GetDocument();
Resource* resource =
FetchManifest(document, ToKURL(base_url_ + "link-manifest-fetch.json"));
EXPECT_TRUE(resource->IsLoaded());
}
TEST_F(WebFrameTest, ManifestCSPFetchAllow) {
RegisterMockedURLLoadFromBase(not_base_url_, "link-manifest-fetch.json");
RegisterMockedHttpURLLoadWithCSP("foo.html", "manifest-src *");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "foo.html");
Document* document =
web_view_helper.LocalMainFrame()->GetFrame()->GetDocument();
Resource* resource = FetchManifest(
document, ToKURL(not_base_url_ + "link-manifest-fetch.json"));
EXPECT_TRUE(resource->IsLoaded());
}
TEST_F(WebFrameTest, ManifestCSPFetchSelf) {
RegisterMockedURLLoadFromBase(not_base_url_, "link-manifest-fetch.json");
RegisterMockedHttpURLLoadWithCSP("foo.html", "manifest-src 'self'");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "foo.html");
Document* document =
web_view_helper.LocalMainFrame()->GetFrame()->GetDocument();
Resource* resource = FetchManifest(
document, ToKURL(not_base_url_ + "link-manifest-fetch.json"));
// Fetching resource wasn't allowed.
ASSERT_TRUE(resource);
EXPECT_TRUE(resource->ErrorOccurred());
EXPECT_TRUE(resource->GetResourceError().IsAccessCheck());
}
TEST_F(WebFrameTest, ManifestCSPFetchSelfReportOnly) {
RegisterMockedURLLoadFromBase(not_base_url_, "link-manifest-fetch.json");
RegisterMockedHttpURLLoadWithCSP("foo.html", "manifest-src 'self'",
/* report only */ true);
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "foo.html");
Document* document =
web_view_helper.LocalMainFrame()->GetFrame()->GetDocument();
Resource* resource = FetchManifest(
document, ToKURL(not_base_url_ + "link-manifest-fetch.json"));
EXPECT_TRUE(resource->IsLoaded());
}
TEST_F(WebFrameTest, ReloadBypassingCache) {
// Check that a reload bypassing cache on a frame will result in the cache
// policy of the request being set to ReloadBypassingCache.
RegisterMockedHttpURLLoad("foo.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "foo.html");
WebLocalFrame* frame = web_view_helper.LocalMainFrame();
frame_test_helpers::ReloadFrameBypassingCache(frame);
EXPECT_EQ(mojom::FetchCacheMode::kBypassCache,
frame->GetDocumentLoader()->GetRequest().GetCacheMode());
}
static void NodeImageTestValidation(const IntSize& reference_bitmap_size,
DragImage* drag_image) {
// Prepare the reference bitmap.
SkBitmap bitmap;
bitmap.allocN32Pixels(reference_bitmap_size.Width(),
reference_bitmap_size.Height());
SkCanvas canvas(bitmap);
canvas.drawColor(SK_ColorGREEN);
EXPECT_EQ(reference_bitmap_size.Width(), drag_image->Size().Width());
EXPECT_EQ(reference_bitmap_size.Height(), drag_image->Size().Height());
const SkBitmap& drag_bitmap = drag_image->Bitmap();
EXPECT_EQ(0, memcmp(bitmap.getPixels(), drag_bitmap.getPixels(),
bitmap.computeByteSize()));
}
TEST_F(WebFrameTest, NodeImageTestCSSTransformDescendant) {
frame_test_helpers::WebViewHelper web_view_helper;
std::unique_ptr<DragImage> drag_image = NodeImageTestSetup(
&web_view_helper, std::string("case-css-3dtransform-descendant"));
EXPECT_TRUE(drag_image);
NodeImageTestValidation(IntSize(40, 40), drag_image.get());
}
TEST_F(WebFrameTest, NodeImageTestCSSTransform) {
frame_test_helpers::WebViewHelper web_view_helper;
std::unique_ptr<DragImage> drag_image =
NodeImageTestSetup(&web_view_helper, std::string("case-css-transform"));
EXPECT_TRUE(drag_image);
NodeImageTestValidation(IntSize(40, 40), drag_image.get());
}
TEST_F(WebFrameTest, NodeImageTestCSS3DTransform) {
frame_test_helpers::WebViewHelper web_view_helper;
std::unique_ptr<DragImage> drag_image =
NodeImageTestSetup(&web_view_helper, std::string("case-css-3dtransform"));
EXPECT_TRUE(drag_image);
NodeImageTestValidation(IntSize(40, 40), drag_image.get());
}
TEST_F(WebFrameTest, NodeImageTestInlineBlock) {
frame_test_helpers::WebViewHelper web_view_helper;
std::unique_ptr<DragImage> drag_image =
NodeImageTestSetup(&web_view_helper, std::string("case-inlineblock"));
EXPECT_TRUE(drag_image);
NodeImageTestValidation(IntSize(40, 40), drag_image.get());
}
TEST_F(WebFrameTest, NodeImageTestFloatLeft) {
frame_test_helpers::WebViewHelper web_view_helper;
std::unique_ptr<DragImage> drag_image = NodeImageTestSetup(
&web_view_helper, std::string("case-float-left-overflow-hidden"));
EXPECT_TRUE(drag_image);
NodeImageTestValidation(IntSize(40, 40), drag_image.get());
}
// Crashes on Android: http://crbug.com/403804
#if defined(OS_ANDROID)
TEST_F(WebFrameTest, DISABLED_PrintingBasic)
#else
TEST_F(WebFrameTest, PrintingBasic)
#endif
{
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad("data:text/html,Hello, world.");
WebLocalFrame* frame = web_view_helper.LocalMainFrame();
WebPrintParams print_params;
print_params.print_content_area.width = 500;
print_params.print_content_area.height = 500;
int page_count = frame->PrintBegin(print_params);
EXPECT_EQ(1, page_count);
frame->PrintEnd();
}
class ThemeColorTestWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
ThemeColorTestWebFrameClient() : did_notify_(false) {}
~ThemeColorTestWebFrameClient() override = default;
void Reset() { did_notify_ = false; }
bool DidNotify() const { return did_notify_; }
private:
// frame_test_helpers::TestWebFrameClient:
void DidChangeThemeColor() override { did_notify_ = true; }
bool did_notify_;
};
TEST_F(WebFrameTest, ThemeColor) {
RegisterMockedHttpURLLoad("theme_color_test.html");
ThemeColorTestWebFrameClient client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "theme_color_test.html",
&client);
EXPECT_TRUE(client.DidNotify());
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
EXPECT_EQ(0xff0000ff, frame->GetDocument().ThemeColor());
// Change color by rgb.
client.Reset();
frame->ExecuteScript(
WebScriptSource("document.getElementById('tc1').setAttribute('content', "
"'rgb(0, 0, 0)');"));
EXPECT_TRUE(client.DidNotify());
EXPECT_EQ(0xff000000, frame->GetDocument().ThemeColor());
// Change color by hsl.
client.Reset();
frame->ExecuteScript(
WebScriptSource("document.getElementById('tc1').setAttribute('content', "
"'hsl(240,100%, 50%)');"));
EXPECT_TRUE(client.DidNotify());
EXPECT_EQ(0xff0000ff, frame->GetDocument().ThemeColor());
// Change of second theme-color meta tag will not change frame's theme
// color.
client.Reset();
frame->ExecuteScript(WebScriptSource(
"document.getElementById('tc2').setAttribute('content', '#00FF00');"));
EXPECT_TRUE(client.DidNotify());
EXPECT_EQ(0xff0000ff, frame->GetDocument().ThemeColor());
}
// Make sure that an embedder-triggered detach with a remote frame parent
// doesn't leave behind dangling pointers.
TEST_F(WebFrameTest, EmbedderTriggeredDetachWithRemoteMainFrame) {
frame_test_helpers::WebViewHelper helper;
helper.InitializeRemote();
WebLocalFrame* child_frame =
frame_test_helpers::CreateLocalChild(*helper.RemoteMainFrame());
// Purposely keep the LocalFrame alive so it's the last thing to be destroyed.
Persistent<Frame> child_core_frame = WebFrame::ToCoreFrame(*child_frame);
helper.Reset();
child_core_frame.Clear();
}
class WebFrameSwapTest : public WebFrameTest {
protected:
WebFrameSwapTest() {
RegisterMockedHttpURLLoad("frame-a-b-c.html");
RegisterMockedHttpURLLoad("subframe-a.html");
RegisterMockedHttpURLLoad("subframe-b.html");
RegisterMockedHttpURLLoad("subframe-c.html");
RegisterMockedHttpURLLoad("subframe-hello.html");
web_view_helper_.InitializeAndLoad(base_url_ + "frame-a-b-c.html");
}
void Reset() { web_view_helper_.Reset(); }
WebLocalFrame* MainFrame() const { return web_view_helper_.LocalMainFrame(); }
WebViewImpl* WebView() const { return web_view_helper_.GetWebView(); }
private:
frame_test_helpers::WebViewHelper web_view_helper_;
};
TEST_F(WebFrameSwapTest, SwapMainFrame) {
WebRemoteFrame* remote_frame = frame_test_helpers::CreateRemote();
MainFrame()->Swap(remote_frame);
WebLocalFrame* local_frame =
frame_test_helpers::CreateProvisional(*remote_frame);
remote_frame->Swap(local_frame);
// Finally, make sure an embedder triggered load in the local frame swapped
// back in works.
frame_test_helpers::LoadFrame(local_frame, base_url_ + "subframe-hello.html");
std::string content =
WebFrameContentDumper::DumpWebViewAsText(WebView(), 1024).Utf8();
EXPECT_EQ("hello", content);
}
TEST_F(WebFrameSwapTest, ValidateSizeOnRemoteToLocalMainFrameSwap) {
WebSize size(111, 222);
WebRemoteFrame* remote_frame = frame_test_helpers::CreateRemote();
MainFrame()->Swap(remote_frame);
remote_frame->View()->MainFrameWidget()->Resize(size);
WebLocalFrame* local_frame =
frame_test_helpers::CreateProvisional(*remote_frame);
remote_frame->Swap(local_frame);
// Verify that the size that was set with a remote main frame is correct
// after swapping to a local frame.
Page* page = static_cast<WebViewImpl*>(local_frame->View())
->GetPage()
->MainFrame()
->GetPage();
EXPECT_EQ(size.width, page->GetVisualViewport().Size().Width());
EXPECT_EQ(size.height, page->GetVisualViewport().Size().Height());
}
// Verify that size changes to browser controls while the main frame is remote
// are preserved when the main frame swaps to a local frame. See
// https://crbug.com/769321.
TEST_F(WebFrameSwapTest,
ValidateBrowserControlsSizeOnRemoteToLocalMainFrameSwap) {
WebRemoteFrame* remote_frame = frame_test_helpers::CreateRemote();
MainFrame()->Swap(remote_frame);
// Create a provisional main frame frame but don't swap it in yet.
WebLocalFrame* local_frame =
frame_test_helpers::CreateProvisional(*remote_frame);
WebViewImpl* web_view = static_cast<WebViewImpl*>(local_frame->View());
EXPECT_TRUE(web_view->MainFrame() &&
web_view->MainFrame()->IsWebRemoteFrame());
// Resize the browser controls.
float top_browser_controls_height = 40;
float bottom_browser_controls_height = 60;
web_view->ResizeWithBrowserControls(WebSize(100, 100),
top_browser_controls_height,
bottom_browser_controls_height, false);
// Swap the provisional frame in and verify that the browser controls size is
// correct.
remote_frame->Swap(local_frame);
Page* page = static_cast<WebViewImpl*>(local_frame->View())
->GetPage()
->MainFrame()
->GetPage();
EXPECT_EQ(top_browser_controls_height,
page->GetBrowserControls().TopHeight());
EXPECT_EQ(bottom_browser_controls_height,
page->GetBrowserControls().BottomHeight());
}
namespace {
class SwapMainFrameWhenTitleChangesWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
SwapMainFrameWhenTitleChangesWebFrameClient() = default;
~SwapMainFrameWhenTitleChangesWebFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
void DidReceiveTitle(const WebString& title, WebTextDirection) override {
if (title.IsEmpty())
return;
if (!Frame()->Parent())
Frame()->Swap(frame_test_helpers::CreateRemote());
}
};
} // namespace
TEST_F(WebFrameTest, SwapMainFrameWhileLoading) {
SwapMainFrameWhenTitleChangesWebFrameClient frame_client;
frame_test_helpers::WebViewHelper web_view_helper;
RegisterMockedHttpURLLoad("frame-a-b-c.html");
RegisterMockedHttpURLLoad("subframe-a.html");
RegisterMockedHttpURLLoad("subframe-b.html");
RegisterMockedHttpURLLoad("subframe-c.html");
RegisterMockedHttpURLLoad("subframe-hello.html");
web_view_helper.InitializeAndLoad(base_url_ + "frame-a-b-c.html",
&frame_client);
}
void WebFrameTest::SwapAndVerifyFirstChildConsistency(const char* const message,
WebFrame* parent,
WebFrame* new_child) {
SCOPED_TRACE(message);
parent->FirstChild()->Swap(new_child);
EXPECT_EQ(new_child, parent->FirstChild());
EXPECT_EQ(new_child->Parent(), parent);
EXPECT_EQ(new_child,
parent->last_child_->previous_sibling_->previous_sibling_);
EXPECT_EQ(new_child->NextSibling(), parent->last_child_->previous_sibling_);
}
TEST_F(WebFrameSwapTest, SwapFirstChild) {
WebRemoteFrame* remote_frame = frame_test_helpers::CreateRemote();
SwapAndVerifyFirstChildConsistency("local->remote", MainFrame(),
remote_frame);
WebLocalFrame* local_frame =
frame_test_helpers::CreateProvisional(*remote_frame);
SwapAndVerifyFirstChildConsistency("remote->local", MainFrame(), local_frame);
// FIXME: This almost certainly fires more load events on the iframe element
// than it should.
// Finally, make sure an embedder triggered load in the local frame swapped
// back in works.
frame_test_helpers::LoadFrame(local_frame, base_url_ + "subframe-hello.html");
std::string content =
WebFrameContentDumper::DumpWebViewAsText(WebView(), 1024).Utf8();
EXPECT_EQ(" \n\nhello\n\nb \n\na\n\nc", content);
}
void WebFrameTest::SwapAndVerifyMiddleChildConsistency(
const char* const message,
WebFrame* parent,
WebFrame* new_child) {
SCOPED_TRACE(message);
parent->FirstChild()->NextSibling()->Swap(new_child);
EXPECT_EQ(new_child, parent->FirstChild()->NextSibling());
EXPECT_EQ(new_child, parent->last_child_->previous_sibling_);
EXPECT_EQ(new_child->Parent(), parent);
EXPECT_EQ(new_child, parent->FirstChild()->NextSibling());
EXPECT_EQ(new_child->previous_sibling_, parent->FirstChild());
EXPECT_EQ(new_child, parent->last_child_->previous_sibling_);
EXPECT_EQ(new_child->NextSibling(), parent->last_child_);
}
TEST_F(WebFrameSwapTest, SwapMiddleChild) {
WebRemoteFrame* remote_frame = frame_test_helpers::CreateRemote();
SwapAndVerifyMiddleChildConsistency("local->remote", MainFrame(),
remote_frame);
WebLocalFrame* local_frame =
frame_test_helpers::CreateProvisional(*remote_frame);
SwapAndVerifyMiddleChildConsistency("remote->local", MainFrame(),
local_frame);
// FIXME: This almost certainly fires more load events on the iframe element
// than it should.
// Finally, make sure an embedder triggered load in the local frame swapped
// back in works.
frame_test_helpers::LoadFrame(local_frame, base_url_ + "subframe-hello.html");
std::string content =
WebFrameContentDumper::DumpWebViewAsText(WebView(), 1024).Utf8();
EXPECT_EQ(" \n\na\n\nhello\n\nc", content);
}
void WebFrameTest::SwapAndVerifyLastChildConsistency(const char* const message,
WebFrame* parent,
WebFrame* new_child) {
SCOPED_TRACE(message);
LastChild(parent)->Swap(new_child);
EXPECT_EQ(new_child, LastChild(parent));
EXPECT_EQ(new_child->Parent(), parent);
EXPECT_EQ(new_child, parent->FirstChild()->NextSibling()->NextSibling());
EXPECT_EQ(new_child->previous_sibling_, parent->FirstChild()->NextSibling());
}
TEST_F(WebFrameSwapTest, SwapLastChild) {
WebRemoteFrame* remote_frame = frame_test_helpers::CreateRemote();
SwapAndVerifyLastChildConsistency("local->remote", MainFrame(), remote_frame);
WebLocalFrame* local_frame =
frame_test_helpers::CreateProvisional(*remote_frame);
SwapAndVerifyLastChildConsistency("remote->local", MainFrame(), local_frame);
// FIXME: This almost certainly fires more load events on the iframe element
// than it should.
// Finally, make sure an embedder triggered load in the local frame swapped
// back in works.
frame_test_helpers::LoadFrame(local_frame, base_url_ + "subframe-hello.html");
std::string content =
WebFrameContentDumper::DumpWebViewAsText(WebView(), 1024).Utf8();
EXPECT_EQ(" \n\na\n\nb \n\na\n\nhello", content);
}
TEST_F(WebFrameSwapTest, DetachProvisionalFrame) {
WebRemoteFrameImpl* remote_frame = frame_test_helpers::CreateRemote();
SwapAndVerifyMiddleChildConsistency("local->remote", MainFrame(),
remote_frame);
WebLocalFrameImpl* provisional_frame =
frame_test_helpers::CreateProvisional(*remote_frame);
// The provisional frame should have a local frame owner.
FrameOwner* owner = provisional_frame->GetFrame()->Owner();
ASSERT_TRUE(owner->IsLocal());
// But the owner should point to |remoteFrame|, since the new frame is still
// provisional.
EXPECT_EQ(remote_frame->GetFrame(), owner->ContentFrame());
// After detaching the provisional frame, the frame owner should still point
// at |remoteFrame|.
provisional_frame->Detach();
// The owner should not be affected by detaching the provisional frame, so it
// should still point to |remoteFrame|.
EXPECT_EQ(remote_frame->GetFrame(), owner->ContentFrame());
}
void WebFrameTest::SwapAndVerifySubframeConsistency(const char* const message,
WebFrame* old_frame,
WebFrame* new_frame) {
SCOPED_TRACE(message);
EXPECT_TRUE(old_frame->FirstChild());
old_frame->Swap(new_frame);
EXPECT_FALSE(new_frame->FirstChild());
EXPECT_FALSE(new_frame->last_child_);
}
TEST_F(WebFrameSwapTest, EventsOnDisconnectedSubDocumentSkipped) {
WebRemoteFrame* remote_frame = frame_test_helpers::CreateRemote();
WebFrame* target_frame = MainFrame()->FirstChild()->NextSibling();
EXPECT_TRUE(target_frame);
SwapAndVerifySubframeConsistency("local->remote", target_frame, remote_frame);
WebLocalFrameImpl* local_child = frame_test_helpers::CreateLocalChild(
*remote_frame, "local-inside-remote");
LocalFrame* main_frame = WebView()->MainFrameImpl()->GetFrame();
Document* child_document = local_child->GetFrame()->GetDocument();
EventHandlerRegistry& event_registry =
local_child->GetFrame()->GetEventHandlerRegistry();
// Add the non-connected, but local, child document as having an event.
event_registry.DidAddEventHandler(
*child_document, EventHandlerRegistry::kTouchStartOrMoveEventBlocking);
// Passes if this does not crash or DCHECK.
main_frame->View()->UpdateAllLifecyclePhases(
DocumentLifecycle::LifecycleUpdateReason::kTest);
}
TEST_F(WebFrameSwapTest, EventsOnDisconnectedElementSkipped) {
WebRemoteFrame* remote_frame = frame_test_helpers::CreateRemote();
WebFrame* target_frame = MainFrame()->FirstChild()->NextSibling();
EXPECT_TRUE(target_frame);
SwapAndVerifySubframeConsistency("local->remote", target_frame, remote_frame);
WebLocalFrameImpl* local_child = frame_test_helpers::CreateLocalChild(
*remote_frame, "local-inside-remote");
LocalFrame* main_frame = WebView()->MainFrameImpl()->GetFrame();
// Layout ensures that elements in the local_child frame get LayoutObjects
// attached, but doesn't paint, because the child frame needs to not have
// been composited for the purpose of this test.
local_child->GetFrameView()->UpdateLayout();
Document* child_document = local_child->GetFrame()->GetDocument();
EventHandlerRegistry& event_registry =
local_child->GetFrame()->GetEventHandlerRegistry();
// Add the non-connected body element as having an event.
event_registry.DidAddEventHandler(
*child_document->body(),
EventHandlerRegistry::kTouchStartOrMoveEventBlocking);
// Passes if this does not crash or DCHECK.
main_frame->View()->UpdateAllLifecyclePhases(
DocumentLifecycle::LifecycleUpdateReason::kTest);
}
TEST_F(WebFrameSwapTest, SwapParentShouldDetachChildren) {
WebRemoteFrame* remote_frame = frame_test_helpers::CreateRemote();
WebFrame* target_frame = MainFrame()->FirstChild()->NextSibling();
EXPECT_TRUE(target_frame);
SwapAndVerifySubframeConsistency("local->remote", target_frame, remote_frame);
target_frame = MainFrame()->FirstChild()->NextSibling();
EXPECT_TRUE(target_frame);
// Create child frames in the target frame before testing the swap.
frame_test_helpers::CreateRemoteChild(*remote_frame);
WebLocalFrame* local_frame =
frame_test_helpers::CreateProvisional(*remote_frame);
SwapAndVerifySubframeConsistency("remote->local", target_frame, local_frame);
// FIXME: This almost certainly fires more load events on the iframe element
// than it should.
// Finally, make sure an embedder triggered load in the local frame swapped
// back in works.
frame_test_helpers::LoadFrame(local_frame, base_url_ + "subframe-hello.html");
std::string content =
WebFrameContentDumper::DumpWebViewAsText(WebView(), 1024).Utf8();
EXPECT_EQ(" \n\na\n\nhello\n\nc", content);
}
TEST_F(WebFrameSwapTest, SwapPreservesGlobalContext) {
v8::HandleScope scope(v8::Isolate::GetCurrent());
v8::Local<v8::Value> window_top =
MainFrame()->ExecuteScriptAndReturnValue(WebScriptSource("window"));
ASSERT_TRUE(window_top->IsObject());
v8::Local<v8::Value> original_window =
MainFrame()->ExecuteScriptAndReturnValue(
WebScriptSource("document.querySelector('#frame2').contentWindow;"));
ASSERT_TRUE(original_window->IsObject());
// Make sure window reference stays the same when swapping to a remote frame.
WebRemoteFrame* remote_frame = frame_test_helpers::CreateRemote();
WebFrame* target_frame = MainFrame()->FirstChild()->NextSibling();
target_frame->Swap(remote_frame);
v8::Local<v8::Value> remote_window = MainFrame()->ExecuteScriptAndReturnValue(
WebScriptSource("document.querySelector('#frame2').contentWindow;"));
EXPECT_TRUE(original_window->StrictEquals(remote_window));
// Check that its view is consistent with the world.
v8::Local<v8::Value> remote_window_top =
MainFrame()->ExecuteScriptAndReturnValue(WebScriptSource(
"document.querySelector('#frame2').contentWindow.top;"));
EXPECT_TRUE(window_top->StrictEquals(remote_window_top));
// Now check that remote -> local works too, since it goes through a different
// code path.
WebLocalFrame* local_frame =
frame_test_helpers::CreateProvisional(*remote_frame);
remote_frame->Swap(local_frame);
v8::Local<v8::Value> local_window = MainFrame()->ExecuteScriptAndReturnValue(
WebScriptSource("document.querySelector('#frame2').contentWindow;"));
EXPECT_TRUE(original_window->StrictEquals(local_window));
v8::Local<v8::Value> local_window_top =
MainFrame()->ExecuteScriptAndReturnValue(WebScriptSource(
"document.querySelector('#frame2').contentWindow.top;"));
EXPECT_TRUE(window_top->StrictEquals(local_window_top));
}
TEST_F(WebFrameSwapTest, SetTimeoutAfterSwap) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::HandleScope scope(isolate);
MainFrame()->ExecuteScript(
WebScriptSource("savedSetTimeout = window[0].setTimeout"));
// Swap the frame to a remote frame.
WebRemoteFrame* remote_frame = frame_test_helpers::CreateRemote();
WebFrame* target_frame = MainFrame()->FirstChild();
target_frame->Swap(remote_frame);
remote_frame->SetReplicatedOrigin(
WebSecurityOrigin(SecurityOrigin::CreateUniqueOpaque()), false);
// Invoking setTimeout should throw a security error.
{
v8::Local<v8::Value> exception = MainFrame()->ExecuteScriptAndReturnValue(
WebScriptSource("try {\n"
" savedSetTimeout.call(window[0], () => {}, 0);\n"
"} catch (e) { e; }"));
ASSERT_TRUE(!exception.IsEmpty());
EXPECT_EQ(
"SecurityError: Blocked a frame with origin \"http://internal.test\" "
"from accessing a cross-origin frame.",
ToCoreString(exception
->ToString(ToScriptStateForMainWorld(
WebView()->MainFrameImpl()->GetFrame())
->GetContext())
.ToLocalChecked()));
}
}
TEST_F(WebFrameSwapTest, SwapInitializesGlobal) {
v8::HandleScope scope(v8::Isolate::GetCurrent());
v8::Local<v8::Value> window_top =
MainFrame()->ExecuteScriptAndReturnValue(WebScriptSource("window"));
ASSERT_TRUE(window_top->IsObject());
v8::Local<v8::Value> last_child = MainFrame()->ExecuteScriptAndReturnValue(
WebScriptSource("saved = window[2]"));
ASSERT_TRUE(last_child->IsObject());
WebRemoteFrame* remote_frame = frame_test_helpers::CreateRemote();
WebFrameTest::LastChild(MainFrame())->Swap(remote_frame);
v8::Local<v8::Value> remote_window_top =
MainFrame()->ExecuteScriptAndReturnValue(WebScriptSource("saved.top"));
EXPECT_TRUE(remote_window_top->IsObject());
EXPECT_TRUE(window_top->StrictEquals(remote_window_top));
WebLocalFrame* local_frame =
frame_test_helpers::CreateProvisional(*remote_frame);
remote_frame->Swap(local_frame);
v8::Local<v8::Value> local_window_top =
MainFrame()->ExecuteScriptAndReturnValue(WebScriptSource("saved.top"));
EXPECT_TRUE(local_window_top->IsObject());
EXPECT_TRUE(window_top->StrictEquals(local_window_top));
}
TEST_F(WebFrameSwapTest, RemoteFramesAreIndexable) {
v8::HandleScope scope(v8::Isolate::GetCurrent());
WebRemoteFrame* remote_frame = frame_test_helpers::CreateRemote();
LastChild(MainFrame())->Swap(remote_frame);
v8::Local<v8::Value> remote_window =
MainFrame()->ExecuteScriptAndReturnValue(WebScriptSource("window[2]"));
EXPECT_TRUE(remote_window->IsObject());
v8::Local<v8::Value> window_length = MainFrame()->ExecuteScriptAndReturnValue(
WebScriptSource("window.length"));
ASSERT_TRUE(window_length->IsInt32());
EXPECT_EQ(3, window_length.As<v8::Int32>()->Value());
}
TEST_F(WebFrameSwapTest, RemoteFrameLengthAccess) {
v8::HandleScope scope(v8::Isolate::GetCurrent());
WebRemoteFrame* remote_frame = frame_test_helpers::CreateRemote();
LastChild(MainFrame())->Swap(remote_frame);
v8::Local<v8::Value> remote_window_length =
MainFrame()->ExecuteScriptAndReturnValue(
WebScriptSource("window[2].length"));
ASSERT_TRUE(remote_window_length->IsInt32());
EXPECT_EQ(0, remote_window_length.As<v8::Int32>()->Value());
}
TEST_F(WebFrameSwapTest, RemoteWindowNamedAccess) {
v8::HandleScope scope(v8::Isolate::GetCurrent());
// TODO(dcheng): Once OOPIF unit test infrastructure is in place, test that
// named window access on a remote window works. For now, just test that
// accessing a named property doesn't crash.
WebRemoteFrame* remote_frame = frame_test_helpers::CreateRemote();
LastChild(MainFrame())->Swap(remote_frame);
remote_frame->SetReplicatedOrigin(
WebSecurityOrigin(SecurityOrigin::CreateUniqueOpaque()), false);
v8::Local<v8::Value> remote_window_property =
MainFrame()->ExecuteScriptAndReturnValue(
WebScriptSource("window[2].foo"));
EXPECT_TRUE(remote_window_property.IsEmpty());
}
TEST_F(WebFrameSwapTest, RemoteWindowToString) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::HandleScope scope(isolate);
WebRemoteFrame* remote_frame = frame_test_helpers::CreateRemote();
LastChild(MainFrame())->Swap(remote_frame);
v8::Local<v8::Value> to_string_result =
MainFrame()->ExecuteScriptAndReturnValue(
WebScriptSource("Object.prototype.toString.call(window[2])"));
ASSERT_FALSE(to_string_result.IsEmpty());
EXPECT_STREQ("[object Object]",
*v8::String::Utf8Value(isolate, to_string_result));
}
// TODO(alexmos, dcheng): This test and some other OOPIF tests use
// very little of the test fixture support in WebFrameSwapTest. We should
// clean these tests up.
TEST_F(WebFrameSwapTest, FramesOfRemoteParentAreIndexable) {
v8::HandleScope scope(v8::Isolate::GetCurrent());
WebRemoteFrame* remote_parent_frame = frame_test_helpers::CreateRemote();
MainFrame()->Swap(remote_parent_frame);
remote_parent_frame->SetReplicatedOrigin(
WebSecurityOrigin(SecurityOrigin::CreateUniqueOpaque()), false);
WebLocalFrame* child_frame =
frame_test_helpers::CreateLocalChild(*remote_parent_frame);
frame_test_helpers::LoadFrame(child_frame, base_url_ + "subframe-hello.html");
v8::Local<v8::Value> window =
child_frame->ExecuteScriptAndReturnValue(WebScriptSource("window"));
v8::Local<v8::Value> child_of_remote_parent =
child_frame->ExecuteScriptAndReturnValue(
WebScriptSource("parent.frames[0]"));
EXPECT_TRUE(child_of_remote_parent->IsObject());
EXPECT_TRUE(window->StrictEquals(child_of_remote_parent));
v8::Local<v8::Value> window_length = child_frame->ExecuteScriptAndReturnValue(
WebScriptSource("parent.frames.length"));
ASSERT_TRUE(window_length->IsInt32());
EXPECT_EQ(1, window_length.As<v8::Int32>()->Value());
}
// Check that frames with a remote parent don't crash while accessing
// window.frameElement.
TEST_F(WebFrameSwapTest, FrameElementInFramesWithRemoteParent) {
v8::HandleScope scope(v8::Isolate::GetCurrent());
WebRemoteFrame* remote_parent_frame = frame_test_helpers::CreateRemote();
MainFrame()->Swap(remote_parent_frame);
remote_parent_frame->SetReplicatedOrigin(
WebSecurityOrigin(SecurityOrigin::CreateUniqueOpaque()), false);
WebLocalFrame* child_frame =
frame_test_helpers::CreateLocalChild(*remote_parent_frame);
frame_test_helpers::LoadFrame(child_frame, base_url_ + "subframe-hello.html");
v8::Local<v8::Value> frame_element = child_frame->ExecuteScriptAndReturnValue(
WebScriptSource("window.frameElement"));
// frameElement should be null if cross-origin.
ASSERT_FALSE(frame_element.IsEmpty());
EXPECT_TRUE(frame_element->IsNull());
}
class RemoteToLocalSwapWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
explicit RemoteToLocalSwapWebFrameClient(WebRemoteFrame* remote_frame)
: history_commit_type_(kWebHistoryInertCommit),
remote_frame_(remote_frame) {}
~RemoteToLocalSwapWebFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
void DidCommitProvisionalLoad(const WebHistoryItem&,
WebHistoryCommitType history_commit_type,
WebGlobalObjectReusePolicy) override {
history_commit_type_ = history_commit_type;
remote_frame_->Swap(Frame());
}
WebHistoryCommitType HistoryCommitType() const {
return history_commit_type_;
}
WebHistoryCommitType history_commit_type_;
WebRemoteFrame* remote_frame_;
};
// The commit type should be Initial if we are swapping a RemoteFrame to a
// LocalFrame as it is first being created. This happens when another frame
// exists in the same process, such that we create the RemoteFrame before the
// first navigation occurs.
TEST_F(WebFrameSwapTest, HistoryCommitTypeAfterNewRemoteToLocalSwap) {
WebRemoteFrame* remote_frame = frame_test_helpers::CreateRemote();
WebFrame* target_frame = MainFrame()->FirstChild();
ASSERT_TRUE(target_frame);
target_frame->Swap(remote_frame);
ASSERT_TRUE(MainFrame()->FirstChild());
ASSERT_EQ(MainFrame()->FirstChild(), remote_frame);
RemoteToLocalSwapWebFrameClient client(remote_frame);
WebLocalFrame* local_frame =
frame_test_helpers::CreateProvisional(*remote_frame, &client);
frame_test_helpers::LoadFrame(local_frame, base_url_ + "subframe-hello.html");
EXPECT_EQ(kWebHistoryInertCommit, client.HistoryCommitType());
// Manually reset to break WebViewHelper's dependency on the stack allocated
// TestWebFrameClient.
Reset();
}
// The commit type should be Standard if we are swapping a RemoteFrame to a
// LocalFrame after commits have already happened in the frame. The browser
// process will inform us via setCommittedFirstRealLoad.
TEST_F(WebFrameSwapTest, HistoryCommitTypeAfterExistingRemoteToLocalSwap) {
WebRemoteFrame* remote_frame = frame_test_helpers::CreateRemote();
WebFrame* target_frame = MainFrame()->FirstChild();
ASSERT_TRUE(target_frame);
target_frame->Swap(remote_frame);
ASSERT_TRUE(MainFrame()->FirstChild());
ASSERT_EQ(MainFrame()->FirstChild(), remote_frame);
RemoteToLocalSwapWebFrameClient client(remote_frame);
WebLocalFrameImpl* local_frame =
frame_test_helpers::CreateProvisional(*remote_frame, &client);
local_frame->SetCommittedFirstRealLoad();
frame_test_helpers::LoadFrame(local_frame, base_url_ + "subframe-hello.html");
EXPECT_EQ(kWebStandardCommit, client.HistoryCommitType());
// Manually reset to break WebViewHelper's dependency on the stack allocated
// TestWebFrameClient.
Reset();
}
class RemoteNavigationClient
: public frame_test_helpers::TestWebRemoteFrameClient {
public:
RemoteNavigationClient() = default;
~RemoteNavigationClient() override = default;
// frame_test_helpers::TestWebRemoteFrameClient:
void Navigate(const WebURLRequest& request,
bool should_replace_current_entry,
mojo::ScopedMessagePipeHandle) override {
last_request_ = request;
}
const WebURLRequest& LastRequest() const { return last_request_; }
private:
WebURLRequest last_request_;
};
TEST_F(WebFrameSwapTest, NavigateRemoteFrameViaLocation) {
RemoteNavigationClient client;
WebRemoteFrame* remote_frame = frame_test_helpers::CreateRemote(&client);
WebFrame* target_frame = MainFrame()->FirstChild();
ASSERT_TRUE(target_frame);
target_frame->Swap(remote_frame);
ASSERT_TRUE(MainFrame()->FirstChild());
ASSERT_EQ(MainFrame()->FirstChild(), remote_frame);
remote_frame->SetReplicatedOrigin(
WebSecurityOrigin::CreateFromString("http://127.0.0.1"), false);
MainFrame()->ExecuteScript(
WebScriptSource("document.getElementsByTagName('iframe')[0]."
"contentWindow.location = 'data:text/html,hi'"));
ASSERT_FALSE(client.LastRequest().IsNull());
EXPECT_EQ(WebURL(ToKURL("data:text/html,hi")), client.LastRequest().Url());
// Manually reset to break WebViewHelper's dependency on the stack allocated
// TestWebFrameClient.
Reset();
}
TEST_F(WebFrameSwapTest, WindowOpenOnRemoteFrame) {
RemoteNavigationClient remote_client;
WebRemoteFrame* remote_frame =
frame_test_helpers::CreateRemote(&remote_client);
MainFrame()->FirstChild()->Swap(remote_frame);
remote_frame->SetReplicatedOrigin(
WebSecurityOrigin(SecurityOrigin::CreateUniqueOpaque()), false);
ASSERT_TRUE(MainFrame()->FirstChild()->IsWebRemoteFrame());
LocalDOMWindow* main_window =
ToWebLocalFrameImpl(MainFrame())->GetFrame()->DomWindow();
KURL destination = ToKURL("data:text/html:destination");
NonThrowableExceptionState exception_state;
main_window->open(
USVStringOrTrustedURL::FromTrustedURL(TrustedURL::Create(destination)),
"frame1", "", main_window, main_window, exception_state);
ASSERT_FALSE(remote_client.LastRequest().IsNull());
EXPECT_EQ(remote_client.LastRequest().Url(), WebURL(destination));
// Pointing a named frame to an empty URL should just return a reference to
// the frame's window without navigating it.
DOMWindow* result = main_window->open(
USVStringOrTrustedURL::FromTrustedURL(TrustedURL::Create(ToKURL(""))),
"frame1", "", main_window, main_window, exception_state);
EXPECT_EQ(remote_client.LastRequest().Url(), WebURL(destination));
EXPECT_EQ(result, WebFrame::ToCoreFrame(*remote_frame)->DomWindow());
Reset();
}
class RemoteWindowCloseClient : public frame_test_helpers::TestWebWidgetClient {
public:
// WebWidgetClient implementation.
void CloseWidgetSoon() override { closed_ = true; }
bool Closed() const { return closed_; }
private:
bool closed_ = false;
};
TEST_F(WebFrameTest, WindowOpenRemoteClose) {
frame_test_helpers::WebViewHelper main_web_view;
main_web_view.Initialize();
// Create a remote window that will be closed later in the test.
RemoteWindowCloseClient client;
frame_test_helpers::TestWebViewClient view_client(&client);
frame_test_helpers::WebViewHelper popup;
popup.InitializeRemote(nullptr, nullptr, &view_client);
popup.RemoteMainFrame()->SetOpener(main_web_view.LocalMainFrame());
LocalFrame* local_frame = main_web_view.LocalMainFrame()->GetFrame();
RemoteFrame* remote_frame = popup.RemoteMainFrame()->GetFrame();
// Attempt to close the window, which should fail as it isn't opened
// by a script.
remote_frame->DomWindow()->close(local_frame->DomWindow());
EXPECT_FALSE(client.Closed());
// Marking it as opened by a script should now allow it to be closed.
remote_frame->GetPage()->SetOpenedByDOM();
remote_frame->DomWindow()->close(local_frame->DomWindow());
EXPECT_TRUE(client.Closed());
}
TEST_F(WebFrameTest, NavigateRemoteToLocalWithOpener) {
frame_test_helpers::WebViewHelper main_web_view;
main_web_view.Initialize();
WebLocalFrame* main_frame = main_web_view.LocalMainFrame();
// Create a popup with a remote frame and set its opener to the main frame.
frame_test_helpers::WebViewHelper popup_helper;
popup_helper.InitializeRemote(
nullptr, SecurityOrigin::CreateFromString("http://foo.com"));
WebRemoteFrame* popup_remote_frame = popup_helper.RemoteMainFrame();
popup_remote_frame->SetOpener(main_frame);
EXPECT_FALSE(main_frame->GetSecurityOrigin().CanAccess(
popup_remote_frame->GetSecurityOrigin()));
// Do a remote-to-local swap in the popup.
WebLocalFrame* popup_local_frame =
frame_test_helpers::CreateProvisional(*popup_remote_frame);
popup_remote_frame->Swap(popup_local_frame);
// The initial document created during the remote-to-local swap should have
// inherited its opener's SecurityOrigin.
EXPECT_TRUE(main_frame->GetSecurityOrigin().CanAccess(
popup_helper.LocalMainFrame()->GetSecurityOrigin()));
}
TEST_F(WebFrameTest, SwapWithOpenerCycle) {
// First, create a remote main frame with itself as the opener.
frame_test_helpers::WebViewHelper helper;
helper.InitializeRemote();
WebRemoteFrame* remote_frame = helper.RemoteMainFrame();
helper.RemoteMainFrame()->SetOpener(remote_frame);
// Now swap in a local frame. It shouldn't crash.
WebLocalFrame* local_frame =
frame_test_helpers::CreateProvisional(*remote_frame);
remote_frame->Swap(local_frame);
// And the opener cycle should still be preserved.
EXPECT_EQ(local_frame, local_frame->Opener());
}
class CommitTypeWebFrameClient : public frame_test_helpers::TestWebFrameClient {
public:
CommitTypeWebFrameClient() : history_commit_type_(kWebHistoryInertCommit) {}
~CommitTypeWebFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
void DidCommitProvisionalLoad(const WebHistoryItem&,
WebHistoryCommitType history_commit_type,
WebGlobalObjectReusePolicy) override {
history_commit_type_ = history_commit_type;
}
WebHistoryCommitType HistoryCommitType() const {
return history_commit_type_;
}
private:
WebHistoryCommitType history_commit_type_;
};
TEST_F(WebFrameTest, RemoteFrameInitialCommitType) {
frame_test_helpers::WebViewHelper helper;
helper.InitializeRemote(nullptr, SecurityOrigin::CreateFromString(
WebString::FromUTF8(base_url_)));
// If an iframe has a remote main frame, ensure the inital commit is correctly
// identified as kWebHistoryInertCommit.
CommitTypeWebFrameClient child_frame_client;
WebLocalFrame* child_frame = frame_test_helpers::CreateLocalChild(
*helper.RemoteMainFrame(), "frameName", WebFrameOwnerProperties(),
nullptr, &child_frame_client);
RegisterMockedHttpURLLoad("foo.html");
frame_test_helpers::LoadFrame(child_frame, base_url_ + "foo.html");
EXPECT_EQ(kWebHistoryInertCommit, child_frame_client.HistoryCommitType());
helper.Reset();
}
class GestureEventTestWebWidgetClient
: public frame_test_helpers::TestWebWidgetClient {
public:
GestureEventTestWebWidgetClient() : did_handle_gesture_event_(false) {}
~GestureEventTestWebWidgetClient() override = default;
// frame_test_helpers::TestWebWidgetClient:
void DidHandleGestureEvent(const WebGestureEvent& event,
bool event_cancelled) override {
did_handle_gesture_event_ = true;
}
bool DidHandleGestureEvent() const { return did_handle_gesture_event_; }
private:
bool did_handle_gesture_event_;
};
TEST_F(WebFrameTest, FrameWidgetTest) {
frame_test_helpers::WebViewHelper helper;
helper.InitializeRemote();
GestureEventTestWebWidgetClient child_widget_client;
WebLocalFrame* child_frame = frame_test_helpers::CreateLocalChild(
*helper.RemoteMainFrame(), WebString(), WebFrameOwnerProperties(),
nullptr, nullptr, &child_widget_client);
helper.GetWebView()->MainFrameWidget()->Resize(WebSize(1000, 1000));
WebGestureEvent event(WebInputEvent::kGestureTap, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
kWebGestureDeviceTouchscreen);
event.SetPositionInWidget(WebFloatPoint(20, 20));
child_frame->FrameWidget()->HandleInputEvent(WebCoalescedInputEvent(event));
EXPECT_TRUE(child_widget_client.DidHandleGestureEvent());
helper.Reset();
}
TEST_F(WebFrameTest, DetachRemoteFrame) {
frame_test_helpers::WebViewHelper helper;
helper.InitializeRemote();
WebRemoteFrame* child_frame =
frame_test_helpers::CreateRemoteChild(*helper.RemoteMainFrame());
child_frame->Detach();
}
class TestConsoleMessageWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
TestConsoleMessageWebFrameClient() = default;
~TestConsoleMessageWebFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
void DidAddMessageToConsole(const WebConsoleMessage& message,
const WebString& source_name,
unsigned source_line,
const WebString& stack_trace) override {
messages.push_back(message);
}
Vector<WebConsoleMessage> messages;
};
TEST_F(WebFrameTest, CrossDomainAccessErrorsUseCallingWindow) {
RegisterMockedHttpURLLoad("hidden_frames.html");
RegisterMockedChromeURLLoad("hello_world.html");
frame_test_helpers::WebViewHelper web_view_helper;
TestConsoleMessageWebFrameClient web_frame_client;
web_view_helper.InitializeAndLoad(base_url_ + "hidden_frames.html",
&web_frame_client);
// Create another window with a cross-origin page, and point its opener to
// first window.
frame_test_helpers::WebViewHelper popup_web_view_helper;
TestConsoleMessageWebFrameClient popup_web_frame_client;
WebViewImpl* popup_view = popup_web_view_helper.InitializeAndLoad(
chrome_url_ + "hello_world.html", &popup_web_frame_client);
popup_view->MainFrame()->SetOpener(web_view_helper.GetWebView()->MainFrame());
// Attempt a blocked navigation of an opener's subframe, and ensure that
// the error shows up on the popup (calling) window's console, rather than
// the target window.
popup_view->MainFrameImpl()->ExecuteScript(WebScriptSource(
"try { opener.frames[1].location.href='data:text/html,foo'; } catch (e) "
"{}"));
EXPECT_TRUE(web_frame_client.messages.IsEmpty());
ASSERT_EQ(1u, popup_web_frame_client.messages.size());
EXPECT_TRUE(std::string::npos !=
popup_web_frame_client.messages[0].text.Utf8().find(
"Unsafe JavaScript attempt to initiate navigation"));
// Try setting a cross-origin iframe element's source to a javascript: URL,
// and check that this error is also printed on the calling window.
popup_view->MainFrameImpl()->ExecuteScript(
WebScriptSource("opener.document.querySelectorAll('iframe')[1].src='"
"javascript:alert()'"));
EXPECT_TRUE(web_frame_client.messages.IsEmpty());
ASSERT_EQ(2u, popup_web_frame_client.messages.size());
EXPECT_TRUE(
std::string::npos !=
popup_web_frame_client.messages[1].text.Utf8().find("Blocked a frame"));
// Manually reset to break WebViewHelpers' dependencies on the stack
// allocated WebLocalFrameClients.
web_view_helper.Reset();
popup_web_view_helper.Reset();
}
TEST_F(WebFrameTest, ResizeInvalidatesDeviceMediaQueries) {
RegisterMockedHttpURLLoad("device_media_queries.html");
FixedLayoutTestWebViewClient client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "device_media_queries.html",
nullptr, &client, ConfigureAndroid);
LocalFrame* frame =
ToLocalFrame(web_view_helper.GetWebView()->GetPage()->MainFrame());
Element* element = frame->GetDocument()->getElementById("test");
ASSERT_TRUE(element);
client.screen_info_.rect = WebRect(0, 0, 700, 500);
client.screen_info_.available_rect = client.screen_info_.rect;
web_view_helper.Resize(WebSize(700, 500));
EXPECT_EQ(300, element->OffsetWidth());
EXPECT_EQ(300, element->OffsetHeight());
client.screen_info_.rect = WebRect(0, 0, 710, 500);
client.screen_info_.available_rect = client.screen_info_.rect;
web_view_helper.Resize(WebSize(710, 500));
EXPECT_EQ(400, element->OffsetWidth());
EXPECT_EQ(300, element->OffsetHeight());
client.screen_info_.rect = WebRect(0, 0, 690, 500);
client.screen_info_.available_rect = client.screen_info_.rect;
web_view_helper.Resize(WebSize(690, 500));
EXPECT_EQ(200, element->OffsetWidth());
EXPECT_EQ(300, element->OffsetHeight());
client.screen_info_.rect = WebRect(0, 0, 700, 510);
client.screen_info_.available_rect = client.screen_info_.rect;
web_view_helper.Resize(WebSize(700, 510));
EXPECT_EQ(300, element->OffsetWidth());
EXPECT_EQ(400, element->OffsetHeight());
client.screen_info_.rect = WebRect(0, 0, 700, 490);
client.screen_info_.available_rect = client.screen_info_.rect;
web_view_helper.Resize(WebSize(700, 490));
EXPECT_EQ(300, element->OffsetWidth());
EXPECT_EQ(200, element->OffsetHeight());
client.screen_info_.rect = WebRect(0, 0, 690, 510);
client.screen_info_.available_rect = client.screen_info_.rect;
web_view_helper.Resize(WebSize(690, 510));
EXPECT_EQ(200, element->OffsetWidth());
EXPECT_EQ(400, element->OffsetHeight());
}
class DeviceEmulationTest : public WebFrameTest {
protected:
DeviceEmulationTest() {
RegisterMockedHttpURLLoad("device_emulation.html");
client_.screen_info_.device_scale_factor = 1;
web_view_helper_.InitializeAndLoad(base_url_ + "device_emulation.html",
nullptr, &client_);
}
void TestResize(const WebSize size, const String& expected_size) {
client_.screen_info_.rect = WebRect(0, 0, size.width, size.height);
client_.screen_info_.available_rect = client_.screen_info_.rect;
web_view_helper_.Resize(size);
EXPECT_EQ(expected_size, DumpSize("test"));
}
String DumpSize(const String& id) {
String code = "dumpSize('" + id + "')";
v8::HandleScope scope(v8::Isolate::GetCurrent());
ScriptExecutionCallbackHelper callback_helper(
web_view_helper_.LocalMainFrame()->MainWorldScriptContext());
web_view_helper_.GetWebView()
->MainFrameImpl()
->RequestExecuteScriptAndReturnValue(WebScriptSource(WebString(code)),
false, &callback_helper);
RunPendingTasks();
EXPECT_TRUE(callback_helper.DidComplete());
return callback_helper.StringValue();
}
FixedLayoutTestWebViewClient client_;
frame_test_helpers::WebViewHelper web_view_helper_;
};
TEST_F(DeviceEmulationTest, DeviceSizeInvalidatedOnResize) {
WebDeviceEmulationParams params;
params.screen_position = WebDeviceEmulationParams::kMobile;
web_view_helper_.GetWebView()->EnableDeviceEmulation(params);
TestResize(WebSize(700, 500), "300x300");
TestResize(WebSize(710, 500), "400x300");
TestResize(WebSize(690, 500), "200x300");
TestResize(WebSize(700, 510), "300x400");
TestResize(WebSize(700, 490), "300x200");
TestResize(WebSize(710, 510), "400x400");
TestResize(WebSize(690, 490), "200x200");
TestResize(WebSize(800, 600), "400x400");
web_view_helper_.GetWebView()->DisableDeviceEmulation();
}
TEST_F(DeviceEmulationTest, PointerAndHoverTypes) {
web_view_helper_.GetWebView()
->GetDevToolsEmulator()
->SetTouchEventEmulationEnabled(true, 1);
EXPECT_EQ("20x20", DumpSize("pointer"));
web_view_helper_.GetWebView()
->GetDevToolsEmulator()
->SetTouchEventEmulationEnabled(false, 1);
}
TEST_F(WebFrameTest, CreateLocalChildWithPreviousSibling) {
frame_test_helpers::WebViewHelper helper;
helper.InitializeRemote();
WebRemoteFrame* parent = helper.RemoteMainFrame();
WebLocalFrame* second_frame(
frame_test_helpers::CreateLocalChild(*parent, "name2"));
WebLocalFrame* fourth_frame(frame_test_helpers::CreateLocalChild(
*parent, "name4", WebFrameOwnerProperties(), second_frame));
WebLocalFrame* third_frame(frame_test_helpers::CreateLocalChild(
*parent, "name3", WebFrameOwnerProperties(), second_frame));
WebLocalFrame* first_frame(
frame_test_helpers::CreateLocalChild(*parent, "name1"));
EXPECT_EQ(first_frame, parent->FirstChild());
EXPECT_EQ(nullptr, PreviousSibling(first_frame));
EXPECT_EQ(second_frame, first_frame->NextSibling());
EXPECT_EQ(first_frame, PreviousSibling(second_frame));
EXPECT_EQ(third_frame, second_frame->NextSibling());
EXPECT_EQ(second_frame, PreviousSibling(third_frame));
EXPECT_EQ(fourth_frame, third_frame->NextSibling());
EXPECT_EQ(third_frame, PreviousSibling(fourth_frame));
EXPECT_EQ(nullptr, fourth_frame->NextSibling());
EXPECT_EQ(fourth_frame, LastChild(parent));
EXPECT_EQ(parent, first_frame->Parent());
EXPECT_EQ(parent, second_frame->Parent());
EXPECT_EQ(parent, third_frame->Parent());
EXPECT_EQ(parent, fourth_frame->Parent());
}
TEST_F(WebFrameTest, SendBeaconFromChildWithRemoteMainFrame) {
frame_test_helpers::WebViewHelper helper;
helper.InitializeRemote();
WebLocalFrame* local_frame =
frame_test_helpers::CreateLocalChild(*helper.RemoteMainFrame());
// Finally, make sure an embedder triggered load in the local frame swapped
// back in works.
RegisterMockedHttpURLLoad("send_beacon.html");
RegisterMockedHttpURLLoad("reload_post.html"); // url param to sendBeacon()
frame_test_helpers::LoadFrame(local_frame, base_url_ + "send_beacon.html");
}
TEST_F(WebFrameTest, SiteForCookiesFromChildWithRemoteMainFrame) {
frame_test_helpers::WebViewHelper helper;
helper.InitializeRemote(nullptr,
SecurityOrigin::Create(ToKURL(not_base_url_)));
WebLocalFrame* local_frame =
frame_test_helpers::CreateLocalChild(*helper.RemoteMainFrame());
RegisterMockedHttpURLLoad("foo.html");
frame_test_helpers::LoadFrame(local_frame, base_url_ + "foo.html");
EXPECT_EQ(WebURL(NullURL()), local_frame->GetDocument().SiteForCookies());
SchemeRegistry::RegisterURLSchemeAsFirstPartyWhenTopLevel("http");
EXPECT_EQ(WebURL(ToKURL(not_base_url_)),
local_frame->GetDocument().SiteForCookies());
SchemeRegistry::RemoveURLSchemeAsFirstPartyWhenTopLevel("http");
}
// See https://crbug.com/525285.
TEST_F(WebFrameTest, RemoteToLocalSwapOnMainFrameInitializesCoreFrame) {
frame_test_helpers::WebViewHelper helper;
helper.InitializeRemote();
frame_test_helpers::CreateLocalChild(*helper.RemoteMainFrame());
// Do a remote-to-local swap of the top frame.
WebLocalFrame* local_root =
frame_test_helpers::CreateProvisional(*helper.RemoteMainFrame());
helper.RemoteMainFrame()->Swap(local_root);
// Load a page with a child frame in the new root to make sure this doesn't
// crash when the child frame invokes setCoreFrame.
RegisterMockedHttpURLLoad("single_iframe.html");
RegisterMockedHttpURLLoad("visible_iframe.html");
frame_test_helpers::LoadFrame(local_root, base_url_ + "single_iframe.html");
}
// See https://crbug.com/628942.
TEST_F(WebFrameTest, PausedPageLoadWithRemoteMainFrame) {
frame_test_helpers::WebViewHelper helper;
helper.InitializeRemote();
WebRemoteFrameImpl* remote_root = helper.RemoteMainFrame();
// Check that ScopedPagePauser properly triggers deferred loading for
// the current Page.
Page* page = remote_root->GetFrame()->GetPage();
EXPECT_FALSE(page->Paused());
{
ScopedPagePauser pauser;
EXPECT_TRUE(page->Paused());
}
EXPECT_FALSE(page->Paused());
// Repeat this for a page with a local child frame, and ensure that the
// child frame's loads are also suspended.
WebLocalFrameImpl* web_local_child =
frame_test_helpers::CreateLocalChild(*remote_root);
RegisterMockedHttpURLLoad("foo.html");
frame_test_helpers::LoadFrame(web_local_child, base_url_ + "foo.html");
LocalFrame* local_child = web_local_child->GetFrame();
EXPECT_FALSE(page->Paused());
EXPECT_FALSE(
local_child->GetDocument()->Fetcher()->Context().DefersLoading());
{
ScopedPagePauser pauser;
EXPECT_TRUE(page->Paused());
EXPECT_TRUE(
local_child->GetDocument()->Fetcher()->Context().DefersLoading());
}
EXPECT_FALSE(page->Paused());
EXPECT_FALSE(
local_child->GetDocument()->Fetcher()->Context().DefersLoading());
}
class OverscrollWebWidgetClient
: public frame_test_helpers::TestWebWidgetClient {
public:
MOCK_METHOD5(DidOverscroll,
void(const WebFloatSize&,
const WebFloatSize&,
const WebFloatPoint&,
const WebFloatSize&,
const cc::OverscrollBehavior&));
};
class WebFrameOverscrollTest
: public WebFrameTest,
public testing::WithParamInterface<WebGestureDevice> {
public:
WebFrameOverscrollTest() {}
protected:
WebCoalescedInputEvent GenerateEvent(WebInputEvent::Type type,
float delta_x = 0.0,
float delta_y = 0.0) {
WebGestureEvent event(type, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
GetParam());
// TODO(wjmaclean): Make sure that touchpad device is only ever used for
// gesture scrolling event types.
event.SetPositionInWidget(WebFloatPoint(100, 100));
if (type == WebInputEvent::kGestureScrollUpdate) {
event.data.scroll_update.delta_x = delta_x;
event.data.scroll_update.delta_y = delta_y;
} else if (type == WebInputEvent::kGestureScrollBegin) {
event.data.scroll_begin.delta_x_hint = delta_x;
event.data.scroll_begin.delta_y_hint = delta_y;
}
return WebCoalescedInputEvent(event);
}
void ScrollBegin(frame_test_helpers::WebViewHelper* web_view_helper,
float delta_x_hint,
float delta_y_hint) {
web_view_helper->GetWebView()->MainFrameWidget()->HandleInputEvent(
GenerateEvent(WebInputEvent::kGestureScrollBegin, delta_x_hint,
delta_y_hint));
}
void ScrollUpdate(frame_test_helpers::WebViewHelper* web_view_helper,
float delta_x,
float delta_y) {
web_view_helper->GetWebView()->MainFrameWidget()->HandleInputEvent(
GenerateEvent(WebInputEvent::kGestureScrollUpdate, delta_x, delta_y));
}
void ScrollEnd(frame_test_helpers::WebViewHelper* web_view_helper) {
web_view_helper->GetWebView()->MainFrameWidget()->HandleInputEvent(
GenerateEvent(WebInputEvent::kGestureScrollEnd));
}
};
INSTANTIATE_TEST_CASE_P(All,
WebFrameOverscrollTest,
testing::Values(kWebGestureDeviceTouchpad,
kWebGestureDeviceTouchscreen));
TEST_P(WebFrameOverscrollTest,
AccumulatedRootOverscrollAndUnsedDeltaValuesOnOverscroll) {
OverscrollWebWidgetClient client;
frame_test_helpers::TestWebViewClient view_client(&client);
RegisterMockedHttpURLLoad("overscroll/overscroll.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "overscroll/overscroll.html",
nullptr, &view_client, ConfigureAndroid);
web_view_helper.Resize(WebSize(200, 200));
// Calculation of accumulatedRootOverscroll and unusedDelta on multiple
// scrollUpdate.
ScrollBegin(&web_view_helper, -300, -316);
EXPECT_CALL(client, DidOverscroll(WebFloatSize(8, 16), WebFloatSize(8, 16),
WebFloatPoint(100, 100), WebFloatSize(),
cc::OverscrollBehavior()));
ScrollUpdate(&web_view_helper, -308, -316);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client, DidOverscroll(WebFloatSize(0, 13), WebFloatSize(8, 29),
WebFloatPoint(100, 100), WebFloatSize(),
cc::OverscrollBehavior()));
ScrollUpdate(&web_view_helper, 0, -13);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client, DidOverscroll(WebFloatSize(20, 13), WebFloatSize(28, 42),
WebFloatPoint(100, 100), WebFloatSize(),
cc::OverscrollBehavior()));
ScrollUpdate(&web_view_helper, -20, -13);
Mock::VerifyAndClearExpectations(&client);
// Overscroll is not reported.
EXPECT_CALL(client, DidOverscroll(_, _, _, _, _)).Times(0);
ScrollUpdate(&web_view_helper, 0, 1);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client, DidOverscroll(_, _, _, _, _)).Times(0);
ScrollUpdate(&web_view_helper, 1, 0);
Mock::VerifyAndClearExpectations(&client);
// Overscroll is reported.
EXPECT_CALL(client,
DidOverscroll(WebFloatSize(0, -701), WebFloatSize(0, -701),
WebFloatPoint(100, 100), WebFloatSize(),
cc::OverscrollBehavior()));
ScrollUpdate(&web_view_helper, 0, 1000);
Mock::VerifyAndClearExpectations(&client);
// Overscroll is not reported.
EXPECT_CALL(client, DidOverscroll(_, _, _, _, _)).Times(0);
ScrollEnd(&web_view_helper);
Mock::VerifyAndClearExpectations(&client);
}
TEST_P(WebFrameOverscrollTest,
AccumulatedOverscrollAndUnusedDeltaValuesOnDifferentAxesOverscroll) {
OverscrollWebWidgetClient client;
frame_test_helpers::TestWebViewClient view_client(&client);
RegisterMockedHttpURLLoad("overscroll/div-overscroll.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "overscroll/div-overscroll.html", nullptr, &view_client,
ConfigureAndroid);
web_view_helper.Resize(WebSize(200, 200));
ScrollBegin(&web_view_helper, 0, -316);
// Scroll the Div to the end.
EXPECT_CALL(client, DidOverscroll(_, _, _, _, _)).Times(0);
ScrollUpdate(&web_view_helper, 0, -316);
Mock::VerifyAndClearExpectations(&client);
ScrollEnd(&web_view_helper);
ScrollBegin(&web_view_helper, 0, -100);
// Now On Scrolling DIV, scroll is bubbled and root layer is over-scrolled.
EXPECT_CALL(client, DidOverscroll(WebFloatSize(0, 100), WebFloatSize(0, 100),
WebFloatPoint(100, 100), WebFloatSize(),
cc::OverscrollBehavior()));
ScrollUpdate(&web_view_helper, 0, -100);
ScrollUpdate(&web_view_helper, 0, -100);
Mock::VerifyAndClearExpectations(&client);
// TODO(bokan): This has never worked but by the accident that this test was
// being run in a WebView without a size. This test should be fixed along with
// the bug, crbug.com/589320.
// Page scrolls vertically, but over-scrolls horizontally.
// EXPECT_CALL(client, didOverscroll(WebFloatSize(-100, 0), WebFloatSize(-100,
// 0), WebFloatPoint(100, 100), WebFloatSize(), cc::OverscrollBehavior()));
// ScrollUpdate(&webViewHelper, 100, 50);
// Mock::VerifyAndClearExpectations(&client);
// Scrolling up, Overscroll is not reported.
// EXPECT_CALL(client, didOverscroll(_, _, _, _, _)).Times(0);
// ScrollUpdate(&webViewHelper, 0, -50);
// Mock::VerifyAndClearExpectations(&client);
// Page scrolls horizontally, but over-scrolls vertically.
// EXPECT_CALL(client, didOverscroll(WebFloatSize(0, 100), WebFloatSize(0,
// 100), WebFloatPoint(100, 100), WebFloatSize(),
// cc::OverscrollBehavior()));
// ScrollUpdate(&webViewHelper, -100, -100);
// Mock::VerifyAndClearExpectations(&client);
}
TEST_P(WebFrameOverscrollTest, RootLayerOverscrolledOnInnerDivOverScroll) {
OverscrollWebWidgetClient client;
frame_test_helpers::TestWebViewClient view_client(&client);
RegisterMockedHttpURLLoad("overscroll/div-overscroll.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "overscroll/div-overscroll.html", nullptr, &view_client,
ConfigureAndroid);
web_view_helper.Resize(WebSize(200, 200));
ScrollBegin(&web_view_helper, 0, -316);
// Scroll the Div to the end.
EXPECT_CALL(client, DidOverscroll(_, _, _, _, _)).Times(0);
ScrollUpdate(&web_view_helper, 0, -316);
Mock::VerifyAndClearExpectations(&client);
ScrollEnd(&web_view_helper);
ScrollBegin(&web_view_helper, 0, -150);
// Now On Scrolling DIV, scroll is bubbled and root layer is over-scrolled.
EXPECT_CALL(client, DidOverscroll(WebFloatSize(0, 50), WebFloatSize(0, 50),
WebFloatPoint(100, 100), WebFloatSize(),
cc::OverscrollBehavior()));
ScrollUpdate(&web_view_helper, 0, -150);
Mock::VerifyAndClearExpectations(&client);
}
TEST_P(WebFrameOverscrollTest, RootLayerOverscrolledOnInnerIFrameOverScroll) {
OverscrollWebWidgetClient client;
frame_test_helpers::TestWebViewClient view_client(&client);
RegisterMockedHttpURLLoad("overscroll/iframe-overscroll.html");
RegisterMockedHttpURLLoad("overscroll/scrollable-iframe.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "overscroll/iframe-overscroll.html", nullptr, &view_client,
ConfigureAndroid);
web_view_helper.Resize(WebSize(200, 200));
ScrollBegin(&web_view_helper, 0, -320);
// Scroll the IFrame to the end.
EXPECT_CALL(client, DidOverscroll(_, _, _, _, _)).Times(0);
// This scroll will fully scroll the iframe but will be consumed before being
// counted as overscroll.
ScrollUpdate(&web_view_helper, 0, -320);
// This scroll will again target the iframe but wont bubble further up. Make
// sure that the unused scroll isn't handled as overscroll.
ScrollUpdate(&web_view_helper, 0, -50);
Mock::VerifyAndClearExpectations(&client);
ScrollEnd(&web_view_helper);
ScrollBegin(&web_view_helper, 0, -150);
// Now On Scrolling IFrame, scroll is bubbled and root layer is over-scrolled.
EXPECT_CALL(client, DidOverscroll(WebFloatSize(0, 50), WebFloatSize(0, 50),
WebFloatPoint(100, 100), WebFloatSize(),
cc::OverscrollBehavior()));
ScrollUpdate(&web_view_helper, 0, -150);
Mock::VerifyAndClearExpectations(&client);
ScrollEnd(&web_view_helper);
}
TEST_P(WebFrameOverscrollTest, ScaledPageRootLayerOverscrolled) {
OverscrollWebWidgetClient client;
frame_test_helpers::TestWebViewClient view_client(&client);
RegisterMockedHttpURLLoad("overscroll/overscroll.html");
frame_test_helpers::WebViewHelper web_view_helper;
WebViewImpl* web_view_impl = web_view_helper.InitializeAndLoad(
base_url_ + "overscroll/overscroll.html", nullptr, &view_client,
ConfigureAndroid);
web_view_helper.Resize(WebSize(200, 200));
web_view_impl->SetPageScaleFactor(3.0);
// Calculation of accumulatedRootOverscroll and unusedDelta on scaled page.
// The point is (99, 99) because we clamp in the division by 3 to 33 so when
// we go back to viewport coordinates it becomes (99, 99).
ScrollBegin(&web_view_helper, 0, 30);
EXPECT_CALL(client, DidOverscroll(WebFloatSize(0, -30), WebFloatSize(0, -30),
WebFloatPoint(99, 99), WebFloatSize(),
cc::OverscrollBehavior()));
ScrollUpdate(&web_view_helper, 0, 30);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client, DidOverscroll(WebFloatSize(0, -30), WebFloatSize(0, -60),
WebFloatPoint(99, 99), WebFloatSize(),
cc::OverscrollBehavior()));
ScrollUpdate(&web_view_helper, 0, 30);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client,
DidOverscroll(WebFloatSize(-30, -30), WebFloatSize(-30, -90),
WebFloatPoint(99, 99), WebFloatSize(),
cc::OverscrollBehavior()));
ScrollUpdate(&web_view_helper, 30, 30);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client,
DidOverscroll(WebFloatSize(-30, 0), WebFloatSize(-60, -90),
WebFloatPoint(99, 99), WebFloatSize(),
cc::OverscrollBehavior()));
ScrollUpdate(&web_view_helper, 30, 0);
Mock::VerifyAndClearExpectations(&client);
// Overscroll is not reported.
EXPECT_CALL(client, DidOverscroll(_, _, _, _, _)).Times(0);
ScrollEnd(&web_view_helper);
Mock::VerifyAndClearExpectations(&client);
}
TEST_P(WebFrameOverscrollTest, NoOverscrollForSmallvalues) {
OverscrollWebWidgetClient client;
frame_test_helpers::TestWebViewClient view_client(&client);
RegisterMockedHttpURLLoad("overscroll/overscroll.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "overscroll/overscroll.html",
nullptr, &view_client, ConfigureAndroid);
web_view_helper.Resize(WebSize(200, 200));
ScrollBegin(&web_view_helper, 10, 10);
EXPECT_CALL(client,
DidOverscroll(WebFloatSize(-10, -10), WebFloatSize(-10, -10),
WebFloatPoint(100, 100), WebFloatSize(),
cc::OverscrollBehavior()));
ScrollUpdate(&web_view_helper, 10, 10);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client,
DidOverscroll(WebFloatSize(0, -0.10), WebFloatSize(-10, -10.10),
WebFloatPoint(100, 100), WebFloatSize(),
cc::OverscrollBehavior()));
ScrollUpdate(&web_view_helper, 0, 0.10);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client, DidOverscroll(WebFloatSize(-0.10, 0),
WebFloatSize(-10.10, -10.10),
WebFloatPoint(100, 100), WebFloatSize(),
cc::OverscrollBehavior()));
ScrollUpdate(&web_view_helper, 0.10, 0);
Mock::VerifyAndClearExpectations(&client);
// For residual values overscrollDelta should be reset and didOverscroll
// shouldn't be called.
EXPECT_CALL(client, DidOverscroll(_, _, _, _, _)).Times(0);
ScrollUpdate(&web_view_helper, 0, 0.09);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client, DidOverscroll(_, _, _, _, _)).Times(0);
ScrollUpdate(&web_view_helper, 0.09, 0.09);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client, DidOverscroll(_, _, _, _, _)).Times(0);
ScrollUpdate(&web_view_helper, 0.09, 0);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client, DidOverscroll(_, _, _, _, _)).Times(0);
ScrollUpdate(&web_view_helper, 0, -0.09);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client, DidOverscroll(_, _, _, _, _)).Times(0);
ScrollUpdate(&web_view_helper, -0.09, -0.09);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client, DidOverscroll(_, _, _, _, _)).Times(0);
ScrollUpdate(&web_view_helper, -0.09, 0);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client, DidOverscroll(_, _, _, _, _)).Times(0);
ScrollEnd(&web_view_helper);
Mock::VerifyAndClearExpectations(&client);
}
TEST_P(WebFrameOverscrollTest, OverscrollBehaviorAffectsDidOverscroll) {
OverscrollWebWidgetClient client;
frame_test_helpers::TestWebViewClient view_client(&client);
RegisterMockedHttpURLLoad("overscroll/overscroll.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "overscroll/overscroll.html",
nullptr, &view_client, ConfigureAndroid);
web_view_helper.Resize(WebSize(200, 200));
WebLocalFrame* mainFrame =
web_view_helper.GetWebView()->MainFrame()->ToWebLocalFrame();
mainFrame->ExecuteScript(
WebScriptSource(WebString("document.body.style="
"'overscroll-behavior: auto;'")));
ScrollBegin(&web_view_helper, 100, 116);
EXPECT_CALL(client,
DidOverscroll(WebFloatSize(-100, -100), WebFloatSize(-100, -100),
WebFloatPoint(100, 100), WebFloatSize(),
cc::OverscrollBehavior(
cc::OverscrollBehavior::OverscrollBehaviorType::
kOverscrollBehaviorTypeAuto)));
ScrollUpdate(&web_view_helper, 100, 100);
Mock::VerifyAndClearExpectations(&client);
mainFrame->ExecuteScript(
WebScriptSource(WebString("document.body.style="
"'overscroll-behavior: contain;'")));
ScrollBegin(&web_view_helper, 100, 116);
EXPECT_CALL(client,
DidOverscroll(WebFloatSize(-100, -100), WebFloatSize(-200, -200),
WebFloatPoint(100, 100), WebFloatSize(),
cc::OverscrollBehavior(
cc::OverscrollBehavior::OverscrollBehaviorType::
kOverscrollBehaviorTypeContain)));
ScrollUpdate(&web_view_helper, 100, 100);
Mock::VerifyAndClearExpectations(&client);
mainFrame->ExecuteScript(
WebScriptSource(WebString("document.body.style="
"'overscroll-behavior: none;'")));
ScrollBegin(&web_view_helper, 100, 116);
EXPECT_CALL(client,
DidOverscroll(WebFloatSize(-100, -100), WebFloatSize(-300, -300),
WebFloatPoint(100, 100), WebFloatSize(),
cc::OverscrollBehavior(
cc::OverscrollBehavior::OverscrollBehaviorType::
kOverscrollBehaviorTypeNone)));
ScrollUpdate(&web_view_helper, 100, 100);
Mock::VerifyAndClearExpectations(&client);
}
TEST_P(WebFrameOverscrollTest, OnlyMainFrameOverscrollBehaviorHasEffect) {
OverscrollWebWidgetClient client;
frame_test_helpers::TestWebViewClient view_client(&client);
RegisterMockedHttpURLLoad("overscroll/iframe-overscroll.html");
RegisterMockedHttpURLLoad("overscroll/scrollable-iframe.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "overscroll/iframe-overscroll.html", nullptr, &view_client,
ConfigureAndroid);
web_view_helper.Resize(WebSize(200, 200));
WebLocalFrame* mainFrame =
web_view_helper.GetWebView()->MainFrame()->ToWebLocalFrame();
mainFrame->ExecuteScript(
WebScriptSource(WebString("document.body.style="
"'overscroll-behavior: auto;'")));
WebLocalFrame* subframe = web_view_helper.GetWebView()
->MainFrame()
->FirstChild()
->ToWebLocalFrame();
subframe->ExecuteScript(
WebScriptSource(WebString("document.body.style="
"'overscroll-behavior: none;'")));
ScrollBegin(&web_view_helper, 100, 116);
EXPECT_CALL(client,
DidOverscroll(WebFloatSize(-100, -100), WebFloatSize(-100, -100),
WebFloatPoint(100, 100), WebFloatSize(),
cc::OverscrollBehavior(
cc::OverscrollBehavior::OverscrollBehaviorType::
kOverscrollBehaviorTypeAuto)));
ScrollUpdate(&web_view_helper, 100, 100);
Mock::VerifyAndClearExpectations(&client);
mainFrame->ExecuteScript(
WebScriptSource(WebString("document.body.style="
"'overscroll-behavior: contain;'")));
EXPECT_CALL(client,
DidOverscroll(WebFloatSize(-100, -100), WebFloatSize(-200, -200),
WebFloatPoint(100, 100), WebFloatSize(),
cc::OverscrollBehavior(
cc::OverscrollBehavior::OverscrollBehaviorType::
kOverscrollBehaviorTypeContain)));
ScrollUpdate(&web_view_helper, 100, 100);
Mock::VerifyAndClearExpectations(&client);
}
TEST_F(WebFrameTest, OrientationFrameDetach) {
ScopedOrientationEventForTest orientation_event(true);
RegisterMockedHttpURLLoad("orientation-frame-detach.html");
frame_test_helpers::WebViewHelper web_view_helper;
WebViewImpl* web_view_impl = web_view_helper.InitializeAndLoad(
base_url_ + "orientation-frame-detach.html");
web_view_impl->MainFrameImpl()->SendOrientationChangeEvent();
}
#if defined(THREAD_SANITIZER)
TEST_F(WebFrameTest, DISABLED_MaxFramesDetach) {
#else
TEST_F(WebFrameTest, MaxFramesDetach) {
#endif // defined(THREAD_SANITIZER)
RegisterMockedHttpURLLoad("max-frames-detach.html");
frame_test_helpers::WebViewHelper web_view_helper;
WebViewImpl* web_view_impl =
web_view_helper.InitializeAndLoad(base_url_ + "max-frames-detach.html");
web_view_impl->MainFrameImpl()->CollectGarbageForTesting();
}
TEST_F(WebFrameTest, ImageDocumentLoadFinishTime) {
// Loading an image resource directly generates an ImageDocument with
// the document loader feeding image data into the resource of a generated
// img tag. We expect the load finish time to be the same for the document
// and the image resource.
RegisterMockedHttpURLLoadWithMimeType("white-1x1.png", "image/png");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "white-1x1.png");
WebViewImpl* web_view = web_view_helper.GetWebView();
Document* document = web_view->MainFrameImpl()->GetFrame()->GetDocument();
EXPECT_TRUE(document);
EXPECT_TRUE(document->IsImageDocument());
ImageDocument* img_document = ToImageDocument(document);
ImageResource* resource = img_document->CachedImageResourceDeprecated();
EXPECT_TRUE(resource);
EXPECT_NE(TimeTicks(), resource->LoadFinishTime());
DocumentLoader* loader = document->Loader();
EXPECT_TRUE(loader);
EXPECT_EQ(loader->GetTiming().ResponseEnd(), resource->LoadFinishTime());
}
TEST_F(WebFrameTest, CopyImageDocument) {
// After loading an image document, we should be able to copy it directly.
RegisterMockedHttpURLLoadWithMimeType("white-1x1.png", "image/png");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "white-1x1.png");
WebViewImpl* web_view = web_view_helper.GetWebView();
WebLocalFrameImpl* web_frame = web_view->MainFrameImpl();
Document* document = web_frame->GetFrame()->GetDocument();
ASSERT_TRUE(document);
EXPECT_TRUE(document->IsImageDocument());
EXPECT_TRUE(SystemClipboard::GetInstance().ReadAvailableTypes().IsEmpty());
bool result = web_frame->ExecuteCommand("Copy");
test::RunPendingTasks();
EXPECT_TRUE(result);
Vector<String> types = SystemClipboard::GetInstance().ReadAvailableTypes();
EXPECT_EQ(2u, types.size());
EXPECT_EQ("text/html", types[0]);
EXPECT_EQ("image/png", types[1]);
// Clear clipboard data
SystemClipboard::GetInstance().WritePlainText("");
}
class CallbackOrderingWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
CallbackOrderingWebFrameClient() : callback_count_(0) {}
~CallbackOrderingWebFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
void DidStartLoading() override {
EXPECT_EQ(0, callback_count_++);
frame_test_helpers::TestWebFrameClient::DidStartLoading();
}
void DidStartProvisionalLoad(WebDocumentLoader*) override {
EXPECT_EQ(1, callback_count_++);
}
void DidCommitProvisionalLoad(const WebHistoryItem&,
WebHistoryCommitType,
WebGlobalObjectReusePolicy) override {
EXPECT_EQ(2, callback_count_++);
}
void DidFinishDocumentLoad() override { EXPECT_EQ(3, callback_count_++); }
void DidHandleOnloadEvents() override { EXPECT_EQ(4, callback_count_++); }
void DidFinishLoad() override { EXPECT_EQ(5, callback_count_++); }
void DidStopLoading() override {
EXPECT_EQ(6, callback_count_++);
frame_test_helpers::TestWebFrameClient::DidStopLoading();
}
private:
int callback_count_;
};
TEST_F(WebFrameTest, CallbackOrdering) {
RegisterMockedHttpURLLoad("foo.html");
CallbackOrderingWebFrameClient client;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "foo.html", &client);
}
class TestWebRemoteFrameClientForVisibility
: public frame_test_helpers::TestWebRemoteFrameClient {
public:
TestWebRemoteFrameClientForVisibility() : visible_(true) {}
~TestWebRemoteFrameClientForVisibility() override = default;
// frame_test_helpers::TestWebRemoteFrameClient:
void VisibilityChanged(bool visible) override { visible_ = visible; }
bool IsVisible() const { return visible_; }
private:
bool visible_;
};
class WebFrameVisibilityChangeTest : public WebFrameTest {
public:
WebFrameVisibilityChangeTest() {
RegisterMockedHttpURLLoad("visible_iframe.html");
RegisterMockedHttpURLLoad("single_iframe.html");
frame_ =
web_view_helper_.InitializeAndLoad(base_url_ + "single_iframe.html")
->MainFrameImpl();
web_remote_frame_ = frame_test_helpers::CreateRemote(&remote_frame_client_);
}
~WebFrameVisibilityChangeTest() override = default;
void ExecuteScriptOnMainFrame(const WebScriptSource& script) {
MainFrame()->ExecuteScript(script);
MainFrame()->View()->MainFrameWidget()->UpdateAllLifecyclePhases(
WebWidget::LifecycleUpdateReason::kTest);
RunPendingTasks();
}
void SwapLocalFrameToRemoteFrame() {
LastChild(MainFrame())->Swap(RemoteFrame());
}
WebLocalFrame* MainFrame() { return frame_; }
WebRemoteFrameImpl* RemoteFrame() { return web_remote_frame_; }
TestWebRemoteFrameClientForVisibility* RemoteFrameClient() {
return &remote_frame_client_;
}
private:
TestWebRemoteFrameClientForVisibility remote_frame_client_;
frame_test_helpers::WebViewHelper web_view_helper_;
WebLocalFrame* frame_;
Persistent<WebRemoteFrameImpl> web_remote_frame_;
};
TEST_F(WebFrameVisibilityChangeTest, RemoteFrameVisibilityChange) {
SwapLocalFrameToRemoteFrame();
ExecuteScriptOnMainFrame(WebScriptSource(
"document.querySelector('iframe').style.display = 'none';"));
EXPECT_FALSE(RemoteFrameClient()->IsVisible());
ExecuteScriptOnMainFrame(WebScriptSource(
"document.querySelector('iframe').style.display = 'block';"));
EXPECT_TRUE(RemoteFrameClient()->IsVisible());
}
TEST_F(WebFrameVisibilityChangeTest, RemoteFrameParentVisibilityChange) {
SwapLocalFrameToRemoteFrame();
ExecuteScriptOnMainFrame(
WebScriptSource("document.querySelector('iframe').parentElement.style."
"display = 'none';"));
EXPECT_FALSE(RemoteFrameClient()->IsVisible());
}
static void EnableGlobalReuseForUnownedMainFrames(WebSettings* settings) {
settings->SetShouldReuseGlobalForUnownedMainFrame(true);
}
// A main frame with no opener should have a unique security origin. Thus, the
// global should never be reused on the initial navigation.
TEST(WebFrameGlobalReuseTest, MainFrameWithNoOpener) {
frame_test_helpers::WebViewHelper helper;
helper.Initialize();
WebLocalFrame* main_frame = helper.LocalMainFrame();
v8::HandleScope scope(v8::Isolate::GetCurrent());
main_frame->ExecuteScript(WebScriptSource("hello = 'world';"));
frame_test_helpers::LoadFrame(main_frame, "data:text/html,new page");
v8::Local<v8::Value> result =
main_frame->ExecuteScriptAndReturnValue(WebScriptSource("hello"));
EXPECT_TRUE(result.IsEmpty());
}
// Child frames should never reuse the global on a cross-origin navigation, even
// if the setting is enabled. It's not safe to since the parent could have
// injected script before the initial navigation.
TEST(WebFrameGlobalReuseTest, ChildFrame) {
frame_test_helpers::WebViewHelper helper;
helper.Initialize(nullptr, nullptr, EnableGlobalReuseForUnownedMainFrames);
WebLocalFrame* main_frame = helper.LocalMainFrame();
frame_test_helpers::LoadFrame(main_frame, "data:text/html,<iframe></iframe>");
WebLocalFrame* child_frame = main_frame->FirstChild()->ToWebLocalFrame();
v8::HandleScope scope(v8::Isolate::GetCurrent());
child_frame->ExecuteScript(WebScriptSource("hello = 'world';"));
frame_test_helpers::LoadFrame(child_frame, "data:text/html,new page");
v8::Local<v8::Value> result =
child_frame->ExecuteScriptAndReturnValue(WebScriptSource("hello"));
EXPECT_TRUE(result.IsEmpty());
}
// A main frame with an opener should never reuse the global on a cross-origin
// navigation, even if the setting is enabled. It's not safe to since the opener
// could have injected script.
TEST(WebFrameGlobalReuseTest, MainFrameWithOpener) {
frame_test_helpers::WebViewHelper opener_helper;
opener_helper.Initialize();
frame_test_helpers::WebViewHelper helper;
helper.InitializeWithOpener(opener_helper.GetWebView()->MainFrame(), nullptr,
nullptr, EnableGlobalReuseForUnownedMainFrames);
WebLocalFrame* main_frame = helper.LocalMainFrame();
v8::HandleScope scope(v8::Isolate::GetCurrent());
main_frame->ExecuteScript(WebScriptSource("hello = 'world';"));
frame_test_helpers::LoadFrame(main_frame, "data:text/html,new page");
v8::Local<v8::Value> result =
main_frame->ExecuteScriptAndReturnValue(WebScriptSource("hello"));
EXPECT_TRUE(result.IsEmpty());
}
// A main frame that is unrelated to any other frame /can/ reuse the global if
// the setting is enabled. In this case, it's impossible for any other frames to
// have touched the global. Only the embedder could have injected script, and
// the embedder enabling this setting is a signal that the injected script needs
// to persist on the first navigation away from the initial empty document.
TEST(WebFrameGlobalReuseTest, ReuseForMainFrameIfEnabled) {
frame_test_helpers::WebViewHelper helper;
helper.Initialize(nullptr, nullptr, EnableGlobalReuseForUnownedMainFrames);
WebLocalFrame* main_frame = helper.LocalMainFrame();
v8::HandleScope scope(v8::Isolate::GetCurrent());
main_frame->ExecuteScript(WebScriptSource("hello = 'world';"));
frame_test_helpers::LoadFrame(main_frame, "data:text/html,new page");
v8::Local<v8::Value> result =
main_frame->ExecuteScriptAndReturnValue(WebScriptSource("hello"));
ASSERT_TRUE(result->IsString());
EXPECT_EQ("world",
ToCoreString(result->ToString(main_frame->MainWorldScriptContext())
.ToLocalChecked()));
}
class SaveImageFromDataURLWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
SaveImageFromDataURLWebFrameClient() = default;
~SaveImageFromDataURLWebFrameClient() override = default;
// WebLocalFrameClient:
void SaveImageFromDataURL(const WebString& data_url) override {
data_url_ = data_url;
}
// Local methods
const WebString& Result() const { return data_url_; }
void Reset() { data_url_ = WebString(); }
private:
WebString data_url_;
};
TEST_F(WebFrameTest, SaveImageAt) {
std::string url = base_url_ + "image-with-data-url.html";
RegisterMockedURLLoadFromBase(base_url_, "image-with-data-url.html");
url_test_helpers::RegisterMockedURLLoad(
ToKURL("http://test"), test::CoreTestDataPath("white-1x1.png"));
frame_test_helpers::WebViewHelper helper;
SaveImageFromDataURLWebFrameClient client;
WebViewImpl* web_view = helper.InitializeAndLoad(url, &client);
web_view->MainFrameWidget()->Resize(WebSize(400, 400));
UpdateAllLifecyclePhases(web_view);
WebLocalFrame* local_frame = web_view->MainFrameImpl();
client.Reset();
local_frame->SaveImageAt(WebPoint(1, 1));
EXPECT_EQ(
WebString::FromUTF8("data:image/gif;base64"
",R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="),
client.Result());
client.Reset();
local_frame->SaveImageAt(WebPoint(1, 2));
EXPECT_EQ(WebString(), client.Result());
web_view->SetPageScaleFactor(4);
web_view->SetVisualViewportOffset(WebFloatPoint(1, 1));
client.Reset();
local_frame->SaveImageAt(WebPoint(3, 3));
EXPECT_EQ(
WebString::FromUTF8("data:image/gif;base64"
",R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="),
client.Result());
// Explicitly reset to break dependency on locally scoped client.
helper.Reset();
}
TEST_F(WebFrameTest, SaveImageWithImageMap) {
std::string url = base_url_ + "image-map.html";
RegisterMockedURLLoadFromBase(base_url_, "image-map.html");
frame_test_helpers::WebViewHelper helper;
SaveImageFromDataURLWebFrameClient client;
WebViewImpl* web_view = helper.InitializeAndLoad(url, &client);
web_view->MainFrameWidget()->Resize(WebSize(400, 400));
WebLocalFrame* local_frame = web_view->MainFrameImpl();
client.Reset();
local_frame->SaveImageAt(WebPoint(25, 25));
EXPECT_EQ(
WebString::FromUTF8("data:image/gif;base64"
",R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="),
client.Result());
client.Reset();
local_frame->SaveImageAt(WebPoint(75, 25));
EXPECT_EQ(
WebString::FromUTF8("data:image/gif;base64"
",R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="),
client.Result());
client.Reset();
local_frame->SaveImageAt(WebPoint(125, 25));
EXPECT_EQ(WebString(), client.Result());
// Explicitly reset to break dependency on locally scoped client.
helper.Reset();
}
TEST_F(WebFrameTest, CopyImageWithImageMap) {
SaveImageFromDataURLWebFrameClient client;
std::string url = base_url_ + "image-map.html";
RegisterMockedURLLoadFromBase(base_url_, "image-map.html");
frame_test_helpers::WebViewHelper helper;
WebViewImpl* web_view = helper.InitializeAndLoad(url, &client);
web_view->MainFrameWidget()->Resize(WebSize(400, 400));
client.Reset();
WebLocalFrame* local_frame = web_view->MainFrameImpl();
local_frame->SaveImageAt(WebPoint(25, 25));
EXPECT_EQ(
WebString::FromUTF8("data:image/gif;base64"
",R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="),
client.Result());
client.Reset();
local_frame->SaveImageAt(WebPoint(75, 25));
EXPECT_EQ(
WebString::FromUTF8("data:image/gif;base64"
",R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="),
client.Result());
client.Reset();
local_frame->SaveImageAt(WebPoint(125, 25));
EXPECT_EQ(WebString(), client.Result());
// Explicitly reset to break dependency on locally scoped client.
helper.Reset();
}
TEST_F(WebFrameTest, LoadJavascriptURLInNewFrame) {
frame_test_helpers::WebViewHelper helper;
helper.Initialize();
std::string redirect_url = base_url_ + "foo.html";
KURL javascript_url = ToKURL("javascript:location='" + redirect_url + "'");
url_test_helpers::RegisterMockedURLLoad(ToKURL(redirect_url),
test::CoreTestDataPath("foo.html"));
helper.LocalMainFrame()->LoadJavaScriptURL(javascript_url);
// Normally, the result of the JS url replaces the existing contents on the
// Document. However, if the JS triggers a navigation, the contents should
// not be replaced.
EXPECT_EQ("", ToLocalFrame(helper.GetWebView()->GetPage()->MainFrame())
->GetDocument()
->documentElement()
->innerText());
}
class TestResourcePriorityWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
class ExpectedRequest {
public:
ExpectedRequest(const KURL& url, WebURLRequest::Priority priority)
: url(url), priority(priority), seen(false) {}
KURL url;
WebURLRequest::Priority priority;
bool seen;
};
TestResourcePriorityWebFrameClient() = default;
~TestResourcePriorityWebFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
void WillSendRequest(WebURLRequest& request) override {
ExpectedRequest* expected_request = expected_requests_.at(request.Url());
DCHECK(expected_request);
EXPECT_EQ(expected_request->priority, request.GetPriority());
expected_request->seen = true;
}
void AddExpectedRequest(const KURL& url, WebURLRequest::Priority priority) {
expected_requests_.insert(url,
std::make_unique<ExpectedRequest>(url, priority));
}
void VerifyAllRequests() {
for (const auto& request : expected_requests_)
EXPECT_TRUE(request.value->seen);
}
private:
HashMap<KURL, std::unique_ptr<ExpectedRequest>> expected_requests_;
};
TEST_F(WebFrameTest, ChangeResourcePriority) {
TestResourcePriorityWebFrameClient client;
RegisterMockedHttpURLLoad("promote_img_in_viewport_priority.html");
RegisterMockedHttpURLLoad("image_slow.pl");
RegisterMockedHttpURLLoad("image_slow_out_of_viewport.pl");
client.AddExpectedRequest(
ToKURL("http://internal.test/promote_img_in_viewport_priority.html"),
WebURLRequest::Priority::kVeryHigh);
client.AddExpectedRequest(ToKURL("http://internal.test/image_slow.pl"),
WebURLRequest::Priority::kLow);
client.AddExpectedRequest(
ToKURL("http://internal.test/image_slow_out_of_viewport.pl"),
WebURLRequest::Priority::kLow);
frame_test_helpers::WebViewHelper helper;
helper.Initialize(&client);
helper.Resize(WebSize(640, 480));
frame_test_helpers::LoadFrame(
helper.GetWebView()->MainFrameImpl(),
base_url_ + "promote_img_in_viewport_priority.html");
// Ensure the image in the viewport got promoted after the request was sent.
Resource* image = ToWebLocalFrameImpl(helper.GetWebView()->MainFrame())
->GetFrame()
->GetDocument()
->Fetcher()
->AllResources()
.at(ToKURL("http://internal.test/image_slow.pl"));
DCHECK(image);
EXPECT_EQ(ResourceLoadPriority::kHigh,
image->GetResourceRequest().Priority());
client.VerifyAllRequests();
}
TEST_F(WebFrameTest, ScriptPriority) {
TestResourcePriorityWebFrameClient client;
RegisterMockedHttpURLLoad("script_priority.html");
RegisterMockedHttpURLLoad("priorities/defer.js");
RegisterMockedHttpURLLoad("priorities/async.js");
RegisterMockedHttpURLLoad("priorities/head.js");
RegisterMockedHttpURLLoad("priorities/document-write.js");
RegisterMockedHttpURLLoad("priorities/injected.js");
RegisterMockedHttpURLLoad("priorities/injected-async.js");
RegisterMockedHttpURLLoad("priorities/body.js");
client.AddExpectedRequest(ToKURL("http://internal.test/script_priority.html"),
WebURLRequest::Priority::kVeryHigh);
client.AddExpectedRequest(ToKURL("http://internal.test/priorities/defer.js"),
WebURLRequest::Priority::kLow);
client.AddExpectedRequest(ToKURL("http://internal.test/priorities/async.js"),
WebURLRequest::Priority::kLow);
client.AddExpectedRequest(ToKURL("http://internal.test/priorities/head.js"),
WebURLRequest::Priority::kHigh);
client.AddExpectedRequest(
ToKURL("http://internal.test/priorities/document-write.js"),
WebURLRequest::Priority::kHigh);
client.AddExpectedRequest(
ToKURL("http://internal.test/priorities/injected.js"),
WebURLRequest::Priority::kLow);
client.AddExpectedRequest(
ToKURL("http://internal.test/priorities/injected-async.js"),
WebURLRequest::Priority::kLow);
client.AddExpectedRequest(ToKURL("http://internal.test/priorities/body.js"),
WebURLRequest::Priority::kHigh);
frame_test_helpers::WebViewHelper helper;
helper.InitializeAndLoad(base_url_ + "script_priority.html", &client);
client.VerifyAllRequests();
}
class MultipleDataChunkDelegate : public WebURLLoaderTestDelegate {
public:
MultipleDataChunkDelegate() = default;
~MultipleDataChunkDelegate() override = default;
// WebURLLoaderTestDelegate:
void DidReceiveData(WebURLLoaderClient* original_client,
const char* data,
int data_length) override {
EXPECT_GT(data_length, 16);
original_client->DidReceiveData(data, 16);
// This didReceiveData call shouldn't crash due to a failed assertion.
original_client->DidReceiveData(data + 16, data_length - 16);
}
};
TEST_F(WebFrameTest, ImageDocumentDecodeError) {
std::string url = base_url_ + "not_an_image.ico";
url_test_helpers::RegisterMockedURLLoad(
ToKURL(url), test::CoreTestDataPath("not_an_image.ico"), "image/x-icon");
MultipleDataChunkDelegate delegate;
Platform::Current()->GetURLLoaderMockFactory()->SetLoaderDelegate(&delegate);
frame_test_helpers::WebViewHelper helper;
helper.InitializeAndLoad(url);
Platform::Current()->GetURLLoaderMockFactory()->SetLoaderDelegate(nullptr);
Document* document =
ToLocalFrame(helper.GetWebView()->GetPage()->MainFrame())->GetDocument();
EXPECT_TRUE(document->IsImageDocument());
EXPECT_EQ(ResourceStatus::kDecodeError,
ToImageDocument(document)->CachedImage()->GetContentStatus());
}
// Ensure that the root layer -- whose size is ordinarily derived from the
// content size -- maintains a minimum height matching the viewport in cases
// where the content is smaller.
TEST_F(WebFrameTest, RootLayerMinimumHeight) {
constexpr int kViewportWidth = 320;
constexpr int kViewportHeight = 640;
constexpr int kBrowserControlsHeight = 100;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(nullptr, nullptr, ConfigureAndroid);
WebViewImpl* web_view = web_view_helper.GetWebView();
web_view->ResizeWithBrowserControls(
WebSize(kViewportWidth, kViewportHeight - kBrowserControlsHeight),
kBrowserControlsHeight, 0, true);
InitializeWithHTML(
*web_view->MainFrameImpl()->GetFrame(),
"<!DOCTYPE html>"
"<meta name='viewport' content='width=device-width, initial-scale=1'>"
"<style>"
" html, body {width:100%;height:540px;margin:0px}"
" #elem {"
" overflow: scroll;"
" width: 100px;"
" height: 10px;"
" position: fixed;"
" left: 0px;"
" bottom: 0px;"
" }"
"</style>"
"<div id='elem'></div>");
UpdateAllLifecyclePhases(web_view);
Document* document = web_view->MainFrameImpl()->GetFrame()->GetDocument();
LocalFrameView* frame_view = web_view->MainFrameImpl()->GetFrameView();
PaintLayerCompositor* compositor = frame_view->GetLayoutView()->Compositor();
EXPECT_EQ(kViewportHeight - kBrowserControlsHeight,
compositor->RootLayer()->BoundingBoxForCompositing().Height());
document->View()->SetTracksPaintInvalidations(true);
web_view->ResizeWithBrowserControls(WebSize(kViewportWidth, kViewportHeight),
kBrowserControlsHeight, 0, false);
EXPECT_EQ(kViewportHeight,
compositor->RootLayer()->BoundingBoxForCompositing().Height());
EXPECT_EQ(kViewportHeight, compositor->RootGraphicsLayer()->Size().height());
EXPECT_EQ(kViewportHeight, compositor->RootGraphicsLayer()->Size().height());
const RasterInvalidationTracking* invalidation_tracking =
document->GetLayoutView()
->Layer()
->GetCompositedLayerMapping()
->MainGraphicsLayer()
->GetRasterInvalidationTracking();
ASSERT_TRUE(invalidation_tracking);
const auto& raster_invalidations = invalidation_tracking->Invalidations();
// We don't issue raster invalidation, because the content paints into the
// scrolling contents layer whose size hasn't changed.
EXPECT_TRUE(raster_invalidations.IsEmpty());
document->View()->SetTracksPaintInvalidations(false);
}
// Load a page with display:none set and try to scroll it. It shouldn't crash
// due to lack of layoutObject. crbug.com/653327.
TEST_F(WebFrameTest, ScrollBeforeLayoutDoesntCrash) {
RegisterMockedHttpURLLoad("display-none.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "display-none.html");
WebViewImpl* web_view = web_view_helper.GetWebView();
web_view_helper.Resize(WebSize(640, 480));
Document* document = web_view->MainFrameImpl()->GetFrame()->GetDocument();
document->documentElement()->SetLayoutObject(nullptr);
WebGestureEvent begin_event(
WebInputEvent::kGestureScrollBegin, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(), kWebGestureDeviceTouchpad);
WebGestureEvent update_event(
WebInputEvent::kGestureScrollUpdate, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(), kWebGestureDeviceTouchpad);
WebGestureEvent end_event(
WebInputEvent::kGestureScrollEnd, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(), kWebGestureDeviceTouchpad);
// Try GestureScrollEnd and GestureScrollUpdate first to make sure that not
// seeing a Begin first doesn't break anything. (This currently happens).
web_view_helper.GetWebView()->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(end_event));
web_view_helper.GetWebView()->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(update_event));
// Try a full Begin/Update/End cycle.
web_view_helper.GetWebView()->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(begin_event));
web_view_helper.GetWebView()->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(update_event));
web_view_helper.GetWebView()->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(end_event));
}
TEST_F(WebFrameTest, MouseOverDifferntNodeClearsTooltip) {
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize();
web_view_helper.Resize(WebSize(200, 200));
WebViewImpl* web_view = web_view_helper.GetWebView();
InitializeWithHTML(
*web_view->MainFrameImpl()->GetFrame(),
"<head>"
" <style type='text/css'>"
" div"
" {"
" width: 200px;"
" height: 100px;"
" background-color: #eeeeff;"
" }"
" div:hover"
" {"
" background-color: #ddddff;"
" }"
" </style>"
"</head>"
"<body>"
" <div id='div1' title='Title Attribute Value'>Hover HERE</div>"
" <div id='div2' title='Title Attribute Value'>Then HERE</div>"
" <br><br><br>"
"</body>");
UpdateAllLifecyclePhases(web_view);
Document* document = web_view->MainFrameImpl()->GetFrame()->GetDocument();
Element* div1_tag = document->getElementById("div1");
HitTestResult hit_test_result = web_view->CoreHitTestResultAt(
gfx::Point(div1_tag->OffsetLeft() + 5, div1_tag->OffsetTop() + 5));
EXPECT_TRUE(hit_test_result.InnerElement());
// Mouse over link. Mouse cursor should be hand.
WebMouseEvent mouse_move_over_link_event(
WebInputEvent::kMouseMove,
WebFloatPoint(div1_tag->OffsetLeft() + 5, div1_tag->OffsetTop() + 5),
WebFloatPoint(div1_tag->OffsetLeft() + 5, div1_tag->OffsetTop() + 5),
WebPointerProperties::Button::kNoButton, 0, WebInputEvent::kNoModifiers,
CurrentTimeTicks());
mouse_move_over_link_event.SetFrameScale(1);
document->GetFrame()->GetEventHandler().HandleMouseMoveEvent(
mouse_move_over_link_event, Vector<WebMouseEvent>(),
Vector<WebMouseEvent>());
EXPECT_EQ(
document->HoverElement(),
document->GetFrame()->GetChromeClient().LastSetTooltipNodeForTesting());
EXPECT_EQ(
div1_tag,
document->GetFrame()->GetChromeClient().LastSetTooltipNodeForTesting());
Element* div2_tag = document->getElementById("div2");
WebMouseEvent mouse_move_event(
WebInputEvent::kMouseMove,
WebFloatPoint(div2_tag->OffsetLeft() + 5, div2_tag->OffsetTop() + 5),
WebFloatPoint(div2_tag->OffsetLeft() + 5, div2_tag->OffsetTop() + 5),
WebPointerProperties::Button::kNoButton, 0, WebInputEvent::kNoModifiers,
CurrentTimeTicks());
mouse_move_event.SetFrameScale(1);
document->GetFrame()->GetEventHandler().HandleMouseMoveEvent(
mouse_move_event, Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
EXPECT_EQ(
document->HoverElement(),
document->GetFrame()->GetChromeClient().LastSetTooltipNodeForTesting());
EXPECT_EQ(
div2_tag,
document->GetFrame()->GetChromeClient().LastSetTooltipNodeForTesting());
}
class WebFrameSimTest : public SimTest {
public:
void UseAndroidSettings() {
WebView().GetPage()->GetSettings().SetViewportMetaEnabled(true);
WebView().GetPage()->GetSettings().SetViewportEnabled(true);
WebView().GetPage()->GetSettings().SetMainFrameResizesAreOrientationChanges(
true);
WebView().GetPage()->GetSettings().SetViewportStyle(
WebViewportStyle::kMobile);
WebView().GetSettings()->SetAutoZoomFocusedNodeToLegibleScale(true);
WebView().GetSettings()->SetShrinksViewportContentToFit(true);
WebView().SetDefaultPageScaleLimits(0.25f, 5);
}
};
TEST_F(WebFrameSimTest, HitTestWithIgnoreClippingAtNegativeOffset) {
WebView().MainFrameWidget()->Resize(WebSize(500, 300));
WebView().GetPage()->GetSettings().SetTextAutosizingEnabled(false);
SimRequest r("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
r.Complete(R"HTML(
<!DOCTYPE html>
<style>
body, html {
width: 100%;
height: 1000px;
margin: 0;
}
#top {
position: absolute;
top: 500px;
height: 100px;
width: 100%;
}
#bottom {
position: absolute;
top: 600px;
width: 100%;
height: 500px;
}
</style>
<div id="top"></div>
<div id="bottom"></div>
)HTML");
Compositor().BeginFrame();
LocalFrameView* frame_view =
ToLocalFrame(WebView().GetPage()->MainFrame())->View();
frame_view->GetScrollableArea()->SetScrollOffset(ScrollOffset(0, 600),
kProgrammaticScroll);
Compositor().BeginFrame();
HitTestRequest request = HitTestRequest::kMove | HitTestRequest::kReadOnly |
HitTestRequest::kActive |
HitTestRequest::kIgnoreClipping;
HitTestLocation location(
frame_view->ConvertFromRootFrame(LayoutPoint(100, -50)));
HitTestResult result(request, location);
frame_view->GetLayoutView()->HitTest(location, result);
EXPECT_EQ(GetDocument().getElementById("top"), result.InnerNode());
}
TEST_F(WebFrameSimTest, TickmarksDocumentRelative) {
WebView().MainFrameWidget()->Resize(WebSize(500, 300));
WebView().GetPage()->GetSettings().SetTextAutosizingEnabled(false);
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
body, html {
width: 4000px;
height: 4000px;
margin: 0;
}
div {
position: absolute;
left: 800px;
top: 2000px;
}
</style>
<div>test</div>
)HTML");
Compositor().BeginFrame();
WebLocalFrameImpl* frame = ToWebLocalFrameImpl(WebView().MainFrame());
LocalFrameView* frame_view =
ToLocalFrame(WebView().GetPage()->MainFrame())->View();
frame_view->GetScrollableArea()->SetScrollOffset(ScrollOffset(3000, 1000),
kProgrammaticScroll);
auto options = mojom::blink::FindOptions::New();
options->run_synchronously_for_testing = true;
WebString search_text = WebString::FromUTF8("test");
const int kFindIdentifier = 12345;
EXPECT_TRUE(frame->GetFindInPage()->FindInternal(kFindIdentifier, search_text,
*options, false));
frame->EnsureTextFinder().ResetMatchCount();
frame->EnsureTextFinder().StartScopingStringMatches(kFindIdentifier,
search_text, *options);
// Get the tickmarks for the original find request.
Vector<IntRect> original_tickmarks;
frame_view->LayoutViewport()->GetTickmarks(original_tickmarks);
EXPECT_EQ(1u, original_tickmarks.size());
EXPECT_EQ(IntPoint(800, 2000), original_tickmarks[0].Location());
}
TEST_F(WebFrameSimTest, FindInPageSelectNextMatch) {
WebView().MainFrameWidget()->Resize(WebSize(500, 300));
WebView().GetPage()->GetSettings().SetTextAutosizingEnabled(false);
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
body, html {
width: 4000px;
height: 4000px;
margin: 0;
}
#box1 {
position: absolute;
left: 800px;
top: 2000px;
}
#box2 {
position: absolute;
left: 1000px;
top: 3000px;
}
</style>
<div id="box1">test</div>
<div id="box2">test</div>
)HTML");
Compositor().BeginFrame();
WebLocalFrameImpl* frame = ToWebLocalFrameImpl(WebView().MainFrame());
LocalFrameView* frame_view =
ToLocalFrame(WebView().GetPage()->MainFrame())->View();
Element* box1 = GetDocument().getElementById("box1");
Element* box2 = GetDocument().getElementById("box2");
IntRect box1_rect = box1->GetLayoutObject()->AbsoluteBoundingBoxRect();
IntRect box2_rect = box2->GetLayoutObject()->AbsoluteBoundingBoxRect();
frame_view->GetScrollableArea()->SetScrollOffset(ScrollOffset(3000, 1000),
kProgrammaticScroll);
auto options = mojom::blink::FindOptions::New();
options->run_synchronously_for_testing = true;
WebString search_text = WebString::FromUTF8("test");
const int kFindIdentifier = 12345;
EXPECT_TRUE(frame->GetFindInPage()->FindInternal(kFindIdentifier, search_text,
*options, false));
frame->EnsureTextFinder().ResetMatchCount();
frame->EnsureTextFinder().StartScopingStringMatches(kFindIdentifier,
search_text, *options);
WebVector<WebFloatRect> web_match_rects =
frame->EnsureTextFinder().FindMatchRects();
ASSERT_EQ(2ul, web_match_rects.size());
FloatRect result_rect = static_cast<FloatRect>(web_match_rects[0]);
frame->EnsureTextFinder().SelectNearestFindMatch(result_rect.Center(),
nullptr);
LocalFrame* local_frame = ToLocalFrame(WebView().GetPage()->MainFrame());
VisualViewport& visual_viewport = local_frame->GetPage()->GetVisualViewport();
EXPECT_TRUE(visual_viewport.VisibleRectInDocument().Contains(box1_rect));
result_rect = static_cast<FloatRect>(web_match_rects[1]);
frame->EnsureTextFinder().SelectNearestFindMatch(result_rect.Center(),
nullptr);
EXPECT_TRUE(visual_viewport.VisibleRectInDocument().Contains(box2_rect))
<< "Box [" << box2_rect.ToString() << "] is not visible in viewport ["
<< visual_viewport.VisibleRectInDocument().ToString() << "]";
}
// Test bubbling a document (End key) scroll from an inner iframe. This test
// passes if it does not crash. https://crbug.com/904247.
TEST_F(WebFrameSimTest, ScrollToEndBubblingCrash) {
WebView().MainFrameWidget()->Resize(WebSize(500, 300));
WebView().GetPage()->GetSettings().SetScrollAnimatorEnabled(false);
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
body, html {
width: 100%;
height: 100%;
margin: 0;
}
#frame {
width: 100%;
height: 100%;
border: 0;
}
</style>
<iframe id="frame" srcdoc="
<!DOCTYPE html>
<style>html {height: 300%;}</style>
"></iframe>
)HTML");
Compositor().BeginFrame();
RunPendingTasks();
// Focus the iframe.
WebView().AdvanceFocus(false);
WebKeyboardEvent key_event(WebInputEvent::kRawKeyDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
key_event.windows_key_code = VKEY_END;
// Scroll the iframe to the end.
key_event.SetType(WebInputEvent::kRawKeyDown);
WebView().MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event));
key_event.SetType(WebInputEvent::kKeyUp);
WebView().MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event));
Compositor().BeginFrame();
// End key should now bubble from the iframe up to the main viewport.
key_event.SetType(WebInputEvent::kRawKeyDown);
WebView().MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event));
key_event.SetType(WebInputEvent::kKeyUp);
WebView().MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(key_event));
}
// Basic smoke test of the paint path used by the Android disambiguation popup.
TEST_F(WebFrameSimTest, DisambiguationPopupPixelTest) {
WebView().MainFrameWidget()->Resize(WebSize(400, 600));
WebView().GetPage()->GetSettings().SetTextAutosizingEnabled(false);
UseAndroidSettings();
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
body, html {
width: 4000px;
height: 4000px;
margin: 0;
}
#box {
position: absolute;
left: 200px;
top: 300px;
width: 100px;
height: 100px;
background-color: red;
}
</style>
<div id="box"></div>
)HTML");
Compositor().BeginFrame();
ASSERT_EQ(0.25f, WebView().PageScaleFactor());
// Pick exactly the rect covered by the red <div> on the page. Paint it at 4x
// magnification.
float scale = 4.f;
WebRect zoom_rect(200, 300, 100, 100);
gfx::Size canvas_size(zoom_rect.width * scale, zoom_rect.height * scale);
SkImageInfo info =
SkImageInfo::MakeN32Premul(canvas_size.width(), canvas_size.height());
size_t size = info.computeMinByteSize();
auto buffer = std::make_unique<uint8_t[]>(size);
SkBitmap bitmap;
bitmap.installPixels(info, buffer.get(), info.minRowBytes());
cc::SkiaPaintCanvas canvas(bitmap);
canvas.scale(scale, scale);
canvas.translate(-zoom_rect.x, -zoom_rect.y);
WebView().MainFrameWidget()->UpdateAllLifecyclePhases(
WebWidget::LifecycleUpdateReason::kTest);
WebView().MainFrameWidget()->PaintContentIgnoringCompositing(&canvas,
zoom_rect);
// All the pixels in the canvas should be the <div> color.
for (int x = 0; x < canvas_size.width(); ++x) {
for (int y = 0; y < canvas_size.height(); ++y) {
ASSERT_EQ(bitmap.getColor(x, y), SK_ColorRED)
<< "Mismatching pixel at (" << x << ", " << y << ")";
}
}
}
TEST_F(WebFrameSimTest, TestScrollFocusedEditableElementIntoView) {
WebView().MainFrameWidget()->Resize(WebSize(500, 300));
WebView().SetDefaultPageScaleLimits(1.f, 4);
WebView().EnableFakePageScaleAnimationForTesting(true);
WebView().GetPage()->GetSettings().SetTextAutosizingEnabled(false);
WebView().GetPage()->GetSettings().SetViewportEnabled(false);
WebView().GetSettings()->SetAutoZoomFocusedNodeToLegibleScale(true);
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
body {
margin: 0px;
}
input {
border: 0;
padding: 0;
position: absolute;
left: 200px;
top: 600px;
width: 100px;
height: 20px;
}
#content {
background: silver;
width: 500px;
height: 600px;
}
</style>
<div id="content">a</div>
<input type="text">
)HTML");
Compositor().BeginFrame();
WebView().AdvanceFocus(false);
LocalFrame* frame = ToLocalFrame(WebView().GetPage()->MainFrame());
LocalFrameView* frame_view = frame->View();
VisualViewport& visual_viewport = frame->GetPage()->GetVisualViewport();
FloatRect inputRect(200, 600, 100, 20);
frame_view->GetScrollableArea()->SetScrollOffset(ScrollOffset(0, 0),
kProgrammaticScroll);
ASSERT_EQ(FloatPoint(), visual_viewport.VisibleRectInDocument().Location());
WebView()
.MainFrameImpl()
->FrameWidget()
->ScrollFocusedEditableElementIntoView();
EXPECT_EQ(1, WebView().FakePageScaleAnimationPageScaleForTesting());
frame_view->LayoutViewport()->SetScrollOffset(
ToFloatSize(FloatPoint(
WebView().FakePageScaleAnimationTargetPositionForTesting())),
kProgrammaticScroll);
EXPECT_TRUE(visual_viewport.VisibleRectInDocument().Contains(inputRect));
// Reset the testing getters.
WebView().EnableFakePageScaleAnimationForTesting(true);
// This input is already in view, this shouldn't cause a scroll.
WebView()
.MainFrameImpl()
->FrameWidget()
->ScrollFocusedEditableElementIntoView();
EXPECT_EQ(0, WebView().FakePageScaleAnimationPageScaleForTesting());
EXPECT_EQ(IntPoint(),
WebView().FakePageScaleAnimationTargetPositionForTesting());
// Now resize the visual viewport so that the input box is no longer in view
// (e.g. a keyboard is overlayed).
WebView().MainFrameWidget()->ResizeVisualViewport(IntSize(200, 100));
ASSERT_FALSE(visual_viewport.VisibleRectInDocument().Contains(inputRect));
WebView()
.MainFrameImpl()
->FrameWidget()
->ScrollFocusedEditableElementIntoView();
frame_view->GetScrollableArea()->SetScrollOffset(
ToFloatSize(FloatPoint(
WebView().FakePageScaleAnimationTargetPositionForTesting())),
kProgrammaticScroll);
EXPECT_TRUE(visual_viewport.VisibleRectInDocument().Contains(inputRect));
EXPECT_EQ(1, WebView().FakePageScaleAnimationPageScaleForTesting());
}
TEST_F(WebFrameSimTest, ScrollFocusedIntoViewClipped) {
// The Android On-Screen Keyboard (OSK) resizes the Widget Blink is hosted
// in. When the keyboard is shown, we scroll and zoom in on the currently
// focused editable element. However, the scroll and zoom is a smoothly
// animated "PageScaleAnimation" that's performed in CC only on the viewport
// layers. There are some situations in which the widget resize causes the
// focued input to be hidden by clipping parents that aren't the main frame.
// In these cases, there's no way to scroll just the viewport to make the
// input visible, we need to also scroll those clip/scroller elements This
// test ensures we do so. https://crbug.com/270018.
UseAndroidSettings();
WebView().MainFrameWidget()->Resize(WebSize(400, 600));
WebView().EnableFakePageScaleAnimationForTesting(true);
WebView().GetPage()->GetSettings().SetTextAutosizingEnabled(false);
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
body, html {
margin: 0px;
width: 100%;
height: 100%;
}
input {
padding: 0;
position: relative;
top: 1400px;
width: 100px;
height: 20px;
}
#clip {
width: 100%;
height: 100%;
overflow: hidden;
}
#container {
width: 980px;
height: 1470px;
}
</style>
<div id="clip">
<div id="container">
<input type="text" id="target">
</div>
</div>
)HTML");
Compositor().BeginFrame();
WebView().AdvanceFocus(false);
LocalFrame* frame = ToLocalFrame(WebView().GetPage()->MainFrame());
LocalFrameView* frame_view = frame->View();
VisualViewport& visual_viewport = frame->GetPage()->GetVisualViewport();
ASSERT_EQ(FloatPoint(), visual_viewport.VisibleRectInDocument().Location());
// Simulate the keyboard being shown and resizing the widget. Cause a scroll
// into view after.
WebView().MainFrameWidget()->Resize(WebSize(400, 300));
float scale_before = visual_viewport.Scale();
WebView()
.MainFrameImpl()
->FrameWidget()
->ScrollFocusedEditableElementIntoView();
Element* input = GetDocument().getElementById("target");
IntRect input_rect(input->getBoundingClientRect()->top(),
input->getBoundingClientRect()->left(),
input->getBoundingClientRect()->width(),
input->getBoundingClientRect()->height());
IntRect visible_content_rect(IntPoint(), frame_view->Size());
EXPECT_TRUE(visible_content_rect.Contains(input_rect))
<< "Layout viewport [" << visible_content_rect.ToString()
<< "] does not contain input rect [" << input_rect.ToString()
<< "] after scroll into view.";
EXPECT_TRUE(visual_viewport.VisibleRect().Contains(input_rect))
<< "Visual viewport [" << visual_viewport.VisibleRect().ToString()
<< "] does not contain input rect [" << input_rect.ToString()
<< "] after scroll into view.";
// Make sure we also zoomed in on the input.
EXPECT_GT(WebView().FakePageScaleAnimationPageScaleForTesting(),
scale_before);
// Additional gut-check that we actually scrolled the non-user-scrollable
// clip element to make sure the input is in view.
Element* clip = GetDocument().getElementById("clip");
EXPECT_GT(clip->scrollTop(), 0);
}
TEST_F(WebFrameSimTest, DoubleTapZoomWhileScrolled) {
UseAndroidSettings();
WebView().MainFrameWidget()->Resize(WebSize(490, 500));
WebView().EnableFakePageScaleAnimationForTesting(true);
WebView().GetSettings()->SetTextAutosizingEnabled(false);
WebView().SetDefaultPageScaleLimits(0.5f, 4);
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
body {
margin: 0px;
width: 10000px;
height: 10000px;
}
#target {
position: absolute;
left: 2000px;
top: 3000px;
width: 100px;
height: 100px;
background-color: blue;
}
</style>
<div id="target"></div>
)HTML");
Compositor().BeginFrame();
LocalFrame* frame = ToLocalFrame(WebView().GetPage()->MainFrame());
LocalFrameView* frame_view = frame->View();
VisualViewport& visual_viewport = frame->GetPage()->GetVisualViewport();
FloatRect target_rect_in_document(2000, 3000, 100, 100);
ASSERT_EQ(0.5f, visual_viewport.Scale());
// Center the target in the screen.
frame_view->GetScrollableArea()->SetScrollOffset(
ScrollOffset(2000 - 440, 3000 - 450), kProgrammaticScroll);
Element* target = GetDocument().QuerySelector("#target");
DOMRect* rect = target->getBoundingClientRect();
ASSERT_EQ(440, rect->left());
ASSERT_EQ(450, rect->top());
// Double-tap on the target. Expect that we zoom in and the target is
// contained in the visual viewport.
{
gfx::Point point(445, 455);
WebRect block_bounds = ComputeBlockBoundHelper(&WebView(), point, false);
WebView().AnimateDoubleTapZoom(IntPoint(point), block_bounds);
EXPECT_TRUE(WebView().FakeDoubleTapAnimationPendingForTesting());
ScrollOffset new_offset = ToScrollOffset(
FloatPoint(WebView().FakePageScaleAnimationTargetPositionForTesting()));
float new_scale = WebView().FakePageScaleAnimationPageScaleForTesting();
visual_viewport.SetScale(new_scale);
frame_view->GetScrollableArea()->SetScrollOffset(new_offset,
kProgrammaticScroll);
EXPECT_FLOAT_EQ(1, visual_viewport.Scale());
EXPECT_TRUE(visual_viewport.VisibleRectInDocument().Contains(
target_rect_in_document));
}
// Reset the testing getters.
WebView().EnableFakePageScaleAnimationForTesting(true);
// Double-tap on the target again. We should zoom out and the target should
// remain on screen.
{
gfx::Point point(445, 455);
WebRect block_bounds = ComputeBlockBoundHelper(&WebView(), point, false);
WebView().AnimateDoubleTapZoom(IntPoint(point), block_bounds);
EXPECT_TRUE(WebView().FakeDoubleTapAnimationPendingForTesting());
FloatPoint target_offset(
WebView().FakePageScaleAnimationTargetPositionForTesting());
float new_scale = WebView().FakePageScaleAnimationPageScaleForTesting();
EXPECT_FLOAT_EQ(0.5f, new_scale);
EXPECT_TRUE(target_rect_in_document.Contains(target_offset));
}
}
TEST_F(WebFrameSimTest, ChangeBackgroundColor) {
SimRequest main_resource("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
main_resource.Complete("<!DOCTYPE html><body></body>");
Element* body = GetDocument().QuerySelector("body");
EXPECT_TRUE(!!body);
Compositor().BeginFrame();
// White is the default background of a web page.
EXPECT_EQ(SK_ColorWHITE, Compositor().background_color());
// Setting the background of the body to red will cause the background
// color of the WebView to switch to red.
body->SetInlineStyleProperty(CSSPropertyBackgroundColor, "red");
Compositor().BeginFrame();
EXPECT_EQ(SK_ColorRED, Compositor().background_color());
}
// Ensure we don't crash if we try to scroll into view the focused editable
// element which doesn't have a LayoutObject.
TEST_F(WebFrameSimTest, ScrollFocusedEditableIntoViewNoLayoutObject) {
WebView().MainFrameWidget()->Resize(WebSize(500, 600));
WebView().GetPage()->GetSettings().SetTextAutosizingEnabled(false);
SimRequest r("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
r.Complete(R"HTML(
<!DOCTYPE html>
<style>
input {
position: absolute;
top: 1000px;
left: 800px;
}
@media (max-height: 500px) {
input {
display: none;
}
}
</style>
<input id="target" type="text"></input>
)HTML");
Compositor().BeginFrame();
Element* input = GetDocument().getElementById("target");
input->focus();
ScrollableArea* area = GetDocument().View()->LayoutViewport();
area->SetScrollOffset(ScrollOffset(0, 0), kProgrammaticScroll);
ASSERT_TRUE(input->GetLayoutObject());
ASSERT_EQ(input, WebView().FocusedElement());
ASSERT_EQ(ScrollOffset(0, 0), area->GetScrollOffset());
// The resize should cause the focused element to lose its LayoutObject. If
// this resize came from the Android on-screen keyboard, this would be
// followed by a ScrollFocusedEditableElementIntoView. Ensure we don't crash.
WebView().MainFrameWidget()->Resize(WebSize(500, 300));
ASSERT_FALSE(input->GetLayoutObject());
ASSERT_EQ(input, WebView().FocusedElement());
WebFrameWidget* widget = WebView().MainFrameImpl()->FrameWidgetImpl();
widget->ScrollFocusedEditableElementIntoView();
Compositor().BeginFrame();
// Shouldn't cause any scrolling either.
EXPECT_EQ(ScrollOffset(0, 0), area->GetScrollOffset());
}
TEST_F(WebFrameSimTest, DisplayNoneIFrameHasNoLayoutObjects) {
SimRequest main_resource("https://example.com/test.html", "text/html");
SimRequest frame_resource("https://example.com/frame.html", "text/html");
LoadURL("https://example.com/test.html");
main_resource.Complete(
"<!DOCTYPE html>"
"<iframe src=frame.html style='display: none'></iframe>");
frame_resource.Complete(
"<!DOCTYPE html>"
"<html><body>This is a visible iframe.</body></html>");
Element* element = GetDocument().QuerySelector("iframe");
HTMLFrameOwnerElement* frame_owner_element = ToHTMLFrameOwnerElement(element);
Document* iframe_doc = frame_owner_element->contentDocument();
EXPECT_FALSE(iframe_doc->documentElement()->GetLayoutObject());
// Changing the display from 'none' -> 'block' should cause layout objects to
// appear.
element->SetInlineStyleProperty(CSSPropertyDisplay, CSSValueBlock);
Compositor().BeginFrame();
EXPECT_TRUE(iframe_doc->documentElement()->GetLayoutObject());
// Changing the display from 'block' -> 'none' should cause layout objects to
// disappear.
element->SetInlineStyleProperty(CSSPropertyDisplay, CSSValueNone);
Compositor().BeginFrame();
EXPECT_FALSE(iframe_doc->documentElement()->GetLayoutObject());
}
// Although it is not spec compliant, many websites intentionally call
// Window.print() on display:none iframes. https://crbug.com/819327.
TEST_F(WebFrameSimTest, DisplayNoneIFramePrints) {
SimRequest main_resource("https://example.com/test.html", "text/html");
SimRequest frame_resource("https://example.com/frame.html", "text/html");
LoadURL("https://example.com/test.html");
main_resource.Complete(
"<!DOCTYPE html>"
"<iframe src=frame.html style='display: none'></iframe>");
frame_resource.Complete(
"<!DOCTYPE html>"
"<html><body>This is a visible iframe.</body></html>");
Element* element = GetDocument().QuerySelector("iframe");
HTMLFrameOwnerElement* frame_owner_element = ToHTMLFrameOwnerElement(element);
Document* iframe_doc = frame_owner_element->contentDocument();
EXPECT_FALSE(iframe_doc->documentElement()->GetLayoutObject());
FloatSize page_size(400, 400);
float maximum_shrink_ratio = 1.0;
iframe_doc->GetFrame()->StartPrinting(page_size, page_size,
maximum_shrink_ratio);
EXPECT_TRUE(iframe_doc->documentElement()->GetLayoutObject());
iframe_doc->GetFrame()->EndPrinting();
EXPECT_FALSE(iframe_doc->documentElement()->GetLayoutObject());
}
TEST_F(WebFrameSimTest, NormalIFrameHasLayoutObjects) {
SimRequest main_resource("https://example.com/test.html", "text/html");
SimRequest frame_resource("https://example.com/frame.html", "text/html");
LoadURL("https://example.com/test.html");
main_resource.Complete(
"<!DOCTYPE html>"
"<iframe src=frame.html style='display: block'></iframe>");
frame_resource.Complete(
"<!DOCTYPE html>"
"<html><body>This is a visible iframe.</body></html>");
Element* element = GetDocument().QuerySelector("iframe");
HTMLFrameOwnerElement* frame_owner_element = ToHTMLFrameOwnerElement(element);
Document* iframe_doc = frame_owner_element->contentDocument();
EXPECT_TRUE(iframe_doc->documentElement()->GetLayoutObject());
// Changing the display from 'block' -> 'none' should cause layout objects to
// disappear.
element->SetInlineStyleProperty(CSSPropertyDisplay, CSSValueNone);
Compositor().BeginFrame();
EXPECT_FALSE(iframe_doc->documentElement()->GetLayoutObject());
}
TEST_F(WebFrameSimTest, RtlInitialScrollOffsetWithViewport) {
UseAndroidSettings();
WebView().MainFrameWidget()->Resize(WebSize(400, 400));
WebView().SetDefaultPageScaleLimits(0.25f, 2);
SimRequest main_resource("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
main_resource.Complete(R"HTML(
<meta name='viewport' content='width=device-width, minimum-scale=1'>
<body dir='rtl'>
<div style='width: 3000px; height: 20px'></div>
)HTML");
Compositor().BeginFrame();
ScrollableArea* area = GetDocument().View()->LayoutViewport();
ASSERT_EQ(ScrollOffset(0, 0), area->GetScrollOffset());
}
TEST_F(WebFrameSimTest, LayoutViewportExceedsLayoutOverflow) {
UseAndroidSettings();
WebView().ResizeWithBrowserControls(WebSize(400, 540), 60, 0, true);
WebView().SetDefaultPageScaleLimits(0.25f, 2);
SimRequest main_resource("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
main_resource.Complete(R"HTML(
<meta name='viewport' content='width=device-width, minimum-scale=1'>
<body style='margin: 0; height: 95vh'>
)HTML");
Compositor().BeginFrame();
ScrollableArea* area = GetDocument().View()->LayoutViewport();
ASSERT_EQ(540, area->VisibleHeight());
ASSERT_EQ(IntSize(400, 570), area->ContentsSize());
// Hide browser controls, growing layout viewport without affecting ICB.
WebView().ResizeWithBrowserControls(WebSize(400, 600), 60, 0, false);
Compositor().BeginFrame();
// ContentsSize() should grow to accomodate new visible size.
ASSERT_EQ(600, area->VisibleHeight());
ASSERT_EQ(IntSize(400, 600), area->ContentsSize());
}
TEST_F(WebFrameSimTest, LayoutViewLocalVisualRect) {
UseAndroidSettings();
WebView().MainFrameWidget()->Resize(WebSize(600, 400));
WebView().SetDefaultPageScaleLimits(0.5f, 2);
SimRequest main_resource("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
main_resource.Complete(R"HTML(
<meta name='viewport' content='width=device-width, minimum-scale=0.5'>
<body style='margin: 0; width: 1800px; height: 1200px'></div>
)HTML");
Compositor().BeginFrame();
ASSERT_EQ(LayoutRect(0, 0, 1200, 800),
GetDocument().GetLayoutView()->LocalVisualRect());
}
TEST_F(WebFrameSimTest, NamedLookupIgnoresEmptyNames) {
SimRequest main_resource("https://example.com/main.html", "text/html");
LoadURL("https://example.com/main.html");
main_resource.Complete(R"HTML(
<body>
<iframe name="" src="data:text/html,"></iframe>
</body>)HTML");
EXPECT_EQ(nullptr, MainFrame().GetFrame()->Tree().ScopedChild(""));
EXPECT_EQ(nullptr,
MainFrame().GetFrame()->Tree().ScopedChild(AtomicString()));
EXPECT_EQ(nullptr, MainFrame().GetFrame()->Tree().ScopedChild(g_empty_atom));
}
TEST_F(WebFrameTest, NoLoadingCompletionCallbacksInDetach) {
class LoadingObserverFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
LoadingObserverFrameClient() = default;
~LoadingObserverFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
void FrameDetached(DetachType type) override {
did_call_frame_detached_ = true;
TestWebFrameClient::FrameDetached(type);
}
void DidStopLoading() override {
// TODO(dcheng): Investigate not calling this as well during frame detach.
did_call_did_stop_loading_ = true;
TestWebFrameClient::DidStopLoading();
}
void DidFailProvisionalLoad(const WebURLError&,
WebHistoryCommitType) override {
EXPECT_TRUE(false) << "The load should not have failed.";
}
void DidFinishDocumentLoad() override {
// TODO(dcheng): Investigate not calling this as well during frame detach.
did_call_did_finish_document_load_ = true;
}
void DidHandleOnloadEvents() override {
// TODO(dcheng): Investigate not calling this as well during frame detach.
did_call_did_handle_onload_events_ = true;
}
void DidFinishLoad() override {
EXPECT_TRUE(false) << "didFinishLoad() should not have been called.";
}
void DispatchLoad() override {
EXPECT_TRUE(false) << "dispatchLoad() should not have been called.";
}
bool DidCallFrameDetached() const { return did_call_frame_detached_; }
bool DidCallDidStopLoading() const { return did_call_did_stop_loading_; }
bool DidCallDidFinishDocumentLoad() const {
return did_call_did_finish_document_load_;
}
bool DidCallDidHandleOnloadEvents() const {
return did_call_did_handle_onload_events_;
}
private:
bool did_call_frame_detached_ = false;
bool did_call_did_stop_loading_ = false;
bool did_call_did_finish_document_load_ = false;
bool did_call_did_handle_onload_events_ = false;
};
class MainFrameClient : public frame_test_helpers::TestWebFrameClient {
public:
MainFrameClient() = default;
~MainFrameClient() override = default;
// frame_test_helpers::TestWebFrameClient:
WebLocalFrame* CreateChildFrame(WebLocalFrame* parent,
WebTreeScopeType scope,
const WebString& name,
const WebString& fallback_name,
WebSandboxFlags sandbox_flags,
const ParsedFeaturePolicy& container_policy,
const WebFrameOwnerProperties&,
FrameOwnerElementType) override {
return CreateLocalChild(*parent, scope, &child_client_);
}
LoadingObserverFrameClient& ChildClient() { return child_client_; }
private:
LoadingObserverFrameClient child_client_;
};
RegisterMockedHttpURLLoad("single_iframe.html");
url_test_helpers::RegisterMockedURLLoad(
ToKURL(base_url_ + "visible_iframe.html"),
test::CoreTestDataPath("frame_with_frame.html"));
RegisterMockedHttpURLLoad("parent_detaching_frame.html");
frame_test_helpers::WebViewHelper web_view_helper;
MainFrameClient main_frame_client;
web_view_helper.InitializeAndLoad(base_url_ + "single_iframe.html",
&main_frame_client);
EXPECT_TRUE(main_frame_client.ChildClient().DidCallFrameDetached());
EXPECT_TRUE(main_frame_client.ChildClient().DidCallDidStopLoading());
EXPECT_TRUE(main_frame_client.ChildClient().DidCallDidFinishDocumentLoad());
EXPECT_TRUE(main_frame_client.ChildClient().DidCallDidHandleOnloadEvents());
web_view_helper.Reset();
}
TEST_F(WebFrameTest, ClearClosedOpener) {
frame_test_helpers::WebViewHelper opener_helper;
opener_helper.Initialize();
frame_test_helpers::WebViewHelper helper;
helper.InitializeWithOpener(opener_helper.GetWebView()->MainFrame());
opener_helper.Reset();
EXPECT_EQ(nullptr, helper.LocalMainFrame()->Opener());
}
class ShowVirtualKeyboardObserverWidgetClient
: public frame_test_helpers::TestWebWidgetClient {
public:
ShowVirtualKeyboardObserverWidgetClient()
: did_show_virtual_keyboard_(false) {}
~ShowVirtualKeyboardObserverWidgetClient() override = default;
// frame_test_helpers::TestWebWidgetClient:
void ShowVirtualKeyboardOnElementFocus() override {
did_show_virtual_keyboard_ = true;
}
bool DidShowVirtualKeyboard() const { return did_show_virtual_keyboard_; }
private:
bool did_show_virtual_keyboard_;
};
TEST_F(WebFrameTest, ShowVirtualKeyboardOnElementFocus) {
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeRemote();
ShowVirtualKeyboardObserverWidgetClient web_widget_client;
WebLocalFrameImpl* local_frame = frame_test_helpers::CreateLocalChild(
*web_view_helper.RemoteMainFrame(), "child", WebFrameOwnerProperties(),
nullptr, nullptr, &web_widget_client);
RegisterMockedHttpURLLoad("input_field_default.html");
frame_test_helpers::LoadFrame(local_frame,
base_url_ + "input_field_default.html");
// Simulate an input element focus leading to Element::focus() call with a
// user gesture.
LocalFrame::NotifyUserActivation(local_frame->GetFrame(),
UserGestureToken::kNewGesture);
local_frame->ExecuteScript(
WebScriptSource("window.focus();"
"document.querySelector('input').focus();"));
// Verify that the right WebWidgetClient has been notified.
EXPECT_TRUE(web_widget_client.DidShowVirtualKeyboard());
web_view_helper.Reset();
}
class ContextMenuWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
ContextMenuWebFrameClient() = default;
~ContextMenuWebFrameClient() override = default;
// WebLocalFrameClient:
void ShowContextMenu(const WebContextMenuData& data) override {
menu_data_ = data;
}
WebContextMenuData GetMenuData() { return menu_data_; }
private:
WebContextMenuData menu_data_;
DISALLOW_COPY_AND_ASSIGN(ContextMenuWebFrameClient);
};
bool TestSelectAll(const std::string& html) {
ContextMenuWebFrameClient frame;
frame_test_helpers::WebViewHelper web_view_helper;
WebViewImpl* web_view = web_view_helper.Initialize(&frame);
frame_test_helpers::LoadHTMLString(web_view->MainFrameImpl(), html,
ToKURL("about:blank"));
web_view->MainFrameWidget()->Resize(WebSize(500, 300));
web_view->MainFrameWidget()->UpdateAllLifecyclePhases(
WebWidget::LifecycleUpdateReason::kTest);
RunPendingTasks();
web_view->SetInitialFocus(false);
RunPendingTasks();
WebMouseEvent mouse_event(WebInputEvent::kMouseDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
mouse_event.button = WebMouseEvent::Button::kRight;
mouse_event.SetPositionInWidget(8, 8);
mouse_event.click_count = 1;
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(mouse_event));
RunPendingTasks();
web_view_helper.Reset();
return frame.GetMenuData().edit_flags & WebContextMenuData::kCanSelectAll;
}
TEST_F(WebFrameTest, ContextMenuDataSelectAll) {
EXPECT_FALSE(TestSelectAll("<textarea></textarea>"));
EXPECT_TRUE(TestSelectAll("<textarea>nonempty</textarea>"));
EXPECT_FALSE(TestSelectAll("<input>"));
EXPECT_TRUE(TestSelectAll("<input value='nonempty'>"));
EXPECT_FALSE(TestSelectAll("<div contenteditable></div>"));
EXPECT_TRUE(TestSelectAll("<div contenteditable>nonempty</div>"));
EXPECT_TRUE(TestSelectAll("<div contenteditable>\n</div>"));
}
TEST_F(WebFrameTest, ContextMenuDataSelectedText) {
ContextMenuWebFrameClient frame;
frame_test_helpers::WebViewHelper web_view_helper;
WebViewImpl* web_view = web_view_helper.Initialize(&frame);
const std::string& html = "<input value=' '>";
frame_test_helpers::LoadHTMLString(web_view->MainFrameImpl(), html,
ToKURL("about:blank"));
web_view->MainFrameWidget()->Resize(WebSize(500, 300));
UpdateAllLifecyclePhases(web_view);
RunPendingTasks();
web_view->SetInitialFocus(false);
RunPendingTasks();
web_view->MainFrameImpl()->ExecuteCommand(WebString::FromUTF8("SelectAll"));
WebMouseEvent mouse_event(WebInputEvent::kMouseDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
mouse_event.button = WebMouseEvent::Button::kRight;
mouse_event.SetPositionInWidget(8, 8);
mouse_event.click_count = 1;
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(mouse_event));
RunPendingTasks();
web_view_helper.Reset();
EXPECT_EQ(frame.GetMenuData().selected_text, " ");
}
TEST_F(WebFrameTest, ContextMenuDataPasswordSelectedText) {
ContextMenuWebFrameClient frame;
frame_test_helpers::WebViewHelper web_view_helper;
WebViewImpl* web_view = web_view_helper.Initialize(&frame);
const std::string& html = "<input type='password' value='password'>";
frame_test_helpers::LoadHTMLString(web_view->MainFrameImpl(), html,
ToKURL("about:blank"));
web_view->MainFrameWidget()->Resize(WebSize(500, 300));
UpdateAllLifecyclePhases(web_view);
RunPendingTasks();
web_view->SetInitialFocus(false);
RunPendingTasks();
web_view->MainFrameImpl()->ExecuteCommand(WebString::FromUTF8("SelectAll"));
WebMouseEvent mouse_event(WebInputEvent::kMouseDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
mouse_event.button = WebMouseEvent::Button::kRight;
mouse_event.SetPositionInWidget(8, 8);
mouse_event.click_count = 1;
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(mouse_event));
RunPendingTasks();
web_view_helper.Reset();
EXPECT_EQ(frame.GetMenuData().input_field_type,
blink::WebContextMenuData::kInputFieldTypePassword);
EXPECT_FALSE(frame.GetMenuData().selected_text.IsEmpty());
}
TEST_F(WebFrameTest, ContextMenuDataNonLocatedMenu) {
ContextMenuWebFrameClient frame;
frame_test_helpers::WebViewHelper web_view_helper;
WebViewImpl* web_view = web_view_helper.Initialize(&frame);
const std::string& html =
"<div style='font-size: 1000%; line-height: 0.7em'>Select me<br/>"
"Next line</div>";
frame_test_helpers::LoadHTMLString(web_view->MainFrameImpl(), html,
ToKURL("about:blank"));
web_view->MainFrameWidget()->Resize(WebSize(500, 300));
UpdateAllLifecyclePhases(web_view);
RunPendingTasks();
web_view->SetInitialFocus(false);
RunPendingTasks();
WebMouseEvent mouse_event(WebInputEvent::kMouseDown,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
mouse_event.button = WebMouseEvent::Button::kLeft;
mouse_event.SetPositionInWidget(0, 0);
mouse_event.click_count = 2;
web_view->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(mouse_event));
web_view->MainFrameWidget()->ShowContextMenu(kMenuSourceTouch);
RunPendingTasks();
web_view_helper.Reset();
EXPECT_EQ(frame.GetMenuData().source_type, kMenuSourceTouch);
EXPECT_FALSE(frame.GetMenuData().selected_text.IsEmpty());
}
TEST_F(WebFrameTest, LocalFrameWithRemoteParentIsTransparent) {
frame_test_helpers::WebViewHelper helper;
helper.InitializeRemote();
WebLocalFrameImpl* local_frame =
frame_test_helpers::CreateLocalChild(*helper.RemoteMainFrame());
frame_test_helpers::LoadFrame(local_frame, "data:text/html,some page");
// Local frame with remote parent should have transparent baseBackgroundColor.
Color color = local_frame->GetFrameView()->BaseBackgroundColor();
EXPECT_EQ(Color::kTransparent, color);
}
class TestFallbackWebFrameClient
: public frame_test_helpers::TestWebFrameClient {
public:
TestFallbackWebFrameClient() : child_client_(nullptr) {}
~TestFallbackWebFrameClient() override = default;
void SetChildWebFrameClient(TestFallbackWebFrameClient* client) {
child_client_ = client;
}
// frame_test_helpers::TestWebFrameClient:
WebLocalFrame* CreateChildFrame(
WebLocalFrame* parent,
WebTreeScopeType scope,
const WebString&,
const WebString&,
WebSandboxFlags,
const ParsedFeaturePolicy& container_policy,
const WebFrameOwnerProperties& frameOwnerProperties,
FrameOwnerElementType) override {
DCHECK(child_client_);
return CreateLocalChild(*parent, scope, child_client_);
}
void BeginNavigation(std::unique_ptr<WebNavigationInfo> info) override {
if (child_client_ || KURL(info->url_request.Url()) == BlankURL()) {
TestWebFrameClient::BeginNavigation(std::move(info));
return;
}
Frame()->CreatePlaceholderDocumentLoader(
WebNavigationParams::CreateFromInfo(*info), info->navigation_type,
nullptr /* extra_data */);
}
private:
TestFallbackWebFrameClient* child_client_;
};
TEST_F(WebFrameTest, FallbackForNonexistentProvisionalNavigation) {
RegisterMockedHttpURLLoad("fallback.html");
TestFallbackWebFrameClient main_client;
TestFallbackWebFrameClient child_client;
main_client.SetChildWebFrameClient(&child_client);
frame_test_helpers::WebViewHelper web_view_helper_;
web_view_helper_.Initialize(&main_client);
WebLocalFrameImpl* main_frame = web_view_helper_.LocalMainFrame();
WebURLRequest request(ToKURL(base_url_ + "fallback.html"));
main_frame->StartNavigation(request);
// Because the child frame will have placeholder document loader, the main
// frame will not finish loading, so
// frame_test_helpers::PumpPendingRequestsForFrameToLoad doesn't work here.
Platform::Current()->GetURLLoaderMockFactory()->ServeAsynchronousRequests();
// Overwrite the client-handled child frame navigation with about:blank.
WebLocalFrame* child = main_frame->FirstChild()->ToWebLocalFrame();
child->StartNavigation(WebURLRequest(BlankURL()));
// Failing the original child frame navigation and trying to render fallback
// content shouldn't crash. It should return NoLoadInProgress. This is so the
// caller won't attempt to replace the correctly empty frame with an error
// page.
EXPECT_EQ(WebNavigationControl::NoLoadInProgress,
ToWebLocalFrameImpl(child)->MaybeRenderFallbackContent(
ResourceError::Failure(request.Url())));
}
TEST_F(WebFrameTest, AltTextOnAboutBlankPage) {
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad("about:blank");
web_view_helper.Resize(WebSize(640, 480));
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
const char kSource[] =
"<img id='foo' src='foo' alt='foo alt' width='200' height='200'>";
frame_test_helpers::LoadHTMLString(frame, kSource, ToKURL("about:blank"));
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
RunPendingTasks();
// Check LayoutText with alt text "foo alt"
LayoutObject* layout_object = frame->GetFrame()
->GetDocument()
->getElementById("foo")
->GetLayoutObject()
->SlowFirstChild();
String text = "";
for (LayoutObject* obj = layout_object; obj; obj = obj->NextInPreOrder()) {
if (obj->IsText()) {
LayoutText* layout_text = ToLayoutText(obj);
text = layout_text->GetText();
break;
}
}
EXPECT_EQ("foo alt", text.Utf8());
}
TEST_F(WebFrameTest, NavigatorPluginsClearedWhenPluginsDisabled) {
ScopedFakePluginRegistry fake_plugins;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize();
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::Local<v8::Context> context = isolate->GetCurrentContext();
v8::HandleScope scope(isolate);
v8::Local<v8::Value> result =
web_view_helper.LocalMainFrame()->ExecuteScriptAndReturnValue(
WebScriptSource("navigator.plugins.length"));
EXPECT_NE(0, result->Int32Value(context).ToChecked());
web_view_helper.GetWebView()->GetPage()->GetSettings().SetPluginsEnabled(
false);
result = web_view_helper.LocalMainFrame()->ExecuteScriptAndReturnValue(
WebScriptSource("navigator.plugins.length"));
EXPECT_EQ(0, result->Int32Value(context).ToChecked());
}
TEST_F(WebFrameTest, RecordSameDocumentNavigationToHistogram) {
const char* histogramName =
"RendererScheduler.UpdateForSameDocumentNavigationCount";
frame_test_helpers::WebViewHelper web_view_helper;
HistogramTester tester;
web_view_helper.InitializeAndLoad("about:blank");
LocalFrame* frame =
ToLocalFrame(web_view_helper.GetWebView()->GetPage()->MainFrame());
FrameLoader& main_frame_loader =
web_view_helper.GetWebView()->MainFrameImpl()->GetFrame()->Loader();
scoped_refptr<SerializedScriptValue> message =
SerializeString("message", ToScriptStateForMainWorld(frame));
tester.ExpectTotalCount(histogramName, 0);
main_frame_loader.UpdateForSameDocumentNavigation(
ToKURL("about:blank"), kSameDocumentNavigationHistoryApi, message,
kScrollRestorationAuto, WebFrameLoadType::kReplaceCurrentItem,
frame->GetDocument());
// The bucket index corresponds to the definition of
// |SinglePageAppNavigationType|.
tester.ExpectBucketCount(histogramName,
kSPANavTypeHistoryPushStateOrReplaceState, 1);
main_frame_loader.UpdateForSameDocumentNavigation(
ToKURL("about:blank"), kSameDocumentNavigationDefault, message,
kScrollRestorationManual, WebFrameLoadType::kBackForward,
frame->GetDocument());
tester.ExpectBucketCount(histogramName,
kSPANavTypeSameDocumentBackwardOrForward, 1);
main_frame_loader.UpdateForSameDocumentNavigation(
ToKURL("about:blank"), kSameDocumentNavigationDefault, message,
kScrollRestorationManual, WebFrameLoadType::kReplaceCurrentItem,
frame->GetDocument());
tester.ExpectBucketCount(histogramName, kSPANavTypeOtherFragmentNavigation,
1);
// kSameDocumentNavigationHistoryApi and WebFrameLoadType::kBackForward is an
// illegal combination, which has been caught by DCHECK in
// UpdateForSameDocumentNavigation().
tester.ExpectTotalCount(histogramName, 3);
}
TEST_F(WebFrameTest, DidScrollCallbackAfterScrollableAreaChanges) {
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize();
web_view_helper.Resize(WebSize(200, 200));
WebViewImpl* web_view = web_view_helper.GetWebView();
InitializeWithHTML(*web_view->MainFrameImpl()->GetFrame(),
"<style>"
" #scrollable {"
" height: 100px;"
" width: 100px;"
" overflow: scroll;"
" will-change: transform;"
" }"
" #forceScroll { height: 120px; width: 50px; }"
"</style>"
"<div id='scrollable'>"
" <div id='forceScroll'></div>"
"</div>");
UpdateAllLifecyclePhases(web_view);
Document* document = web_view->MainFrameImpl()->GetFrame()->GetDocument();
Element* scrollable = document->getElementById("scrollable");
auto* scrollable_area =
ToLayoutBox(scrollable->GetLayoutObject())->GetScrollableArea();
EXPECT_NE(nullptr, scrollable_area);
// We should have a composited layer for scrolling due to will-change.
cc::Layer* cc_scroll_layer = scrollable_area->LayerForScrolling()->CcLayer();
EXPECT_NE(nullptr, cc_scroll_layer);
// Ensure a synthetic impl-side scroll offset propagates to the scrollable
// area using the DidScroll callback.
EXPECT_EQ(ScrollOffset(), scrollable_area->GetScrollOffset());
cc_scroll_layer->SetScrollOffsetFromImplSide(gfx::ScrollOffset(0, 1));
UpdateAllLifecyclePhases(web_view);
EXPECT_EQ(ScrollOffset(0, 1), scrollable_area->GetScrollOffset());
// Make the scrollable area non-scrollable.
scrollable->setAttribute(html_names::kStyleAttr, "overflow: visible");
// Update layout without updating compositing state.
WebLocalFrame* frame = web_view_helper.LocalMainFrame();
frame->ExecuteScript(
WebScriptSource("var forceLayoutFromScript = scrollable.offsetTop;"));
EXPECT_EQ(document->Lifecycle().GetState(), DocumentLifecycle::kLayoutClean);
EXPECT_EQ(nullptr,
ToLayoutBox(scrollable->GetLayoutObject())->GetScrollableArea());
// The web scroll layer has not been deleted yet and we should be able to
// apply impl-side offsets without crashing.
cc_scroll_layer->SetScrollOffsetFromImplSide(gfx::ScrollOffset(0, 3));
}
static void TestFramePrinting(WebLocalFrameImpl* frame) {
WebPrintParams print_params;
WebSize page_size(500, 500);
print_params.print_content_area.width = page_size.width;
print_params.print_content_area.height = page_size.height;
EXPECT_EQ(1, frame->PrintBegin(print_params, WebNode()));
PaintRecorder recorder;
frame->PrintPagesForTesting(recorder.beginRecording(IntRect()), page_size);
frame->PrintEnd();
}
TEST_F(WebFrameTest, PrintDetachedIframe) {
RegisterMockedHttpURLLoad("print-detached-iframe.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "print-detached-iframe.html");
TestFramePrinting(
ToWebLocalFrameImpl(web_view_helper.LocalMainFrame()->FirstChild()));
}
TEST_F(WebFrameTest, PrintIframeUnderDetached) {
RegisterMockedHttpURLLoad("print-detached-iframe.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "print-detached-iframe.html");
TestFramePrinting(ToWebLocalFrameImpl(
web_view_helper.LocalMainFrame()->FirstChild()->FirstChild()));
}
TEST_F(WebFrameTest, ExecuteCommandProducesUserGesture) {
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad("about:blank");
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
EXPECT_FALSE(frame->GetFrame()->HasBeenActivated());
frame->ExecuteScript(WebScriptSource(WebString("document.execCommand('copy');")));
EXPECT_FALSE(frame->GetFrame()->HasBeenActivated());
frame->ExecuteCommand(WebString::FromUTF8("Paste"));
EXPECT_TRUE(frame->GetFrame()->HasBeenActivated());
}
TEST_F(WebFrameTest, GetCanonicalUrlForSharingNone) {
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad("about:blank");
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
EXPECT_TRUE(frame->GetDocument().CanonicalUrlForSharing().IsNull());
}
TEST_F(WebFrameTest, GetCanonicalUrlForSharingNotInHead) {
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize();
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
frame_test_helpers::LoadHTMLString(
frame, R"(
<body>
<link rel="canonical" href="https://example.com/canonical.html">
</body>)", ToKURL("https://example.com/test_page.html"));
EXPECT_TRUE(frame->GetDocument().CanonicalUrlForSharing().IsNull());
}
TEST_F(WebFrameTest, GetCanonicalUrlForSharing) {
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize();
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
frame_test_helpers::LoadHTMLString(
frame, R"(
<head>
<link rel="canonical" href="https://example.com/canonical.html">
</head>)", ToKURL("https://example.com/test_page.html"));
EXPECT_EQ(WebURL(ToKURL("https://example.com/canonical.html")),
frame->GetDocument().CanonicalUrlForSharing());
}
TEST_F(WebFrameTest, GetCanonicalUrlForSharingMultiple) {
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize();
WebLocalFrameImpl* frame = web_view_helper.LocalMainFrame();
frame_test_helpers::LoadHTMLString(
frame, R"(
<head>
<link rel="canonical" href="https://example.com/canonical1.html">
<link rel="canonical" href="https://example.com/canonical2.html">
</head>)", ToKURL("https://example.com/test_page.html"));
EXPECT_EQ(WebURL(ToKURL("https://example.com/canonical1.html")),
frame->GetDocument().CanonicalUrlForSharing());
}
TEST_F(WebFrameSimTest, EnterFullscreenResetScrollAndScaleState) {
UseAndroidSettings();
WebView().MainFrameWidget()->Resize(WebSize(490, 500));
WebView().EnableFakePageScaleAnimationForTesting(true);
WebView().GetSettings()->SetTextAutosizingEnabled(false);
WebView().SetDefaultPageScaleLimits(0.5f, 4);
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
body {
margin: 0px;
width: 10000px;
height: 10000px;
}
</style>
)HTML");
Compositor().BeginFrame();
// Make the page scale and scroll with the given parameters.
EXPECT_EQ(0.5f, WebView().PageScaleFactor());
WebView().SetPageScaleFactor(2.0f);
WebView().MainFrameImpl()->SetScrollOffset(WebSize(94, 111));
WebView().SetVisualViewportOffset(WebFloatPoint(12, 20));
EXPECT_EQ(2.0f, WebView().PageScaleFactor());
EXPECT_EQ(94, WebView().MainFrameImpl()->GetScrollOffset().width);
EXPECT_EQ(111, WebView().MainFrameImpl()->GetScrollOffset().height);
EXPECT_EQ(12, WebView().VisualViewportOffset().x);
EXPECT_EQ(20, WebView().VisualViewportOffset().y);
LocalFrame* frame = ToLocalFrame(WebView().GetPage()->MainFrame());
Element* element = frame->GetDocument()->body();
std::unique_ptr<UserGestureIndicator> gesture =
LocalFrame::NotifyUserActivation(frame);
Fullscreen::RequestFullscreen(*element);
WebView().MainFrameWidget()->DidEnterFullscreen();
// Page scale factor must be 1.0 during fullscreen for elements to be sized
// properly.
EXPECT_EQ(1.0f, WebView().PageScaleFactor());
// Confirm that exiting fullscreen restores back to default values.
WebView().MainFrameWidget()->DidExitFullscreen();
WebView().MainFrameWidget()->UpdateAllLifecyclePhases(
WebWidget::LifecycleUpdateReason::kTest);
EXPECT_EQ(0.5f, WebView().PageScaleFactor());
EXPECT_EQ(94, WebView().MainFrameImpl()->GetScrollOffset().width);
EXPECT_EQ(111, WebView().MainFrameImpl()->GetScrollOffset().height);
EXPECT_EQ(0, WebView().VisualViewportOffset().x);
EXPECT_EQ(0, WebView().VisualViewportOffset().y);
}
TEST_F(WebFrameTest, MediaQueriesInLocalFrameInsideRemote) {
frame_test_helpers::WebViewHelper helper;
FixedLayoutTestWebViewClient client;
client.screen_info_.is_monochrome = false;
client.screen_info_.depth_per_component = 8;
helper.InitializeRemote(nullptr, nullptr, &client);
WebLocalFrameImpl* local_frame =
frame_test_helpers::CreateLocalChild(*helper.RemoteMainFrame());
ASSERT_TRUE(local_frame->GetFrame());
MediaValues* media_values =
MediaValues::CreateDynamicIfFrameExists(local_frame->GetFrame());
ASSERT_TRUE(media_values);
EXPECT_EQ(0, media_values->MonochromeBitsPerComponent());
EXPECT_EQ(8, media_values->ColorBitsPerComponent());
// Need to explicitly reset helper to make sure local_frame is not deleted
// first.
helper.Reset();
}
} // namespace blink