blob: d85767a3502963122bb0ff7a056b5fe01c6c58fa [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 <initializer_list>
#include <limits>
#include <memory>
#include "base/callback_helpers.h"
#include "base/optional.h"
#include "base/stl_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/unguessable_token.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "cc/input/overscroll_behavior.h"
#include "cc/layers/picture_layer.h"
#include "cc/paint/paint_op_buffer.h"
#include "cc/trees/layer_tree_host.h"
#include "cc/trees/scroll_node.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "mojo/public/cpp/system/data_pipe_drainer.h"
#include "mojo/public/cpp/system/data_pipe_utils.h"
#include "skia/public/mojom/skcolor.mojom-blink.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/browser_interface_broker_proxy.h"
#include "third_party/blink/public/common/context_menu_data/context_menu_data.h"
#include "third_party/blink/public/common/context_menu_data/edit_flags.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/input/web_coalesced_input_event.h"
#include "third_party/blink/public/common/input/web_keyboard_event.h"
#include "third_party/blink/public/common/loader/referrer_utils.h"
#include "third_party/blink/public/common/messaging/transferable_message.h"
#include "third_party/blink/public/common/page/launching_process_state.h"
#include "third_party/blink/public/common/widget/device_emulation_params.h"
#include "third_party/blink/public/mojom/blob/blob.mojom-blink.h"
#include "third_party/blink/public/mojom/blob/data_element.mojom-blink.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
#include "third_party/blink/public/mojom/frame/find_in_page.mojom-blink.h"
#include "third_party/blink/public/mojom/frame/frame_owner_element_type.mojom-blink.h"
#include "third_party/blink/public/mojom/frame/viewport_intersection_state.mojom-blink.h"
#include "third_party/blink/public/mojom/page_state/page_state.mojom-blink.h"
#include "third_party/blink/public/mojom/scroll/scrollbar_mode.mojom-blink.h"
#include "third_party/blink/public/mojom/webpreferences/web_preferences.mojom-blink.h"
#include "third_party/blink/public/platform/web_cache.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/test/test_web_frame_content_dumper.h"
#include "third_party/blink/public/web/web_console_message.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_document_loader.h"
#include "third_party/blink/public/web/web_form_element.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.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_page_description.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.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/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_node.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_pointer_event_init.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/css_page_rule.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/events/native_event_listener.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/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/selection_template.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/event_type_names.h"
#include "third_party/blink/renderer/core/events/message_event.h"
#include "third_party/blink/renderer/core/events/mouse_event.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_local_frame_impl.h"
#include "third_party/blink/renderer/core/frame/web_remote_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/forms/html_input_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/messaging/blink_transferable_message.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/drag_image.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/page/scrolling/top_document_root_scroller_controller.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/core_unit_test_helper.h"
#include "third_party/blink/renderer/core/testing/fake_local_frame_host.h"
#include "third_party/blink/renderer/core/testing/fake_remote_frame_host.h"
#include "third_party/blink/renderer/core/testing/fake_remote_main_frame_host.h"
#include "third_party/blink/renderer/core/testing/mock_clipboard_host.h"
#include "third_party/blink/renderer/core/testing/mock_policy_container_host.h"
#include "third_party/blink/renderer/core/testing/null_execution_context.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
#include "third_party/blink/renderer/core/testing/scoped_fake_plugin_registry.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/platform/bindings/microtask.h"
#include "third_party/blink/renderer/platform/blob/testing/fake_blob.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/fetch_context.h"
#include "third_party/blink/renderer/platform/loader/fetch/raw_resource.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher_properties.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_timing_info.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread.h"
#include "third_party/blink/renderer/platform/testing/find_cc_layer.h"
#include "third_party/blink/renderer/platform/testing/histogram_tester.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
#include "third_party/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 "third_party/blink/renderer/platform/wtf/hash_map.h"
#include "third_party/blink/renderer/platform/wtf/hash_set.h"
#include "ui/base/ime/mojom/text_input_state.mojom-blink.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/events/keycodes/dom/dom_key.h"
#include "ui/gfx/transform.h"
#include "v8/include/v8.h"
using blink::mojom::SelectionMenuBehavior;
using blink::test::RunPendingTasks;
using blink::url_test_helpers::ToKURL;
using testing::_;
using testing::ElementsAre;
using testing::Mock;
namespace blink {
namespace {
const cc::ScrollNode* GetScrollNode(const cc::Layer* layer) {
return layer->layer_tree_host()
->property_trees()
->scroll_tree.FindNodeFromElementId(layer->element_id());
}
std::string GetHTMLStringForReferrerPolicy(const std::string& meta_policy,
const std::string& referrer_policy) {
std::string meta_tag =
meta_policy.empty()
? ""
: base::StringPrintf("<meta name='referrer' content='%s'>",
meta_policy.c_str());
std::string referrer_policy_attr =
referrer_policy.empty()
? ""
: base::StringPrintf("referrerpolicy='%s'", referrer_policy.c_str());
return base::StringPrintf(
"<!DOCTYPE html>"
"%s"
"<a id='dl' href='download_test' download='foo' %s>Click me</a>"
"<script>"
"(function () {"
" var evt = document.createEvent('MouseEvent');"
" evt.initMouseEvent('click', true, true);"
" document.getElementById('dl').dispatchEvent(evt);"
"})();"
"</script>",
meta_tag.c_str(), referrer_policy_attr.c_str());
}
} // namespace
const int kTouchPointPadding = 32;
const cc::OverscrollBehavior kOverscrollBehaviorAuto =
cc::OverscrollBehavior(cc::OverscrollBehavior::Type::kAuto);
const cc::OverscrollBehavior kOverscrollBehaviorContain =
cc::OverscrollBehavior(cc::OverscrollBehavior::Type::kContain);
const cc::OverscrollBehavior kOverscrollBehaviorNone =
cc::OverscrollBehavior(cc::OverscrollBehavior::Type::kNone);
class WebFrameTest : public testing::Test {
protected:
WebFrameTest()
: base_url_("http://internal.test/"),
not_base_url_("http://external.test/"),
chrome_url_("chrome://") {}
~WebFrameTest() override {
url_test_helpers::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) {
// TODO(crbug.com/751425): We should use the mock functionality
// via the WebViewHelper instance in each test case.
RegisterMockedURLLoadFromBase(base_url_, file_name);
}
void RegisterMockedChromeURLLoad(const std::string& file_name) {
// TODO(crbug.com/751425): We should use the mock functionality
// via the WebViewHelper instance in each test case.
RegisterMockedURLLoadFromBase(chrome_url_, file_name);
}
void RegisterMockedURLLoadFromBase(const std::string& base_url,
const std::string& file_name) {
// TODO(crbug.com/751425): We should use the mock functionality
// via the WebViewHelper instance in each test case.
url_test_helpers::RegisterMockedURLLoadFromBase(
WebString::FromUTF8(base_url), test::CoreTestDataPath(),
WebString::FromUTF8(file_name));
}
void RegisterMockedURLLoadWithCustomResponse(const WebURL& full_url,
const WebString& file_path,
WebURLResponse response) {
url_test_helpers::RegisterMockedURLLoadWithCustomResponse(
full_url, file_path, response);
}
void RegisterMockedHttpURLLoadWithCSP(const std::string& file_name,
const std::string& csp,
bool report_only = false) {
std::string full_string = base_url_ + file_name;
KURL url = ToKURL(full_string);
WebURLResponse response = WebURLResponse(url);
response.SetMimeType("text/html");
response.AddHttpHeaderField(
report_only ? WebString("Content-Security-Policy-Report-Only")
: WebString("Content-Security-Policy"),
WebString::FromUTF8(csp));
RegisterMockedURLLoadWithCustomResponse(
url, test::CoreTestDataPath(WebString::FromUTF8(file_name)), response);
}
void RegisterMockedHttpURLLoadWithMimeType(const std::string& file_name,
const std::string& mime_type) {
// TODO(crbug.com/751425): We should use the mock functionality
// via the WebViewHelper instance in each test case.
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(mojom::blink::ViewportStyle::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->GetWebView()->MainFrameWidget()->SetFocus(true);
web_view_helper->Resize(gfx::Size(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(gfx::Size(640, 480));
auto* frame =
To<LocalFrame>(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()->setInnerHTML(html_content);
frame.GetDocument()->View()->UpdateAllLifecyclePhasesForTest();
}
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(To<Text>(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(
DocumentUpdateReason::kTest);
}
static void GetElementAndCaretBoundsForFocusedEditableElement(
frame_test_helpers::WebViewHelper& helper,
IntRect& element_bounds,
IntRect& caret_bounds) {
Element* element = helper.GetWebView()->FocusedElement();
gfx::Rect caret_in_viewport, unused;
helper.GetWebView()->MainFrameViewWidget()->CalculateSelectionBounds(
caret_in_viewport, unused);
caret_bounds =
helper.GetWebView()->GetPage()->GetVisualViewport().ViewportToRootFrame(
IntRect(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 = TestWebFrameContentDumper::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.empty()) {
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");
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()->GetPage()->SetPaused(true);
web_view_helper.GetWebView()
->MainFrameImpl()
->RequestExecuteScriptAndReturnValue(
WebScriptSource(WebString("'hello';")), false, &callback_helper);
RunPendingTasks();
EXPECT_FALSE(callback_helper.DidComplete());
web_view_helper.Reset();
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) {
EXPECT_EQ(2, info.Length());
EXPECT_TRUE(info[0]->IsUndefined());
info.GetReturnValue().Set(info[1]);
};
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::HandleScope scope(isolate);
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();
v8::Local<v8::Value> args[] = {v8::Undefined(isolate),
V8String(isolate, "hello")};
web_view_helper.GetWebView()
->MainFrame()
->ToWebLocalFrame()
->RequestExecuteV8Function(context, function, v8::Undefined(isolate),
base::size(args), args, &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();
web_view_helper.GetWebView()->GetPage()->SetPaused(true);
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());
web_view_helper.GetWebView()->GetPage()->SetPaused(false);
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");
v8::HandleScope scope(v8::Isolate::GetCurrent());
// Suspend scheduled tasks so the script doesn't run.
web_view_helper.GetWebView()->GetPage()->SetPaused(true);
LocalFrame::NotifyUserActivation(
web_view_helper.LocalMainFrame()->GetFrame(),
mojom::UserActivationNotificationType::kTest);
ScriptExecutionCallbackHelper callback_helper(
web_view_helper.LocalMainFrame()->MainWorldScriptContext());
WebScriptSource script_source("navigator.userActivation.isActive;");
web_view_helper.GetWebView()
->MainFrameImpl()
->RequestExecuteScriptAndReturnValue(script_source, false,
&callback_helper);
RunPendingTasks();
EXPECT_FALSE(callback_helper.DidComplete());
web_view_helper.GetWebView()->GetPage()->SetPaused(false);
RunPendingTasks();
EXPECT_TRUE(callback_helper.DidComplete());
EXPECT_EQ(true, callback_helper.BoolValue());
}
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());
}
namespace {
class CapabilityDelegationMessageListener final : public NativeEventListener {
public:
void Invoke(ExecutionContext*, Event* event) override {
delegate_payment_request_ =
static_cast<MessageEvent*>(event)->delegatePaymentRequest();
}
bool DelegatePaymentRequest() {
bool value = delegate_payment_request_.value();
delegate_payment_request_.reset();
return value;
}
private:
base::Optional<bool> delegate_payment_request_;
};
} // namespace
TEST_F(WebFrameTest, CapabilityDelegationMessageEventTest) {
RuntimeEnabledFeatures::SetCapabilityDelegationPaymentRequestEnabled(true);
RegisterMockedHttpURLLoad("single_iframe.html");
RegisterMockedHttpURLLoad("visible_iframe.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "single_iframe.html");
auto* main_frame =
To<LocalFrame>(web_view_helper.GetWebView()->GetPage()->MainFrame());
auto* child_frame = To<LocalFrame>(main_frame->FirstChild());
DCHECK(main_frame);
DCHECK(child_frame);
auto* message_event_listener =
MakeGarbageCollected<CapabilityDelegationMessageListener>();
child_frame->GetDocument()->domWindow()->addEventListener(
event_type_names::kMessage, message_event_listener);
v8::HandleScope scope(v8::Isolate::GetCurrent());
ScriptExecutionCallbackHelper callback_helper(
web_view_helper.LocalMainFrame()->MainWorldScriptContext());
WebScriptSource post_message_wo_payment_request(
WebString("window.frames[0].postMessage('0', {targetOrigin: '*'});"));
WebScriptSource post_message_w_payment_request(
WebString("window.frames[0].postMessage("
"'1', {targetOrigin: '*', createToken: 'paymentrequest'});"));
// The delegation info is not passed through a postMessage that is sent
// without either user activation or the delegation option.
web_view_helper.GetWebView()
->MainFrameImpl()
->RequestExecuteScriptAndReturnValue(post_message_wo_payment_request,
false, &callback_helper);
RunPendingTasks();
EXPECT_TRUE(callback_helper.DidComplete());
EXPECT_FALSE(message_event_listener->DelegatePaymentRequest());
// The delegation info is not passed through a postMessage that is sent
// without user activation but with the delegation option.
web_view_helper.GetWebView()
->MainFrameImpl()
->RequestExecuteScriptAndReturnValue(post_message_w_payment_request,
false, &callback_helper);
RunPendingTasks();
EXPECT_TRUE(callback_helper.DidComplete());
EXPECT_FALSE(message_event_listener->DelegatePaymentRequest());
// The delegation info is not passed through a postMessage that is sent with
// user activation but without the delegation option.
web_view_helper.GetWebView()
->MainFrameImpl()
->RequestExecuteScriptAndReturnValue(post_message_wo_payment_request,
true, &callback_helper);
RunPendingTasks();
EXPECT_TRUE(callback_helper.DidComplete());
EXPECT_FALSE(message_event_listener->DelegatePaymentRequest());
// The delegation info is passed through a postMessage that is sent with both
// user activation and the delegation option.
web_view_helper.GetWebView()
->MainFrameImpl()
->RequestExecuteScriptAndReturnValue(post_message_w_payment_request, true,
&callback_helper);
RunPendingTasks();
EXPECT_TRUE(callback_helper.DidComplete());
EXPECT_TRUE(message_event_listener->DelegatePaymentRequest());
}
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();
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 = TestWebFrameContentDumper::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 = TestWebFrameContentDumper::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);
// TODO(crbug.com/751425): We should use the mock functionality
// via the WebViewHelper instance in each test case.
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 = TestWebFrameContentDumper::DumpWebViewAsText(
web_view_helper.GetWebView(), 1024)
.Utf8();
EXPECT_EQ("http://internal.test/" + file_name, content);
}
TEST_F(WebFrameTest, LocationSetEmptyPort) {
std::string file_name = "print-location-href.html";
RegisterMockedHttpURLLoad(file_name);
// TODO(crbug.com/751425): We should use the mock functionality
// via the WebViewHelper instance in each test case.
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 = TestWebFrameContentDumper::DumpWebViewAsText(
web_view_helper.GetWebView(), 1024)
.Utf8();
EXPECT_EQ("http://internal.test/" + 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;
HashSet<String>& MatchedSelectors() {
auto it = matched_selectors_.find(Frame());
if (it != matched_selectors_.end())
return it->value;
auto add_result = matched_selectors_.insert(Frame(), HashSet<String>());
return add_result.stored_value->value;
}
HashMap<WebLocalFrame*, HashSet<String>> matched_selectors_;
int update_count_;
};
void CSSCallbackWebFrameClient::DidMatchCSS(
const WebVector<WebString>& newly_matching_selectors,
const WebVector<WebString>& stopped_matching_selectors) {
++update_count_;
HashSet<String>& frame_selectors = MatchedSelectors();
for (size_t i = 0; i < newly_matching_selectors.size(); ++i) {
String selector = newly_matching_selectors[i];
EXPECT_TRUE(frame_selectors.find(selector) == frame_selectors.end())
<< selector;
frame_selectors.insert(selector);
}
for (size_t i = 0; i < stopped_matching_selectors.size(); ++i) {
String selector = stopped_matching_selectors[i];
EXPECT_TRUE(frame_selectors.find(selector) != frame_selectors.end())
<< selector;
frame_selectors.erase(selector);
EXPECT_TRUE(frame_selectors.find(selector) == frame_selectors.end())
<< 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 HashSet<String>& MatchedSelectors() {
auto it = client_.matched_selectors_.find(frame_);
if (it != client_.matched_selectors_.end())
return it->value;
auto add_result =
client_.matched_selectors_.insert(frame_, HashSet<String>());
return add_result.stored_value->value;
}
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(
DocumentUpdateReason::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(
DocumentUpdateReason::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(
DocumentUpdateReason::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(
DocumentUpdateReason::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(
DocumentUpdateReason::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(
DocumentUpdateReason::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(
DocumentUpdateReason::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(
DocumentUpdateReason::kTest);
RunPendingTasks();
EXPECT_EQ(1, UpdateCount());
EXPECT_THAT(MatchedSelectors(), ElementsAre("span"))
<< "An invalid selector shouldn't prevent other selectors from matching.";
}
TEST_F(WebFrameTest, PostMessageEvent) {
RegisterMockedHttpURLLoad("postmessage_test.html");
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "postmessage_test.html");
auto* frame =
To<LocalFrame>(web_view_helper.GetWebView()->GetPage()->MainFrame());
scoped_refptr<SerializedScriptValue> data = SerializedScriptValue::Create();
MessageEvent* message_event = MessageEvent::Create(
/*ports=*/nullptr, std::move(data), "http://origin.com");
// Send a message with the correct origin.
scoped_refptr<SecurityOrigin> correct_origin =
SecurityOrigin::Create(ToKURL(base_url_));
frame->PostMessageEvent(
base::nullopt, g_empty_string, correct_origin->ToString(),
BlinkTransferableMessage::FromMessageEvent(message_event));
// Send another message with incorrect origin.
scoped_refptr<SecurityOrigin> incorrect_origin =
SecurityOrigin::Create(ToKURL(chrome_url_));
frame->PostMessageEvent(
base::nullopt, g_empty_string, incorrect_origin->ToString(),
BlinkTransferableMessage::FromMessageEvent(message_event));
// Verify that only the first addition is in the body of the page.
std::string content = TestWebFrameContentDumper::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");
auto* frame =
To<LocalFrame>(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 {
// 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->SetModifiedStyleOutsideStyleRecalc(
std::move(modified_style), LayoutObject::ApplyStyleChanges::kNo);
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;
}
void UpdateScreenInfoAndResizeView(
frame_test_helpers::WebViewHelper* web_view_helper,
const ScreenInfo& screen_info) {
web_view_helper->GetWebView()->MainFrameViewWidget()->UpdateScreenInfo(
screen_info);
web_view_helper->Resize(screen_info.rect.size());
}
void UpdateScreenInfoAndResizeView(
frame_test_helpers::WebViewHelper* web_view_helper,
int viewport_width,
int viewport_height) {
ScreenInfo screen_info =
web_view_helper->GetMainFrameWidget()->GetOriginalScreenInfo();
screen_info.rect = gfx::Rect(viewport_width, viewport_height);
UpdateScreenInfoAndResizeView(web_view_helper, screen_info);
}
} // namespace
TEST_F(WebFrameTest, ChangeInFixedLayoutResetsTextAutosizingMultipliers) {
RegisterMockedHttpURLLoad("fixed_layout.html");
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,
nullptr, ConfigureAndroid);
Document* document =
To<LocalFrame>(web_view_helper.GetWebView()->GetPage()->MainFrame())
->GetDocument();
document->GetSettings()->SetTextAutosizingEnabled(true);
EXPECT_TRUE(document->GetSettings()->GetTextAutosizingEnabled());
web_view_helper.Resize(gfx::Size(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::Fixed(100);
description.max_width = Length::Fixed(100);
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);
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + html_file, nullptr, nullptr,
ConfigureAndroid);
Document* document =
To<LocalFrame>(web_view_helper.GetWebView()->GetPage()->MainFrame())
->GetDocument();
document->GetSettings()->SetTextAutosizingEnabled(true);
EXPECT_TRUE(document->GetSettings()->GetTextAutosizingEnabled());
web_view_helper.Resize(gfx::Size(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");
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,
nullptr, ConfigureAndroid);
auto* main_frame =
To<LocalFrame>(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()->GetTextAutosizingEnabled());
web_view_helper.Resize(gfx::Size(viewport_width, viewport_height));
for (Frame* frame = main_frame; frame; frame = frame->Tree().TraverseNext()) {
auto* local_frame = DynamicTo<LocalFrame>(frame);
if (!local_frame)
continue;
EXPECT_TRUE(SetTextAutosizingMultiplier(local_frame->GetDocument(), 2));
for (LayoutObject* layout_object =
local_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()) {
auto* local_frame = DynamicTo<LocalFrame>(frame);
if (!local_frame)
continue;
for (LayoutObject* layout_object =
local_frame->GetDocument()->GetLayoutView();
!layout_object; layout_object = layout_object->NextInPreOrder()) {
if (layout_object->IsText())
EXPECT_TRUE(layout_object->NeedsLayout());
}
}
}
TEST_F(WebFrameTest, ZeroHeightPositiveWidthNotIgnored) {
int viewport_width = 1280;
int viewport_height = 0;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(nullptr, nullptr, ConfigureAndroid);
web_view_helper.Resize(gfx::Size(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;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "no_viewport_tag.html", nullptr,
nullptr, ConfigureAndroid);
web_view_helper.GetWebView()
->MainFrameWidget()
->SetDeviceScaleFactorForTesting(2.f);
web_view_helper.Resize(gfx::Size(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");
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, nullptr, 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(gfx::Size(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(gfx::Size(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");
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, nullptr, 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(gfx::Size(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(gfx::Size(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");
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, nullptr,
ConfigureAndroid);
web_view_helper.Resize(gfx::Size(viewport_width, viewport_height));
EXPECT_EQ(0.25f, web_view_helper.GetWebView()->PageScaleFactor());
ViewportData& viewport =
To<LocalFrame>(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");
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, nullptr,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetLoadWithOverviewMode(false);
web_view_helper.Resize(gfx::Size(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");
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,
nullptr, 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(gfx::Size(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");
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, nullptr,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(false);
web_view_helper.Resize(gfx::Size(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");
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, nullptr,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(false);
web_view_helper.Resize(gfx::Size(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");
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,
nullptr, ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(true);
web_view_helper.Resize(gfx::Size(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");
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(nullptr, nullptr, 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(gfx::Size(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");
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, nullptr, ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(false);
web_view_helper.Resize(gfx::Size(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");
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, nullptr,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(true);
web_view_helper.Resize(gfx::Size(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");
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, nullptr,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetLoadWithOverviewMode(false);
web_view_helper.Resize(gfx::Size(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");
float enforced_page_scale_factor = 2.0f;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "fixed_layout.html", nullptr,
nullptr, 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(gfx::Size(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");
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, nullptr,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetLoadWithOverviewMode(false);
web_view_helper.GetWebView()->SetInitialPageScaleOverride(
enforced_page_scale_factor);
web_view_helper.Resize(gfx::Size(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");
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, nullptr,
ConfigureAndroid);
web_view_helper.GetWebView()->SetInitialPageScaleOverride(
enforced_page_scale_factor);
web_view_helper.Resize(gfx::Size(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 < base::size(pages); ++i)
RegisterMockedHttpURLLoad(pages[i]);
int viewport_width = 400;
int viewport_height = 300;
float enforced_page_scale_factor = 0.75f;
for (size_t i = 0; i < base::size(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, nullptr,
ConfigureAndroid);
web_view_helper.GetWebView()
->GetSettings()
->SetClobberUserAgentInitialScaleQuirk(quirk_enabled);
web_view_helper.GetWebView()->SetInitialPageScaleOverride(
enforced_page_scale_factor);
web_view_helper.Resize(gfx::Size(viewport_width, viewport_height));
float expected_page_scale_factor =
quirk_enabled && i < base::size(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) {
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, nullptr,
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(gfx::Size(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");
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, nullptr,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetForceZeroLayoutHeight(true);
web_view_helper.Resize(gfx::Size(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");
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, nullptr,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetForceZeroLayoutHeight(true);
UpdateAllLifecyclePhases(web_view_helper.GetWebView());
LocalFrameView* frame_view =
web_view_helper.GetWebView()->MainFrameImpl()->GetFrameView();
GraphicsLayer* scroll_container =
frame_view->GetLayoutView()->Compositor()->RootGraphicsLayer();
EXPECT_EQ(IntSize(), frame_view->GetLayoutSize());
EXPECT_EQ(gfx::Size(), scroll_container->Size());
web_view_helper.Resize(gfx::Size(viewport_width, 0));
EXPECT_EQ(IntSize(viewport_width, 0), frame_view->GetLayoutSize());
EXPECT_EQ(gfx::Size(viewport_width, 0), scroll_container->Size());
// 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(gfx::Size(viewport_width, viewport_height));
EXPECT_FALSE(frame_view->NeedsLayout());
EXPECT_EQ(IntSize(viewport_width, 0), frame_view->GetLayoutSize());
EXPECT_EQ(gfx::Size(viewport_width, viewport_height),
scroll_container->Size());
LocalFrame* frame = web_view_helper.LocalMainFrame()->GetFrame();
VisualViewport& visual_viewport = frame->GetPage()->GetVisualViewport();
auto* scroll_node = visual_viewport.GetScrollTranslationNode()->ScrollNode();
EXPECT_EQ(IntRect(0, 0, viewport_width, viewport_height),
scroll_node->ContainerRect());
EXPECT_EQ(IntSize(viewport_width, viewport_height),
scroll_node->ContentsSize());
}
TEST_F(WebFrameTest, SetForceZeroLayoutHeight) {
RegisterMockedHttpURLLoad("200-by-300.html");
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,
nullptr, ConfigureAndroid);
web_view_helper.Resize(gfx::Size(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(gfx::Size(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(gfx::Size(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");
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, nullptr);
WebSettings* settings = web_view_helper.GetWebView()->GetSettings();
settings->SetViewportMetaEnabled(false);
settings->SetViewportEnabled(true);
settings->SetMainFrameResizesAreOrientationChanges(true);
settings->SetShrinksViewportContentToFit(true);
web_view_helper.Resize(gfx::Size(viewport_width, viewport_height));
ViewportData& viewport =
To<LocalFrame>(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");
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(base_url_ + "button.html", nullptr, nullptr,
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(gfx::Size(viewport_width, 0));
web_view_helper.GetWebView()->GetSettings()->SetForceZeroLayoutHeight(true);
web_view_helper.Resize(gfx::Size(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::Type::kGestureTap,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
WebGestureDevice::kTouchscreen);
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 = helper.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->UpdateAllLifecyclePhasesForTest();
// 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.scrollbar_mode = mojom::blink::ScrollbarMode::kAlwaysOff;
WebLocalFrameImpl* local_frame = helper.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");
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,
nullptr, ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetForceZeroLayoutHeight(true);
web_view_helper.Resize(gfx::Size(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");
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,
nullptr, 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(gfx::Size(viewport_width, viewport_height));
EXPECT_EQ(0, web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height());
}
TEST_F(WebFrameTest, WideViewportQuirkClobbersHeight) {
RegisterMockedHttpURLLoad("viewport-height-1000.html");
int viewport_width = 600;
int viewport_height = 800;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad("about:blank", nullptr, nullptr,
ConfigureAndroid);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()->GetSettings()->SetUseWideViewport(false);
web_view_helper.Resize(gfx::Size(viewport_width, viewport_height));
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "viewport-height-1000.html");
web_view_helper.Resize(gfx::Size(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, OverflowHiddenDisablesScrolling) {
RegisterMockedHttpURLLoad("body-overflow-hidden.html");
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(nullptr, nullptr);
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "body-overflow-hidden.html");
web_view_helper.Resize(gfx::Size(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");
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(nullptr, nullptr);
frame_test_helpers::LoadFrame(web_view_helper.GetWebView()->MainFrameImpl(),
base_url_ + "body-overflow-hidden-short.html");
web_view_helper.Resize(gfx::Size(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");
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(nullptr, nullptr);
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(gfx::Size(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");
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, nullptr, 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(gfx::Size(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");
// 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,
nullptr, ConfigureAndroid);
web_view_helper.Resize(gfx::Size(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");
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,
nullptr, ConfigureAndroid);
web_view_helper.Resize(gfx::Size(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");
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,
nullptr, ConfigureAndroid);
web_view_helper.Resize(gfx::Size(viewport_width, viewport_height));
web_view_helper.GetWebView()->SetPageScaleFactor(3);
EXPECT_EQ(3,
To<LocalFrame>(web_view_helper.GetWebView()->GetPage()->MainFrame())
->Loader()
.GetDocumentLoader()
->GetHistoryItem()
->GetViewState()
->page_scale_factor_);
}
TEST_F(WebFrameTest, initialScaleWrittenToHistoryItem) {
RegisterMockedHttpURLLoad("fixed_layout.html");
int viewport_width = 640;
int viewport_height = 480;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.Initialize(nullptr, nullptr, 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(gfx::Size(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,
To<LocalFrame>(web_view_helper.GetWebView()->GetPage()->MainFrame())
->Loader()
.GetDocumentLoader()
->GetHistoryItem()
->GetViewState()
->page_scale_factor_);
}
TEST_F(WebFrameTest, pageScaleFactorDoesntShrinkFrameView) {
RegisterMockedHttpURLLoad("large-div.html");
// 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,
nullptr, ConfigureAndroid);
web_view_helper.Resize(gfx::Size(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");
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,
nullptr, ConfigureAndroid);
web_view_helper.Resize(gfx::Size(viewport_width, viewport_height));
web_view_helper.GetWebView()->SetPageScaleFactor(2);
EXPECT_EQ(980,
To<LocalFrame>(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");
// 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 < base::size(device_scale_factors); ++i) {
float device_scale_factor = device_scale_factors[i];
float device_dpi = device_scale_factor * 160.0f;
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "viewport-target-densitydpi-high.html", nullptr, nullptr,
ConfigureAndroid);
web_view_helper.GetWebView()
->MainFrameWidget()
->SetDeviceScaleFactorForTesting(device_scale_factor);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()
->GetSettings()
->SetSupportDeprecatedTargetDensityDPI(true);
web_view_helper.Resize(gfx::Size(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};
int viewport_width = 640;
int viewport_height = 480;
for (size_t i = 0; i < base::size(device_scale_factors); ++i) {
frame_test_helpers::WebViewHelper web_view_helper;
web_view_helper.InitializeAndLoad(
base_url_ + "viewport-target-densitydpi-device.html", nullptr, nullptr,
ConfigureAndroid);
web_view_helper.GetWebView()
->MainFrameWidget()
->SetDeviceScaleFactorForTesting(device_scale_factors[i]);
web_view_helper.GetWebView()->GetSettings()->SetWideViewportQuirkEnabled(
true);
web_view_helper.GetWebView()
->GetSettings()
->SetSupportDeprecatedTargetDensityDPI(true);
web_view_helper.Resize(gfx::Size(viewport_width, viewport_height));
EXPECT_NEAR(viewport_width * device_scale_factors[i],
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width(),
1.0f);
EXPECT_NEAR(viewport_height * device_scale_factors[i],
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height(),
1.0f);
EXPECT_NEAR(1.0f / device_scale_factors[i],
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};
int viewport_width = 640;
int viewport_height = 480;
for (size_t i = 0; i < base::size(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, nullptr, ConfigureAndroid);
web_view_helper.GetWebView()
->MainFrameWidget()
->SetDeviceScaleFactorForTesting(device_scale_factors[i]);
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(gfx::Size(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");
float 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, nullptr,
ConfigureAndroid);
web_view_helper.GetWebView()
->MainFrameWidget()
->SetDeviceScaleFactorForTesting(device_scale_factor);
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(gfx::Size(viewport_width, viewport_height));
EXPECT_NEAR(viewport_width * device_scale_factor,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width(),
1.0f);
EXPECT_NEAR(viewport_height * device_scale_factor,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height(),
1.0f);
EXPECT_NEAR(1.0f / device_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor(), 0.01f);
}
TEST_F(WebFrameTest, NoWideViewportAndScaleLessThanOneWithDeviceWidth) {
RegisterMockedHttpURLLoad(
"viewport-initial-scale-less-than-1-device-width.html");
float 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, nullptr, ConfigureAndroid);
web_view_helper.GetWebView()
->MainFrameWidget()
->SetDeviceScaleFactorForTesting(device_scale_factor);
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(gfx::Size(viewport_width, viewport_height));
const float kPageZoom = 0.25f;
EXPECT_NEAR(viewport_width * device_scale_factor / kPageZoom,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width(),
1.0f);
EXPECT_NEAR(viewport_height * device_scale_factor / kPageZoom,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height(),
1.0f);
EXPECT_NEAR(1.0f / device_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor(), 0.01f);
}
TEST_F(WebFrameTest, NoWideViewportAndNoViewportWithInitialPageScaleOverride) {
RegisterMockedHttpURLLoad("large-div.html");
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,
nullptr, 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(gfx::Size(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");
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,
nullptr, ConfigureAndroid);
web_view_helper.GetWebView()
->GetSettings()
->SetViewportMetaNonUserScalableQuirk(true);
web_view_helper.Resize(gfx::Size(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");
float 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,
nullptr, ConfigureAndroid);
web_view_helper.GetWebView()
->MainFrameWidget()
->SetDeviceScaleFactorForTesting(device_scale_factor);
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(gfx::Size(viewport_width, viewport_height));
EXPECT_NEAR(viewport_width * device_scale_factor,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Width(),
1.0f);
EXPECT_NEAR(viewport_height * device_scale_factor,
web_view_helper.GetWebView()
->MainFrameImpl()
->GetFrameView()
->GetLayoutSize()
.Height(),
1.0f);
EXPECT_NEAR(1.0f / device_scale_factor,
web_view_helper.GetWebView()->PageScaleFactor(), 0.01f);
}
TEST_F(WebFrameTest, NoUserScalableQuirkIgnoresViewportScaleForWideViewport) {
RegisterMockedHttpURLLoad("viewport-2x-initial-scale-non-user-scalable.html");
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,
nullptr, 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(gfx::Size(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);
}