blob: c375ac7523dfd9183ef69395a012826d97bde1cf [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 "public/web/WebFrame.h"
#include "SkBitmap.h"
#include "SkCanvas.h"
#include "bindings/core/v8/SerializedScriptValueFactory.h"
#include "bindings/core/v8/V8Node.h"
#include "core/clipboard/DataTransfer.h"
#include "core/css/StyleSheetContents.h"
#include "core/css/resolver/StyleResolver.h"
#include "core/css/resolver/ViewportStyleResolver.h"
#include "core/dom/Fullscreen.h"
#include "core/dom/NodeComputedStyle.h"
#include "core/dom/Range.h"
#include "core/editing/Editor.h"
#include "core/editing/EphemeralRange.h"
#include "core/editing/FrameSelection.h"
#include "core/editing/VisiblePosition.h"
#include "core/editing/markers/DocumentMarkerController.h"
#include "core/editing/spellcheck/SpellChecker.h"
#include "core/events/MouseEvent.h"
#include "core/fetch/FetchRequest.h"
#include "core/fetch/MemoryCache.h"
#include "core/fetch/ResourceFetcher.h"
#include "core/frame/FrameHost.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/RemoteFrame.h"
#include "core/frame/Settings.h"
#include "core/frame/VisualViewport.h"
#include "core/html/HTMLBodyElement.h"
#include "core/html/HTMLDocument.h"
#include "core/html/HTMLFormElement.h"
#include "core/html/HTMLMediaElement.h"
#include "core/html/ImageDocument.h"
#include "core/input/EventHandler.h"
#include "core/layout/HitTestResult.h"
#include "core/layout/LayoutFullScreen.h"
#include "core/layout/api/LayoutViewItem.h"
#include "core/layout/compositing/PaintLayerCompositor.h"
#include "core/loader/DocumentLoader.h"
#include "core/loader/DocumentThreadableLoader.h"
#include "core/loader/DocumentThreadableLoaderClient.h"
#include "core/loader/FrameLoadRequest.h"
#include "core/loader/ThreadableLoader.h"
#include "core/page/Page.h"
#include "core/paint/PaintLayer.h"
#include "core/testing/NullExecutionContext.h"
#include "modules/mediastream/MediaStream.h"
#include "modules/mediastream/MediaStreamRegistry.h"
#include "platform/DragImage.h"
#include "platform/PlatformResourceLoader.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/UserGestureIndicator.h"
#include "platform/geometry/FloatRect.h"
#include "platform/network/ResourceError.h"
#include "platform/scroll/ScrollbarTheme.h"
#include "platform/testing/URLTestHelpers.h"
#include "platform/testing/UnitTestHelpers.h"
#include "platform/weborigin/SchemeRegistry.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "public/platform/Platform.h"
#include "public/platform/WebCachePolicy.h"
#include "public/platform/WebFloatRect.h"
#include "public/platform/WebSecurityOrigin.h"
#include "public/platform/WebThread.h"
#include "public/platform/WebURL.h"
#include "public/platform/WebURLLoaderMockFactory.h"
#include "public/platform/WebURLResponse.h"
#include "public/web/WebCache.h"
#include "public/web/WebConsoleMessage.h"
#include "public/web/WebDataSource.h"
#include "public/web/WebDeviceEmulationParams.h"
#include "public/web/WebDocument.h"
#include "public/web/WebFindOptions.h"
#include "public/web/WebFormElement.h"
#include "public/web/WebFrameClient.h"
#include "public/web/WebFrameContentDumper.h"
#include "public/web/WebFrameWidget.h"
#include "public/web/WebHistoryItem.h"
#include "public/web/WebPrintParams.h"
#include "public/web/WebRange.h"
#include "public/web/WebRemoteFrame.h"
#include "public/web/WebScriptExecutionCallback.h"
#include "public/web/WebScriptSource.h"
#include "public/web/WebSearchableFormData.h"
#include "public/web/WebSecurityPolicy.h"
#include "public/web/WebSelection.h"
#include "public/web/WebSettings.h"
#include "public/web/WebSpellCheckClient.h"
#include "public/web/WebTextCheckingCompletion.h"
#include "public/web/WebTextCheckingResult.h"
#include "public/web/WebViewClient.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "web/TextFinder.h"
#include "web/WebLocalFrameImpl.h"
#include "web/WebRemoteFrameImpl.h"
#include "web/WebViewImpl.h"
#include "web/tests/FrameTestHelpers.h"
#include "wtf/Forward.h"
#include "wtf/dtoa/utils.h"
#include <map>
#include <stdarg.h>
#include <v8.h>
using blink::URLTestHelpers::toKURL;
using blink::testing::runPendingTasks;
using testing::ElementsAre;
using testing::Mock;
using testing::_;
namespace blink {
::std::ostream& operator<<(::std::ostream& os, const WebFloatSize& size)
{
return os << "WebFloatSize: ["
<< size.width<< ", " << size.height<< "]";
}
::std::ostream& operator<<(::std::ostream& os, const WebFloatPoint& point)
{
return os << "WebFloatPoint: ["
<< point.x<< ", " << point.y<< "]";
}
const int touchPointPadding = 32;
#define EXPECT_RECT_EQ(expected, actual) \
do { \
EXPECT_EQ(expected.x(), actual.x()); \
EXPECT_EQ(expected.y(), actual.y()); \
EXPECT_EQ(expected.width(), actual.width()); \
EXPECT_EQ(expected.height(), actual.height()); \
} while (false)
#define EXPECT_POINT_EQ(expected, actual) \
do { \
EXPECT_EQ(expected.x(), actual.x()); \
EXPECT_EQ(expected.y(), actual.y()); \
} while (false)
#define EXPECT_FLOAT_POINT_EQ(expected, actual) \
do { \
EXPECT_FLOAT_EQ(expected.x(), actual.x()); \
EXPECT_FLOAT_EQ(expected.y(), actual.y()); \
} while (false)
class WebFrameTest : public ::testing::Test {
protected:
WebFrameTest()
: m_baseURL("http://internal.test/")
, m_notBaseURL("http://external.test/")
, m_chromeURL("chrome://")
{
}
~WebFrameTest() override
{
Platform::current()->getURLLoaderMockFactory()->unregisterAllURLs();
WebCache::clear();
}
void registerMockedHttpURLLoad(const std::string& fileName)
{
URLTestHelpers::registerMockedURLFromBaseURL(WebString::fromUTF8(m_baseURL.c_str()), WebString::fromUTF8(fileName.c_str()));
}
void registerMockedChromeURLLoad(const std::string& fileName)
{
URLTestHelpers::registerMockedURLFromBaseURL(WebString::fromUTF8(m_chromeURL.c_str()), WebString::fromUTF8(fileName.c_str()));
}
void registerMockedHttpURLLoadWithCSP(const std::string& fileName, const std::string& csp, bool reportOnly = false)
{
WebURLResponse response;
response.initialize();
response.setMIMEType("text/html");
response.addHTTPHeaderField(reportOnly ? WebString("Content-Security-Policy-Report-Only") : WebString("Content-Security-Policy"), WebString::fromUTF8(csp));
std::string fullString = m_baseURL + fileName;
URLTestHelpers::registerMockedURLLoadWithCustomResponse(toKURL(fullString.c_str()), WebString::fromUTF8(fileName.c_str()), WebString::fromUTF8(""), response);
}
void registerMockedHttpURLLoadWithMimeType(const std::string& fileName, const std::string& mimeType)
{
URLTestHelpers::registerMockedURLFromBaseURL(WebString::fromUTF8(m_baseURL.c_str()), WebString::fromUTF8(fileName.c_str()), WebString::fromUTF8(mimeType));
}
void applyViewportStyleOverride(FrameTestHelpers::WebViewHelper* webViewHelper)
{
StyleSheetContents* styleSheet = StyleSheetContents::create(CSSParserContext(UASheetMode, 0));
styleSheet->parseString(loadResourceAsASCIIString("viewportAndroid.css"));
RuleSet* ruleSet = RuleSet::create();
ruleSet->addRulesFromSheet(styleSheet, MediaQueryEvaluator("screen"));
Document* document = toLocalFrame(webViewHelper->webViewImpl()->page()->mainFrame())->document();
document->ensureStyleResolver().viewportStyleResolver()->collectViewportRules(ruleSet, ViewportStyleResolver::UserAgentOrigin);
document->ensureStyleResolver().viewportStyleResolver()->resolve();
}
static void configueCompositingWebView(WebSettings* settings)
{
settings->setAcceleratedCompositingEnabled(true);
settings->setPreferCompositingToLCDTextEnabled(true);
}
static void configureAndroid(WebSettings* settings)
{
settings->setViewportMetaEnabled(true);
settings->setViewportEnabled(true);
settings->setMainFrameResizesAreOrientationChanges(true);
settings->setShrinksViewportContentToFit(true);
}
static void configureLoadsImagesAutomatically(WebSettings* settings)
{
settings->setLoadsImagesAutomatically(true);
}
void initializeTextSelectionWebView(const std::string& url, FrameTestHelpers::WebViewHelper* webViewHelper)
{
webViewHelper->initializeAndLoad(url, true);
webViewHelper->webView()->settings()->setDefaultFontSize(12);
webViewHelper->resize(WebSize(640, 480));
}
PassOwnPtr<DragImage> nodeImageTestSetup(FrameTestHelpers::WebViewHelper* webViewHelper, const std::string& testcase)
{
registerMockedHttpURLLoad("nodeimage.html");
webViewHelper->initializeAndLoad(m_baseURL + "nodeimage.html");
webViewHelper->resize(WebSize(640, 480));
LocalFrame* frame = toLocalFrame(webViewHelper->webViewImpl()->page()->mainFrame());
DCHECK(frame);
Element* element = frame->document()->getElementById(testcase.c_str());
return frame->nodeImage(*element);
}
void removeElementById(WebLocalFrameImpl* frame, const AtomicString& id)
{
Element* element = frame->frame()->document()->getElementById(id);
DCHECK(element);
element->remove();
}
std::string m_baseURL;
std::string m_notBaseURL;
std::string m_chromeURL;
};
enum ParameterizedWebFrameTestConfig {
Default,
RootLayerScrolls
};
class ParameterizedWebFrameTest
: public WebFrameTest
, public ::testing::WithParamInterface<ParameterizedWebFrameTestConfig>
, public FrameTestHelpers::SettingOverrider {
public:
void overrideSettings(WebSettings* settings)
{
switch (GetParam()) {
case Default:
break;
case RootLayerScrolls:
settings->setRootLayerScrolls(true);
break;
}
}
};
// Friendly string for gtest failure messages.
void PrintTo(ParameterizedWebFrameTestConfig config, ::std::ostream* os)
{
switch (config) {
case Default:
*os << "Default";
break;
case RootLayerScrolls:
*os << "RootLayerScrolls";
break;
}
}
INSTANTIATE_TEST_CASE_P(All, ParameterizedWebFrameTest, ::testing::Values(
ParameterizedWebFrameTestConfig::Default,
ParameterizedWebFrameTestConfig::RootLayerScrolls));
TEST_P(ParameterizedWebFrameTest, ContentText)
{
registerMockedHttpURLLoad("iframes_test.html");
registerMockedHttpURLLoad("visible_iframe.html");
registerMockedHttpURLLoad("invisible_iframe.html");
registerMockedHttpURLLoad("zero_sized_iframe.html");
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "iframes_test.html");
// Now retrieve the frames text and test it only includes visible elements.
std::string content = WebFrameContentDumper::dumpWebViewAsText(webViewHelper.webView(), 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_P(ParameterizedWebFrameTest, FrameForEnteredContext)
{
registerMockedHttpURLLoad("iframes_test.html");
registerMockedHttpURLLoad("visible_iframe.html");
registerMockedHttpURLLoad("invisible_iframe.html");
registerMockedHttpURLLoad("zero_sized_iframe.html");
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "iframes_test.html", true);
v8::HandleScope scope(v8::Isolate::GetCurrent());
EXPECT_EQ(webViewHelper.webView()->mainFrame(), WebLocalFrame::frameForContext(webViewHelper.webView()->mainFrame()->mainWorldScriptContext()));
EXPECT_EQ(webViewHelper.webView()->mainFrame()->firstChild(), WebLocalFrame::frameForContext(webViewHelper.webView()->mainFrame()->firstChild()->mainWorldScriptContext()));
}
class ScriptExecutionCallbackHelper : public WebScriptExecutionCallback {
public:
explicit ScriptExecutionCallbackHelper(v8::Local<v8::Context> context)
: m_didComplete(false)
, m_context(context) { }
~ScriptExecutionCallbackHelper() { }
bool didComplete() const { return m_didComplete; }
const String& stringValue() const { return m_stringValue; }
private:
void completed(const WebVector<v8::Local<v8::Value>>& values) override
{
m_didComplete = true;
if (!values.isEmpty() && values[0]->IsString()) {
m_stringValue = toCoreString(values[0]->ToString(m_context).ToLocalChecked());
}
}
bool m_didComplete;
String m_stringValue;
v8::Local<v8::Context> m_context;
};
TEST_P(ParameterizedWebFrameTest, RequestExecuteScript)
{
registerMockedHttpURLLoad("foo.html");
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "foo.html", true);
v8::HandleScope scope(v8::Isolate::GetCurrent());
ScriptExecutionCallbackHelper callbackHelper(webViewHelper.webView()->mainFrame()->mainWorldScriptContext());
webViewHelper.webView()->mainFrame()->toWebLocalFrame()->requestExecuteScriptAndReturnValue(WebScriptSource(WebString("'hello';")), false, &callbackHelper);
runPendingTasks();
EXPECT_TRUE(callbackHelper.didComplete());
EXPECT_EQ("hello", callbackHelper.stringValue());
}
TEST_P(ParameterizedWebFrameTest, SuspendedRequestExecuteScript)
{
registerMockedHttpURLLoad("foo.html");
registerMockedHttpURLLoad("bar.html");
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "foo.html", true);
v8::HandleScope scope(v8::Isolate::GetCurrent());
ScriptExecutionCallbackHelper callbackHelper(webViewHelper.webView()->mainFrame()->mainWorldScriptContext());
// Suspend scheduled tasks so the script doesn't run.
toWebLocalFrameImpl(webViewHelper.webView()->mainFrame())->frame()->document()->suspendScheduledTasks();
webViewHelper.webView()->mainFrame()->toWebLocalFrame()->requestExecuteScriptAndReturnValue(WebScriptSource(WebString("'hello';")), false, &callbackHelper);
runPendingTasks();
EXPECT_FALSE(callbackHelper.didComplete());
// If the frame navigates, pending scripts should be removed, but the callback should always be ran.
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(), m_baseURL + "bar.html");
EXPECT_TRUE(callbackHelper.didComplete());
EXPECT_EQ(String(), callbackHelper.stringValue());
}
TEST_P(ParameterizedWebFrameTest, IframeScriptRemovesSelf)
{
registerMockedHttpURLLoad("single_iframe.html");
registerMockedHttpURLLoad("visible_iframe.html");
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "single_iframe.html", true);
v8::HandleScope scope(v8::Isolate::GetCurrent());
ScriptExecutionCallbackHelper callbackHelper(webViewHelper.webView()->mainFrame()->mainWorldScriptContext());
webViewHelper.webView()->mainFrame()->firstChild()->toWebLocalFrame()->requestExecuteScriptAndReturnValue(WebScriptSource(WebString("var iframe = window.top.document.getElementsByTagName('iframe')[0]; window.top.document.body.removeChild(iframe); 'hello';")), false, &callbackHelper);
runPendingTasks();
EXPECT_TRUE(callbackHelper.didComplete());
EXPECT_EQ(String(), callbackHelper.stringValue());
}
TEST_P(ParameterizedWebFrameTest, FormWithNullFrame)
{
registerMockedHttpURLLoad("form.html");
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "form.html");
WebVector<WebFormElement> forms;
webViewHelper.webView()->mainFrame()->document().forms(forms);
webViewHelper.reset();
EXPECT_EQ(forms.size(), 1U);
// This test passes if this doesn't crash.
WebSearchableFormData searchableDataForm(forms[0]);
}
TEST_P(ParameterizedWebFrameTest, ChromePageJavascript)
{
registerMockedChromeURLLoad("history.html");
// Pass true to enable JavaScript.
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_chromeURL + "history.html", true);
// Try to run JS against the chrome-style URL.
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(), "javascript:document.body.appendChild(document.createTextNode('Clobbered'))");
// Now retrieve the frame's text and ensure it was modified by running javascript.
std::string content = WebFrameContentDumper::dumpWebViewAsText(webViewHelper.webView(), 1024).utf8();
EXPECT_NE(std::string::npos, content.find("Clobbered"));
}
TEST_P(ParameterizedWebFrameTest, ChromePageNoJavascript)
{
registerMockedChromeURLLoad("history.html");
/// Pass true to enable JavaScript.
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_chromeURL + "history.html", true);
// Try to run JS against the chrome-style URL after prohibiting it.
WebSecurityPolicy::registerURLSchemeAsNotAllowingJavascriptURLs("chrome");
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(), "javascript:document.body.appendChild(document.createTextNode('Clobbered'))");
// Now retrieve the frame's text and ensure it wasn't modified by running javascript.
std::string content = WebFrameContentDumper::dumpWebViewAsText(webViewHelper.webView(), 1024).utf8();
EXPECT_EQ(std::string::npos, content.find("Clobbered"));
}
TEST_P(ParameterizedWebFrameTest, LocationSetHostWithMissingPort)
{
std::string fileName = "print-location-href.html";
registerMockedHttpURLLoad(fileName);
URLTestHelpers::registerMockedURLLoad(toKURL("http://internal.test:0/" + fileName), WebString::fromUTF8(fileName));
FrameTestHelpers::WebViewHelper webViewHelper(this);
/// Pass true to enable JavaScript.
webViewHelper.initializeAndLoad(m_baseURL + fileName, true);
// Setting host to "hostname:" should be treated as "hostname:0".
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(), "javascript:location.host = 'internal.test:'; void 0;");
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(), "javascript:document.body.textContent = location.href; void 0;");
std::string content = WebFrameContentDumper::dumpWebViewAsText(webViewHelper.webView(), 1024).utf8();
EXPECT_EQ("http://internal.test:0/" + fileName, content);
}
TEST_P(ParameterizedWebFrameTest, LocationSetEmptyPort)
{
std::string fileName = "print-location-href.html";
registerMockedHttpURLLoad(fileName);
URLTestHelpers::registerMockedURLLoad(toKURL("http://internal.test:0/" + fileName), WebString::fromUTF8(fileName));
FrameTestHelpers::WebViewHelper webViewHelper(this);
/// Pass true to enable JavaScript.
webViewHelper.initializeAndLoad(m_baseURL + fileName, true);
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(), "javascript:location.port = ''; void 0;");
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(), "javascript:document.body.textContent = location.href; void 0;");
std::string content = WebFrameContentDumper::dumpWebViewAsText(webViewHelper.webView(), 1024).utf8();
EXPECT_EQ("http://internal.test:0/" + fileName, content);
}
class EvaluateOnLoadWebFrameClient : public FrameTestHelpers::TestWebFrameClient {
public:
EvaluateOnLoadWebFrameClient() : m_executing(false), m_wasExecuted(false) { }
void didClearWindowObject(WebLocalFrame* frame) override
{
EXPECT_FALSE(m_executing);
m_wasExecuted = true;
m_executing = true;
v8::HandleScope handleScope(v8::Isolate::GetCurrent());
frame->executeScriptAndReturnValue(WebScriptSource(WebString("window.someProperty = 42;")));
m_executing = false;
}
bool m_executing;
bool m_wasExecuted;
};
TEST_P(ParameterizedWebFrameTest, DidClearWindowObjectIsNotRecursive)
{
EvaluateOnLoadWebFrameClient webFrameClient;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad("about:blank", true, &webFrameClient);
EXPECT_TRUE(webFrameClient.m_wasExecuted);
}
class CSSCallbackWebFrameClient : public FrameTestHelpers::TestWebFrameClient {
public:
CSSCallbackWebFrameClient() : m_updateCount(0) { }
void didMatchCSS(WebLocalFrame*, const WebVector<WebString>& newlyMatchingSelectors, const WebVector<WebString>& stoppedMatchingSelectors) override;
std::map<WebLocalFrame*, std::set<std::string>> m_matchedSelectors;
int m_updateCount;
};
void CSSCallbackWebFrameClient::didMatchCSS(WebLocalFrame* frame, const WebVector<WebString>& newlyMatchingSelectors, const WebVector<WebString>& stoppedMatchingSelectors)
{
++m_updateCount;
std::set<std::string>& frameSelectors = m_matchedSelectors[frame];
for (size_t i = 0; i < newlyMatchingSelectors.size(); ++i) {
std::string selector = newlyMatchingSelectors[i].utf8();
EXPECT_EQ(0U, frameSelectors.count(selector)) << selector;
frameSelectors.insert(selector);
}
for (size_t i = 0; i < stoppedMatchingSelectors.size(); ++i) {
std::string selector = stoppedMatchingSelectors[i].utf8();
EXPECT_EQ(1U, frameSelectors.count(selector)) << selector;
frameSelectors.erase(selector);
}
}
class WebFrameCSSCallbackTest : public ::testing::Test {
protected:
WebFrameCSSCallbackTest()
{
m_frame = m_helper.initializeAndLoad("about:blank", true, &m_client)->mainFrame()->toWebLocalFrame();
}
~WebFrameCSSCallbackTest()
{
EXPECT_EQ(1U, m_client.m_matchedSelectors.size());
}
WebDocument doc() const
{
return m_frame->document();
}
int updateCount() const
{
return m_client.m_updateCount;
}
const std::set<std::string>& matchedSelectors()
{
return m_client.m_matchedSelectors[m_frame];
}
void loadHTML(const std::string& html)
{
FrameTestHelpers::loadHTMLString(m_frame, html, toKURL("about:blank"));
}
void executeScript(const WebString& code)
{
m_frame->executeScript(WebScriptSource(code));
m_frame->view()->updateAllLifecyclePhases();
runPendingTasks();
}
CSSCallbackWebFrameClient m_client;
FrameTestHelpers::WebViewHelper m_helper;
WebLocalFrame* m_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.append(WebString::fromUTF8("div.initial_on"));
m_frame->document().watchCSSSelectors(WebVector<WebString>(selectors));
m_frame->view()->updateAllLifecyclePhases();
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.append(WebString::fromUTF8("div.initial_off"));
doc().watchCSSSelectors(WebVector<WebString>(selectors));
m_frame->view()->updateAllLifecyclePhases();
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>());
m_frame->view()->updateAllLifecyclePhases();
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.append(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.append(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.append(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, Reparenting)
{
loadHTML(
"<div id='d1'><span></span></div>"
"<div id='d2'></div>");
Vector<WebString> selectors;
selectors.append(WebString::fromUTF8("span"));
doc().watchCSSSelectors(WebVector<WebString>(selectors));
m_frame->view()->updateAllLifecyclePhases();
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.append(WebString::fromUTF8("span"));
selectors.append(WebString::fromUTF8("span,p"));
doc().watchCSSSelectors(WebVector<WebString>(selectors));
m_frame->view()->updateAllLifecyclePhases();
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.append(WebString::fromUTF8("span"));
selectors.append(WebString::fromUTF8("[")); // Invalid.
selectors.append(WebString::fromUTF8("p span")); // Not compound.
doc().watchCSSSelectors(WebVector<WebString>(selectors));
m_frame->view()->updateAllLifecyclePhases();
runPendingTasks();
EXPECT_EQ(1, updateCount());
EXPECT_THAT(matchedSelectors(), ElementsAre("span"))
<< "An invalid selector shouldn't prevent other selectors from matching.";
}
TEST_P(ParameterizedWebFrameTest, DispatchMessageEventWithOriginCheck)
{
registerMockedHttpURLLoad("postmessage_test.html");
// Pass true to enable JavaScript.
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "postmessage_test.html", true);
// Send a message with the correct origin.
WebSecurityOrigin correctOrigin(WebSecurityOrigin::create(toKURL(m_baseURL)));
WebDocument document = webViewHelper.webView()->mainFrame()->document();
WebSerializedScriptValue data(WebSerializedScriptValue::fromString("foo"));
WebDOMMessageEvent message(data, "http://origin.com");
webViewHelper.webView()->mainFrame()->dispatchMessageEventWithOriginCheck(correctOrigin, message);
// Send another message with incorrect origin.
WebSecurityOrigin incorrectOrigin(WebSecurityOrigin::create(toKURL(m_chromeURL)));
webViewHelper.webView()->mainFrame()->dispatchMessageEventWithOriginCheck(incorrectOrigin, message);
// Verify that only the first addition is in the body of the page.
std::string content = WebFrameContentDumper::dumpWebViewAsText(webViewHelper.webView(), 1024).utf8();
EXPECT_NE(std::string::npos, content.find("Message 1."));
EXPECT_EQ(std::string::npos, content.find("Message 2."));
}
TEST_P(ParameterizedWebFrameTest, PostMessageThenDetach)
{
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad("about:blank");
LocalFrame* frame = toLocalFrame(webViewHelper.webViewImpl()->page()->mainFrame());
NonThrowableExceptionState exceptionState;
MessagePortArray messagePorts;
frame->domWindow()->postMessage(SerializedScriptValue::serialize("message"), messagePorts, "*", frame->localDOMWindow(), exceptionState);
webViewHelper.reset();
EXPECT_FALSE(exceptionState.hadException());
// Success is not crashing.
runPendingTasks();
}
namespace {
class FixedLayoutTestWebViewClient : public FrameTestHelpers::TestWebViewClient {
public:
WebScreenInfo screenInfo() override { return m_screenInfo; }
WebScreenInfo m_screenInfo;
};
class FakeCompositingWebViewClient : public FixedLayoutTestWebViewClient {
};
// Viewport settings need to be set before the page gets loaded
void enableViewportSettings(WebSettings* settings)
{
settings->setViewportMetaEnabled(true);
settings->setViewportEnabled(true);
settings->setMainFrameResizesAreOrientationChanges(true);
settings->setShrinksViewportContentToFit(true);
}
// Helper function to set autosizing multipliers on a document.
bool setTextAutosizingMultiplier(Document* document, float multiplier)
{
bool multiplierSet = false;
for (LayoutObject* layoutObject = document->layoutView(); layoutObject; layoutObject = layoutObject->nextInPreOrder()) {
if (layoutObject->style()) {
layoutObject->mutableStyleRef().setTextAutosizingMultiplier(multiplier);
EXPECT_EQ(multiplier, layoutObject->style()->textAutosizingMultiplier());
multiplierSet = true;
}
}
return multiplierSet;
}
// Helper function to check autosizing multipliers on a document.
bool checkTextAutosizingMultiplier(Document* document, float multiplier)
{
bool multiplierChecked = false;
for (LayoutObject* layoutObject = document->layoutView(); layoutObject; layoutObject = layoutObject->nextInPreOrder()) {
if (layoutObject->style() && layoutObject->isText()) {
EXPECT_EQ(multiplier, layoutObject->style()->textAutosizingMultiplier());
multiplierChecked = true;
}
}
return multiplierChecked;
}
} // anonymous namespace
TEST_P(ParameterizedWebFrameTest, ChangeInFixedLayoutResetsTextAutosizingMultipliers)
{
registerMockedHttpURLLoad("fixed_layout.html");
FixedLayoutTestWebViewClient client;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "fixed_layout.html", true, nullptr, &client, nullptr, enableViewportSettings);
Document* document = toLocalFrame(webViewHelper.webViewImpl()->page()->mainFrame())->document();
document->settings()->setTextAutosizingEnabled(true);
EXPECT_TRUE(document->settings()->textAutosizingEnabled());
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_TRUE(setTextAutosizingMultiplier(document, 2));
ViewportDescription description = document->viewportDescription();
// Choose a width that's not going match the viewport width of the loaded document.
description.minWidth = Length(100, blink::Fixed);
description.maxWidth = Length(100, blink::Fixed);
webViewHelper.webViewImpl()->updatePageDefinedViewportConstraints(description);
EXPECT_TRUE(checkTextAutosizingMultiplier(document, 1));
}
TEST_P(ParameterizedWebFrameTest, WorkingTextAutosizingMultipliers_VirtualViewport)
{
const std::string htmlFile = "fixed_layout.html";
registerMockedHttpURLLoad(htmlFile);
FixedLayoutTestWebViewClient client;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + htmlFile, true, nullptr, &client, nullptr, configureAndroid);
Document* document = toLocalFrame(webViewHelper.webViewImpl()->page()->mainFrame())->document();
document->settings()->setTextAutosizingEnabled(true);
EXPECT_TRUE(document->settings()->textAutosizingEnabled());
webViewHelper.resize(WebSize(490, 800));
// Multiplier: 980 / 490 = 2.0
EXPECT_TRUE(checkTextAutosizingMultiplier(document, 2.0));
}
TEST_P(ParameterizedWebFrameTest, VisualViewportSetSizeInvalidatesTextAutosizingMultipliers)
{
registerMockedHttpURLLoad("iframe_reload.html");
registerMockedHttpURLLoad("visible_iframe.html");
FixedLayoutTestWebViewClient client;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "iframe_reload.html", true, nullptr, &client, nullptr, enableViewportSettings);
LocalFrame* mainFrame = toLocalFrame(webViewHelper.webViewImpl()->page()->mainFrame());
Document* document = mainFrame->document();
FrameView* frameView = webViewHelper.webViewImpl()->mainFrameImpl()->frameView();
document->settings()->setTextAutosizingEnabled(true);
EXPECT_TRUE(document->settings()->textAutosizingEnabled());
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
for (Frame* frame = mainFrame; frame; frame = frame->tree().traverseNext()) {
if (!frame->isLocalFrame())
continue;
EXPECT_TRUE(setTextAutosizingMultiplier(toLocalFrame(frame)->document(), 2));
for (LayoutObject* layoutObject = toLocalFrame(frame)->document()->layoutView(); layoutObject; layoutObject = layoutObject->nextInPreOrder()) {
if (layoutObject->isText())
EXPECT_FALSE(layoutObject->needsLayout());
}
}
frameView->page()->frameHost().visualViewport().setSize(IntSize(200, 200));
for (Frame* frame = mainFrame; frame; frame = frame->tree().traverseNext()) {
if (!frame->isLocalFrame())
continue;
for (LayoutObject* layoutObject = toLocalFrame(frame)->document()->layoutView(); layoutObject; layoutObject = layoutObject->nextInPreOrder()) {
if (layoutObject->isText())
EXPECT_TRUE(layoutObject->needsLayout());
}
}
}
TEST_P(ParameterizedWebFrameTest, ZeroHeightPositiveWidthNotIgnored)
{
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 1280;
int viewportHeight = 0;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initialize(true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_EQ(viewportWidth, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().width());
EXPECT_EQ(viewportHeight, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height());
}
TEST_P(ParameterizedWebFrameTest, DeviceScaleFactorUsesDefaultWithoutViewportTag)
{
registerMockedHttpURLLoad("no_viewport_tag.html");
int viewportWidth = 640;
int viewportHeight = 480;
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 2;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "no_viewport_tag.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_EQ(2, webViewHelper.webViewImpl()->page()->deviceScaleFactor());
// Device scale factor should be independent of page scale.
webViewHelper.webView()->setDefaultPageScaleLimits(1, 2);
webViewHelper.webView()->setPageScaleFactor(0.5);
webViewHelper.webView()->updateAllLifecyclePhases();
EXPECT_EQ(1, webViewHelper.webView()->pageScaleFactor());
// Force the layout to happen before leaving the test.
webViewHelper.webView()->updateAllLifecyclePhases();
}
TEST_P(ParameterizedWebFrameTest, FixedLayoutInitializeAtMinimumScale)
{
registerMockedHttpURLLoad("fixed_layout.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
// Make sure we initialize to minimum scale, even if the window size
// only becomes available after the load begins.
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initialize(true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->setDefaultPageScaleLimits(0.25f, 5);
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(), m_baseURL + "fixed_layout.html");
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
int defaultFixedLayoutWidth = 980;
float minimumPageScaleFactor = viewportWidth / (float) defaultFixedLayoutWidth;
EXPECT_EQ(minimumPageScaleFactor, webViewHelper.webViewImpl()->pageScaleFactor());
EXPECT_EQ(minimumPageScaleFactor, webViewHelper.webViewImpl()->minimumPageScaleFactor());
// Assume the user has pinch zoomed to page scale factor 2.
float userPinchPageScaleFactor = 2;
webViewHelper.webView()->setPageScaleFactor(userPinchPageScaleFactor);
webViewHelper.webView()->updateAllLifecyclePhases();
// Make sure we don't reset to initial scale if the page continues to load.
webViewHelper.webViewImpl()->didCommitLoad(false, false);
webViewHelper.webViewImpl()->didChangeContentsSize();
EXPECT_EQ(userPinchPageScaleFactor, webViewHelper.webView()->pageScaleFactor());
// Make sure we don't reset to initial scale if the viewport size changes.
webViewHelper.resize(WebSize(viewportWidth, viewportHeight + 100));
EXPECT_EQ(userPinchPageScaleFactor, webViewHelper.webView()->pageScaleFactor());
}
TEST_P(ParameterizedWebFrameTest, WideDocumentInitializeAtMinimumScale)
{
registerMockedHttpURLLoad("wide_document.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
// Make sure we initialize to minimum scale, even if the window size
// only becomes available after the load begins.
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initialize(true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->setDefaultPageScaleLimits(0.25f, 5);
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(), m_baseURL + "wide_document.html");
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
int wideDocumentWidth = 1500;
float minimumPageScaleFactor = viewportWidth / (float) wideDocumentWidth;
EXPECT_EQ(minimumPageScaleFactor, webViewHelper.webViewImpl()->pageScaleFactor());
EXPECT_EQ(minimumPageScaleFactor, webViewHelper.webViewImpl()->minimumPageScaleFactor());
// Assume the user has pinch zoomed to page scale factor 2.
float userPinchPageScaleFactor = 2;
webViewHelper.webView()->setPageScaleFactor(userPinchPageScaleFactor);
webViewHelper.webView()->updateAllLifecyclePhases();
// Make sure we don't reset to initial scale if the page continues to load.
webViewHelper.webViewImpl()->didCommitLoad(false, false);
webViewHelper.webViewImpl()->didChangeContentsSize();
EXPECT_EQ(userPinchPageScaleFactor, webViewHelper.webView()->pageScaleFactor());
// Make sure we don't reset to initial scale if the viewport size changes.
webViewHelper.resize(WebSize(viewportWidth, viewportHeight + 100));
EXPECT_EQ(userPinchPageScaleFactor, webViewHelper.webView()->pageScaleFactor());
}
TEST_P(ParameterizedWebFrameTest, DelayedViewportInitialScale)
{
registerMockedHttpURLLoad("viewport-auto-initial-scale.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "viewport-auto-initial-scale.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_EQ(0.25f, webViewHelper.webView()->pageScaleFactor());
Document* document = toLocalFrame(webViewHelper.webViewImpl()->page()->mainFrame())->document();
ViewportDescription description = document->viewportDescription();
description.zoom = 2;
document->setViewportDescription(description);
webViewHelper.webView()->updateAllLifecyclePhases();
EXPECT_EQ(2, webViewHelper.webView()->pageScaleFactor());
}
TEST_P(ParameterizedWebFrameTest, setLoadWithOverviewModeToFalse)
{
registerMockedHttpURLLoad("viewport-auto-initial-scale.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "viewport-auto-initial-scale.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setLoadWithOverviewMode(false);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
// The page must be displayed at 100% zoom.
EXPECT_EQ(1.0f, webViewHelper.webView()->pageScaleFactor());
}
TEST_P(ParameterizedWebFrameTest, SetLoadWithOverviewModeToFalseAndNoWideViewport)
{
registerMockedHttpURLLoad("large-div.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "large-div.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setLoadWithOverviewMode(false);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setUseWideViewport(false);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
// The page must be displayed at 100% zoom, despite that it hosts a wide div element.
EXPECT_EQ(1.0f, webViewHelper.webView()->pageScaleFactor());
}
TEST_P(ParameterizedWebFrameTest, NoWideViewportIgnoresPageViewportWidth)
{
registerMockedHttpURLLoad("viewport-auto-initial-scale.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "viewport-auto-initial-scale.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setUseWideViewport(false);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
// The page sets viewport width to 3000, but with UseWideViewport == false is must be ignored.
EXPECT_EQ(viewportWidth, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->contentsSize().width());
EXPECT_EQ(viewportHeight, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->contentsSize().height());
}
TEST_P(ParameterizedWebFrameTest, NoWideViewportIgnoresPageViewportWidthButAccountsScale)
{
registerMockedHttpURLLoad("viewport-wide-2x-initial-scale.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "viewport-wide-2x-initial-scale.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setUseWideViewport(false);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
// 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(viewportWidth / 2, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->contentsSize().width());
EXPECT_EQ(viewportHeight / 2, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->contentsSize().height());
}
TEST_P(ParameterizedWebFrameTest, WideViewportSetsTo980WithoutViewportTag)
{
registerMockedHttpURLLoad("no_viewport_tag.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "no_viewport_tag.html", true, nullptr, &client, nullptr, enableViewportSettings);
applyViewportStyleOverride(&webViewHelper);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setUseWideViewport(true);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_EQ(980, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->contentsSize().width());
EXPECT_EQ(980.0 / viewportWidth * viewportHeight, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->contentsSize().height());
}
TEST_P(ParameterizedWebFrameTest, WideViewportSetsTo980WithXhtmlMp)
{
registerMockedHttpURLLoad("viewport/viewport-legacy-xhtmlmp.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initialize(true, nullptr, &client, nullptr, enableViewportSettings);
applyViewportStyleOverride(&webViewHelper);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setUseWideViewport(true);
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(), m_baseURL + "viewport/viewport-legacy-xhtmlmp.html");
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_EQ(viewportWidth, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->contentsSize().width());
EXPECT_EQ(viewportHeight, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->contentsSize().height());
}
TEST_P(ParameterizedWebFrameTest, NoWideViewportAndHeightInMeta)
{
registerMockedHttpURLLoad("viewport-height-1000.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "viewport-height-1000.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setUseWideViewport(false);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_EQ(viewportWidth, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->contentsSize().width());
}
TEST_P(ParameterizedWebFrameTest, WideViewportSetsTo980WithAutoWidth)
{
registerMockedHttpURLLoad("viewport-2x-initial-scale.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "viewport-2x-initial-scale.html", true, nullptr, &client, nullptr, enableViewportSettings);
applyViewportStyleOverride(&webViewHelper);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setUseWideViewport(true);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_EQ(980, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->contentsSize().width());
EXPECT_EQ(980.0 / viewportWidth * viewportHeight, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->contentsSize().height());
}
TEST_P(ParameterizedWebFrameTest, PageViewportInitialScaleOverridesLoadWithOverviewMode)
{
registerMockedHttpURLLoad("viewport-wide-2x-initial-scale.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "viewport-wide-2x-initial-scale.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setLoadWithOverviewMode(false);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
// The page must be displayed at 200% zoom, as specified in its viewport meta tag.
EXPECT_EQ(2.0f, webViewHelper.webView()->pageScaleFactor());
}
TEST_P(ParameterizedWebFrameTest, setInitialPageScaleFactorPermanently)
{
registerMockedHttpURLLoad("fixed_layout.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
float enforcedPageScaleFactor = 2.0f;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "fixed_layout.html", true, nullptr, &client, nullptr, enableViewportSettings);
applyViewportStyleOverride(&webViewHelper);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setLoadWithOverviewMode(false);
webViewHelper.webView()->setInitialPageScaleOverride(enforcedPageScaleFactor);
webViewHelper.webView()->updateAllLifecyclePhases();
EXPECT_EQ(enforcedPageScaleFactor, webViewHelper.webView()->pageScaleFactor());
int viewportWidth = 640;
int viewportHeight = 480;
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_EQ(enforcedPageScaleFactor, webViewHelper.webView()->pageScaleFactor());
webViewHelper.webView()->setInitialPageScaleOverride(-1);
webViewHelper.webView()->updateAllLifecyclePhases();
EXPECT_EQ(1.0, webViewHelper.webView()->pageScaleFactor());
}
TEST_P(ParameterizedWebFrameTest, PermanentInitialPageScaleFactorOverridesLoadWithOverviewMode)
{
registerMockedHttpURLLoad("viewport-auto-initial-scale.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
float enforcedPageScaleFactor = 0.5f;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "viewport-auto-initial-scale.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setLoadWithOverviewMode(false);
webViewHelper.webView()->setInitialPageScaleOverride(enforcedPageScaleFactor);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_EQ(enforcedPageScaleFactor, webViewHelper.webView()->pageScaleFactor());
}
TEST_P(ParameterizedWebFrameTest, PermanentInitialPageScaleFactorOverridesPageViewportInitialScale)
{
registerMockedHttpURLLoad("viewport-wide-2x-initial-scale.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
float enforcedPageScaleFactor = 0.5f;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "viewport-wide-2x-initial-scale.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->setInitialPageScaleOverride(enforcedPageScaleFactor);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_EQ(enforcedPageScaleFactor, webViewHelper.webView()->pageScaleFactor());
}
TEST_P(ParameterizedWebFrameTest, 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 pageScaleFactors[] = { 0.5f, 1.0f };
for (size_t i = 0; i < WTF_ARRAY_LENGTH(pages); ++i)
registerMockedHttpURLLoad(pages[i]);
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 400;
int viewportHeight = 300;
float enforcedPageScaleFactor = 0.75f;
for (size_t i = 0; i < WTF_ARRAY_LENGTH(pages); ++i) {
for (int quirkEnabled = 0; quirkEnabled <= 1; ++quirkEnabled) {
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + pages[i], true, nullptr, &client, nullptr, enableViewportSettings);
applyViewportStyleOverride(&webViewHelper);
webViewHelper.webView()->settings()->setClobberUserAgentInitialScaleQuirk(quirkEnabled);
webViewHelper.webView()->setInitialPageScaleOverride(enforcedPageScaleFactor);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
float expectedPageScaleFactor = quirkEnabled && i < WTF_ARRAY_LENGTH(pageScaleFactors) ? pageScaleFactors[i] : enforcedPageScaleFactor;
EXPECT_EQ(expectedPageScaleFactor, webViewHelper.webView()->pageScaleFactor());
}
}
}
TEST_P(ParameterizedWebFrameTest, PermanentInitialPageScaleFactorAffectsLayoutWidth)
{
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
float enforcedPageScaleFactor = 0.5;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad("about:blank", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setUseWideViewport(false);
webViewHelper.webView()->settings()->setLoadWithOverviewMode(false);
webViewHelper.webView()->setInitialPageScaleOverride(enforcedPageScaleFactor);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_EQ(viewportWidth / enforcedPageScaleFactor, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->contentsSize().width());
EXPECT_EQ(enforcedPageScaleFactor, webViewHelper.webView()->pageScaleFactor());
}
TEST_P(ParameterizedWebFrameTest, DocumentElementClientHeightWorksWithWrapContentMode)
{
registerMockedHttpURLLoad("0-by-0.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "0-by-0.html", true, nullptr, &client, nullptr, configureAndroid);
webViewHelper.webView()->settings()->setForceZeroLayoutHeight(true);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
LocalFrame* frame = webViewHelper.webViewImpl()->mainFrameImpl()->frame();
Document* document = frame->document();
EXPECT_EQ(viewportHeight, document->documentElement()->clientHeight());
EXPECT_EQ(viewportWidth, document->documentElement()->clientWidth());
}
TEST_P(ParameterizedWebFrameTest, SetForceZeroLayoutHeightWorksWithWrapContentMode)
{
registerMockedHttpURLLoad("0-by-0.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "0-by-0.html", true, nullptr, &client, nullptr, configureAndroid);
webViewHelper.webView()->settings()->setForceZeroLayoutHeight(true);
PaintLayerCompositor* compositor = webViewHelper.webViewImpl()->compositor();
EXPECT_EQ(0, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().width());
EXPECT_EQ(0, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height());
EXPECT_EQ(0.0, compositor->containerLayer()->size().width());
EXPECT_EQ(0.0, compositor->containerLayer()->size().height());
webViewHelper.resize(WebSize(viewportWidth, 0));
EXPECT_EQ(viewportWidth, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().width());
EXPECT_EQ(0, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height());
EXPECT_EQ(viewportWidth, compositor->containerLayer()->size().width());
EXPECT_EQ(0.0, compositor->containerLayer()->size().height());
// The flag ForceZeroLayoutHeight will cause the following resize of viewport
// height to be ignored by the outer viewport (the container layer of
// LayerCompositor). The height of the visualViewport, however, is not affected.
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_FALSE(webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->needsLayout());
EXPECT_EQ(viewportWidth, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().width());
EXPECT_EQ(0, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height());
EXPECT_EQ(viewportWidth, compositor->containerLayer()->size().width());
EXPECT_EQ(viewportHeight, compositor->containerLayer()->size().height());
LocalFrame* frame = webViewHelper.webViewImpl()->mainFrameImpl()->frame();
VisualViewport& visualViewport = frame->page()->frameHost().visualViewport();
EXPECT_EQ(viewportHeight, visualViewport.containerLayer()->size().height());
EXPECT_TRUE(visualViewport.containerLayer()->platformLayer()->masksToBounds());
EXPECT_FALSE(compositor->containerLayer()->platformLayer()->masksToBounds());
}
TEST_P(ParameterizedWebFrameTest, SetForceZeroLayoutHeight)
{
registerMockedHttpURLLoad("200-by-300.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "200-by-300.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_LE(viewportHeight, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height());
webViewHelper.webView()->settings()->setForceZeroLayoutHeight(true);
EXPECT_TRUE(webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->needsLayout());
EXPECT_EQ(0, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height());
webViewHelper.resize(WebSize(viewportWidth, viewportHeight * 2));
EXPECT_FALSE(webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->needsLayout());
EXPECT_EQ(0, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height());
webViewHelper.resize(WebSize(viewportWidth * 2, viewportHeight));
EXPECT_EQ(0, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height());
webViewHelper.webView()->settings()->setForceZeroLayoutHeight(false);
EXPECT_LE(viewportHeight, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height());
}
TEST_F(WebFrameTest, ToggleViewportMetaOnOff)
{
registerMockedHttpURLLoad("viewport-device-width.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "viewport-device-width.html", true, 0, &client);
WebSettings* settings = webViewHelper.webView()->settings();
settings->setViewportMetaEnabled(false);
settings->setViewportEnabled(true);
settings->setMainFrameResizesAreOrientationChanges(true);
settings->setShrinksViewportContentToFit(true);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
Document* document = toLocalFrame(webViewHelper.webViewImpl()->page()->mainFrame())->document();
EXPECT_FALSE(document->viewportDescription().isLegacyViewportType());
settings->setViewportMetaEnabled(true);
EXPECT_TRUE(document->viewportDescription().isLegacyViewportType());
settings->setViewportMetaEnabled(false);
EXPECT_FALSE(document->viewportDescription().isLegacyViewportType());
}
TEST_F(WebFrameTest, SetForceZeroLayoutHeightWorksWithRelayoutsWhenHeightChanged)
{
// this unit test is an attempt to target a real world case where an app could
// 1. call resize(width, 0) and setForceZeroLayoutHeight(true)
// 2. load content (hoping that the viewport height would increase
// as more content is added)
// 3. fail to register touch events aimed at the loaded content
// because the layout is only updated if either width or height is changed
registerMockedHttpURLLoad("button.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "button.html", true, nullptr, &client, 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)
webViewHelper.resize(WebSize(viewportWidth, 0));
webViewHelper.webView()->settings()->setForceZeroLayoutHeight(true);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
IntPoint hitPoint = IntPoint(30, 30); // button size is 100x100
WebLocalFrameImpl* frame = toWebLocalFrameImpl(webViewHelper.webView()->mainFrame());
Document* document = frame->frame()->document();
Element* element = document->getElementById("tap_button");
ASSERT_NE(nullptr, element);
EXPECT_EQ(String("oldValue"), element->innerText());
PlatformGestureEvent gestureEvent(PlatformEvent::EventType::GestureTap, hitPoint, hitPoint, IntSize(0, 0), 0, PlatformEvent::NoModifiers, PlatformGestureSourceTouchscreen);
webViewHelper.webViewImpl()->mainFrameImpl()->frame()->eventHandler().handleGestureEvent(gestureEvent);
// when pressed, the button changes its own text to "updatedValue"
EXPECT_EQ(String("updatedValue"), element->innerText());
}
TEST_F(WebFrameTest, FrameOwnerPropertiesMargin)
{
FrameTestHelpers::TestWebViewClient viewClient;
FrameTestHelpers::TestWebRemoteFrameClient remoteClient;
WebView* view = WebView::create(&viewClient);
view->settings()->setJavaScriptEnabled(true);
view->setMainFrame(remoteClient.frame());
WebRemoteFrame* root = view->mainFrame()->toWebRemoteFrame();
root->setReplicatedOrigin(SecurityOrigin::createUnique());
WebFrameOwnerProperties properties;
properties.marginWidth = 11;
properties.marginHeight = 22;
WebLocalFrame* localFrame = FrameTestHelpers::createLocalChild(root, "frameName", nullptr, nullptr, properties);
registerMockedHttpURLLoad("frame_owner_properties.html");
FrameTestHelpers::loadFrame(localFrame, m_baseURL + "frame_owner_properties.html");
// Check if the LocalFrame has seen the marginwidth and marginheight
// properties.
Document* childDocument = toWebLocalFrameImpl(localFrame)->frame()->document();
EXPECT_EQ(11, childDocument->firstBodyElement()->getIntegralAttribute(HTMLNames::marginwidthAttr));
EXPECT_EQ(22, childDocument->firstBodyElement()->getIntegralAttribute(HTMLNames::marginheightAttr));
FrameView* frameView = toWebLocalFrameImpl(localFrame)->frameView();
// Expect scrollbars to be enabled by default.
EXPECT_NE(nullptr, frameView->horizontalScrollbar());
EXPECT_NE(nullptr, frameView->verticalScrollbar());
view->close();
}
TEST_F(WebFrameTest, FrameOwnerPropertiesScrolling)
{
FrameTestHelpers::TestWebViewClient viewClient;
FrameTestHelpers::TestWebRemoteFrameClient remoteClient;
WebView* view = WebView::create(&viewClient);
view->settings()->setJavaScriptEnabled(true);
view->setMainFrame(remoteClient.frame());
WebRemoteFrame* root = view->mainFrame()->toWebRemoteFrame();
root->setReplicatedOrigin(SecurityOrigin::createUnique());
WebFrameOwnerProperties properties;
// Turn off scrolling in the subframe.
properties.scrollingMode = WebFrameOwnerProperties::ScrollingMode::AlwaysOff;
WebLocalFrame* localFrame = FrameTestHelpers::createLocalChild(root, "frameName", nullptr, nullptr, properties);
registerMockedHttpURLLoad("frame_owner_properties.html");
FrameTestHelpers::loadFrame(localFrame, m_baseURL + "frame_owner_properties.html");
Document* childDocument = toWebLocalFrameImpl(localFrame)->frame()->document();
EXPECT_EQ(0, childDocument->firstBodyElement()->getIntegralAttribute(HTMLNames::marginwidthAttr));
EXPECT_EQ(0, childDocument->firstBodyElement()->getIntegralAttribute(HTMLNames::marginheightAttr));
FrameView* frameView = static_cast<WebLocalFrameImpl*>(localFrame)->frameView();
EXPECT_EQ(nullptr, frameView->horizontalScrollbar());
EXPECT_EQ(nullptr, frameView->verticalScrollbar());
view->close();
}
TEST_P(ParameterizedWebFrameTest, SetForceZeroLayoutHeightWorksAcrossNavigations)
{
registerMockedHttpURLLoad("200-by-300.html");
registerMockedHttpURLLoad("large-div.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "200-by-300.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setForceZeroLayoutHeight(true);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(), m_baseURL + "large-div.html");
webViewHelper.webView()->updateAllLifecyclePhases();
EXPECT_EQ(0, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height());
}
TEST_P(ParameterizedWebFrameTest, SetForceZeroLayoutHeightWithWideViewportQuirk)
{
registerMockedHttpURLLoad("200-by-300.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "200-by-300.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setUseWideViewport(true);
webViewHelper.webView()->settings()->setForceZeroLayoutHeight(true);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_EQ(0, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height());
}
TEST_P(ParameterizedWebFrameTest, WideViewportAndWideContentWithInitialScale)
{
registerMockedHttpURLLoad("wide_document_width_viewport.html");
registerMockedHttpURLLoad("white-1x1.png");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 600;
int viewportHeight = 800;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad("about:blank", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setUseWideViewport(true);
webViewHelper.webView()->settings()->setViewportMetaLayoutSizeQuirk(true);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(), m_baseURL + "wide_document_width_viewport.html");
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
int wideDocumentWidth = 800;
float minimumPageScaleFactor = viewportWidth / (float) wideDocumentWidth;
EXPECT_EQ(minimumPageScaleFactor, webViewHelper.webViewImpl()->pageScaleFactor());
EXPECT_EQ(minimumPageScaleFactor, webViewHelper.webViewImpl()->minimumPageScaleFactor());
}
TEST_P(ParameterizedWebFrameTest, WideViewportQuirkClobbersHeight)
{
registerMockedHttpURLLoad("viewport-height-1000.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 600;
int viewportHeight = 800;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad("about:blank", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setUseWideViewport(false);
webViewHelper.webView()->settings()->setViewportMetaLayoutSizeQuirk(true);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(), m_baseURL + "viewport-height-1000.html");
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_EQ(800, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height());
EXPECT_EQ(1, webViewHelper.webView()->pageScaleFactor());
}
TEST_P(ParameterizedWebFrameTest, LayoutSize320Quirk)
{
registerMockedHttpURLLoad("viewport/viewport-30.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 600;
int viewportHeight = 800;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad("about:blank", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setUseWideViewport(true);
webViewHelper.webView()->settings()->setViewportMetaLayoutSizeQuirk(true);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(), m_baseURL + "viewport/viewport-30.html");
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_EQ(600, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().width());
EXPECT_EQ(800, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height());
EXPECT_EQ(1, webViewHelper.webView()->pageScaleFactor());
// The magic number to snap to device-width is 320, so test that 321 is
// respected.
Document* document = toLocalFrame(webViewHelper.webViewImpl()->page()->mainFrame())->document();
ViewportDescription description = document->viewportDescription();
description.minWidth = Length(321, blink::Fixed);
description.maxWidth = Length(321, blink::Fixed);
document->setViewportDescription(description);
webViewHelper.webView()->updateAllLifecyclePhases();
EXPECT_EQ(321, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().width());
description.minWidth = Length(320, blink::Fixed);
description.maxWidth = Length(320, blink::Fixed);
document->setViewportDescription(description);
webViewHelper.webView()->updateAllLifecyclePhases();
EXPECT_EQ(600, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().width());
description = document->viewportDescription();
description.maxHeight = Length(1000, blink::Fixed);
document->setViewportDescription(description);
webViewHelper.webView()->updateAllLifecyclePhases();
EXPECT_EQ(1000, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height());
description.maxHeight = Length(320, blink::Fixed);
document->setViewportDescription(description);
webViewHelper.webView()->updateAllLifecyclePhases();
EXPECT_EQ(800, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height());
}
TEST_P(ParameterizedWebFrameTest, ZeroValuesQuirk)
{
registerMockedHttpURLLoad("viewport-zero-values.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initialize(true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setViewportMetaZeroValuesQuirk(true);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setViewportMetaLayoutSizeQuirk(true);
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(), m_baseURL + "viewport-zero-values.html");
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_EQ(viewportWidth, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().width());
EXPECT_EQ(1.0f, webViewHelper.webView()->pageScaleFactor());
webViewHelper.webView()->settings()->setUseWideViewport(true);
webViewHelper.webView()->updateAllLifecyclePhases();
EXPECT_EQ(viewportWidth, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().width());
EXPECT_EQ(1.0f, webViewHelper.webView()->pageScaleFactor());
}
TEST_P(ParameterizedWebFrameTest, OverflowHiddenDisablesScrolling)
{
registerMockedHttpURLLoad("body-overflow-hidden.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initialize(true, nullptr, &client, nullptr);
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(), m_baseURL + "body-overflow-hidden.html");
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
FrameView* view = webViewHelper.webViewImpl()->mainFrameImpl()->frameView();
EXPECT_FALSE(view->userInputScrollable(VerticalScrollbar));
EXPECT_FALSE(view->userInputScrollable(HorizontalScrollbar));
}
TEST_P(ParameterizedWebFrameTest, OverflowHiddenDisablesScrollingWithSetCanHaveScrollbars)
{
registerMockedHttpURLLoad("body-overflow-hidden-short.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initialize(true, nullptr, &client, nullptr);
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(), m_baseURL + "body-overflow-hidden-short.html");
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
FrameView* view = webViewHelper.webViewImpl()->mainFrameImpl()->frameView();
EXPECT_FALSE(view->userInputScrollable(VerticalScrollbar));
EXPECT_FALSE(view->userInputScrollable(HorizontalScrollbar));
webViewHelper.webViewImpl()->mainFrameImpl()->setCanHaveScrollbars(true);
EXPECT_FALSE(view->userInputScrollable(VerticalScrollbar));
EXPECT_FALSE(view->userInputScrollable(HorizontalScrollbar));
}
TEST_F(WebFrameTest, IgnoreOverflowHiddenQuirk)
{
registerMockedHttpURLLoad("body-overflow-hidden.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initialize(true, nullptr, &client, nullptr);
webViewHelper.webView()->settings()->setIgnoreMainFrameOverflowHiddenQuirk(true);
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(), m_baseURL + "body-overflow-hidden.html");
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
FrameView* view = webViewHelper.webViewImpl()->mainFrameImpl()->frameView();
EXPECT_TRUE(view->userInputScrollable(VerticalScrollbar));
}
TEST_P(ParameterizedWebFrameTest, NonZeroValuesNoQuirk)
{
registerMockedHttpURLLoad("viewport-nonzero-values.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
float expectedPageScaleFactor = 0.5f;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initialize(true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setViewportMetaZeroValuesQuirk(true);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(), m_baseURL + "viewport-nonzero-values.html");
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_EQ(viewportWidth / expectedPageScaleFactor, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().width());
EXPECT_EQ(expectedPageScaleFactor, webViewHelper.webView()->pageScaleFactor());
webViewHelper.webView()->settings()->setUseWideViewport(true);
webViewHelper.webView()->updateAllLifecyclePhases();
EXPECT_EQ(viewportWidth / expectedPageScaleFactor, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().width());
EXPECT_EQ(expectedPageScaleFactor, webViewHelper.webView()->pageScaleFactor());
}
TEST_P(ParameterizedWebFrameTest, setPageScaleFactorDoesNotLayout)
{
registerMockedHttpURLLoad("fixed_layout.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
// Small viewport to ensure there are always scrollbars.
int viewportWidth = 64;
int viewportHeight = 48;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "fixed_layout.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
int prevLayoutCount = webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutCount();
webViewHelper.webViewImpl()->setPageScaleFactor(3);
EXPECT_FALSE(webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->needsLayout());
EXPECT_EQ(prevLayoutCount, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutCount());
}
TEST_P(ParameterizedWebFrameTest, setPageScaleFactorWithOverlayScrollbarsDoesNotLayout)
{
registerMockedHttpURLLoad("fixed_layout.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "fixed_layout.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
int prevLayoutCount = webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutCount();
webViewHelper.webViewImpl()->setPageScaleFactor(30);
EXPECT_FALSE(webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->needsLayout());
EXPECT_EQ(prevLayoutCount, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutCount());
}
TEST_P(ParameterizedWebFrameTest, pageScaleFactorWrittenToHistoryItem)
{
registerMockedHttpURLLoad("fixed_layout.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "fixed_layout.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
webViewHelper.webView()->setPageScaleFactor(3);
EXPECT_EQ(3, toLocalFrame(webViewHelper.webViewImpl()->page()->mainFrame())->loader().currentItem()->pageScaleFactor());
}
TEST_P(ParameterizedWebFrameTest, initialScaleWrittenToHistoryItem)
{
registerMockedHttpURLLoad("fixed_layout.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initialize(true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->setDefaultPageScaleLimits(0.25f, 5);
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(), m_baseURL + "fixed_layout.html");
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
int defaultFixedLayoutWidth = 980;
float minimumPageScaleFactor = viewportWidth / (float) defaultFixedLayoutWidth;
EXPECT_EQ(minimumPageScaleFactor, toLocalFrame(webViewHelper.webViewImpl()->page()->mainFrame())->loader().currentItem()->pageScaleFactor());
}
TEST_P(ParameterizedWebFrameTest, pageScaleFactorDoesntShrinkFrameView)
{
registerMockedHttpURLLoad("large-div.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
// Small viewport to ensure there are always scrollbars.
int viewportWidth = 64;
int viewportHeight = 48;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "large-div.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
FrameView* view = webViewHelper.webViewImpl()->mainFrameImpl()->frameView();
int viewportWidthMinusScrollbar = viewportWidth;
int viewportHeightMinusScrollbar = viewportHeight;
if (view->verticalScrollbar() && !view->verticalScrollbar()->isOverlayScrollbar())
viewportWidthMinusScrollbar -= 15;
if (view->horizontalScrollbar() && !view->horizontalScrollbar()->isOverlayScrollbar())
viewportHeightMinusScrollbar -= 15;
webViewHelper.webView()->setPageScaleFactor(2);
IntSize unscaledSize = view->visibleContentSize(IncludeScrollbars);
EXPECT_EQ(viewportWidth, unscaledSize.width());
EXPECT_EQ(viewportHeight, unscaledSize.height());
IntSize unscaledSizeMinusScrollbar = view->visibleContentSize(ExcludeScrollbars);
EXPECT_EQ(viewportWidthMinusScrollbar, unscaledSizeMinusScrollbar.width());
EXPECT_EQ(viewportHeightMinusScrollbar, unscaledSizeMinusScrollbar.height());
IntSize frameViewSize = view->visibleContentRect().size();
EXPECT_EQ(viewportWidthMinusScrollbar, frameViewSize.width());
EXPECT_EQ(viewportHeightMinusScrollbar, frameViewSize.height());
}
TEST_P(ParameterizedWebFrameTest, pageScaleFactorDoesNotApplyCssTransform)
{
registerMockedHttpURLLoad("fixed_layout.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "fixed_layout.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
webViewHelper.webView()->setPageScaleFactor(2);
EXPECT_EQ(980, toLocalFrame(webViewHelper.webViewImpl()->page()->mainFrame())->contentLayoutItem().documentRect().width());
EXPECT_EQ(980, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->contentsSize().width());
}
TEST_P(ParameterizedWebFrameTest, targetDensityDpiHigh)
{
registerMockedHttpURLLoad("viewport-target-densitydpi-high.html");
FixedLayoutTestWebViewClient client;
// high-dpi = 240
float targetDpi = 240.0f;
float deviceScaleFactors[] = { 1.0f, 4.0f / 3.0f, 2.0f };
int viewportWidth = 640;
int viewportHeight = 480;
for (size_t i = 0; i < WTF_ARRAY_LENGTH(deviceScaleFactors); ++i) {
float deviceScaleFactor = deviceScaleFactors[i];
float deviceDpi = deviceScaleFactor * 160.0f;
client.m_screenInfo.deviceScaleFactor = deviceScaleFactor;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "viewport-target-densitydpi-high.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setSupportDeprecatedTargetDensityDPI(true);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
// We need to account for the fact that logical pixels are unconditionally multiplied by deviceScaleFactor to produce
// physical pixels.
float densityDpiScaleRatio = deviceScaleFactor * targetDpi / deviceDpi;
EXPECT_NEAR(viewportWidth * densityDpiScaleRatio, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().width(), 1.0f);
EXPECT_NEAR(viewportHeight * densityDpiScaleRatio, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height(), 1.0f);
EXPECT_NEAR(1.0f / densityDpiScaleRatio, webViewHelper.webView()->pageScaleFactor(), 0.01f);
}
}
TEST_P(ParameterizedWebFrameTest, targetDensityDpiDevice)
{
registerMockedHttpURLLoad("viewport-target-densitydpi-device.html");
float deviceScaleFactors[] = { 1.0f, 4.0f / 3.0f, 2.0f };
FixedLayoutTestWebViewClient client;
int viewportWidth = 640;
int viewportHeight = 480;
for (size_t i = 0; i < WTF_ARRAY_LENGTH(deviceScaleFactors); ++i) {
client.m_screenInfo.deviceScaleFactor = deviceScaleFactors[i];
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "viewport-target-densitydpi-device.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setSupportDeprecatedTargetDensityDPI(true);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_NEAR(viewportWidth * client.m_screenInfo.deviceScaleFactor, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().width(), 1.0f);
EXPECT_NEAR(viewportHeight * client.m_screenInfo.deviceScaleFactor, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height(), 1.0f);
EXPECT_NEAR(1.0f / client.m_screenInfo.deviceScaleFactor, webViewHelper.webView()->pageScaleFactor(), 0.01f);
}
}
TEST_P(ParameterizedWebFrameTest, targetDensityDpiDeviceAndFixedWidth)
{
registerMockedHttpURLLoad("viewport-target-densitydpi-device-and-fixed-width.html");
float deviceScaleFactors[] = { 1.0f, 4.0f / 3.0f, 2.0f };
FixedLayoutTestWebViewClient client;
int viewportWidth = 640;
int viewportHeight = 480;
for (size_t i = 0; i < WTF_ARRAY_LENGTH(deviceScaleFactors); ++i) {
client.m_screenInfo.deviceScaleFactor = deviceScaleFactors[i];
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "viewport-target-densitydpi-device-and-fixed-width.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setSupportDeprecatedTargetDensityDPI(true);
webViewHelper.webView()->settings()->setUseWideViewport(true);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_NEAR(viewportWidth, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().width(), 1.0f);
EXPECT_NEAR(viewportHeight, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height(), 1.0f);
EXPECT_NEAR(1.0f, webViewHelper.webView()->pageScaleFactor(), 0.01f);
}
}
TEST_P(ParameterizedWebFrameTest, NoWideViewportAndScaleLessThanOne)
{
registerMockedHttpURLLoad("viewport-initial-scale-less-than-1.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1.33f;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "viewport-initial-scale-less-than-1.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setSupportDeprecatedTargetDensityDPI(true);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setUseWideViewport(false);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_NEAR(viewportWidth * client.m_screenInfo.deviceScaleFactor, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().width(), 1.0f);
EXPECT_NEAR(viewportHeight * client.m_screenInfo.deviceScaleFactor, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height(), 1.0f);
EXPECT_NEAR(1.0f / client.m_screenInfo.deviceScaleFactor, webViewHelper.webView()->pageScaleFactor(), 0.01f);
}
TEST_P(ParameterizedWebFrameTest, NoWideViewportAndScaleLessThanOneWithDeviceWidth)
{
registerMockedHttpURLLoad("viewport-initial-scale-less-than-1-device-width.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1.33f;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "viewport-initial-scale-less-than-1-device-width.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setSupportDeprecatedTargetDensityDPI(true);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setUseWideViewport(false);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
const float pageZoom = 0.25f;
EXPECT_NEAR(viewportWidth * client.m_screenInfo.deviceScaleFactor / pageZoom, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().width(), 1.0f);
EXPECT_NEAR(viewportHeight * client.m_screenInfo.deviceScaleFactor / pageZoom, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height(), 1.0f);
EXPECT_NEAR(1.0f / client.m_screenInfo.deviceScaleFactor, webViewHelper.webView()->pageScaleFactor(), 0.01f);
}
TEST_P(ParameterizedWebFrameTest, NoWideViewportAndNoViewportWithInitialPageScaleOverride)
{
registerMockedHttpURLLoad("large-div.html");
FixedLayoutTestWebViewClient client;
int viewportWidth = 640;
int viewportHeight = 480;
float enforcedPageScaleFactor = 5.0f;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "large-div.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->setDefaultPageScaleLimits(0.25f, 5);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setUseWideViewport(false);
webViewHelper.webView()->setInitialPageScaleOverride(enforcedPageScaleFactor);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_NEAR(viewportWidth / enforcedPageScaleFactor, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().width(), 1.0f);
EXPECT_NEAR(viewportHeight / enforcedPageScaleFactor, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height(), 1.0f);
EXPECT_NEAR(enforcedPageScaleFactor, webViewHelper.webView()->pageScaleFactor(), 0.01f);
}
TEST_P(ParameterizedWebFrameTest, NoUserScalableQuirkIgnoresViewportScale)
{
registerMockedHttpURLLoad("viewport-initial-scale-and-user-scalable-no.html");
FixedLayoutTestWebViewClient client;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "viewport-initial-scale-and-user-scalable-no.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setViewportMetaNonUserScalableQuirk(true);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_NEAR(viewportWidth, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().width(), 1.0f);
EXPECT_NEAR(viewportHeight, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height(), 1.0f);
EXPECT_NEAR(1.0f, webViewHelper.webView()->pageScaleFactor(), 0.01f);
}
TEST_P(ParameterizedWebFrameTest, NoUserScalableQuirkIgnoresViewportScaleForNonWideViewport)
{
registerMockedHttpURLLoad("viewport-initial-scale-and-user-scalable-no.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1.33f;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "viewport-initial-scale-and-user-scalable-no.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setSupportDeprecatedTargetDensityDPI(true);
webViewHelper.webView()->settings()->setViewportMetaNonUserScalableQuirk(true);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setUseWideViewport(false);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_NEAR(viewportWidth * client.m_screenInfo.deviceScaleFactor, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().width(), 1.0f);
EXPECT_NEAR(viewportHeight * client.m_screenInfo.deviceScaleFactor, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height(), 1.0f);
EXPECT_NEAR(1.0f / client.m_screenInfo.deviceScaleFactor, webViewHelper.webView()->pageScaleFactor(), 0.01f);
}
TEST_P(ParameterizedWebFrameTest, NoUserScalableQuirkIgnoresViewportScaleForWideViewport)
{
registerMockedHttpURLLoad("viewport-2x-initial-scale-non-user-scalable.html");
FixedLayoutTestWebViewClient client;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "viewport-2x-initial-scale-non-user-scalable.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->settings()->setViewportMetaNonUserScalableQuirk(true);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setUseWideViewport(true);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_NEAR(viewportWidth, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().width(), 1.0f);
EXPECT_NEAR(viewportHeight, webViewHelper.webViewImpl()->mainFrameImpl()->frameView()->layoutSize().height(), 1.0f);
EXPECT_NEAR(1.0f, webViewHelper.webView()->pageScaleFactor(), 0.01f);
}
TEST_P(ParameterizedWebFrameTest, DesktopPageCanBeZoomedInWhenWideViewportIsTurnedOff)
{
registerMockedHttpURLLoad("no_viewport_tag.html");
FixedLayoutTestWebViewClient client;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "no_viewport_tag.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->setDefaultPageScaleLimits(0.25f, 5);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setUseWideViewport(false);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_NEAR(1.0f, webViewHelper.webViewImpl()->pageScaleFactor(), 0.01f);
EXPECT_NEAR(1.0f, webViewHelper.webViewImpl()->minimumPageScaleFactor(), 0.01f);
EXPECT_NEAR(5.0f, webViewHelper.webViewImpl()->maximumPageScaleFactor(), 0.01f);
}
class WebFrameResizeTest : public ParameterizedWebFrameTest {
protected:
static FloatSize computeRelativeOffset(const IntPoint& absoluteOffset, const LayoutRect& rect)
{
FloatSize relativeOffset = FloatPoint(absoluteOffset) - FloatPoint(rect.location());
relativeOffset.scale(1.f / rect.width(), 1.f / rect.height());
return relativeOffset;
}
void testResizeYieldsCorrectScrollAndScale(const char* url,
const float initialPageScaleFactor,
const WebSize scrollOffset,
const WebSize viewportSize,
const bool shouldScaleRelativeToViewportWidth) {
registerMockedHttpURLLoad(url);
const float aspectRatio = static_cast<float>(viewportSize.width) / viewportSize.height;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + url, true, nullptr, nullptr, nullptr, enableViewportSettings);
webViewHelper.webViewImpl()->setDefaultPageScaleLimits(0.25f, 5);
// Origin scrollOffsets preserved under resize.
{
webViewHelper.resize(WebSize(viewportSize.width, viewportSize.height));
webViewHelper.webViewImpl()->setPageScaleFactor(initialPageScaleFactor);
ASSERT_EQ(viewportSize, webViewHelper.webViewImpl()->size());
ASSERT_EQ(initialPageScaleFactor, webViewHelper.webViewImpl()->pageScaleFactor());
webViewHelper.resize(WebSize(viewportSize.height, viewportSize.width));
float expectedPageScaleFactor = initialPageScaleFactor * (shouldScaleRelativeToViewportWidth ? 1 / aspectRatio : 1);
EXPECT_NEAR(expectedPageScaleFactor, webViewHelper.webViewImpl()->pageScaleFactor(), 0.05f);
EXPECT_EQ(WebSize(), webViewHelper.webViewImpl()->mainFrame()->scrollOffset());
}
// Resizing just the height should not affect pageScaleFactor or scrollOffset.
{
webViewHelper.resize(WebSize(viewportSize.width, viewportSize.height));
webViewHelper.webViewImpl()->setPageScaleFactor(initialPageScaleFactor);
webViewHelper.webViewImpl()->mainFrame()->setScrollOffset(scrollOffset);
webViewHelper.webViewImpl()->updateAllLifecyclePhases();
const WebSize expectedScrollOffset = webViewHelper.webViewImpl()->mainFrame()->scrollOffset();
webViewHelper.resize(WebSize(viewportSize.width, viewportSize.height * 0.8f));
EXPECT_EQ(initialPageScaleFactor, webViewHelper.webViewImpl()->pageScaleFactor());
EXPECT_EQ(expectedScrollOffset, webViewHelper.webViewImpl()->mainFrame()->scrollOffset());
webViewHelper.resize(WebSize(viewportSize.width, viewportSize.height * 0.8f));
EXPECT_EQ(initialPageScaleFactor, webViewHelper.webViewImpl()->pageScaleFactor());
EXPECT_EQ(expectedScrollOffset, webViewHelper.webViewImpl()->mainFrame()->scrollOffset());
}
}
};
INSTANTIATE_TEST_CASE_P(All, WebFrameResizeTest, ::testing::Values(
ParameterizedWebFrameTestConfig::Default,
ParameterizedWebFrameTestConfig::RootLayerScrolls));
TEST_P(WebFrameResizeTest, ResizeYieldsCorrectScrollAndScaleForWidthEqualsDeviceWidth)
{
// With width=device-width, pageScaleFactor is preserved across resizes as
// long as the content adjusts according to the device-width.
const char* url = "resize_scroll_mobile.html";
const float initialPageScaleFactor = 1;
const WebSize scrollOffset(0, 50);
const WebSize viewportSize(120, 160);
const bool shouldScaleRelativeToViewportWidth = true;
testResizeYieldsCorrectScrollAndScale(
url, initialPageScaleFactor, scrollOffset, viewportSize, shouldScaleRelativeToViewportWidth);
}
TEST_P(WebFrameResizeTest, ResizeYieldsCorrectScrollAndScaleForMinimumScale)
{
// This tests a scenario where minimum-scale is set to 1.0, but some element
// on the page is slightly larger than the portrait width, so our "natural"
// minimum-scale would be lower. In that case, we should stick to 1.0 scale
// on rotation and not do anything strange.
const char* url = "resize_scroll_minimum_scale.html";
const float initialPageScaleFactor = 1;
const WebSize scrollOffset(0, 0);
const WebSize viewportSize(240, 320);
const bool shouldScaleRelativeToViewportWidth = false;
testResizeYieldsCorrectScrollAndScale(
url, initialPageScaleFactor, scrollOffset, viewportSize, shouldScaleRelativeToViewportWidth);
}
TEST_P(WebFrameResizeTest, ResizeYieldsCorrectScrollAndScaleForFixedWidth)
{
// With a fixed width, pageScaleFactor scales by the relative change in viewport width.
const char* url = "resize_scroll_fixed_width.html";
const float initialPageScaleFactor = 2;
const WebSize scrollOffset(0, 200);
const WebSize viewportSize(240, 320);
const bool shouldScaleRelativeToViewportWidth = true;
testResizeYieldsCorrectScrollAndScale(
url, initialPageScaleFactor, scrollOffset, viewportSize, shouldScaleRelativeToViewportWidth);
}
TEST_P(WebFrameResizeTest, ResizeYieldsCorrectScrollAndScaleForFixedLayout)
{
// With a fixed layout, pageScaleFactor scales by the relative change in viewport width.
const char* url = "resize_scroll_fixed_layout.html";
const float initialPageScaleFactor = 2;
const WebSize scrollOffset(200, 400);
const WebSize viewportSize(320, 240);
const bool shouldScaleRelativeToViewportWidth = true;
testResizeYieldsCorrectScrollAndScale(
url, initialPageScaleFactor, scrollOffset, viewportSize, shouldScaleRelativeToViewportWidth);
}
TEST_P(ParameterizedWebFrameTest, pageScaleFactorUpdatesScrollbars)
{
registerMockedHttpURLLoad("fixed_layout.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "fixed_layout.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
FrameView* view = webViewHelper.webViewImpl()->mainFrameImpl()->frameView();
EXPECT_EQ(view->scrollSize(HorizontalScrollbar), view->contentsSize().width() - view->visibleContentRect().width());
EXPECT_EQ(view->scrollSize(VerticalScrollbar), view->contentsSize().height() - view->visibleContentRect().height());
webViewHelper.webView()->setPageScaleFactor(10);
EXPECT_EQ(view->scrollSize(HorizontalScrollbar), view->contentsSize().width() - view->visibleContentRect().width());
EXPECT_EQ(view->scrollSize(VerticalScrollbar), view->contentsSize().height() - view->visibleContentRect().height());
}
TEST_P(ParameterizedWebFrameTest, CanOverrideScaleLimits)
{
registerMockedHttpURLLoad("no_scale_for_you.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper(this);
webViewHelper.initializeAndLoad(m_baseURL + "no_scale_for_you.html", true, nullptr, &client, nullptr, enableViewportSettings);
webViewHelper.webView()->setDefaultPageScaleLimits(0.25f, 5);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_EQ(2.0f, webViewHelper.webViewImpl()->minimumPageScaleFactor());
EXPECT_EQ(2.0f, webViewHelper.webViewImpl()->maximumPageScaleFactor());
webViewHelper.webView()->setIgnoreViewportTagScaleLimits(true);
webViewHelper.webView()->updateAllLifecyclePhases();
EXPECT_EQ(1.0f, webViewHelper.webViewImpl()->minimumPageScaleFactor());
EXPECT_EQ(5.0f, webViewHelper.webViewImpl()->maximumPageScaleFactor());
webViewHelper.webView()->setIgnoreViewportTagScaleLimits(false);
webViewHelper.webView()->updateAllLifecyclePhases();
EXPECT_EQ(2.0f, webViewHelper.webViewImpl()->minimumPageScaleFactor());
EXPECT_EQ(2.0f, webViewHelper.webViewImpl()->maximumPageScaleFactor());
}
// Android doesn't have scrollbars on the main FrameView
#if OS(ANDROID)
TEST_F(WebFrameTest, DISABLED_updateOverlayScrollbarLayers)