blob: 90125c3b68cb5fe98aeddd30222f760ac61cbced [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/Document.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/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/page/ScopedPageLoadDeferrer.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/RuntimeEnabledFeaturesTestHelpers.h"
#include "platform/testing/URLTestHelpers.h"
#include "platform/testing/UnitTestHelpers.h"
#include "platform/weborigin/KURLHash.h"
#include "platform/weborigin/SchemeRegistry.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "public/platform/Platform.h"
#include "public/platform/WebCachePolicy.h"
#include "public/platform/WebClipboard.h"
#include "public/platform/WebFloatRect.h"
#include "public/platform/WebMockClipboard.h"
#include "public/platform/WebSecurityOrigin.h"
#include "public/platform/WebThread.h"
#include "public/platform/WebURL.h"
#include "public/platform/WebURLLoaderClient.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/PtrUtil.h"
#include "wtf/dtoa/utils.h"
#include <map>
#include <memory>
#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.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, nullptr));
styleSheet->parseString(loadResourceAsASCIIString("viewportAndroid.css"));
RuleSet* ruleSet = RuleSet::create();
ruleSet->addRulesFromSheet(styleSheet, MediaQueryEvaluator("screen"));
Document* document =
toLocalFrame(webViewHelper->webView()->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));
}
std::unique_ptr<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->webView()->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;
};
typedef bool TestParamRootLayerScrolling;
class ParameterizedWebFrameTest
: public ::testing::WithParamInterface<TestParamRootLayerScrolling>,
private ScopedRootLayerScrollingForTest,
public WebFrameTest {
public:
ParameterizedWebFrameTest() : ScopedRootLayerScrollingForTest(GetParam()) {}
};
INSTANTIATE_TEST_CASE_P(All, ParameterizedWebFrameTest, ::testing::Bool());
TEST_P(ParameterizedWebFrameTest, ContentText) {
registerMockedHttpURLLoad("iframes_test.html");
registerMockedHttpURLLoad("visible_iframe.html");
registerMockedHttpURLLoad("invisible_iframe.html");
registerMockedHttpURLLoad("zero_sized_iframe.html");
FrameTestHelpers::WebViewHelper webViewHelper;
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;
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;
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;
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.
webViewHelper.webView()
->mainFrameImpl()
->frame()
->document()
->suspendScheduledTasks();
webViewHelper.webView()->mainFrameImpl()->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;
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;
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;
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;
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;
/// 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;
/// 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;
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;
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;
webViewHelper.initializeAndLoad("about:blank");
LocalFrame* frame =
toLocalFrame(webViewHelper.webView()->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 (LayoutItem layoutItem = document->layoutViewItem(); !layoutItem.isNull();
layoutItem = layoutItem.nextInPreOrder()) {
if (layoutItem.style()) {
layoutItem.mutableStyleRef().setTextAutosizingMultiplier(multiplier);
EXPECT_EQ(multiplier, layoutItem.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 (LayoutItem layoutItem = document->layoutViewItem(); !layoutItem.isNull();
layoutItem = layoutItem.nextInPreOrder()) {
if (layoutItem.style() && layoutItem.isText()) {
EXPECT_EQ(multiplier, layoutItem.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;
webViewHelper.initializeAndLoad(m_baseURL + "fixed_layout.html", true,
nullptr, &client, nullptr,
enableViewportSettings);
Document* document =
toLocalFrame(webViewHelper.webView()->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.webView()->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;
webViewHelper.initializeAndLoad(m_baseURL + htmlFile, true, nullptr, &client,
nullptr, configureAndroid);
Document* document =
toLocalFrame(webViewHelper.webView()->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;
webViewHelper.initializeAndLoad(m_baseURL + "iframe_reload.html", true,
nullptr, &client, nullptr,
enableViewportSettings);
LocalFrame* mainFrame =
toLocalFrame(webViewHelper.webView()->page()->mainFrame());
Document* document = mainFrame->document();
FrameView* frameView = webViewHelper.webView()->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 (LayoutItem layoutItem =
toLocalFrame(frame)->document()->layoutViewItem();
!layoutItem.isNull(); layoutItem = layoutItem.nextInPreOrder()) {
if (layoutItem.isText())
EXPECT_FALSE(layoutItem.needsLayout());
}
}
frameView->page()->frameHost().visualViewport().setSize(IntSize(200, 200));
for (Frame* frame = mainFrame; frame; frame = frame->tree().traverseNext()) {
if (!frame->isLocalFrame())
continue;
for (LayoutItem layoutItem =
toLocalFrame(frame)->document()->layoutViewItem();
!layoutItem.isNull(); layoutItem = layoutItem.nextInPreOrder()) {
if (layoutItem.isText())
EXPECT_TRUE(layoutItem.needsLayout());
}
}
}
TEST_P(ParameterizedWebFrameTest, ZeroHeightPositiveWidthNotIgnored) {
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 1280;
int viewportHeight = 0;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initialize(true, nullptr, &client, nullptr,
enableViewportSettings);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_EQ(viewportWidth, webViewHelper.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.width());
EXPECT_EQ(viewportHeight, webViewHelper.webView()
->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;
webViewHelper.initializeAndLoad(m_baseURL + "no_viewport_tag.html", true,
nullptr, &client, nullptr,
enableViewportSettings);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_EQ(2, webViewHelper.webView()->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;
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.webView()->pageScaleFactor());
EXPECT_EQ(minimumPageScaleFactor,
webViewHelper.webView()->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.webView()->didCommitLoad(false, false);
webViewHelper.webView()->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;
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.webView()->pageScaleFactor());
EXPECT_EQ(minimumPageScaleFactor,
webViewHelper.webView()->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.webView()->didCommitLoad(false, false);
webViewHelper.webView()->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;
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.webView()->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;
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;
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;
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.webView()
->mainFrameImpl()
->frameView()
->contentsSize()
.width());
EXPECT_EQ(viewportHeight, webViewHelper.webView()
->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;
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.webView()
->mainFrameImpl()
->frameView()
->contentsSize()
.width());
EXPECT_EQ(viewportHeight / 2, webViewHelper.webView()
->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;
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.webView()
->mainFrameImpl()
->frameView()
->contentsSize()
.width());
EXPECT_EQ(980.0 / viewportWidth * viewportHeight, webViewHelper.webView()
->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;
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.webView()
->mainFrameImpl()
->frameView()
->contentsSize()
.width());
EXPECT_EQ(viewportHeight, webViewHelper.webView()
->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;
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.webView()
->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;
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.webView()
->mainFrameImpl()
->frameView()
->contentsSize()
.width());
EXPECT_EQ(980.0 / viewportWidth * viewportHeight, webViewHelper.webView()
->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;
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;
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;
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;
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;
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;
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.webView()
->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;
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.webView()->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;
webViewHelper.initializeAndLoad(m_baseURL + "0-by-0.html", true, nullptr,
&client, nullptr, configureAndroid);
webViewHelper.webView()->settings()->setForceZeroLayoutHeight(true);
PaintLayerCompositor* compositor = webViewHelper.webView()->compositor();
EXPECT_EQ(0, webViewHelper.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.width());
EXPECT_EQ(0, webViewHelper.webView()
->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.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.width());
EXPECT_EQ(0, webViewHelper.webView()
->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.webView()->mainFrameImpl()->frameView()->needsLayout());
EXPECT_EQ(viewportWidth, webViewHelper.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.width());
EXPECT_EQ(0, webViewHelper.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.height());
EXPECT_EQ(viewportWidth, compositor->containerLayer()->size().width());
EXPECT_EQ(viewportHeight, compositor->containerLayer()->size().height());
LocalFrame* frame = webViewHelper.webView()->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;
webViewHelper.initializeAndLoad(m_baseURL + "200-by-300.html", true, nullptr,
&client, nullptr, enableViewportSettings);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
EXPECT_LE(viewportHeight, webViewHelper.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.height());
webViewHelper.webView()->settings()->setForceZeroLayoutHeight(true);
EXPECT_TRUE(
webViewHelper.webView()->mainFrameImpl()->frameView()->needsLayout());
EXPECT_EQ(0, webViewHelper.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.height());
webViewHelper.resize(WebSize(viewportWidth, viewportHeight * 2));
EXPECT_FALSE(
webViewHelper.webView()->mainFrameImpl()->frameView()->needsLayout());
EXPECT_EQ(0, webViewHelper.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.height());
webViewHelper.resize(WebSize(viewportWidth * 2, viewportHeight));
EXPECT_EQ(0, webViewHelper.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.height());
webViewHelper.webView()->settings()->setForceZeroLayoutHeight(false);
EXPECT_LE(viewportHeight, webViewHelper.webView()
->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.webView()->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 = webViewHelper.webView()->mainFrameImpl();
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.webView()
->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, WebPageVisibilityStateVisible);
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;
WebLocalFrameImpl* localFrame = FrameTestHelpers::createLocalChild(
root, "frameName", nullptr, 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 = localFrame->frame()->document();
EXPECT_EQ(11, childDocument->firstBodyElement()->getIntegralAttribute(
HTMLNames::marginwidthAttr));
EXPECT_EQ(22, childDocument->firstBodyElement()->getIntegralAttribute(
HTMLNames::marginheightAttr));
FrameView* frameView = 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, WebPageVisibilityStateVisible);
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;
WebLocalFrameImpl* localFrame = FrameTestHelpers::createLocalChild(
root, "frameName", nullptr, nullptr, nullptr, properties);
registerMockedHttpURLLoad("frame_owner_properties.html");
FrameTestHelpers::loadFrame(localFrame,
m_baseURL + "frame_owner_properties.html");
Document* childDocument = 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;
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.webView()
->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;
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.webView()
->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;
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.webView()->pageScaleFactor());
EXPECT_EQ(minimumPageScaleFactor,
webViewHelper.webView()->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;
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.webView()
->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;
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.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.width());
EXPECT_EQ(800, webViewHelper.webView()
->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.webView()->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.webView()
->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.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.width());
description = document->viewportDescription();
description.maxHeight = Length(1000, blink::Fixed);
document->setViewportDescription(description);
webViewHelper.webView()->updateAllLifecyclePhases();
EXPECT_EQ(1000, webViewHelper.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.height());
description.maxHeight = Length(320, blink::Fixed);
document->setViewportDescription(description);
webViewHelper.webView()->updateAllLifecyclePhases();
EXPECT_EQ(800, webViewHelper.webView()
->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;
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.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.width());
EXPECT_EQ(1.0f, webViewHelper.webView()->pageScaleFactor());
webViewHelper.webView()->settings()->setUseWideViewport(true);
webViewHelper.webView()->updateAllLifecyclePhases();
EXPECT_EQ(viewportWidth, webViewHelper.webView()
->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;
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.webView()->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;
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.webView()->mainFrameImpl()->frameView();
EXPECT_FALSE(view->userInputScrollable(VerticalScrollbar));
EXPECT_FALSE(view->userInputScrollable(HorizontalScrollbar));
webViewHelper.webView()->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.webView()->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;
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.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.width());
EXPECT_EQ(expectedPageScaleFactor,
webViewHelper.webView()->pageScaleFactor());
webViewHelper.webView()->settings()->setUseWideViewport(true);
webViewHelper.webView()->updateAllLifecyclePhases();
EXPECT_EQ(viewportWidth / expectedPageScaleFactor, webViewHelper.webView()
->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;
webViewHelper.initializeAndLoad(m_baseURL + "fixed_layout.html", true,
nullptr, &client, nullptr,
enableViewportSettings);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
int prevLayoutCount =
webViewHelper.webView()->mainFrameImpl()->frameView()->layoutCount();
webViewHelper.webView()->setPageScaleFactor(3);
EXPECT_FALSE(
webViewHelper.webView()->mainFrameImpl()->frameView()->needsLayout());
EXPECT_EQ(
prevLayoutCount,
webViewHelper.webView()->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;
webViewHelper.initializeAndLoad(m_baseURL + "fixed_layout.html", true,
nullptr, &client, nullptr,
enableViewportSettings);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
int prevLayoutCount =
webViewHelper.webView()->mainFrameImpl()->frameView()->layoutCount();
webViewHelper.webView()->setPageScaleFactor(30);
EXPECT_FALSE(
webViewHelper.webView()->mainFrameImpl()->frameView()->needsLayout());
EXPECT_EQ(
prevLayoutCount,
webViewHelper.webView()->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;
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.webView()->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;
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.webView()->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;
webViewHelper.initializeAndLoad(m_baseURL + "large-div.html", true, nullptr,
&client, nullptr, enableViewportSettings);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
FrameView* view = webViewHelper.webView()->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;
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.webView()->page()->mainFrame())
->contentLayoutItem()
.documentRect()
.width());
EXPECT_EQ(980, webViewHelper.webView()
->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;
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.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.width(),
1.0f);
EXPECT_NEAR(viewportHeight * densityDpiScaleRatio, webViewHelper.webView()
->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;
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.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.width(),
1.0f);
EXPECT_NEAR(viewportHeight * client.m_screenInfo.deviceScaleFactor,
webViewHelper.webView()
->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;
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.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.width(),
1.0f);
EXPECT_NEAR(viewportHeight, webViewHelper.webView()
->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;
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.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.width(),
1.0f);
EXPECT_NEAR(viewportHeight * client.m_screenInfo.deviceScaleFactor,
webViewHelper.webView()
->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;
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.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.width(),
1.0f);
EXPECT_NEAR(viewportHeight * client.m_screenInfo.deviceScaleFactor / pageZoom,
webViewHelper.webView()
->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;
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.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.width(),
1.0f);
EXPECT_NEAR(viewportHeight / enforcedPageScaleFactor, webViewHelper.webView()
->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;
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.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.width(),
1.0f);
EXPECT_NEAR(viewportHeight, webViewHelper.webView()
->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;
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.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.width(),
1.0f);
EXPECT_NEAR(viewportHeight * client.m_screenInfo.deviceScaleFactor,
webViewHelper.webView()
->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;
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.webView()
->mainFrameImpl()
->frameView()
->layoutSize()
.width(),
1.0f);
EXPECT_NEAR(viewportHeight, webViewHelper.webView()
->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;
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.webView()->pageScaleFactor(), 0.01f);
EXPECT_NEAR(1.0f, webViewHelper.webView()->minimumPageScaleFactor(), 0.01f);
EXPECT_NEAR(5.0f, webViewHelper.webView()->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;
webViewHelper.initializeAndLoad(m_baseURL + url, true, nullptr, nullptr,
nullptr, enableViewportSettings);
webViewHelper.webView()->setDefaultPageScaleLimits(0.25f, 5);
// Origin scrollOffsets preserved under resize.
{
webViewHelper.resize(WebSize(viewportSize.width, viewportSize.height));
webViewHelper.webView()->setPageScaleFactor(initialPageScaleFactor);
ASSERT_EQ(viewportSize, webViewHelper.webView()->size());
ASSERT_EQ(initialPageScaleFactor,
webViewHelper.webView()->pageScaleFactor());
webViewHelper.resize(WebSize(viewportSize.height, viewportSize.width));
float expectedPageScaleFactor =
initialPageScaleFactor *
(shouldScaleRelativeToViewportWidth ? 1 / aspectRatio : 1);
EXPECT_NEAR(expectedPageScaleFactor,
webViewHelper.webView()->pageScaleFactor(), 0.05f);
EXPECT_EQ(WebSize(),
webViewHelper.webView()->mainFrame()->scrollOffset());
}
// Resizing just the height should not affect pageScaleFactor or
// scrollOffset.
{
webViewHelper.resize(WebSize(viewportSize.width, viewportSize.height));
webViewHelper.webView()->setPageScaleFactor(initialPageScaleFactor);
webViewHelper.webView()->mainFrame()->setScrollOffset(scrollOffset);
webViewHelper.webView()->updateAllLifecyclePhases();
const WebSize expectedScrollOffset =
webViewHelper.webView()->mainFrame()->scrollOffset();
webViewHelper.resize(
WebSize(viewportSize.width, viewportSize.height * 0.8f));
EXPECT_EQ(initialPageScaleFactor,
webViewHelper.webView()->pageScaleFactor());
EXPECT_EQ(expectedScrollOffset,
webViewHelper.webView()->mainFrame()->scrollOffset());
webViewHelper.resize(
WebSize(viewportSize.width, viewportSize.height * 0.8f));
EXPECT_EQ(initialPageScaleFactor,
webViewHelper.webView()->pageScaleFactor());
EXPECT_EQ(expectedScrollOffset,
webViewHelper.webView()->mainFrame()->scrollOffset());
}
}
};
INSTANTIATE_TEST_CASE_P(All, WebFrameResizeTest, ::testing::Bool());
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;
webViewHelper.initializeAndLoad(m_baseURL + "fixed_layout.html", true,
nullptr, &client, nullptr,
enableViewportSettings);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
FrameView* view = webViewHelper.webView()->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;
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.webView()->minimumPageScaleFactor());
EXPECT_EQ(2.0f, webViewHelper.webView()->maximumPageScaleFactor());
webViewHelper.webView()->setIgnoreViewportTagScaleLimits(true);
webViewHelper.webView()->updateAllLifecyclePhases();
EXPECT_EQ(1.0f, webViewHelper.webView()->minimumPageScaleFactor());
EXPECT_EQ(5.0f, webViewHelper.webView()->maximumPageScaleFactor());
webViewHelper.webView()->setIgnoreViewportTagScaleLimits(false);
webViewHelper.webView()->updateAllLifecyclePhases();
EXPECT_EQ(2.0f, webViewHelper.webView()->minimumPageScaleFactor());
EXPECT_EQ(2.0f, webViewHelper.webView()->maximumPageScaleFactor());
}
// Android doesn't have scrollbars on the main FrameView
#if OS(ANDROID)
TEST_F(WebFrameTest, DISABLED_updateOverlayScrollbarLayers)
#else
TEST_F(WebFrameTest, updateOverlayScrollbarLayers)
#endif
{
registerMockedHttpURLLoad("large-div.html");
int viewWidth = 500;
int viewHeight = 500;
std::unique_ptr<FakeCompositingWebViewClient> fakeCompositingWebViewClient =
wrapUnique(new FakeCompositingWebViewClient());
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initialize(true, nullptr, fakeCompositingWebViewClient.get(),
nullptr, &configueCompositingWebView);
webViewHelper.resize(WebSize(viewWidth, viewHeight));
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(),
m_baseURL + "large-div.html");
FrameView* view = webViewHelper.webView()->mainFrameImpl()->frameView();
EXPECT_TRUE(
view->layoutViewItem().compositor()->layerForHorizontalScrollbar());
EXPECT_TRUE(view->layoutViewItem().compositor()->layerForVerticalScrollbar());
webViewHelper.resize(WebSize(viewWidth * 10, viewHeight * 10));
EXPECT_FALSE(
view->layoutViewItem().compositor()->layerForHorizontalScrollbar());
EXPECT_FALSE(
view->layoutViewItem().compositor()->layerForVerticalScrollbar());
}
void setScaleAndScrollAndLayout(WebViewImpl* webView,
WebPoint scroll,
float scale) {
webView->setPageScaleFactor(scale);
webView->mainFrame()->setScrollOffset(WebSize(scroll.x, scroll.y));
webView->updateAllLifecyclePhases();
}
void simulatePageScale(WebViewImpl* webViewImpl, float& scale) {
IntSize scrollDelta =
webViewImpl->fakePageScaleAnimationTargetPositionForTesting() -
webViewImpl->mainFrameImpl()->frameView()->scrollPosition();
float scaleDelta = webViewImpl->fakePageScaleAnimationPageScaleForTesting() /
webViewImpl->pageScaleFactor();
webViewImpl->applyViewportDeltas(WebFloatSize(), FloatSize(scrollDelta),
WebFloatSize(), scaleDelta, 0);
scale = webViewImpl->pageScaleFactor();
}
void simulateMultiTargetZoom(WebViewImpl* webViewImpl,
const WebRect& rect,
float& scale) {
if (webViewImpl->zoomToMultipleTargetsRect(rect))
simulatePageScale(webViewImpl, scale);
}
void simulateDoubleTap(WebViewImpl* webViewImpl,
WebPoint& point,
float& scale) {
webViewImpl->animateDoubleTapZoom(point);
EXPECT_TRUE(webViewImpl->fakeDoubleTapAnimationPendingForTesting());
simulatePageScale(webViewImpl, scale);
}
TEST_P(ParameterizedWebFrameTest, DivAutoZoomParamsTest) {
registerMockedHttpURLLoad("get_scale_for_auto_zoom_into_div_test.html");
const float deviceScaleFactor = 2.0f;
int viewportWidth = 640 / deviceScaleFactor;
int viewportHeight = 1280 / deviceScaleFactor;
float doubleTapZoomAlreadyLegibleRatio = 1.2f;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(
m_baseURL + "get_scale_for_auto_zoom_into_div_test.html", false, nullptr,
nullptr, nullptr, configureAndroid);
webViewHelper.webView()->setDeviceScaleFactor(deviceScaleFactor);
webViewHelper.webView()->setDefaultPageScaleLimits(0.01f, 4);
webViewHelper.webView()->setPageScaleFactor(0.5f);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
WebRect wideDiv(200, 100, 400, 150);
WebRect tallDiv(200, 300, 400, 800);
WebPoint doubleTapPointWide(wideDiv.x + 50, wideDiv.y + 50);
WebPoint doubleTapPointTall(tallDiv.x + 50, tallDiv.y + 50);
float scale;
WebPoint scroll;
float doubleTapZoomAlreadyLegibleScale =
webViewHelper.webView()->minimumPageScaleFactor() *
doubleTapZoomAlreadyLegibleRatio;
// Test double-tap zooming into wide div.
WebRect wideBlockBound =
webViewHelper.webView()->computeBlockBound(doubleTapPointWide, false);
webViewHelper.webView()->computeScaleAndScrollForBlockRect(
WebPoint(doubleTapPointWide.x, doubleTapPointWide.y), wideBlockBound,
touchPointPadding, doubleTapZoomAlreadyLegibleScale, scale, scroll);
// The div should horizontally fill the screen (modulo margins), and
// vertically centered (modulo integer rounding).
EXPECT_NEAR(viewportWidth / (float)wideDiv.width, scale, 0.1);
EXPECT_NEAR(wideDiv.x, scroll.x, 20);
EXPECT_EQ(0, scroll.y);
setScaleAndScrollAndLayout(webViewHelper.webView(), scroll, scale);
// Test zoom out back to minimum scale.
wideBlockBound =
webViewHelper.webView()->computeBlockBound(doubleTapPointWide, false);
webViewHelper.webView()->computeScaleAndScrollForBlockRect(
WebPoint(doubleTapPointWide.x, doubleTapPointWide.y), wideBlockBound,
touchPointPadding, doubleTapZoomAlreadyLegibleScale, scale, scroll);
// FIXME: Looks like we are missing EXPECTs here.
scale = webViewHelper.webView()->minimumPageScaleFactor();
setScaleAndScrollAndLayout(webViewHelper.webView(), WebPoint(0, 0), scale);
// Test double-tap zooming into tall div.
WebRect tallBlockBound =
webViewHelper.webView()->computeBlockBound(doubleTapPointTall, false);
webViewHelper.webView()->computeScaleAndScrollForBlockRect(
WebPoint(doubleTapPointTall.x, doubleTapPointTall.y), tallBlockBound,
touchPointPadding, doubleTapZoomAlreadyLegibleScale, scale, scroll);
// The div should start at the top left of the viewport.
EXPECT_NEAR(viewportWidth / (float)tallDiv.width, scale, 0.1);
EXPECT_NEAR(tallDiv.x, scroll.x, 20);
EXPECT_NEAR(tallDiv.y, scroll.y, 20);
}
TEST_P(ParameterizedWebFrameTest, DivAutoZoomWideDivTest) {
registerMockedHttpURLLoad("get_wide_div_for_auto_zoom_test.html");
const float deviceScaleFactor = 2.0f;
int viewportWidth = 640 / deviceScaleFactor;
int viewportHeight = 1280 / deviceScaleFactor;
float doubleTapZoomAlreadyLegibleRatio = 1.2f;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(
m_baseURL + "get_wide_div_for_auto_zoom_test.html", false, nullptr,
nullptr, nullptr, configureAndroid);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
webViewHelper.webView()->setDeviceScaleFactor(deviceScaleFactor);
webViewHelper.webView()->setPageScaleFactor(1.0f);
webViewHelper.webView()->updateAllLifecyclePhases();
webViewHelper.webView()->enableFakePageScaleAnimationForTesting(true);
float doubleTapZoomAlreadyLegibleScale =
webViewHelper.webView()->minimumPageScaleFactor() *
doubleTapZoomAlreadyLegibleRatio;
WebRect div(0, 100, viewportWidth, 150);
WebPoint point(div.x + 50, div.y + 50);
float scale;
setScaleAndScrollAndLayout(
webViewHelper.webView(), WebPoint(0, 0),
(webViewHelper.webView()->minimumPageScaleFactor()) *
(1 + doubleTapZoomAlreadyLegibleRatio) / 2);
simulateDoubleTap(webViewHelper.webView(), point, scale);
EXPECT_FLOAT_EQ(doubleTapZoomAlreadyLegibleScale, scale);
simulateDoubleTap(webViewHelper.webView(), point, scale);
EXPECT_FLOAT_EQ(webViewHelper.webView()->minimumPageScaleFactor(), scale);
}
TEST_P(ParameterizedWebFrameTest, DivAutoZoomVeryTallTest) {
// When a block is taller than the viewport and a zoom targets a lower part
// of it, then we should keep the target point onscreen instead of snapping
// back up the top of the block.
registerMockedHttpURLLoad("very_tall_div.html");
const float deviceScaleFactor = 2.0f;
int viewportWidth = 640 / deviceScaleFactor;
int viewportHeight = 1280 / deviceScaleFactor;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "very_tall_div.html", true,
nullptr, nullptr, nullptr, configureAndroid);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
webViewHelper.webView()->setDeviceScaleFactor(deviceScaleFactor);
webViewHelper.webView()->setPageScaleFactor(1.0f);
webViewHelper.webView()->updateAllLifecyclePhases();
WebRect div(200, 300, 400, 5000);
WebPoint point(div.x + 50, div.y + 3000);
float scale;
WebPoint scroll;
WebRect blockBound = webViewHelper.webView()->computeBlockBound(point, true);
webViewHelper.webView()->computeScaleAndScrollForBlockRect(
point, blockBound, 0, 1.0f, scale, scroll);
EXPECT_EQ(scale, 1.0f);
EXPECT_EQ(scroll.y, 2660);
}
TEST_F(WebFrameTest, DivAutoZoomMultipleDivsTest) {
registerMockedHttpURLLoad("get_multiple_divs_for_auto_zoom_test.html");
const float deviceScaleFactor = 2.0f;
int viewportWidth = 640 / deviceScaleFactor;
int viewportHeight = 1280 / deviceScaleFactor;
float doubleTapZoomAlreadyLegibleRatio = 1.2f;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(
m_baseURL + "get_multiple_divs_for_auto_zoom_test.html", false, nullptr,
nullptr, nullptr, configureAndroid);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
webViewHelper.webView()->setDefaultPageScaleLimits(0.5f, 4);
webViewHelper.webView()->setDeviceScaleFactor(deviceScaleFactor);
webViewHelper.webView()->setPageScaleFactor(0.5f);
webViewHelper.webView()->setMaximumLegibleScale(1.f);
webViewHelper.webView()->updateAllLifecyclePhases();
webViewHelper.webView()->enableFakePageScaleAnimationForTesting(true);
WebRect topDiv(200, 100, 200, 150);
WebRect bottomDiv(200, 300, 200, 150);
WebPoint topPoint(topDiv.x + 50, topDiv.y + 50);
WebPoint bottomPoint(bottomDiv.x + 50, bottomDiv.y + 50);
float scale;
setScaleAndScrollAndLayout(
webViewHelper.webView(), WebPoint(0, 0),
(webViewHelper.webView()->minimumPageScaleFactor()) *
(1 + doubleTapZoomAlreadyLegibleRatio) / 2);
// Test double tap on two different divs. After first zoom, we should go back
// to minimum page scale with a second double tap.
simulateDoubleTap(webViewHelper.webView(), topPoint, scale);
EXPECT_FLOAT_EQ(1, scale);
simulateDoubleTap(webViewHelper.webView(), bottomPoint, scale);
EXPECT_FLOAT_EQ(webViewHelper.webView()->minimumPageScaleFactor(), scale);
// If the user pinch zooms after double tap, a second double tap should zoom
// back to the div.
simulateDoubleTap(webViewHelper.webView(), topPoint, scale);
EXPECT_FLOAT_EQ(1, scale);
webViewHelper.webView()->applyViewportDeltas(WebFloatSize(), WebFloatSize(),
WebFloatSize(), 0.6f, 0);
simulateDoubleTap(webViewHelper.webView(), bottomPoint, scale);
EXPECT_FLOAT_EQ(1, scale);
simulateDoubleTap(webViewHelper.webView(), bottomPoint, scale);
EXPECT_FLOAT_EQ(webViewHelper.webView()->minimumPageScaleFactor(), scale);
// If we didn't yet get an auto-zoom update and a second double-tap arrives,
// should go back to minimum scale.
webViewHelper.webView()->applyViewportDeltas(WebFloatSize(), WebFloatSize(),
WebFloatSize(), 1.1f, 0);
webViewHelper.webView()->animateDoubleTapZoom(topPoint);
EXPECT_TRUE(
webViewHelper.webView()->fakeDoubleTapAnimationPendingForTesting());
simulateDoubleTap(webViewHelper.webView(), bottomPoint, scale);
EXPECT_FLOAT_EQ(webViewHelper.webView()->minimumPageScaleFactor(), scale);
}
TEST_F(WebFrameTest, DivAutoZoomScaleBoundsTest) {
registerMockedHttpURLLoad("get_scale_bounds_check_for_auto_zoom_test.html");
int viewportWidth = 320;
int viewportHeight = 480;
float doubleTapZoomAlreadyLegibleRatio = 1.2f;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(
m_baseURL + "get_scale_bounds_check_for_auto_zoom_test.html", false,
nullptr, nullptr, nullptr, configureAndroid);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
webViewHelper.webView()->setDeviceScaleFactor(1.5f);
webViewHelper.webView()->setMaximumLegibleScale(1.f);
webViewHelper.webView()->updateAllLifecyclePhases();
webViewHelper.webView()->enableFakePageScaleAnimationForTesting(true);
WebRect div(200, 100, 200, 150);
WebPoint doubleTapPoint(div.x + 50, div.y + 50);
float scale;
// Test double tap scale bounds.
// minimumPageScale < doubleTapZoomAlreadyLegibleScale < 1
webViewHelper.webView()->setDefaultPageScaleLimits(0.5f, 4);
webViewHelper.webView()->updateAllLifecyclePhases();
float doubleTapZoomAlreadyLegibleScale =
webViewHelper.webView()->minimumPageScaleFactor() *
doubleTapZoomAlreadyLegibleRatio;
setScaleAndScrollAndLayout(
webViewHelper.webView(), WebPoint(0, 0),
(webViewHelper.webView()->minimumPageScaleFactor()) *
(1 + doubleTapZoomAlreadyLegibleRatio) / 2);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(1, scale);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(webViewHelper.webView()->minimumPageScaleFactor(), scale);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(1, scale);
// Zoom in to reset double_tap_zoom_in_effect flag.
webViewHelper.webView()->applyViewportDeltas(WebFloatSize(), WebFloatSize(),
WebFloatSize(), 1.1f, 0);
// 1 < minimumPageScale < doubleTapZoomAlreadyLegibleScale
webViewHelper.webView()->setDefaultPageScaleLimits(1.1f, 4);
webViewHelper.webView()->updateAllLifecyclePhases();
doubleTapZoomAlreadyLegibleScale =
webViewHelper.webView()->minimumPageScaleFactor() *
doubleTapZoomAlreadyLegibleRatio;
setScaleAndScrollAndLayout(
webViewHelper.webView(), WebPoint(0, 0),
(webViewHelper.webView()->minimumPageScaleFactor()) *
(1 + doubleTapZoomAlreadyLegibleRatio) / 2);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(doubleTapZoomAlreadyLegibleScale, scale);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(webViewHelper.webView()->minimumPageScaleFactor(), scale);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(doubleTapZoomAlreadyLegibleScale, scale);
// Zoom in to reset double_tap_zoom_in_effect flag.
webViewHelper.webView()->applyViewportDeltas(WebFloatSize(), WebFloatSize(),
WebFloatSize(), 1.1f, 0);
// minimumPageScale < 1 < doubleTapZoomAlreadyLegibleScale
webViewHelper.webView()->setDefaultPageScaleLimits(0.95f, 4);
webViewHelper.webView()->updateAllLifecyclePhases();
doubleTapZoomAlreadyLegibleScale =
webViewHelper.webView()->minimumPageScaleFactor() *
doubleTapZoomAlreadyLegibleRatio;
setScaleAndScrollAndLayout(
webViewHelper.webView(), WebPoint(0, 0),
(webViewHelper.webView()->minimumPageScaleFactor()) *
(1 + doubleTapZoomAlreadyLegibleRatio) / 2);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(doubleTapZoomAlreadyLegibleScale, scale);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(webViewHelper.webView()->minimumPageScaleFactor(), scale);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(doubleTapZoomAlreadyLegibleScale, scale);
}
TEST_F(WebFrameTest, DivAutoZoomScaleLegibleScaleTest) {
registerMockedHttpURLLoad("get_scale_bounds_check_for_auto_zoom_test.html");
int viewportWidth = 320;
int viewportHeight = 480;
float doubleTapZoomAlreadyLegibleRatio = 1.2f;
float maximumLegibleScaleFactor = 1.13f;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(
m_baseURL + "get_scale_bounds_check_for_auto_zoom_test.html", false,
nullptr, nullptr, nullptr, configureAndroid);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
webViewHelper.webView()->setMaximumLegibleScale(maximumLegibleScaleFactor);
webViewHelper.webView()->updateAllLifecyclePhases();
webViewHelper.webView()->enableFakePageScaleAnimationForTesting(true);
webViewHelper.webView()->page()->settings().setTextAutosizingEnabled(true);
WebRect div(200, 100, 200, 150);
WebPoint doubleTapPoint(div.x + 50, div.y + 50);
float scale;
// Test double tap scale bounds.
// minimumPageScale < doubleTapZoomAlreadyLegibleScale < 1 <
// maximumLegibleScaleFactor
float legibleScale = maximumLegibleScaleFactor;
setScaleAndScrollAndLayout(
webViewHelper.webView(), WebPoint(0, 0),
(webViewHelper.webView()->minimumPageScaleFactor()) *
(1 + doubleTapZoomAlreadyLegibleRatio) / 2);
float doubleTapZoomAlreadyLegibleScale =
webViewHelper.webView()->minimumPageScaleFactor() *
doubleTapZoomAlreadyLegibleRatio;
webViewHelper.webView()->setDefaultPageScaleLimits(0.5f, 4);
webViewHelper.webView()->updateAllLifecyclePhases();
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(legibleScale, scale);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(webViewHelper.webView()->minimumPageScaleFactor(), scale);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(legibleScale, scale);
// Zoom in to reset double_tap_zoom_in_effect flag.
webViewHelper.webView()->applyViewportDeltas(WebFloatSize(), WebFloatSize(),
WebFloatSize(), 1.1f, 0);
// 1 < maximumLegibleScaleFactor < minimumPageScale <
// doubleTapZoomAlreadyLegibleScale
webViewHelper.webView()->setDefaultPageScaleLimits(1.0f, 4);
webViewHelper.webView()->updateAllLifecyclePhases();
doubleTapZoomAlreadyLegibleScale =
webViewHelper.webView()->minimumPageScaleFactor() *
doubleTapZoomAlreadyLegibleRatio;
setScaleAndScrollAndLayout(
webViewHelper.webView(), WebPoint(0, 0),
(webViewHelper.webView()->minimumPageScaleFactor()) *
(1 + doubleTapZoomAlreadyLegibleRatio) / 2);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(doubleTapZoomAlreadyLegibleScale, scale);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(webViewHelper.webView()->minimumPageScaleFactor(), scale);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(doubleTapZoomAlreadyLegibleScale, scale);
// Zoom in to reset double_tap_zoom_in_effect flag.
webViewHelper.webView()->applyViewportDeltas(WebFloatSize(), WebFloatSize(),
WebFloatSize(), 1.1f, 0);
// minimumPageScale < 1 < maximumLegibleScaleFactor <
// doubleTapZoomAlreadyLegibleScale
webViewHelper.webView()->setDefaultPageScaleLimits(0.95f, 4);
webViewHelper.webView()->updateAllLifecyclePhases();
doubleTapZoomAlreadyLegibleScale =
webViewHelper.webView()->minimumPageScaleFactor() *
doubleTapZoomAlreadyLegibleRatio;
setScaleAndScrollAndLayout(
webViewHelper.webView(), WebPoint(0, 0),
(webViewHelper.webView()->minimumPageScaleFactor()) *
(1 + doubleTapZoomAlreadyLegibleRatio) / 2);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(doubleTapZoomAlreadyLegibleScale, scale);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(webViewHelper.webView()->minimumPageScaleFactor(), scale);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(doubleTapZoomAlreadyLegibleScale, scale);
// Zoom in to reset double_tap_zoom_in_effect flag.
webViewHelper.webView()->applyViewportDeltas(WebFloatSize(), WebFloatSize(),
WebFloatSize(), 1.1f, 0);
// minimumPageScale < 1 < doubleTapZoomAlreadyLegibleScale <
// maximumLegibleScaleFactor
webViewHelper.webView()->setDefaultPageScaleLimits(0.9f, 4);
webViewHelper.webView()->updateAllLifecyclePhases();
doubleTapZoomAlreadyLegibleScale =
webViewHelper.webView()->minimumPageScaleFactor() *
doubleTapZoomAlreadyLegibleRatio;
setScaleAndScrollAndLayout(
webViewHelper.webView(), WebPoint(0, 0),
(webViewHelper.webView()->minimumPageScaleFactor()) *
(1 + doubleTapZoomAlreadyLegibleRatio) / 2);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(legibleScale, scale);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(webViewHelper.webView()->minimumPageScaleFactor(), scale);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(legibleScale, scale);
}
TEST_F(WebFrameTest, DivAutoZoomScaleFontScaleFactorTest) {
registerMockedHttpURLLoad("get_scale_bounds_check_for_auto_zoom_test.html");
int viewportWidth = 320;
int viewportHeight = 480;
float doubleTapZoomAlreadyLegibleRatio = 1.2f;
float accessibilityFontScaleFactor = 1.13f;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(
m_baseURL + "get_scale_bounds_check_for_auto_zoom_test.html", false,
nullptr, nullptr, nullptr, configureAndroid);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
webViewHelper.webView()->setMaximumLegibleScale(1.f);
webViewHelper.webView()->updateAllLifecyclePhases();
webViewHelper.webView()->enableFakePageScaleAnimationForTesting(true);
webViewHelper.webView()->page()->settings().setTextAutosizingEnabled(true);
webViewHelper.webView()->page()->settings().setAccessibilityFontScaleFactor(
accessibilityFontScaleFactor);
WebRect div(200, 100, 200, 150);
WebPoint doubleTapPoint(div.x + 50, div.y + 50);
float scale;
// Test double tap scale bounds.
// minimumPageScale < doubleTapZoomAlreadyLegibleScale < 1 <
// accessibilityFontScaleFactor
float legibleScale = accessibilityFontScaleFactor;
setScaleAndScrollAndLayout(
webViewHelper.webView(), WebPoint(0, 0),
(webViewHelper.webView()->minimumPageScaleFactor()) *
(1 + doubleTapZoomAlreadyLegibleRatio) / 2);
float doubleTapZoomAlreadyLegibleScale =
webViewHelper.webView()->minimumPageScaleFactor() *
doubleTapZoomAlreadyLegibleRatio;
webViewHelper.webView()->setDefaultPageScaleLimits(0.5f, 4);
webViewHelper.webView()->updateAllLifecyclePhases();
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(legibleScale, scale);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(webViewHelper.webView()->minimumPageScaleFactor(), scale);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(legibleScale, scale);
// Zoom in to reset double_tap_zoom_in_effect flag.
webViewHelper.webView()->applyViewportDeltas(WebFloatSize(), WebFloatSize(),
WebFloatSize(), 1.1f, 0);
// 1 < accessibilityFontScaleFactor < minimumPageScale <
// doubleTapZoomAlreadyLegibleScale
webViewHelper.webView()->setDefaultPageScaleLimits(1.0f, 4);
webViewHelper.webView()->updateAllLifecyclePhases();
doubleTapZoomAlreadyLegibleScale =
webViewHelper.webView()->minimumPageScaleFactor() *
doubleTapZoomAlreadyLegibleRatio;
setScaleAndScrollAndLayout(
webViewHelper.webView(), WebPoint(0, 0),
(webViewHelper.webView()->minimumPageScaleFactor()) *
(1 + doubleTapZoomAlreadyLegibleRatio) / 2);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(doubleTapZoomAlreadyLegibleScale, scale);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(webViewHelper.webView()->minimumPageScaleFactor(), scale);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(doubleTapZoomAlreadyLegibleScale, scale);
// Zoom in to reset double_tap_zoom_in_effect flag.
webViewHelper.webView()->applyViewportDeltas(WebFloatSize(), WebFloatSize(),
WebFloatSize(), 1.1f, 0);
// minimumPageScale < 1 < accessibilityFontScaleFactor <
// doubleTapZoomAlreadyLegibleScale
webViewHelper.webView()->setDefaultPageScaleLimits(0.95f, 4);
webViewHelper.webView()->updateAllLifecyclePhases();
doubleTapZoomAlreadyLegibleScale =
webViewHelper.webView()->minimumPageScaleFactor() *
doubleTapZoomAlreadyLegibleRatio;
setScaleAndScrollAndLayout(
webViewHelper.webView(), WebPoint(0, 0),
(webViewHelper.webView()->minimumPageScaleFactor()) *
(1 + doubleTapZoomAlreadyLegibleRatio) / 2);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(doubleTapZoomAlreadyLegibleScale, scale);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(webViewHelper.webView()->minimumPageScaleFactor(), scale);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(doubleTapZoomAlreadyLegibleScale, scale);
// Zoom in to reset double_tap_zoom_in_effect flag.
webViewHelper.webView()->applyViewportDeltas(WebFloatSize(), WebFloatSize(),
WebFloatSize(), 1.1f, 0);
// minimumPageScale < 1 < doubleTapZoomAlreadyLegibleScale <
// accessibilityFontScaleFactor
webViewHelper.webView()->setDefaultPageScaleLimits(0.9f, 4);
webViewHelper.webView()->updateAllLifecyclePhases();
doubleTapZoomAlreadyLegibleScale =
webViewHelper.webView()->minimumPageScaleFactor() *
doubleTapZoomAlreadyLegibleRatio;
setScaleAndScrollAndLayout(
webViewHelper.webView(), WebPoint(0, 0),
(webViewHelper.webView()->minimumPageScaleFactor()) *
(1 + doubleTapZoomAlreadyLegibleRatio) / 2);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(legibleScale, scale);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(webViewHelper.webView()->minimumPageScaleFactor(), scale);
simulateDoubleTap(webViewHelper.webView(), doubleTapPoint, scale);
EXPECT_FLOAT_EQ(legibleScale, scale);
}
TEST_P(ParameterizedWebFrameTest, BlockBoundTest) {
registerMockedHttpURLLoad("block_bound.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "block_bound.html", false,
nullptr, nullptr, nullptr, configureAndroid);
IntRect rectBack = IntRect(0, 0, 200, 200);
IntRect rectLeftTop = IntRect(10, 10, 80, 80);
IntRect rectRightBottom = IntRect(110, 110, 80, 80);
IntRect blockBound;
blockBound =
IntRect(webViewHelper.webView()->computeBlockBound(WebPoint(9, 9), true));
EXPECT_RECT_EQ(rectBack, blockBound);
blockBound = IntRect(
webViewHelper.webView()->computeBlockBound(WebPoint(10, 10), true));
EXPECT_RECT_EQ(rectLeftTop, blockBound);
blockBound = IntRect(
webViewHelper.webView()->computeBlockBound(WebPoint(50, 50), true));
EXPECT_RECT_EQ(rectLeftTop, blockBound);
blockBound = IntRect(
webViewHelper.webView()->computeBlockBound(WebPoint(89, 89), true));
EXPECT_RECT_EQ(rectLeftTop, blockBound);
blockBound = IntRect(
webViewHelper.webView()->computeBlockBound(WebPoint(90, 90), true));
EXPECT_RECT_EQ(rectBack, blockBound);
blockBound = IntRect(
webViewHelper.webView()->computeBlockBound(WebPoint(109, 109), true));
EXPECT_RECT_EQ(rectBack, blockBound);
blockBound = IntRect(
webViewHelper.webView()->computeBlockBound(WebPoint(110, 110), true));
EXPECT_RECT_EQ(rectRightBottom, blockBound);
}
TEST_P(ParameterizedWebFrameTest, DivMultipleTargetZoomMultipleDivsTest) {
registerMockedHttpURLLoad("get_multiple_divs_for_auto_zoom_test.html");
const float deviceScaleFactor = 2.0f;
int viewportWidth = 640 / deviceScaleFactor;
int viewportHeight = 1280 / deviceScaleFactor;
float doubleTapZoomAlreadyLegibleRatio = 1.2f;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL +
"get_multiple_divs_for_auto_zoom_test.html");
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
webViewHelper.webView()->setDefaultPageScaleLimits(0.5f, 4);
webViewHelper.webView()->setDeviceScaleFactor(deviceScaleFactor);
webViewHelper.webView()->setPageScaleFactor(0.5f);
webViewHelper.webView()->setMaximumLegibleScale(1.f);
webViewHelper.webView()->updateAllLifecyclePhases();
webViewHelper.webView()->enableFakePageScaleAnimationForTesting(true);
WebRect viewportRect(0, 0, viewportWidth, viewportHeight);
WebRect topDiv(200, 100, 200, 150);
WebRect bottomDiv(200, 300, 200, 150);
float scale;
setScaleAndScrollAndLayout(
webViewHelper.webView(), WebPoint(0, 0),
(webViewHelper.webView()->minimumPageScaleFactor()) *
(1 + doubleTapZoomAlreadyLegibleRatio) / 2);
simulateMultiTargetZoom(webViewHelper.webView(), topDiv, scale);
EXPECT_FLOAT_EQ(1, scale);
simulateMultiTargetZoom(webViewHelper.webView(), bottomDiv, scale);
EXPECT_FLOAT_EQ(1, scale);
simulateMultiTargetZoom(webViewHelper.webView(), viewportRect, scale);
EXPECT_FLOAT_EQ(1, scale);
webViewHelper.webView()->setPageScaleFactor(
webViewHelper.webView()->minimumPageScaleFactor());
simulateMultiTargetZoom(webViewHelper.webView(), topDiv, scale);
EXPECT_FLOAT_EQ(1, scale);
}
TEST_F(WebFrameTest, DontZoomInOnFocusedInTouchAction) {
registerMockedHttpURLLoad("textbox_in_touch_action.html");
int viewportWidth = 600;
int viewportHeight = 1000;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "textbox_in_touch_action.html");
webViewHelper.webView()->setDefaultPageScaleLimits(0.25f, 4);
webViewHelper.webView()->enableFakePageScaleAnimationForTesting(true);
webViewHelper.webView()->page()->settings().setTextAutosizingEnabled(false);
webViewHelper.webView()->settings()->setAutoZoomFocusedNodeToLegibleScale(
true);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
float initialScale = webViewHelper.webView()->pageScaleFactor();
// Focus the first textbox that's in a touch-action: pan-x ancestor, this
// shouldn't cause an autozoom since pan-x disables pinch-zoom.
webViewHelper.webView()->advanceFocus(false);
webViewHelper.webView()->scrollFocusedEditableElementIntoRect(WebRect());
EXPECT_EQ(
webViewHelper.webView()->fakePageScaleAnimationPageScaleForTesting(), 0);
setScaleAndScrollAndLayout(webViewHelper.webView(), WebPoint(0, 0),
initialScale);
ASSERT_EQ(initialScale, webViewHelper.webView()->pageScaleFactor());
// Focus the second textbox that's in a touch-action: manipulation ancestor,
// this should cause an autozoom since it allows pinch-zoom.
webViewHelper.webView()->advanceFocus(false);
webViewHelper.webView()->scrollFocusedEditableElementIntoRect(WebRect());
EXPECT_GT(
webViewHelper.webView()->fakePageScaleAnimationPageScaleForTesting(),
initialScale);
setScaleAndScrollAndLayout(webViewHelper.webView(), WebPoint(0, 0),
initialScale);
ASSERT_EQ(initialScale, webViewHelper.webView()->pageScaleFactor());
// Focus the third textbox that has a touch-action: pan-x ancestor, this
// should cause an autozoom since it's seperated from the node with the
// touch-action by an overflow:scroll element.
webViewHelper.webView()->advanceFocus(false);
webViewHelper.webView()->scrollFocusedEditableElementIntoRect(WebRect());
EXPECT_GT(
webViewHelper.webView()->fakePageScaleAnimationPageScaleForTesting(),
initialScale);
}
TEST_F(WebFrameTest, DivScrollIntoEditableTest) {
registerMockedHttpURLLoad("get_scale_for_zoom_into_editable_test.html");
const bool autoZoomToLegibleScale = true;
int viewportWidth = 450;
int viewportHeight = 300;
float leftBoxRatio = 0.3f;
int caretPadding = 10;
float minReadableCaretHeight = 16.0f;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL +
"get_scale_for_zoom_into_editable_test.html");
webViewHelper.webView()->page()->settings().setTextAutosizingEnabled(false);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
webViewHelper.webView()->setDefaultPageScaleLimits(0.25f, 4);
webViewHelper.webView()->enableFakePageScaleAnimationForTesting(true);
WebRect editBoxWithText(200, 200, 250, 20);
WebRect editBoxWithNoText(200, 250, 250, 20);
// Test scrolling the focused node
// The edit box is shorter and narrower than the viewport when legible.
webViewHelper.webView()->advanceFocus(false);
// Set the caret to the end of the input box.
webViewHelper.webView()
->mainFrame()
->document()
.getElementById("EditBoxWithText")
.to<WebInputElement>()
.setSelectionRange(1000, 1000);
setScaleAndScrollAndLayout(webViewHelper.webView(), WebPoint(0, 0), 1);
WebRect rect, caret;
webViewHelper.webView()->selectionBounds(caret, rect);
// Set the page scale to be smaller than the minimal readable scale.
float initialScale = minReadableCaretHeight / caret.height * 0.5f;
setScaleAndScrollAndLayout(webViewHelper.webView(), WebPoint(0, 0),
initialScale);
float scale;
IntPoint scroll;
bool needAnimation;
webViewHelper.webView()->computeScaleAndScrollForFocusedNode(
webViewHelper.webView()->focusedElement(), autoZoomToLegibleScale, scale,
scroll, needAnimation);
EXPECT_TRUE(needAnimation);
// The edit box should be left aligned with a margin for possible label.
int hScroll = editBoxWithText.x - leftBoxRatio * viewportWidth / scale;
EXPECT_NEAR(hScroll, scroll.x(), 2);
int vScroll =
editBoxWithText.y - (viewportHeight / scale - editBoxWithText.height) / 2;
EXPECT_NEAR(vScroll, scroll.y(), 2);
EXPECT_NEAR(minReadableCaretHeight / caret.height, scale, 0.1);
// The edit box is wider than the viewport when legible.
viewportWidth = 200;
viewportHeight = 150;
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
setScaleAndScrollAndLayout(webViewHelper.webView(), WebPoint(0, 0),
initialScale);
webViewHelper.webView()->computeScaleAndScrollForFocusedNode(
webViewHelper.webView()->focusedElement(), autoZoomToLegibleScale, scale,
scroll, needAnimation);
EXPECT_TRUE(needAnimation);
// The caret should be right aligned since the caret would be offscreen when
// the edit box is left aligned.
hScroll = caret.x + caret.width + caretPadding - viewportWidth / scale;
EXPECT_NEAR(hScroll, scroll.x(), 2);
EXPECT_NEAR(minReadableCaretHeight / caret.height, scale, 0.1);
setScaleAndScrollAndLayout(webViewHelper.webView(), WebPoint(0, 0),
initialScale);
// Move focus to edit box with text.
webViewHelper.webView()->advanceFocus(false);
webViewHelper.webView()->computeScaleAndScrollForFocusedNode(
webViewHelper.webView()->focusedElement(), autoZoomToLegibleScale, scale,
scroll, needAnimation);
EXPECT_TRUE(needAnimation);
// The edit box should be left aligned.
hScroll = editBoxWithNoText.x;
EXPECT_NEAR(hScroll, scroll.x(), 2);
vScroll = editBoxWithNoText.y -
(viewportHeight / scale - editBoxWithNoText.height) / 2;
EXPECT_NEAR(vScroll, scroll.y(), 2);
EXPECT_NEAR(minReadableCaretHeight / caret.height, scale, 0.1);
// Move focus back to the first edit box.
webViewHelper.webView()->advanceFocus(true);
// Zoom out slightly.
const float withinToleranceScale = scale * 0.9f;
setScaleAndScrollAndLayout(webViewHelper.webView(), scroll,
withinToleranceScale);
// Move focus back to the second edit box.
webViewHelper.webView()->advanceFocus(false);
webViewHelper.webView()->computeScaleAndScrollForFocusedNode(
webViewHelper.webView()->focusedElement(), autoZoomToLegibleScale, scale,
scroll, needAnimation);
// The scale should not be adjusted as the zoomed out scale was sufficiently
// close to the previously focused scale.
EXPECT_FALSE(needAnimation);
}
TEST_F(WebFrameTest, DivScrollIntoEditablePreservePageScaleTest) {
registerMockedHttpURLLoad("get_scale_for_zoom_into_editable_test.html");
const bool autoZoomToLegibleScale = true;
const int viewportWidth = 450;
const int viewportHeight = 300;
const float minReadableCaretHeight = 16.0f;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL +
"get_scale_for_zoom_into_editable_test.html");
webViewHelper.webView()->page()->settings().setTextAutosizingEnabled(false);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
webViewHelper.webView()->enableFakePageScaleAnimationForTesting(true);
const WebRect editBoxWithText(200, 200, 250, 20);
webViewHelper.webView()->advanceFocus(false);
// Set the caret to the begining of the input box.
webViewHelper.webView()
->mainFrame()
->document()
.getElementById("EditBoxWithText")
.to<WebInputElement>()
.setSelectionRange(0, 0);
setScaleAndScrollAndLayout(webViewHelper.webView(), WebPoint(0, 0), 1);
WebRect rect, caret;
webViewHelper.webView()->selectionBounds(caret, rect);
// Set the page scale to be twice as large as the minimal readable scale.
float newScale = minReadableCaretHeight / caret.height * 2.0;
setScaleAndScrollAndLayout(webViewHelper.webView(), WebPoint(0, 0), newScale);
float scale;
IntPoint scroll;
bool needAnimation;
webViewHelper.webView()->computeScaleAndScrollForFocusedNode(
webViewHelper.webView()->focusedElement(), autoZoomToLegibleScale, scale,
scroll, needAnimation);
EXPECT_TRUE(needAnimation);
// Edit box and caret should be left alinged
int hScroll = editBoxWithText.x;
EXPECT_NEAR(hScroll, scroll.x(), 1);
int vScroll =
editBoxWithText.y - (viewportHeight / scale - editBoxWithText.height) / 2;
EXPECT_NEAR(vScroll, scroll.y(), 1);
// Page scale have to be unchanged
EXPECT_EQ(newScale, scale);
// Set page scale and scroll such that edit box will be under the screen
newScale = 3.0;
hScroll = 200;
setScaleAndScrollAndLayout(webViewHelper.webView(), WebPoint(hScroll, 0),
newScale);
webViewHelper.webView()->computeScaleAndScrollForFocusedNode(
webViewHelper.webView()->focusedElement(), autoZoomToLegibleScale, scale,
scroll, needAnimation);
EXPECT_TRUE(needAnimation);
// Horizontal scroll have to be the same
EXPECT_NEAR(hScroll, scroll.x(), 1);
vScroll =
editBoxWithText.y - (viewportHeight / scale - editBoxWithText.height) / 2;
EXPECT_NEAR(vScroll, scroll.y(), 1);
// Page scale have to be unchanged
EXPECT_EQ(newScale, scale);
}
// Tests the scroll into view functionality when
// autoZoomeFocusedNodeToLegibleScale set to false. i.e. The path non-Android
// platforms take.
TEST_F(WebFrameTest, DivScrollIntoEditableTestZoomToLegibleScaleDisabled) {
registerMockedHttpURLLoad("get_scale_for_zoom_into_editable_test.html");
const bool autoZoomToLegibleScale = false;
int viewportWidth = 100;
int viewportHeight = 100;
float leftBoxRatio = 0.3f;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL +
"get_scale_for_zoom_into_editable_test.html");
webViewHelper.webView()->page()->settings().setTextAutosizingEnabled(false);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
webViewHelper.webView()->setDefaultPageScaleLimits(0.25f, 4);
webViewHelper.webView()->enableFakePageScaleAnimationForTesting(true);
WebRect editBoxWithText(200, 200, 250, 20);
WebRect editBoxWithNoText(200, 250, 250, 20);
// Test scrolling the focused node
// Since we're zoomed out, the caret is considered too small to be legible and
// so we'd normally zoom in. Make sure we don't change scale since the
// auto-zoom setting is off.
// Focus the second empty textbox.
webViewHelper.webView()->advanceFocus(false);
webViewHelper.webView()->advanceFocus(false);
// Set the page scale to be smaller than the minimal readable scale.
float initialScale = 0.25f;
setScaleAndScrollAndLayout(webViewHelper.webView(), WebPoint(0, 0),
initialScale);
float scale;
IntPoint scroll;
bool needAnimation;
webViewHelper.webView()->computeScaleAndScrollForFocusedNode(
webViewHelper.webView()->focusedElement(), autoZoomToLegibleScale, scale,
scroll, needAnimation);
// There should be no change in page scale.
EXPECT_EQ(initialScale, scale);
// The edit box should be left aligned with a margin for possible label.
EXPECT_TRUE(needAnimation);
int hScroll = editBoxWithNoText.x - leftBoxRatio * viewportWidth / scale;
EXPECT_NEAR(hScroll, scroll.x(), 2);
int vScroll = editBoxWithNoText.y -
(viewportHeight / scale - editBoxWithNoText.height) / 2;
EXPECT_NEAR(vScroll, scroll.y(), 2);
setScaleAndScrollAndLayout(webViewHelper.webView(), scroll, scale);
// Select the first textbox.
webViewHelper.webView()->advanceFocus(true);
WebRect rect, caret;
webViewHelper.webView()->selectionBounds(caret, rect);
webViewHelper.webView()->computeScaleAndScrollForFocusedNode(
webViewHelper.webView()->focusedElement(), autoZoomToLegibleScale, scale,
scroll, needAnimation);
// There should be no change at all since the textbox is fully visible
// already.
EXPECT_EQ(initialScale, scale);
EXPECT_FALSE(needAnimation);
}
TEST_P(ParameterizedWebFrameTest, CharacterIndexAtPointWithPinchZoom) {
registerMockedHttpURLLoad("sometext.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "sometext.html");
webViewHelper.resize(WebSize(640, 480));
webViewHelper.webView()->setPageScaleFactor(2);
webViewHelper.webView()->setVisualViewportOffset(WebFloatPoint(50, 60));
WebRect baseRect;
WebRect extentRect;
WebLocalFrame* mainFrame =
webViewHelper.webView()->mainFrame()->toWebLocalFrame();
size_t ix = mainFrame->characterIndexForPoint(WebPoint(320, 388));
EXPECT_EQ(2ul, ix);
}
TEST_P(ParameterizedWebFrameTest, FirstRectForCharacterRangeWithPinchZoom) {
registerMockedHttpURLLoad("textbox.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "textbox.html", true);
webViewHelper.resize(WebSize(640, 480));
WebLocalFrame* mainFrame =
webViewHelper.webView()->mainFrame()->toWebLocalFrame();
mainFrame->executeScript(WebScriptSource("selectRange();"));
WebRect oldRect;
mainFrame->firstRectForCharacterRange(0, 5, oldRect);
WebFloatPoint visualOffset(100, 130);
float scale = 2;
webViewHelper.webView()->setPageScaleFactor(scale);
webViewHelper.webView()->setVisualViewportOffset(visualOffset);
WebRect baseRect;
WebRect extentRect;
WebRect rect;
mainFrame->firstRectForCharacterRange(0, 5, rect);
EXPECT_EQ((oldRect.x - visualOffset.x) * scale, rect.x);
EXPECT_EQ((oldRect.y - visualOffset.y) * scale, rect.y);
EXPECT_EQ(oldRect.width * scale, rect.width);
EXPECT_EQ(oldRect.height * scale, rect.height);
}
class TestReloadDoesntRedirectWebFrameClient
: public FrameTestHelpers::TestWebFrameClient {
public:
WebNavigationPolicy decidePolicyForNavigation(
const NavigationPolicyInfo& info) override {
EXPECT_FALSE(info.extraData);
return WebNavigationPolicyCurrentTab;
}
};
TEST_P(ParameterizedWebFrameTest, ReloadDoesntSetRedirect) {
// Test for case in http://crbug.com/73104. Reloading a frame very quickly
// would sometimes call decidePolicyForNavigation with isRedirect=true
registerMockedHttpURLLoad("form.html");
TestReloadDoesntRedirectWebFrameClient webFrameClient;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "form.html", false,
&webFrameClient);
webViewHelper.webView()->mainFrame()->reload(
WebFrameLoadType::ReloadBypassingCache);
// start another reload before request is delivered.
FrameTestHelpers::reloadFrameIgnoringCache(
webViewHelper.webView()->mainFrame());
}
class ClearScrollStateOnCommitWebFrameClient
: public FrameTestHelpers::TestWebFrameClient {
public:
void didCommitProvisionalLoad(WebLocalFrame* frame,
const WebHistoryItem&,
WebHistoryCommitType) override {
frame->view()->resetScrollAndScaleState();
}
};
TEST_F(WebFrameTest, ReloadWithOverrideURLPreservesState) {
const std::string firstURL = "200-by-300.html";
const std::string secondURL = "content-width-1000.html";
const std::string thirdURL = "very_tall_div.html";
const float pageScaleFactor = 1.1684f;
const int pageWidth = 120;
const int pageHeight = 100;
registerMockedHttpURLLoad(firstURL);
registerMockedHttpURLLoad(secondURL);
registerMockedHttpURLLoad(thirdURL);
FrameTestHelpers::WebViewHelper webViewHelper;
ClearScrollStateOnCommitWebFrameClient client;
webViewHelper.initializeAndLoad(m_baseURL + firstURL, true, &client);
webViewHelper.resize(WebSize(pageWidth, pageHeight));
webViewHelper.webView()->mainFrame()->setScrollOffset(
WebSize(pageWidth / 4, pageHeight / 4));
webViewHelper.webView()->setPageScaleFactor(pageScaleFactor);
WebSize previousOffset = webViewHelper.webView()->mainFrame()->scrollOffset();
float previousScale = webViewHelper.webView()->pageScaleFactor();
// Reload the page and end up at the same url. State should be propagated.
webViewHelper.webView()->mainFrame()->reloadWithOverrideURL(
toKURL(m_baseURL + firstURL), WebFrameLoadType::Reload);
FrameTestHelpers::pumpPendingRequestsForFrameToLoad(
webViewHelper.webView()->mainFrame());
EXPECT_EQ(previousOffset.width,
webViewHelper.webView()->mainFrame()->scrollOffset().width);
EXPECT_EQ(previousOffset.height,
webViewHelper.webView()->mainFrame()->scrollOffset().height);
EXPECT_EQ(previousScale, webViewHelper.webView()->pageScaleFactor());
// Reload the page using the cache. State should not be propagated.
webViewHelper.webView()->mainFrame()->reloadWithOverrideURL(
toKURL(m_baseURL + secondURL), WebFrameLoadType::Reload);
FrameTestHelpers::pumpPendingRequestsForFrameToLoad(
webViewHelper.webView()->mainFrame());
EXPECT_EQ(0, webViewHelper.webView()->mainFrame()->scrollOffset().width);
EXPECT_EQ(0, webViewHelper.webView()->mainFrame()->scrollOffset().height);
EXPECT_EQ(1.0f, webViewHelper.webView()->pageScaleFactor());
// Reload the page while ignoring the cache. State should not be propagated.
webViewHelper.webView()->mainFrame()->reloadWithOverrideURL(
toKURL(m_baseURL + thirdURL), WebFrameLoadType::ReloadBypassingCache);
FrameTestHelpers::pumpPendingRequestsForFrameToLoad(
webViewHelper.webView()->mainFrame());
EXPECT_EQ(0, webViewHelper.webView()->mainFrame()->scrollOffset().width);
EXPECT_EQ(0, webViewHelper.webView()->mainFrame()->scrollOffset().height);
EXPECT_EQ(1.0f, webViewHelper.webView()->pageScaleFactor());
}
TEST_P(ParameterizedWebFrameTest, ReloadWhileProvisional) {
// Test that reloading while the previous load is still pending does not cause
// the initial request to get lost.
registerMockedHttpURLLoad("fixed_layout.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initialize();
WebURLRequest request;
request.setURL(toKURL(m_baseURL + "fixed_layout.html"));
request.setRequestorOrigin(WebSecurityOrigin::createUnique());
webViewHelper.webView()->mainFrame()->loadRequest(request);
// start reload before first request is delivered.
FrameTestHelpers::reloadFrameIgnoringCache(
webViewHelper.webView()->mainFrame());
WebDataSource* dataSource =
webViewHelper.webView()->mainFrame()->dataSource();
ASSERT_TRUE(dataSource);
EXPECT_EQ(toKURL(m_baseURL + "fixed_layout.html"),
KURL(dataSource->request().url()));
}
TEST_P(ParameterizedWebFrameTest, AppendRedirects) {
const std::string firstURL = "about:blank";
const std::string secondURL = "http://internal.test";
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(firstURL, true);
WebDataSource* dataSource =
webViewHelper.webView()->mainFrame()->dataSource();
ASSERT_TRUE(dataSource);
dataSource->appendRedirect(toKURL(secondURL));
WebVector<WebURL> redirects;
dataSource->redirectChain(redirects);
ASSERT_EQ(2U, redirects.size());
EXPECT_EQ(toKURL(firstURL), KURL(redirects[0]));
EXPECT_EQ(toKURL(secondURL), KURL(redirects[1]));
}
TEST_P(ParameterizedWebFrameTest, IframeRedirect) {
registerMockedHttpURLLoad("iframe_redirect.html");
registerMockedHttpURLLoad("visible_iframe.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "iframe_redirect.html", true);
// Pump pending requests one more time. The test page loads script that
// navigates.
FrameTestHelpers::pumpPendingRequestsForFrameToLoad(
webViewHelper.webView()->mainFrame());
WebFrame* iframe = webViewHelper.webView()->findFrameByName(
WebString::fromUTF8("ifr"), nullptr);
ASSERT_TRUE(iframe);
WebDataSource* iframeDataSource = iframe->dataSource();
ASSERT_TRUE(iframeDataSource);
WebVector<WebURL> redirects;
iframeDataSource->redirectChain(redirects);
ASSERT_EQ(2U, redirects.size());
EXPECT_EQ(toKURL("about:blank"), KURL(redirects[0]));
EXPECT_EQ(toKURL("http://internal.test/visible_iframe.html"),
KURL(redirects[1]));
}
TEST_P(ParameterizedWebFrameTest, ClearFocusedNodeTest) {
registerMockedHttpURLLoad("iframe_clear_focused_node_test.html");
registerMockedHttpURLLoad("autofocus_input_field_iframe.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(
m_baseURL + "iframe_clear_focused_node_test.html", true);
// Clear the focused node.
webViewHelper.webView()->clearFocusedElement();
// Now retrieve the FocusedNode and test it should be null.
EXPECT_EQ(0, webViewHelper.webView()->focusedElement());
}
// Implementation of WebFrameClient that tracks the v8 contexts that are created
// and destroyed for verification.
class ContextLifetimeTestWebFrameClient
: public FrameTestHelpers::TestWebFrameClient {
public:
struct Notification {
public:
Notification(WebLocalFrame* frame,
v8::Local<v8::Context> context,
int worldId)
: frame(frame),
context(context->GetIsolate(), context),
worldId(worldId) {}
~Notification() { context.Reset(); }
bool Equals(Notification* other) {
return other && frame == other->frame && context == other->context &&
worldId == other->worldId;
}
WebLocalFrame* frame;
v8::Persistent<v8::Context> context;
int worldId;
};
~ContextLifetimeTestWebFrameClient() override { reset(); }
void reset() {
createNotifications.clear();
releaseNotifications.clear();
}
Vector<std::unique_ptr<Notification>> createNotifications;
Vector<std::unique_ptr<Notification>> releaseNotifications;
private:
void didCreateScriptContext(WebLocalFrame* frame,
v8::Local<v8::Context> context,
int extensionGroup,
int worldId) override {
createNotifications.append(
wrapUnique(new Notification(frame, context, worldId)));
}
void willReleaseScriptContext(WebLocalFrame* frame,
v8::Local<v8::Context> context,
int worldId) override {
releaseNotifications.append(
wrapUnique(new Notification(frame, context, worldId)));
}
};
TEST_P(ParameterizedWebFrameTest, ContextNotificationsLoadUnload) {
v8::HandleScope handleScope(v8::Isolate::GetCurrent());
registerMockedHttpURLLoad("context_notifications_test.html");
registerMockedHttpURLLoad("context_notifications_test_frame.html");
// Load a frame with an iframe, make sure we get the right create
// notifications.
ContextLifetimeTestWebFrameClient webFrameClient;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "context_notifications_test.html",
true, &webFrameClient);
WebFrame* mainFrame = webViewHelper.webView()->mainFrame();
WebFrame* childFrame = mainFrame->firstChild();
ASSERT_EQ(2u, webFrameClient.createNotifications.size());
EXPECT_EQ(0u, webFrameClient.releaseNotifications.size());
auto& firstCreateNotification = webFrameClient.createNotifications[0];
auto& secondCreateNotification = webFrameClient.createNotifications[1];
EXPECT_EQ(mainFrame, firstCreateNotification->frame);
EXPECT_EQ(mainFrame->mainWorldScriptContext(),
firstCreateNotification->context);
EXPECT_EQ(0, firstCreateNotification->worldId);
EXPECT_EQ(childFrame, secondCreateNotification->frame);
EXPECT_EQ(childFrame->mainWorldScriptContext(),
secondCreateNotification->context);
EXPECT_EQ(0, secondCreateNotification->worldId);
// Close the view. We should get two release notifications that are exactly
// the same as the create ones, in reverse order.
webViewHelper.reset();
ASSERT_EQ(2u, webFrameClient.releaseNotifications.size());
auto& firstReleaseNotification = webFrameClient.releaseNotifications[0];
auto& secondReleaseNotification = webFrameClient.releaseNotifications[1];
ASSERT_TRUE(firstCreateNotification->Equals(secondReleaseNotification.get()));
ASSERT_TRUE(secondCreateNotification->Equals(firstReleaseNotification.get()));
}
TEST_P(ParameterizedWebFrameTest, ContextNotificationsReload) {
v8::HandleScope handleScope(v8::Isolate::GetCurrent());
registerMockedHttpURLLoad("context_notifications_test.html");
registerMockedHttpURLLoad("context_notifications_test_frame.html");
ContextLifetimeTestWebFrameClient webFrameClient;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "context_notifications_test.html",
true, &webFrameClient);
// Refresh, we should get two release notifications and two more create
// notifications.
FrameTestHelpers::reloadFrame(webViewHelper.webView()->mainFrame());
ASSERT_EQ(4u, webFrameClient.createNotifications.size());
ASSERT_EQ(2u, webFrameClient.releaseNotifications.size());
// The two release notifications we got should be exactly the same as the
// first two create notifications.
for (size_t i = 0; i < webFrameClient.releaseNotifications.size(); ++i) {
EXPECT_TRUE(webFrameClient.releaseNotifications[i]->Equals(
webFrameClient
.createNotifications[webFrameClient.createNotifications.size() - 3 -
i]
.get()));
}
// The last two create notifications should be for the current frames and
// context.
WebFrame* mainFrame = webViewHelper.webView()->mainFrame();
WebFrame* childFrame = mainFrame->firstChild();
auto& firstRefreshNotification = webFrameClient.createNotifications[2];
auto& secondRefreshNotification = webFrameClient.createNotifications[3];
EXPECT_EQ(mainFrame, firstRefreshNotification->frame);
EXPECT_EQ(mainFrame->mainWorldScriptContext(),
firstRefreshNotification->context);
EXPECT_EQ(0, firstRefreshNotification->worldId);
EXPECT_EQ(childFrame, secondRefreshNotification->frame);
EXPECT_EQ(childFrame->mainWorldScriptContext(),
secondRefreshNotification->context);
EXPECT_EQ(0, secondRefreshNotification->worldId);
}
TEST_P(ParameterizedWebFrameTest, ContextNotificationsIsolatedWorlds) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::HandleScope handleScope(isolate);
registerMockedHttpURLLoad("context_notifications_test.html");
registerMockedHttpURLLoad("context_notifications_test_frame.html");
ContextLifetimeTestWebFrameClient webFrameClient;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "context_notifications_test.html",
true, &webFrameClient);
// Add an isolated world.
webFrameClient.reset();
int isolatedWorldId = 42;
WebScriptSource scriptSource("hi!");
int numSources = 1;
int extensionGroup = 0;
webViewHelper.webView()->mainFrame()->executeScriptInIsolatedWorld(
isolatedWorldId, &scriptSource, numSources, extensionGroup);
// We should now have a new create notification.
ASSERT_EQ(1u, webFrameClient.createNotifications.size());
auto& notification = webFrameClient.createNotifications[0];
ASSERT_EQ(isolatedWorldId, notification->worldId);
ASSERT_EQ(webViewHelper.webView()->mainFrame(), notification->frame);
// We don't have an API to enumarate isolated worlds for a frame, but we can
// at least assert that the context we got is *not* the main world's context.
ASSERT_NE(webViewHelper.webView()->mainFrame()->mainWorldScriptContext(),
v8::Local<v8::Context>::New(isolate, notification->context));
webViewHelper.reset();
// We should have gotten three release notifications (one for each of the
// frames, plus one for the isolated context).
ASSERT_EQ(3u, webFrameClient.releaseNotifications.size());
// And one of them should be exactly the same as the create notification for
// the isolated context.
int matchCount = 0;
for (size_t i = 0; i < webFrameClient.releaseNotifications.size(); ++i) {
if (webFrameClient.releaseNotifications[i]->Equals(
webFrameClient.createNotifications[0].get()))
++matchCount;
}
EXPECT_EQ(1, matchCount);
}
TEST_P(ParameterizedWebFrameTest, FindInPage) {
registerMockedHttpURLLoad("find.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "find.html");
ASSERT_TRUE(webViewHelper.webView()->mainFrameImpl());
WebLocalFrame* frame = webViewHelper.webView()->mainFrameImpl();
const int findIdentifier = 12345;
WebFindOptions options;
// Find in a <div> element.
EXPECT_TRUE(
frame->find(findIdentifier, WebString::fromUTF8("bar1"), options, false));
frame->stopFinding(WebLocalFrame::StopFindActionKeepSelection);
WebRange range = frame->selectionRange();
EXPECT_EQ(5, range.startOffset());
EXPECT_EQ(9, range.endOffset());
EXPECT_TRUE(frame->document().focusedElement().isNull());
// Find in an <input> value.
EXPECT_TRUE(
frame->find(findIdentifier, WebString::fromUTF8("bar2"), options, false));
// Confirm stopFinding(WebLocalFrame::StopFindActionKeepSelection) sets the
// selection on the found text.
frame->stopFinding(WebLocalFrame::StopFindActionKeepSelection);
range = frame->selectionRange();
ASSERT_FALSE(range.isNull());
EXPECT_EQ(5, range.startOffset());
EXPECT_EQ(9, range.endOffset());
EXPECT_TRUE(frame->document().focusedElement().hasHTMLTagName("input"));
// Find in a <textarea> content.
EXPECT_TRUE(
frame->find(findIdentifier, WebString::fromUTF8("bar3"), options, false));
// Confirm stopFinding(WebLocalFrame::StopFindActionKeepSelection) sets the
// selection on the found text.
frame->stopFinding(WebLocalFrame::StopFindActionKeepSelection);
range = frame->selectionRange();
ASSERT_FALSE(range.isNull());
EXPECT_EQ(5, range.startOffset());
EXPECT_EQ(9, range.endOffset());
EXPECT_TRUE(frame->document().focusedElement().hasHTMLTagName("textarea"));
// Find in a contentEditable element.
EXPECT_TRUE(
frame->find(findIdentifier, WebString::fromUTF8("bar4"), options, false));
// Confirm stopFinding(WebLocalFrame::StopFindActionKeepSelection) sets the
// selection on the found text.
frame->stopFinding(WebLocalFrame::StopFindActionKeepSelection);
range = frame->selectionRange();
ASSERT_FALSE(range.isNull());
EXPECT_EQ(0, range.startOffset());
EXPECT_EQ(4, range.endOffset());
// "bar4" is surrounded by <span>, but the focusable node should be the parent
// <div>.
EXPECT_TRUE(frame->document().focusedElement().hasHTMLTagName("div"));
// Find in <select> content.
EXPECT_FALSE(
frame->find(findIdentifier, WebString::fromUTF8("bar5"), options, false));
// If there are any matches, stopFinding will set the selection on the found
// text. However, we do not expect any matches, so check that the selection
// is null.
frame->stopFinding(WebLocalFrame::StopFindActionKeepSelection);
range = frame->selectionRange();
ASSERT_TRUE(range.isNull());
}
TEST_P(ParameterizedWebFrameTest, GetContentAsPlainText) {
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad("about:blank", true);
// We set the size because it impacts line wrapping, which changes the
// resulting text value.
webViewHelper.resize(WebSize(640, 480));
WebFrame* frame = webViewHelper.webView()->mainFrame();
// Generate a simple test case.
const char simpleSource[] = "<div>Foo bar</div><div></div>baz";
KURL testURL = toKURL("about:blank");
FrameTestHelpers::loadHTMLString(frame, simpleSource, testURL);
// Make sure it comes out OK.
const std::string expected("Foo bar\nbaz");
WebString text = WebFrameContentDumper::dumpWebViewAsText(
webViewHelper.webView(), std::numeric_limits<size_t>::max());
EXPECT_EQ(expected, text.utf8());
// Try reading the same one with clipping of the text.
const int length = 5;
text =
WebFrameContentDumper::dumpWebViewAsText(webViewHelper.webView(), length);
EXPECT_EQ(expected.substr(0, length), text.utf8());
// Now do a new test with a subframe.
const char outerFrameSource[] = "Hello<iframe></iframe> world";
FrameTestHelpers::loadHTMLString(frame, outerFrameSource, testURL);
// Load something into the subframe.
WebFrame* subframe = frame->firstChild();
ASSERT_TRUE(subframe);
FrameTestHelpers::loadHTMLString(subframe, "sub<p>text", testURL);
text = WebFrameContentDumper::dumpWebViewAsText(
webViewHelper.webView(), std::numeric_limits<size_t>::max());
EXPECT_EQ("Hello world\n\nsub\ntext", text.utf8());
// Get the frame text where the subframe separator falls on the boundary of
// what we'll take. There used to be a crash in this case.
text = WebFrameContentDumper::dumpWebViewAsText(webViewHelper.webView(), 12);
EXPECT_EQ("Hello world", text.utf8());
}
TEST_P(ParameterizedWebFrameTest, GetFullHtmlOfPage) {
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad("about:blank", true);
WebLocalFrame* frame = webViewHelper.webView()->mainFrameImpl();
// Generate a simple test case.
const char simpleSource[] = "<p>Hello</p><p>World</p>";
KURL testURL = toKURL("about:blank");
FrameTestHelpers::loadHTMLString(frame, simpleSource, testURL);
WebString text = WebFrameContentDumper::dumpWebViewAsText(
webViewHelper.webView(), std::numeric_limits<size_t>::max());
EXPECT_EQ("Hello\n\nWorld", text.utf8());
const std::string html = WebFrameContentDumper::dumpAsMarkup(frame).utf8();
// Load again with the output html.
FrameTestHelpers::loadHTMLString(frame, html, testURL);
EXPECT_EQ(html, WebFrameContentDumper::dumpAsMarkup(frame).utf8());
text = WebFrameContentDumper::dumpWebViewAsText(
webViewHelper.webView(), std::numeric_limits<size_t>::max());
EXPECT_EQ("Hello\n\nWorld", text.utf8());
// Test selection check
EXPECT_FALSE(frame->hasSelection());
frame->executeCommand(WebString::fromUTF8("SelectAll"));
EXPECT_TRUE(frame->hasSelection());
frame->executeCommand(WebString::fromUTF8("Unselect"));
EXPECT_FALSE(frame->hasSelection());
WebString selectionHtml = frame->selectionAsMarkup();
EXPECT_TRUE(selectionHtml.isEmpty());
}
class TestExecuteScriptDuringDidCreateScriptContext
: public FrameTestHelpers::TestWebFrameClient {
public:
void didCreateScriptContext(WebLocalFrame* frame,
v8::Local<v8::Context> context,
int extensionGroup,
int worldId) override {
frame->executeScript(WebScriptSource("window.history = 'replaced';"));
}
};
TEST_P(ParameterizedWebFrameTest, ExecuteScriptDuringDidCreateScriptContext) {
registerMockedHttpURLLoad("hello_world.html");
TestExecuteScriptDuringDidCreateScriptContext webFrameClient;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "hello_world.html", true,
&webFrameClient);
FrameTestHelpers::reloadFrame(webViewHelper.webView()->mainFrame());
}
class FindUpdateWebFrameClient : public FrameTestHelpers::TestWebFrameClient {
public:
FindUpdateWebFrameClient()
: m_findResultsAreReady(false), m_count(-1), m_activeIndex(-1) {}
void reportFindInPageMatchCount(int, int count, bool finalUpdate) override {
m_count = count;
if (finalUpdate)
m_findResultsAreReady = true;
}
void reportFindInPageSelection(int,
int activeMatchOrdinal,
const WebRect&) override {
m_activeIndex = activeMatchOrdinal;
}
bool findResultsAreReady() const { return m_findResultsAreReady; }
int count() const { return m_count; }
int activeIndex() const { return m_activeIndex; }
private:
bool m_findResultsAreReady;
int m_count;
int m_activeIndex;
};
TEST_P(ParameterizedWebFrameTest, FindInPageMatchRects) {
registerMockedHttpURLLoad("find_in_page_frame.html");
FindUpdateWebFrameClient client;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "find_in_page_frame.html", true,
&client);
webViewHelper.resize(WebSize(640, 480));
webViewHelper.webView()->setMaximumLegibleScale(1.f);
webViewHelper.webView()->updateAllLifecyclePhases();
runPendingTasks();
// Note that the 'result 19' in the <select> element is not expected to
// produce a match. Also, results 00 and 01 are in a different frame that is
// not included in this test.
const char kFindString[] = "result";
const int kFindIdentifier = 12345;
const int kNumResults = 17;
WebFindOptions options;
WebString searchText = WebString::fromUTF8(kFindString);
WebLocalFrameImpl* mainFrame = webViewHelper.webView()->mainFrameImpl();
EXPECT_TRUE(mainFrame->find(kFindIdentifier, searchText, options, false));
mainFrame->ensureTextFinder().resetMatchCount();
for (WebLocalFrameImpl* frame = mainFrame; frame;
frame = static_cast<WebLocalFrameImpl*>(frame->traverseNext(false)))
frame->ensureTextFinder().scopeStringMatches(kFindIdentifier, searchText,
options, true);
runPendingTasks();
EXPECT_TRUE(client.findResultsAreReady());
WebVector<WebFloatRect> webMatchRects;
mainFrame->findMatchRects(webMatchRects);
ASSERT_EQ(static_cast<size_t>(kNumResults), webMatchRects.size());
int rectsVersion = mainFrame->findMatchMarkersVersion();
for (int resultIndex = 0; resultIndex < kNumResults; ++resultIndex) {
FloatRect resultRect = static_cast<FloatRect>(webMatchRects[resultIndex]);
// Select the match by the center of its rect.
EXPECT_EQ(mainFrame->selectNearestFindMatch(resultRect.center(), 0),
resultIndex + 1);
// Check that the find result ordering matches with our expectations.
Range* result = mainFrame->textFinder()->activeMatch();
ASSERT_TRUE(result);
result->setEnd(result->endContainer(), result->endOffset() + 3);
EXPECT_EQ(result->text(),
String::format("%s %02d", kFindString, resultIndex + 2));
// Verify that the expected match rect also matches the currently active
// match. Compare the enclosing rects to prevent precision issues caused by
// CSS transforms.
FloatRect activeMatch = mainFrame->activeFindMatchRect();
EXPECT_EQ(enclosingIntRect(activeMatch), enclosingIntRect(resultRect));
// The rects version should not have changed.
EXPECT_EQ(mainFrame->findMatchMarkersVersion(), rectsVersion);
}
// Resizing should update the rects version.
webViewHelper.resize(WebSize(800, 600));
runPendingTasks();
EXPECT_TRUE(mainFrame->findMatchMarkersVersion() != rectsVersion);
}
TEST_F(WebFrameTest, FindInPageActiveIndex) {
registerMockedHttpURLLoad("find_match_count.html");
FindUpdateWebFrameClient client;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "find_match_count.html", true,
&client);
webViewHelper.webView()->resize(WebSize(640, 480));
runPendingTasks();
const char* kFindString = "a";
const int kFindIdentifier = 7777;
const int kActiveIndex = 1;
WebFindOptions options;
WebString searchText = WebString::fromUTF8(kFindString);
WebLocalFrameImpl* mainFrame = webViewHelper.webView()->mainFrameImpl();
EXPECT_TRUE(mainFrame->find(kFindIdentifier, searchText, options, false));
mainFrame->ensureTextFinder().resetMatchCount();
for (WebLocalFrameImpl* frame = mainFrame; frame;
frame = static_cast<WebLocalFrameImpl*>(frame->traverseNext(false)))
frame->ensureTextFinder().scopeStringMatches(kFindIdentifier, searchText,
options, true);
runPendingTasks();
EXPECT_TRUE(mainFrame->find(kFindIdentifier, searchText, options, false));
mainFrame->stopFinding(WebLocalFrame::StopFindActionClearSelection);
for (WebLocalFrameImpl* frame = mainFrame; frame;
frame = static_cast<WebLocalFrameImpl*>(frame->traverseNext(false)))
frame->ensureTextFinder().scopeStringMatches(kFindIdentifier, searchText,
options, true);
runPendingTasks();
EXPECT_TRUE(client.findResultsAreReady());
EXPECT_EQ(kActiveIndex, client.activeIndex());
const char* kFindStringNew = "e";
WebString searchTextNew = WebString::fromUTF8(kFindStringNew);
EXPECT_TRUE(mainFrame->find(kFindIdentifier, searchTextNew, options, false));
mainFrame->ensureTextFinder().resetMatchCount();
for (WebLocalFrameImpl* frame = mainFrame; frame;
frame = static_cast<WebLocalFrameImpl*>(frame->traverseNext(false)))
frame->ensureTextFinder().scopeStringMatches(kFindIdentifier, searchTextNew,
options, true);
runPendingTasks();
EXPECT_TRUE(client.findResultsAreReady());
EXPECT_EQ(kActiveIndex, client.activeIndex());
}
TEST_P(ParameterizedWebFrameTest, FindOnDetachedFrame) {
registerMockedHttpURLLoad("find_in_page.html");
registerMockedHttpURLLoad("find_in_page_frame.html");
FindUpdateWebFrameClient client;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "find_in_page.html", true,
&client);
webViewHelper.resize(WebSize(640, 480));
runPendingTasks();
const char kFindString[] = "result";
const int kFindIdentifier = 12345;
WebFindOptions options;
WebString searchText = WebString::fromUTF8(kFindString);
WebLocalFrameImpl* mainFrame = webViewHelper.webView()->mainFrameImpl();
WebLocalFrameImpl* secondFrame =
toWebLocalFrameImpl(mainFrame->traverseNext(false));
// Detach the frame before finding.
removeElementById(mainFrame, "frame");
EXPECT_TRUE(mainFrame->find(kFindIdentifier, searchText, options, false));
EXPECT_FALSE(secondFrame->find(kFindIdentifier, searchText, options, false));
runPendingTasks();
EXPECT_FALSE(client.findResultsAreReady());
mainFrame->ensureTextFinder().resetMatchCount();
for (WebLocalFrameImpl* frame = mainFrame; frame;
frame = static_cast<WebLocalFrameImpl*>(frame->traverseNext(false)))
frame->ensureTextFinder().scopeStringMatches(kFindIdentifier, searchText,
options, true);
runPendingTasks();
EXPECT_TRUE(client.findResultsAreReady());
}
TEST_P(ParameterizedWebFrameTest, FindDetachFrameBeforeScopeStrings) {
registerMockedHttpURLLoad("find_in_page.html");
registerMockedHttpURLLoad("find_in_page_frame.html");
FindUpdateWebFrameClient client;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "find_in_page.html", true,
&client);
webViewHelper.resize(WebSize(640, 480));
runPendingTasks();
const char kFindString[] = "result";
const int kFindIdentifier = 12345;
WebFindOptions options;
WebString searchText = WebString::fromUTF8(kFindString);
WebLocalFrameImpl* mainFrame = webViewHelper.webView()->mainFrameImpl();
for (WebFrame* frame = mainFrame; frame; frame = frame->traverseNext(false))
EXPECT_TRUE(frame->toWebLocalFrame()->find(kFindIdentifier, searchText,
options, false));
runPendingTasks();
EXPECT_FALSE(client.findResultsAreReady());
// Detach the frame between finding and scoping.
removeElementById(mainFrame, "frame");
mainFrame->ensureTextFinder().resetMatchCount();
for (WebLocalFrameImpl* frame = mainFrame; frame;
frame = static_cast<WebLocalFrameImpl*>(frame->traverseNext(false)))
frame->ensureTextFinder().scopeStringMatches(kFindIdentifier, searchText,
options, true);
runPendingTasks();
EXPECT_TRUE(client.findResultsAreReady());
}
TEST_P(ParameterizedWebFrameTest, FindDetachFrameWhileScopingStrings) {
registerMockedHttpURLLoad("find_in_page.html");
registerMockedHttpURLLoad("find_in_page_frame.html");
FindUpdateWebFrameClient client;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "find_in_page.html", true,
&client);
webViewHelper.resize(WebSize(640, 480));
runPendingTasks();
const char kFindString[] = "result";
const int kFindIdentifier = 12345;
WebFindOptions options;
WebString searchText = WebString::fromUTF8(kFindString);
WebLocalFrameImpl* mainFrame = webViewHelper.webView()->mainFrameImpl();
for (WebFrame* frame = mainFrame; frame; frame = frame->traverseNext(false))
EXPECT_TRUE(frame->toWebLocalFrame()->find(kFindIdentifier, searchText,
options, false));
runPendingTasks();
EXPECT_FALSE(client.findResultsAreReady());
mainFrame->ensureTextFinder().resetMatchCount();
for (WebLocalFrameImpl* frame = mainFrame; frame;
frame = static_cast<WebLocalFrameImpl*>(frame->traverseNext(false)))
frame->ensureTextFinder().scopeStringMatches(kFindIdentifier, searchText,
options, true);
// The first scopeStringMatches will have reset the state. Detach before it
// actually scopes.
removeElementById(mainFrame, "frame");
runPendingTasks();
EXPECT_TRUE(client.findResultsAreReady());
}
TEST_P(ParameterizedWebFrameTest, ResetMatchCount) {
registerMockedHttpURLLoad("find_in_generated_frame.html");
FindUpdateWebFrameClient client;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "find_in_generated_frame.html",
true, &client);
webViewHelper.resize(WebSize(640, 480));
runPendingTasks();
const char kFindString[] = "result";
const int kFindIdentifier = 12345;
WebFindOptions options;
WebString searchText = WebString::fromUTF8(kFindString);
WebLocalFrameImpl* mainFrame = webViewHelper.webView()->mainFrameImpl();
// Check that child frame exists.
EXPECT_TRUE(!!mainFrame->traverseNext(false));
for (WebFrame* frame = mainFrame; frame; frame = frame->traverseNext(false))
EXPECT_FALSE(frame->toWebLocalFrame()->find(kFindIdentifier, searchText,
options, false));
runPendingTasks();
EXPECT_FALSE(client.findResultsAreReady());
mainFrame->ensureTextFinder().resetMatchCount();
}
TEST_P(ParameterizedWebFrameTest, SetTickmarks) {
registerMockedHttpURLLoad("find.html");
FindUpdateWebFrameClient client;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "find.html", true, &client);
webViewHelper.resize(WebSize(640, 480));
runPendingTasks();
const char kFindString[] = "foo";
const int kFindIdentifier = 12345;
WebFindOptions options;
WebString searchText = WebString::fromUTF8(kFindString);
WebLocalFrameImpl* mainFrame = webViewHelper.webView()->mainFrameImpl();
EXPECT_TRUE(mainFrame->find(kFindIdentifier, searchText, options, false));
mainFrame->ensureTextFinder().resetMatchCount();
mainFrame->ensureTextFinder().scopeStringMatches(kFindIdentifier, searchText,
options, true);
runPendingTasks();
EXPECT_TRUE(client.findResultsAreReady());
// Get the tickmarks for the original find request.
FrameView* frameView = webViewHelper.webView()->mainFrameImpl()->frameView();
Scrollbar* scrollbar = frameView->createScrollbar(HorizontalScrollbar);
Vector<IntRect> originalTickmarks;
scrollbar->getTickmarks(originalTickmarks);
EXPECT_EQ(4u, originalTickmarks.size());
// Override the tickmarks.
Vector<IntRect> overridingTickmarksExpected;
overridingTickmarksExpected.append(IntRect(0, 0, 100, 100));
overridingTickmarksExpected.append(IntRect(0, 20, 100, 100));
overridingTickmarksExpected.append(IntRect(0, 30, 100, 100));
mainFrame->setTickmarks(overridingTickmarksExpected);
// Check the tickmarks are overriden correctly.
Vector<IntRect> overridingTickmarksActual;
scrollbar->getTickmarks(overridingTickmarksActual);
EXPECT_EQ(overridingTickmarksExpected, overridingTickmarksActual);
// Reset the tickmark behavior.
Vector<IntRect> resetTickmarks;
mainFrame->setTickmarks(resetTickmarks);
// Check that the original tickmarks are returned
Vector<IntRect> originalTickmarksAfterReset;
scrollbar->getTickmarks(originalTickmarksAfterReset);
EXPECT_EQ(originalTickmarks, originalTickmarksAfterReset);
}
TEST_P(ParameterizedWebFrameTest, FindInPageJavaScriptUpdatesDOM) {
registerMockedHttpURLLoad("find.html");
FindUpdateWebFrameClient client;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "find.html", true, &client);
webViewHelper.resize(WebSize(640, 480));
runPendingTasks();
WebLocalFrameImpl* frame = webViewHelper.webView()->mainFrameImpl();
const int findIdentifier = 12345;
static const char* kFindString = "foo";
WebString searchText = WebString::fromUTF8(kFindString);
WebFindOptions options;
bool activeNow;
frame->ensureTextFinder().resetMatchCount();
frame->ensureTextFinder().scopeStringMatches(findIdentifier, searchText,
options, true);
runPendingTasks();
EXPECT_TRUE(client.findResultsAreReady());
// Find in a <div> element.
options.findNext = true;
EXPECT_TRUE(
frame->find(findIdentifier, searchText, options, false, &activeNow));
EXPECT_TRUE(activeNow);
// Insert new text, which contains occurence of |searchText|.
frame->executeScript(WebScriptSource(
"var newTextNode = document.createTextNode('bar5 foo5');"
"var textArea = document.getElementsByTagName('textarea')[0];"
"document.body.insertBefore(newTextNode, textArea);"));
// Find in a <input> element.
EXPECT_TRUE(
frame->find(findIdentifier, searchText, options, false, &activeNow));
EXPECT_TRUE(activeNow);
// Find in the inserted text node.
EXPECT_TRUE(
frame->find(findIdentifier, searchText, options, false, &activeNow));
frame->stopFinding(WebLocalFrame::StopFindActionKeepSelection);
WebRange range = frame->selectionRange();
EXPECT_EQ(5, range.startOffset());
EXPECT_EQ(8, range.endOffset());
EXPECT_TRUE(frame->document().focusedElement().isNull());
EXPECT_FALSE(activeNow);
}
static WebPoint topLeft(const WebRect& rect) {
return WebPoint(rect.x, rect.y);
}
static WebPoint bottomRightMinusOne(const WebRect& rect) {
// FIXME: If we don't subtract 1 from the x- and y-coordinates of the
// selection bounds, selectRange() will select the *next* element. That's
// strictly correct, as hit-testing checks the pixel to the lower-right of
// the input coordinate, but it's a wart on the API.
return WebPoint(rect.x + rect.width - 1, rect.y + rect.height - 1);
}
static WebRect elementBounds(WebFrame* frame, const WebString& id) {
return frame->document().getElementById(id).boundsInViewport();
}
static std::string selectionAsString(WebFrame* frame) {
return frame->toWebLocalFrame()->selectionAsText().utf8();
}
TEST_P(ParameterizedWebFrameTest, SelectRange) {
WebLocalFrame* frame;
WebRect startWebRect;
WebRect endWebRect;
registerMockedHttpURLLoad("select_range_basic.html");
registerMockedHttpURLLoad("select_range_scroll.html");
FrameTestHelpers::WebViewHelper webViewHelper;
initializeTextSelectionWebView(m_baseURL + "select_range_basic.html",
&webViewHelper);
frame = webViewHelper.webView()->mainFrameImpl();
EXPECT_EQ("Some test text for testing.", selectionAsString(frame));
webViewHelper.webView()->selectionBounds(startWebRect, endWebRect);
frame->executeCommand(WebString::fromUTF8("Unselect"));
EXPECT_EQ("", selectionAsString(frame));
frame->selectRange(topLeft(startWebRect), bottomRightMinusOne(endWebRect));
// On some devices, the above bottomRightMinusOne() causes the ending '.' not
// selected.
std::string selectionString = selectionAsString(frame);
EXPECT_TRUE(selectionString == "Some test text for testing." ||
selectionString == "Some test text for testing");
initializeTextSelectionWebView(m_baseURL + "select_range_scroll.html",
&webViewHelper);
frame = webViewHelper.webView()->mainFrameImpl();
EXPECT_EQ("Some offscreen test text for testing.", selectionAsString(frame));
webViewHelper.webView()->selectionBounds(startWebRect, endWebRect);
frame->executeCommand(WebString::fromUTF8("Unselect"));
EXPECT_EQ("", selectionAsString(frame));
frame->selectRange(topLeft(startWebRect), bottomRightMinusOne(endWebRect));
// On some devices, the above bottomRightMinusOne() causes the ending '.' not
// selected.
selectionString = selectionAsString(frame);
EXPECT_TRUE(selectionString == "Some offscreen test text for testing." ||
selectionString == "Some offscreen test text for testing");
}
TEST_P(ParameterizedWebFrameTest, SelectRangeInIframe) {
WebFrame* frame;
WebRect startWebRect;
WebRect endWebRect;
registerMockedHttpURLLoad("select_range_iframe.html");
registerMockedHttpURLLoad("select_range_basic.html");
FrameTestHelpers::WebViewHelper webViewHelper;
initializeTextSelectionWebView(m_baseURL + "select_range_iframe.html",
&webViewHelper);
frame = webViewHelper.webView()->mainFrame();
WebLocalFrame* subframe = frame->firstChild()->toWebLocalFrame();
EXPECT_EQ("Some test text for testing.", selectionAsString(subframe));
webViewHelper.webView()->selectionBounds(startWebRect, endWebRect);
subframe->executeCommand(WebString::fromUTF8("Unselect"));
EXPECT_EQ("", selectionAsString(subframe));
subframe->selectRange(topLeft(startWebRect), bottomRightMinusOne(endWebRect));
// On some devices, the above bottomRightMinusOne() causes the ending '.' not
// selected.
std::string selectionString = selectionAsString(subframe);
EXPECT_TRUE(selectionString == "Some test text for testing." ||
selectionString == "Some test text for testing");
}
TEST_P(ParameterizedWebFrameTest, SelectRangeDivContentEditable) {
WebLocalFrame* frame;
WebRect startWebRect;
WebRect endWebRect;
registerMockedHttpURLLoad("select_range_div_editable.html");
// Select the middle of an editable element, then try to extend the selection
// to the top of the document. The selection range should be clipped to the
// bounds of the editable element.
FrameTestHelpers::WebViewHelper webViewHelper;
initializeTextSelectionWebView(m_baseURL + "select_range_div_editable.html",
&webViewHelper);
frame = webViewHelper.webView()->mainFrameImpl();
EXPECT_EQ("This text is initially selected.", selectionAsString(frame));
webViewHelper.webView()->selectionBounds(startWebRect, endWebRect);
frame->selectRange(bottomRightMinusOne(endWebRect), WebPoint(0, 0));
EXPECT_EQ("16-char header. This text is initially selected.",
selectionAsString(frame));
// As above, but extending the selection to the bottom of the document.
initializeTextSelectionWebView(m_baseURL + "select_range_div_editable.html",
&webViewHelper);
frame = webViewHelper.webView()->mainFrameImpl();
webViewHelper.webView()->selectionBounds(startWebRect, endWebRect);
frame->selectRange(topLeft(startWebRect), bottomRightMinusOne(endWebRect));
EXPECT_EQ("This text is initially selected.", selectionAsString(frame));
webViewHelper.webView()->selectionBounds(startWebRect, endWebRect);
webViewHelper.webView()->selectionBounds(startWebRect, endWebRect);
frame->selectRange(topLeft(startWebRect), WebPoint(640, 480));
EXPECT_EQ("This text is initially selected. 16-char footer.",
selectionAsString(frame));
}
// positionForPoint returns the wrong values for contenteditable spans. See
// http://crbug.com/238334.
TEST_P(ParameterizedWebFrameTest, DISABLED_SelectRangeSpanContentEditable) {
WebLocalFrame* frame;
WebRect startWebRect;
WebRect endWebRect;
registerMockedHttpURLLoad("select_range_span_editable.html");
// Select the middle of an editable element, then try to extend the selection
// to the top of the document.
// The selection range should be clipped to the bounds of the editable
// element.
FrameTestHelpers::WebViewHelper webViewHelper;
initializeTextSelectionWebView(m_baseURL + "select_range_span_editable.html",
&webViewHelper);
frame = webViewHelper.webView()->mainFrameImpl();
EXPECT_EQ("This text is initially selected.", selectionAsString(frame));
webViewHelper.webView()->selectionBounds(startWebRect, endWebRect);
frame->selectRange(bottomRightMinusOne(endWebRect), WebPoint(0, 0));
EXPECT_EQ("16-char header. This text is initially selected.",
selectionAsString(frame));
// As above, but extending the selection to the bottom of the document.
initializeTextSelectionWebView(m_baseURL + "select_range_span_editable.html",
&webViewHelper);
frame = webViewHelper.webView()->mainFrameImpl();
webViewHelper.webView()->selectionBounds(startWebRect, endWebRect);
frame->selectRange(topLeft(startWebRect), bottomRightMinusOne(endWebRect));
EXPECT_EQ("This text is initially selected.", selectionAsString(frame));
webViewHelper.webView()->selectionBounds(startWebRect, endWebRect);
EXPECT_EQ("This text is initially selected.", selectionAsString(frame));
webViewHelper.webView()->selectionBounds(startWebRect, endWebRect);
frame->selectRange(topLeft(startWebRect), WebPoint(640, 480));
EXPECT_EQ("This text is initially selected. 16-char footer.",
selectionAsString(frame));
}
TEST_P(ParameterizedWebFrameTest, SelectRangeCanMoveSelectionStart) {
registerMockedHttpURLLoad("text_selection.html");
FrameTestHelpers::WebViewHelper webViewHelper;
initializeTextSelectionWebView(m_baseURL + "text_selection.html",
&webViewHelper);
WebLocalFrame* frame = webViewHelper.webView()->mainFrameImpl();
// Select second span. We can move the start to include the first span.
frame->executeScript(WebScriptSource("selectElement('header_2');"));
EXPECT_EQ("Header 2.", selectionAsString(frame));
frame->selectRange(bottomRightMinusOne(elementBounds(frame, "header_2")),
topLeft(elementBounds(frame, "header_1")));
EXPECT_EQ("Header 1. Header 2.", selectionAsString(frame));
// We can move the start and end together.
frame->executeScript(WebScriptSource("selectElement('header_1');"));
EXPECT_EQ("Header 1.", selectionAsString(frame));
frame->selectRange(bottomRightMinusOne(elementBounds(frame, "header_1")),
bottomRightMinusOne(elementBounds(frame, "header_1")));
EXPECT_EQ("", selectionAsString(frame));
// Selection is a caret, not empty.
EXPECT_FALSE(frame->selectionRange().isNull());
// We can move the start across the end.
frame->executeScript(WebScriptSource("selectElement('header_1');"));
EXPECT_EQ("Header 1.", selectionAsString(frame));
frame->selectRange(bottomRightMinusOne(elementBounds(frame, "header_1")),
bottomRightMinusOne(elementBounds(frame, "header_2")));
EXPECT_EQ(" Header 2.", selectionAsString(frame));
// Can't extend the selection part-way into an editable element.
frame->executeScript(WebScriptSource("selectElement('footer_2');"));
EXPECT_EQ("Footer 2.", selectionAsString(frame));
frame->selectRange(bottomRightMinusOne(elementBounds(frame, "footer_2")),
topLeft(elementBounds(frame, "editable_2")));
EXPECT_EQ(" [ Footer 1. Footer 2.", selectionAsString(frame));
// Can extend the selection completely across editable elements.
frame->executeScript(WebScriptSource("selectElement('footer_2');"));
EXPECT_EQ("Footer 2.", selectionAsString(frame));
frame->selectRange(bottomRightMinusOne(elementBounds(frame, "footer_2")),
topLeft(elementBounds(frame, "header_2")));
EXPECT_EQ("Header 2. ] [ Editable 1. Editable 2. ] [ Footer 1. Footer 2.",
selectionAsString(frame));
// If the selection is editable text, we can't extend it into non-editable
// text.
frame->executeScript(WebScriptSource("selectElement('editable_2');"));
EXPECT_EQ("Editable 2.", selectionAsString(frame));
frame->selectRange(bottomRightMinusOne(elementBounds(frame, "editable_2")),
topLeft(elementBounds(frame, "header_2")));
// positionForPoint returns the wrong values for contenteditable spans. See
// http://crbug.com/238334.
// EXPECT_EQ("[ Editable 1. Editable 2.", selectionAsString(frame));
}
TEST_P(ParameterizedWebFrameTest, SelectRangeCanMoveSelectionEnd) {
registerMockedHttpURLLoad("text_selection.html");
FrameTestHelpers::WebViewHelper webViewHelper;
initializeTextSelectionWebView(m_baseURL + "text_selection.html",
&webViewHelper);
WebLocalFrame* frame = webViewHelper.webView()->mainFrameImpl();
// Select first span. We can move the end to include the second span.
frame->executeScript(WebScriptSource("selectElement('header_1');"));
EXPECT_EQ("Header 1.", selectionAsString(frame));
frame->selectRange(topLeft(elementBounds(frame, "header_1")),
bottomRightMinusOne(elementBounds(frame, "header_2")));
EXPECT_EQ("Header 1. Header 2.", selectionAsString(frame));
// We can move the start and end together.
frame->executeScript(WebScriptSource("selectElement('header_2');"));
EXPECT_EQ("Header 2.", selectionAsString(frame));
frame->selectRange(topLeft(elementBounds(frame, "header_2")),
topLeft(elementBounds(frame, "header_2")));
EXPECT_EQ("", selectionAsString(frame));
// Selection is a caret, not empty.
EXPECT_FALSE(frame->selectionRange().isNull());
// We can move the end across the start.
frame->executeScript(WebScriptSource("selectElement('header_2');"));
EXPECT_EQ("Header 2.", selectionAsString(frame));
frame->selectRange(topLeft(elementBounds(frame, "header_2")),
topLeft(elementBounds(frame, "header_1")));
EXPECT_EQ("Header 1. ", selectionAsString(frame));
// Can't extend the selection part-way into an editable element.
frame->executeScript(WebScriptSource("selectElement('header_1');"));
EXPECT_EQ("Header 1.", selectionAsString(frame));
frame->selectRange(topLeft(elementBounds(frame, "header_1")),
bottomRightMinusOne(elementBounds(frame, "editable_1")));
EXPECT_EQ("Header 1. Header 2. ] ", selectionAsString(frame));
// Can extend the selection completely across editable elements.
frame->executeScript(WebScriptSource("selectElement('header_1');"));
EXPECT_EQ("Header 1.", selectionAsString(frame));
frame->selectRange(topLeft(elementBounds(frame, "header_1")),
bottomRightMinusOne(elementBounds(frame, "footer_1")));
EXPECT_EQ("Header 1. Header 2. ] [ Editable 1. Editable 2. ] [ Footer 1.",
selectionAsString(frame));
// If the selection is editable text, we can't extend it into non-editable
// text.
frame->executeScript(WebScriptSource("selectElement('editable_1');"));
EXPECT_EQ("Editable 1.", selectionAsString(frame));
frame->selectRange(topLeft(elementBounds(frame, "editable_1")),
bottomRightMinusOne(elementBounds(frame, "footer_1")));
// positionForPoint returns the wrong values for contenteditable spans. See
// http://crbug.com/238334.
// EXPECT_EQ("Editable 1. Editable 2. ]", selectionAsString(frame));
}
TEST_P(ParameterizedWebFrameTest, MoveRangeSelectionExtent) {
WebLocalFrameImpl* frame;
WebRect startWebRect;
WebRect endWebRect;
registerMockedHttpURLLoad("move_range_selection_extent.html");
FrameTestHelpers::WebViewHelper webViewHelper;
initializeTextSelectionWebView(m_baseURL + "move_range_selection_extent.html",
&webViewHelper);
frame = webViewHelper.webView()->mainFrameImpl();
EXPECT_EQ("This text is initially selected.", selectionAsString(frame));
webViewHelper.webView()->selectionBounds(startWebRect, endWebRect);
frame->moveRangeSelectionExtent(WebPoint(640, 480));
EXPECT_EQ("This text is initially selected. 16-char footer.",
selectionAsString(frame));
frame->moveRangeSelectionExtent(WebPoint(0, 0));
EXPECT_EQ("16-char header. ", selectionAsString(frame));
// Reset with swapped base and extent.
frame->selectRange(topLeft(endWebRect), bottomRightMinusOne(startWebRect));
EXPECT_EQ("This text is initially selected.", selectionAsString(frame));
frame->moveRangeSelectionExtent(WebPoint(640, 480));
EXPECT_EQ(" 16-char footer.", selectionAsString(frame));
frame->moveRangeSelectionExtent(WebPoint(0, 0));
EXPECT_EQ("16-char header. This text is initially selected.",
selectionAsString(frame));
frame->executeCommand(WebString::fromUTF8("Unselect"));
EXPECT_EQ("", selectionAsString(frame));
}
TEST_P(ParameterizedWebFrameTest, MoveRangeSelectionExtentCannotCollapse) {
WebLocalFrameImpl* frame;
WebRect startWebRect;
WebRect endWebRect;
registerMockedHttpURLLoad("move_range_selection_extent.html");
FrameTestHelpers::WebViewHelper webViewHelper;
initializeTextSelectionWebView(m_baseURL + "move_range_selection_extent.html",
&webViewHelper);
frame = webViewHelper.webView()->mainFrameImpl();
EXPECT_EQ("This text is initially selected.", selectionAsString(frame));
webViewHelper.webView()->selectionBounds(startWebRect, endWebRect);
frame->moveRangeSelectionExtent(bottomRightMinusOne(startWebRect));
EXPECT_EQ("This text is initially selected.", selectionAsString(frame));
// Reset with swapped base and extent.
frame->selectRange(topLeft(endWebRect), bottomRightMinusOne(startWebRect));
EXPECT_EQ("This text is initially selected.", selectionAsString(frame));
frame->moveRangeSelectionExtent(bottomRightMinusOne(endWebRect));
EXPECT_EQ("This text is initially selected.", selectionAsString(frame));
}
TEST_P(ParameterizedWebFrameTest, MoveRangeSelectionExtentScollsInputField) {
WebLocalFrameImpl* frame;
WebRect startWebRect;
WebRect endWebRect;
registerMockedHttpURLLoad("move_range_selection_extent_input_field.html");
FrameTestHelpers::WebViewHelper webViewHelper;
initializeTextSelectionWebView(
m_baseURL + "move_range_selection_extent_input_field.html",
&webViewHelper);
frame = webViewHelper.webView()->mainFrameImpl();
EXPECT_EQ("Length", selectionAsString(frame));
webViewHelper.webView()->selectionBounds(startWebRect, endWebRect);
EXPECT_EQ(0, frame->frame()->selection().rootEditableElement()->scrollLeft());
frame->moveRangeSelectionExtent(WebPoint(endWebRect.x + 500, endWebRect.y));
EXPECT_GE(frame->frame()->selection().rootEditableElement()->scrollLeft(), 1);
EXPECT_EQ("Lengthy text goes here.", selectionAsString(frame));
}
static int computeOffset(LayoutObject* layoutObject, int x, int y) {
return createVisiblePosition(
layoutObject->positionForPoint(LayoutPoint(x, y)))
.deepEquivalent()
.computeOffsetInContainerNode();
}
// positionForPoint returns the wrong values for contenteditable spans. See
// http://crbug.com/238334.
TEST_P(ParameterizedWebFrameTest, DISABLED_PositionForPointTest) {
registerMockedHttpURLLoad("select_range_span_editable.html");
FrameTestHelpers::WebViewHelper webViewHelper;
initializeTextSelectionWebView(m_baseURL + "select_range_span_editable.html",
&webViewHelper);
WebLocalFrameImpl* mainFrame = webViewHelper.webView()->mainFrameImpl();
LayoutObject* layoutObject =
mainFrame->frame()->selection().rootEditableElement()->layoutObject();
EXPECT_EQ(0, computeOffset(layoutObject, -1, -1));
EXPECT_EQ(64, computeOffset(layoutObject, 1000, 1000));
registerMockedHttpURLLoad("select_range_div_editable.html");
initializeTextSelectionWebView(m_baseURL + "select_range_div_editable.html",
&webViewHelper);
mainFrame = webViewHelper.webView()->mainFrameImpl();
layoutObject =
mainFrame->frame()->selection().rootEditableElement()->layoutObject();
EXPECT_EQ(0, computeOffset(layoutObject, -1, -1));
EXPECT_EQ(64, computeOffset(layoutObject, 1000, 1000));
}
#if !OS(MACOSX) && !OS(LINUX)
TEST_P(ParameterizedWebFrameTest,
SelectRangeStaysHorizontallyAlignedWhenMoved) {
registerMockedHttpURLLoad("move_caret.html");
FrameTestHelpers::WebViewHelper webViewHelper;
initializeTextSelectionWebView(m_baseURL + "move_caret.html", &webViewHelper);
WebLocalFrameImpl* frame = webViewHelper.webView()->mainFrameImpl();
WebRect initialStartRect;
WebRect initialEndRect;
WebRect startRect;
WebRect endRect;
frame->executeScript(WebScriptSource("selectRange();"));
webViewHelper.webView()->selectionBounds(initialStartRect, initialEndRect);
WebPoint movedStart(topLeft(initialStartRect));
movedStart.y += 40;
frame->selectRange(movedStart, bottomRightMinusOne(initialEndRect));
webViewHelper.webView()->selectionBounds(startRect, endRect);
EXPECT_EQ(startRect, initialStartRect);
EXPECT_EQ(endRect, initialEndRect);
movedStart.y -= 80;
frame->selectRange(movedStart, bottomRightMinusOne(initialEndRect));
webViewHelper.webView()->selectionBounds(startRect, endRect);
EXPECT_EQ(startRect, initialStartRect);
EXPECT_EQ(endRect, initialEndRect);
WebPoint movedEnd(bottomRightMinusOne(initialEndRect));
movedEnd.y += 40;
frame->selectRange(topLeft(initialStartRect), movedEnd);
webViewHelper.webView()->selectionBounds(startRect, endRect);
EXPECT_EQ(startRect, initialStartRect);
EXPECT_EQ(endRect, initialEndRect);
movedEnd.y -= 80;
frame->selectRange(topLeft(initialStartRect), movedEnd);
webViewHelper.webView()->selectionBounds(startRect, endRect);
EXPECT_EQ(startRect, initialStartRect);
EXPECT_EQ(endRect, initialEndRect);
}
TEST_P(ParameterizedWebFrameTest, MoveCaretStaysHorizontallyAlignedWhenMoved) {
WebLocalFrameImpl* frame;
registerMockedHttpURLLoad("move_caret.html");
FrameTestHelpers::WebViewHelper webViewHelper;
initializeTextSelectionWebView(m_baseURL + "move_caret.html", &webViewHelper);
frame = (WebLocalFrameImpl*)webViewHelper.webView()->mainFrame();
WebRect initialStartRect;
WebRect initialEndRect;
WebRect startRect;
WebRect endRect;
frame->executeScript(WebScriptSource("selectCaret();"));
webViewHelper.webView()->selectionBounds(initialStartRect, initialEndRect);
WebPoint moveTo(topLeft(initialStartRect));
moveTo.y += 40;
frame->moveCaretSelection(moveTo);
webViewHelper.webView()->selectionBounds(startRect, endRect);
EXPECT_EQ(startRect, initialStartRect);
EXPECT_EQ(endRect, initialEndRect);
moveTo.y -= 80;
frame->moveCaretSelection(moveTo);
webViewHelper.webView()->selectionBounds(startRect, endRect);
EXPECT_EQ(startRect, initialStartRect);
EXPECT_EQ(endRect, initialEndRect);
}
#endif
class CompositedSelectionBoundsTestLayerTreeView : public WebLayerTreeView {
public:
CompositedSelectionBoundsTestLayerTreeView() : m_selectionCleared(false) {}
~CompositedSelectionBoundsTestLayerTreeView() override {}
void registerSelection(const WebSelection& selection) override {
m_selection = wrapUnique(new WebSelection(selection));
}
void clearSelection() override {
m_selectionCleared = true;
m_selection.reset();
}
bool getAndResetSelectionCleared() {
bool selectionCleared = m_selectionCleared;
m_selectionCleared = false;
return selectionCleared;
}
const WebSelection* selection() const { return m_selection.get(); }
const WebSelectionBound* start() const {
return m_selection ? &m_selection->start() : nullptr;
}
const WebSelectionBound* end() const {
return m_selection ? &m_selection->end() : nullptr;
}
private:
bool m_selectionCleared;
std::unique_ptr<WebSelection> m_selection;
};
class CompositedSelectionBoundsTestWebViewClient
: public FrameTestHelpers::TestWebViewClient {
public:
~CompositedSelectionBoundsTestWebViewClient() override {}
WebLayerTreeView* layerTreeView() override { return &m_testLayerTreeView; }
CompositedSelectionBoundsTestLayerTreeView& selectionLayerTreeView() {
return m_testLayerTreeView;
}
private:
CompositedSelectionBoundsTestLayerTreeView m_testLayerTreeView;
};
class CompositedSelectionBoundsTest : public WebFrameTest {
protected:
CompositedSelectionBoundsTest()
: m_fakeSelectionLayerTreeView(
m_fakeSelectionWebViewClient.selectionLayerTreeView()) {
RuntimeEnabledFeatures::setCompositedSelectionUpdateEnabled(true);
registerMockedHttpURLLoad("Ahem.ttf");
m_webViewHelper.initialize(true, nullptr, &m_fakeSelectionWebViewClient,
nullptr);
m_webViewHelper.webView()->settings()->setDefaultFontSize(12);
m_webViewHelper.webView()->setDefaultPageScaleLimits(1, 1);
m_webViewHelper.resize(WebSize(640, 480));
}
void runTest(const char* testFile) {
registerMockedHttpURLLoad(testFile);
m_webViewHelper.webView()->setFocus(true);
FrameTestHelpers::loadFrame(m_webViewHelper.webView()->mainFrame(),
m_baseURL + testFile);
m_webViewHelper.webView()->updateAllLifecyclePhases();
const WebSelection* selection = m_fakeSelectionLayerTreeView.selection();
const WebSelectionBound* selectStart = m_fakeSelectionLayerTreeView.start();
const WebSelectionBound* selectEnd = m_fakeSelectionLayerTreeView.end();
v8::HandleScope handleScope(v8::Isolate::GetCurrent());
v8::Local<v8::Value> result =
m_webViewHelper.webView()->mainFrameImpl()->executeScriptAndReturnValue(
WebScriptSource("expectedResult"));
if (result.IsEmpty() || (*result)->IsUndefined()) {
EXPECT_FALSE(selection);
EXPECT_FALSE(selectStart);
EXPECT_FALSE(selectEnd);
return;
}
ASSERT_TRUE(selection);
ASSERT_TRUE(selectStart);
ASSERT_TRUE(selectEnd);
EXPECT_FALSE(selection->isNone());
ASSERT_TRUE((*result)->IsArray());
v8::Array& expectedResult = *v8::Array::Cast(*result);
ASSERT_GE(expectedResult.Length(), 10u);
blink::Node* layerOwnerNodeForStart = V8Node::toImplWithTypeCheck(
v8::Isolate::GetCurrent(), expectedResult.Get(0));
ASSERT_TRUE(layerOwnerNodeForStart);
EXPECT_EQ(layerOwnerNodeForStart->layoutObject()
->enclosingLayer()
->enclosingLayerForPaintInvalidation()
->graphicsLayerBacking()
->platformLayer()
->id(),
selectStart->layerId);
v8::Local<v8::Context> context =
v8::Isolate::GetCurrent()->GetCurrentContext();
EXPECT_EQ(expectedResult.Get(context, 1)
.ToLocalChecked()
.As<v8::Int32>()
->Value(),
selectStart->edgeTopInLayer.x);
EXPECT_EQ(expectedResult.Get(context, 2)
.ToLocalChecked()
.As<v8::Int32>()
->Value(),
selectStart->edgeTopInLayer.y);
EXPECT_EQ(expectedResult.Get(context, 3)
.ToLocalChecked()
.As<v8::Int32>()
->Value(),
selectStart->edgeBottomInLayer.x);
blink::Node* layerOwnerNodeForEnd = V8Node::toImplWithTypeCheck(
v8::Isolate::GetCurrent(),
expectedResult.Get(context, 5).ToLocalChecked());
ASSERT_TRUE(layerOwnerNodeForEnd);
EXPECT_EQ(layerOwnerNodeForEnd->layoutObject()
->enclosingLayer()
->enclosingLayerForPaintInvalidation()
->graphicsLayerBacking()
->platformLayer()
->id(),
selectEnd->layerId);
EXPECT_EQ(expectedResult.Get(context, 6)
.ToLocalChecked()
.As<v8::Int32>()
->Value(),
selectEnd->edgeTopInLayer.x);
EXPECT_EQ(expectedResult.Get(context, 7)
.ToLocalChecked()
.As<v8::Int32>()
->Value(),
selectEnd->edgeTopInLayer.y);
EXPECT_EQ(expectedResult.Get(context, 8)
.ToLocalChecked()
.As<v8::Int32>()
->Value(),
selectEnd->edgeBottomInLayer.x);
// Platform differences can introduce small stylistic deviations in
// y-axis positioning, the details of which aren't relevant to
// selection behavior. However, such deviations from the expected value
// should be consistent for the corresponding y coordinates.
int yBottomEpsilon = 0;
if (expectedResult.Length() == 13)
yBottomEpsilon = expectedResult.Get(context, 12)
.ToLocalChecked()
.As<v8::Int32>()
->Value();
int yBottomDeviation = expectedResult.Get(context, 4)
.ToLocalChecked()
.As<v8::Int32>()
->Value() -
selectStart->edgeBottomInLayer.y;
EXPECT_GE(yBottomEpsilon, std::abs(yBottomDeviation));
EXPECT_EQ(yBottomDeviation, expectedResult.Get(context, 9)
.ToLocalChecked()
.As<v8::Int32>()
->Value() -
selectEnd->edgeBottomInLayer.y);
if (expectedResult.Length() >= 12) {
EXPECT_EQ(expectedResult.Get(context, 10)
.ToLocalChecked()
.As<v8::Boolean>()
->Value(),
m_fakeSelectionLayerTreeView.selection()->isEditable());
EXPECT_EQ(
expectedResult.Get(context, 11)
.ToLocalChecked()
.As<v8::Boolean>()
->Value(),
m_fakeSelectionLayerTreeView.selection()->isEmptyTextFormControl());
}
}
void runTestWithMultipleFiles(const char* testFile, ...) {
va_list auxFiles;
va_start(auxFiles, testFile);
while (const char* auxFile = va_arg(auxFiles, const char*))
registerMockedHttpURLLoad(auxFile);
va_end(auxFiles);
runTest(testFile);
}
CompositedSelectionBoundsTestWebViewClient m_fakeSelectionWebViewClient;
CompositedSelectionBoundsTestLayerTreeView& m_fakeSelectionLayerTreeView;
FrameTestHelpers::WebViewHelper m_webViewHelper;
};
TEST_F(CompositedSelectionBoundsTest, None) {
runTest("composited_selection_bounds_none.html");
}
TEST_F(CompositedSelectionBoundsTest, NoneReadonlyCaret) {
runTest("composited_selection_bounds_none_readonly_caret.html");
}
TEST_F(CompositedSelectionBoundsTest, Basic) {
runTest("composited_selection_bounds_basic.html");
}
TEST_F(CompositedSelectionBoundsTest, Transformed) {
runTest("composited_selection_bounds_transformed.html");
}
TEST_F(CompositedSelectionBoundsTest, VerticalRightToLeft) {
runTest("composited_selection_bounds_vertical_rl.html");
}
TEST_F(CompositedSelectionBoundsTest, VerticalLeftToRight) {
runTest("composited_selection_bounds_vertical_lr.html");
}
TEST_F(CompositedSelectionBoundsTest, SplitLayer) {
runTest("composited_selection_bounds_split_layer.html");
}
TEST_F(CompositedSelectionBoundsTest, EmptyLayer) {
runTest("composited_selection_bounds_empty_layer.html");
}
TEST_F(CompositedSelectionBoundsTest, Iframe) {
runTestWithMultipleFiles("composited_selection_bounds_iframe.html",
"composited_selection_bounds_basic.html", nullptr);
}
TEST_F(CompositedSelectionBoundsTest, DetachedFrame) {
runTest("composited_selection_bounds_detached_frame.html");
}
TEST_F(CompositedSelectionBoundsTest, Editable) {
runTest("composited_selection_bounds_editable.html");
}
TEST_F(CompositedSelectionBoundsTest, EditableDiv) {
runTest("composited_selection_bounds_editable_div.html");
}
TEST_F(CompositedSelectionBoundsTest, EmptyEditableInput) {
runTest("composited_selection_bounds_empty_editable_input.html");
}
TEST_F(CompositedSelectionBoundsTest, EmptyEditableArea) {
runTest("composited_selection_bounds_empty_editable_area.html");
}
// Fails on Mac ASan 64 bot. https://crbug.com/588769.
#if OS(MACOSX) && defined(ADDRESS_SANITIZER)
TEST_P(ParameterizedWebFrameTest, DISABLED_CompositedSelectionBoundsCleared)
#else
TEST_P(ParameterizedWebFrameTest, CompositedSelectionBoundsCleared)
#endif
{
RuntimeEnabledFeatures::setCompositedSelectionUpdateEnabled(true);
registerMockedHttpURLLoad("select_range_basic.html");
registerMockedHttpURLLoad("select_range_scroll.html");
int viewWidth = 500;
int viewHeight = 500;
CompositedSelectionBoundsTestWebViewClient fakeSelectionWebViewClient;
CompositedSelectionBoundsTestLayerTreeView& fakeSelectionLayerTreeView =
fakeSelectionWebViewClient.selectionLayerTreeView();
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initialize(true, nullptr, &fakeSelectionWebViewClient, nullptr);
webViewHelper.webView()->settings()->setDefaultFontSize(12);
webViewHelper.webView()->setDefaultPageScaleLimits(1, 1);
webViewHelper.resize(WebSize(viewWidth, viewHeight));
webViewHelper.webView()->setFocus(true);
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(),
m_baseURL + "select_range_basic.html");
// The frame starts with no selection.
WebLocalFrame* frame = webViewHelper.webView()->mainFrameImpl();
ASSERT_TRUE(frame->hasSelection());
EXPECT_TRUE(fakeSelectionLayerTreeView.getAndResetSelectionCleared());
// The selection cleared notification should be triggered upon layout.
frame->executeCommand(WebString::fromUTF8("Unselect"));
ASSERT_FALSE(frame->hasSelection());
EXPECT_FALSE(fakeSelectionLayerTreeView.getAndResetSelectionCleared());
webViewHelper.webView()->updateAllLifecyclePhases();
EXPECT_TRUE(fakeSelectionLayerTreeView.getAndResetSelectionCleared());
frame->executeCommand(WebString::fromUTF8("SelectAll"));
webViewHelper.webView()->updateAllLifecyclePhases();
ASSERT_TRUE(frame->hasSelection());
EXPECT_FALSE(fakeSelectionLayerTreeView.getAndResetSelectionCleared());
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(),
m_baseURL + "select_range_scroll.html");
ASSERT_TRUE(frame->hasSelection());
EXPECT_FALSE(fakeSelectionLayerTreeView.getAndResetSelectionCleared());
// Transitions between non-empty selections should not trigger a clearing.
WebRect startWebRect;
WebRect endWebRect;
webViewHelper.webView()->selectionBounds(startWebRect, endWebRect);
WebPoint movedEnd(bottomRightMinusOne(endWebRect));
endWebRect.x -= 20;
frame->selectRange(topLeft(startWebRect), movedEnd);
webViewHelper.webView()->updateAllLifecyclePhases();
ASSERT_TRUE(frame->hasSelection());
EXPECT_FALSE(fakeSelectionLayerTreeView.getAndResetSelectionCleared());
frame = webViewHelper.webView()->mainFrameImpl();
frame->executeCommand(WebString::fromUTF8("Unselect"));
webViewHelper.webView()->updateAllLifecyclePhases();
ASSERT_FALSE(frame->hasSelection());
EXPECT_TRUE(fakeSelectionLayerTreeView.getAndResetSelectionCleared());
}
class DisambiguationPopupTestWebViewClient
: public FrameTestHelpers::TestWebViewClient {
public:
bool didTapMultipleTargets(const WebSize&,
const WebRect&,
const WebVector<WebRect>& targetRects) override {
EXPECT_GE(targetRects.size(), 2u);
m_triggered = true;
return true;
}
bool triggered() const { return m_triggered; }
void resetTriggered() { m_triggered = false; }
bool m_triggered;
};
static WebGestureEvent fatTap(int x, int y) {
WebGestureEvent event;
event.type = WebInputEvent::GestureTap;
event.sourceDevice = WebGestureDeviceTouchscreen;
event.x = x;
event.y = y;
event.data.tap.width = 50;
event.data.tap.height = 50;
return event;
}
TEST_P(ParameterizedWebFrameTest, DisambiguationPopup) {
const std::string htmlFile = "disambiguation_popup.html";
registerMockedHttpURLLoad(htmlFile);
DisambiguationPopupTestWebViewClient client;
// Make sure we initialize to minimum scale, even if the window size
// only becomes available after the load begins.
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + htmlFile, true, 0, &client);
webViewHelper.resize(WebSize(1000, 1000));
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(fatTap(0, 0));
EXPECT_FALSE(client.triggered());
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(fatTap(200, 115));
EXPECT_FALSE(client.triggered());
for (int i = 0; i <= 46; i++) {
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(fatTap(120, 230 + i * 5));
int j = i % 10;
if (j >= 7 && j <= 9)
EXPECT_TRUE(client.triggered());
else
EXPECT_FALSE(client.triggered());
}
for (int i = 0; i <= 46; i++) {
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(fatTap(10 + i * 5, 590));
int j = i % 10;
if (j >= 7 && j <= 9)
EXPECT_TRUE(client.triggered());
else
EXPECT_FALSE(client.triggered());
}
// The same taps shouldn't trigger didTapMultipleTargets() after disabling the
// notification for multi-target-tap.
webViewHelper.webView()->settings()->setMultiTargetTapNotificationEnabled(
false);
for (int i = 0; i <= 46; i++) {
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(fatTap(10 + i * 5, 590));
EXPECT_FALSE(client.triggered());
}
}
TEST_P(ParameterizedWebFrameTest, DisambiguationPopupNoContainer) {
registerMockedHttpURLLoad("disambiguation_popup_no_container.html");
DisambiguationPopupTestWebViewClient client;
// Make sure we initialize to minimum scale, even if the window size
// only becomes available after the load begins.
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(
m_baseURL + "disambiguation_popup_no_container.html", true, 0, &client);
webViewHelper.resize(WebSize(1000, 1000));
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(fatTap(50, 50));
EXPECT_FALSE(client.triggered());
}
TEST_P(ParameterizedWebFrameTest, DisambiguationPopupMobileSite) {
const std::string htmlFile = "disambiguation_popup_mobile_site.html";
registerMockedHttpURLLoad(htmlFile);
DisambiguationPopupTestWebViewClient client;
// Make sure we initialize to minimum scale, even if the window size
// only becomes available after the load begins.
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + htmlFile, true, nullptr, &client,
nullptr, enableViewportSettings);
webViewHelper.resize(WebSize(1000, 1000));
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(fatTap(0, 0));
EXPECT_FALSE(client.triggered());
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(fatTap(200, 115));
EXPECT_FALSE(client.triggered());
for (int i = 0; i <= 46; i++) {
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(fatTap(120, 230 + i * 5));
EXPECT_FALSE(client.triggered());
}
for (int i = 0; i <= 46; i++) {
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(fatTap(10 + i * 5, 590));
EXPECT_FALSE(client.triggered());
}
}
TEST_P(ParameterizedWebFrameTest, DisambiguationPopupViewportSite) {
const std::string htmlFile = "disambiguation_popup_viewport_site.html";
registerMockedHttpURLLoad(htmlFile);
DisambiguationPopupTestWebViewClient client;
// Make sure we initialize to minimum scale, even if the window size
// only becomes available after the load begins.
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + htmlFile, true, nullptr, &client,
nullptr, enableViewportSettings);
webViewHelper.resize(WebSize(1000, 1000));
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(fatTap(0, 0));
EXPECT_FALSE(client.triggered());
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(fatTap(200, 115));
EXPECT_FALSE(client.triggered());
for (int i = 0; i <= 46; i++) {
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(fatTap(120, 230 + i * 5));
EXPECT_FALSE(client.triggered());
}
for (int i = 0; i <= 46; i++) {
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(fatTap(10 + i * 5, 590));
EXPECT_FALSE(client.triggered());
}
}
TEST_F(WebFrameTest, DisambiguationPopupVisualViewport) {
const std::string htmlFile = "disambiguation_popup_200_by_800.html";
registerMockedHttpURLLoad(htmlFile);
DisambiguationPopupTestWebViewClient client;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + htmlFile, true, nullptr, &client,
nullptr, configureAndroid);
WebViewImpl* webViewImpl = webViewHelper.webView();
ASSERT_TRUE(webViewImpl);
LocalFrame* frame = webViewImpl->mainFrameImpl()->frame();
ASSERT_TRUE(frame);
webViewHelper.resize(WebSize(100, 200));
// Scroll main frame to the bottom of the document
webViewImpl->mainFrame()->setScrollOffset(WebSize(0, 400));
EXPECT_POINT_EQ(IntPoint(0, 400), frame->view()->scrollPosition());
webViewImpl->setPageScaleFactor(2.0);
// Scroll visual viewport to the top of the main frame.
VisualViewport& visualViewport = frame->page()->frameHost().visualViewport();
visualViewport.setLocation(FloatPoint(0, 0));
EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 0), visualViewport.location());
// Tap at the top: there is nothing there.
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(fatTap(10, 60));
EXPECT_FALSE(client.triggered());
// Scroll visual viewport to the bottom of the main frame.
visualViewport.setLocation(FloatPoint(0, 200));
EXPECT_FLOAT_POINT_EQ(FloatPoint(0, 200), visualViewport.location());
// Now the tap with the same coordinates should hit two elements.
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(fatTap(10, 60));
EXPECT_TRUE(client.triggered());
// The same tap shouldn't trigger didTapMultipleTargets() after disabling the
// notification for multi-target-tap.
webViewHelper.webView()->settings()->setMultiTargetTapNotificationEnabled(
false);
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(fatTap(10, 60));
EXPECT_FALSE(client.triggered());
}
TEST_P(ParameterizedWebFrameTest, DisambiguationPopupBlacklist) {
const unsigned viewportWidth = 500;
const unsigned viewportHeight = 1000;
const unsigned divHeight = 100;
const std::string htmlFile = "disambiguation_popup_blacklist.html";
registerMockedHttpURLLoad(htmlFile);
DisambiguationPopupTestWebViewClient client;
// Make sure we initialize to minimum scale, even if the window size
// only becomes available after the load begins.
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + htmlFile, true, 0, &client);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
// Click somewhere where the popup shouldn't appear.
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(fatTap(viewportWidth / 2, 0));
EXPECT_FALSE(client.triggered());
// Click directly in between two container divs with click handlers, with
// children that don't handle clicks.
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(
fatTap(viewportWidth / 2, divHeight));
EXPECT_TRUE(client.triggered());
// The third div container should be blacklisted if you click on the link it
// contains.
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(
fatTap(viewportWidth / 2, divHeight * 3.25));
EXPECT_FALSE(client.triggered());
}
TEST_P(ParameterizedWebFrameTest, DisambiguationPopupPageScale) {
registerMockedHttpURLLoad("disambiguation_popup_page_scale.html");
DisambiguationPopupTestWebViewClient client;
// Make sure we initialize to minimum scale, even if the window size
// only becomes available after the load begins.
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(
m_baseURL + "disambiguation_popup_page_scale.html", true, 0, &client);
webViewHelper.resize(WebSize(1000, 1000));
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(fatTap(80, 80));
EXPECT_TRUE(client.triggered());
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(fatTap(230, 190));
EXPECT_TRUE(client.triggered());
webViewHelper.webView()->setPageScaleFactor(3.0f);
webViewHelper.webView()->updateAllLifecyclePhases();
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(fatTap(240, 240));
EXPECT_TRUE(client.triggered());
client.resetTriggered();
webViewHelper.webView()->handleInputEvent(fatTap(690, 570));
EXPECT_FALSE(client.triggered());
}
class TestSubstituteDataWebFrameClient
: public FrameTestHelpers::TestWebFrameClient {
public:
TestSubstituteDataWebFrameClient() : m_commitCalled(false) {}
virtual void didFailProvisionalLoad(WebLocalFrame* frame,
const WebURLError& error,
WebHistoryCommitType) {
frame->loadHTMLString("This should appear",
toKURL("data:text/html,chromewebdata"),
error.unreachableURL, true);
}
virtual void didCommitProvisionalLoad(WebLocalFrame* frame,
const WebHistoryItem&,
WebHistoryCommitType) {
if (frame->dataSource()->response().url() !=
WebURL(URLTestHelpers::toKURL("about:blank")))
m_commitCalled = true;
}
bool commitCalled() const { return m_commitCalled; }
private:
bool m_commitCalled;
};
TEST_P(ParameterizedWebFrameTest, ReplaceNavigationAfterHistoryNavigation) {
TestSubstituteDataWebFrameClient webFrameClient;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad("about:blank", true, &webFrameClient);
WebFrame* frame = webViewHelper.webView()->mainFrame();
// Load a url as a history navigation that will return an error.
// TestSubstituteDataWebFrameClient will start a SubstituteData load in
// response to the load failure, which should get fully committed. Due to
// https://bugs.webkit.org/show_bug.cgi?id=91685,
// FrameLoader::didReceiveData() wasn't getting called in this case, which
// resulted in the SubstituteData document not getting displayed.
WebURLError error;
error.reason = 1337;
error.domain = "WebFrameTest";
std::string errorURL = "http://0.0.0.0";
WebURLResponse response;
response.setURL(URLTestHelpers::toKURL(errorURL));
response.setMIMEType("text/html");
response.setHTTPStatusCode(500);
WebHistoryItem errorHistoryItem;
errorHistoryItem.initialize();
errorHistoryItem.setURLString(
WebString::fromUTF8(errorURL.c_str(), errorURL.length()));
Platform::current()->getURLLoaderMockFactory()->registerErrorURL(
URLTestHelpers::toKURL(errorURL), response, error);
FrameTestHelpers::loadHistoryItem(frame, errorHistoryItem,
WebHistoryDifferentDocumentLoad,
WebCachePolicy::UseProtocolCachePolicy);
WebString text = WebFrameContentDumper::dumpWebViewAsText(
webViewHelper.webView(), std::numeric_limits<size_t>::max());
EXPECT_EQ("This should appear", text.utf8());
EXPECT_TRUE(webFrameClient.commitCalled());
}
class TestWillInsertBodyWebFrameClient
: public FrameTestHelpers::TestWebFrameClient {
public:
TestWillInsertBodyWebFrameClient() : m_numBodies(0), m_didLoad(false) {}
void didCommitProvisionalLoad(WebLocalFrame*,
const WebHistoryItem&,
WebHistoryCommitType) override {
m_numBodies = 0;
m_didLoad = true;
}
void didCreateDocumentElement(WebLocalFrame*) override {
EXPECT_EQ(0, m_numBodies);
}
void willInsertBody(WebLocalFrame*) override { m_numBodies++; }
int m_numBodies;
bool m_didLoad;
};
TEST_P(ParameterizedWebFrameTest, HTMLDocument) {
registerMockedHttpURLLoad("clipped-body.html");
TestWillInsertBodyWebFrameClient webFrameClient;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "clipped-body.html", false,
&webFrameClient);
EXPECT_TRUE(webFrameClient.m_didLoad);
EXPECT_EQ(1, webFrameClient.m_numBodies);
}
TEST_P(ParameterizedWebFrameTest, EmptyDocument) {
registerMockedHttpURLLoad("frameserializer/svg/green_rectangle.svg");
TestWillInsertBodyWebFrameClient webFrameClient;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initialize(false, &webFrameClient);
EXPECT_FALSE(webFrameClient.m_didLoad);
// The empty document that a new frame starts with triggers this.
EXPECT_EQ(1, webFrameClient.m_numBodies);
}
TEST_P(ParameterizedWebFrameTest,
MoveCaretSelectionTowardsWindowPointWithNoSelection) {
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad("about:blank", true);
WebFrame* frame = webViewHelper.webView()->mainFrame();
// This test passes if this doesn't crash.
frame->toWebLocalFrame()->moveCaretSelection(WebPoint(0, 0));
}
class SpellCheckClient : public WebSpellCheckClient {
public:
explicit SpellCheckClient(uint32_t hash = 0)
: m_numberOfTimesChecked(0), m_hash(hash) {}
virtual ~SpellCheckClient() {}
void requestCheckingOfText(const WebString&,
const WebVector<uint32_t>&,
const WebVector<unsigned>&,
WebTextCheckingCompletion* completion) override {
++m_numberOfTimesChecked;
Vector<WebTextCheckingResult> results;
const int misspellingStartOffset = 1;
const int misspellingLength = 8;
results.append(WebTextCheckingResult(
WebTextDecorationTypeSpelling, misspellingStartOffset,
misspellingLength, WebString(), m_hash));
completion->didFinishCheckingText(results);
}
int numberOfTimesChecked() const { return m_numberOfTimesChecked; }
private:
int m_numberOfTimesChecked;
uint32_t m_hash;
};
TEST_P(ParameterizedWebFrameTest, ReplaceMisspelledRange) {
registerMockedHttpURLLoad("spell.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "spell.html");
SpellCheckClient spellcheck;
webViewHelper.webView()->setSpellCheckClient(&spellcheck);
WebLocalFrameImpl* frame = webViewHelper.webView()->mainFrameImpl();
Document* document = frame->frame()->document();
Element* element = document->getElementById("data");
webViewHelper.webView()->settings()->setEditingBehavior(
WebSettings::EditingBehaviorWin);
element->focus();
NonThrowableExceptionState exceptionState;
document->execCommand("InsertText", false, "_wellcome_.", exceptionState);
EXPECT_FALSE(exceptionState.hadException());
const int allTextBeginOffset = 0;
const int allTextLength = 11;
frame->selectRange(WebRange(allTextBeginOffset, allTextLength));
EphemeralRange selectionRange =
frame->frame()->selection().selection().toNormalizedEphemeralRange();
EXPECT_EQ(1, spellcheck.numberOfTimesChecked());
EXPECT_EQ(1U, document->markers()
.markersInRange(selectionRange, DocumentMarker::Spelling)
.size());
frame->replaceMisspelledRange("welcome");
EXPECT_EQ("_welcome_.",
WebFrameContentDumper::dumpWebViewAsText(
webViewHelper.webView(), std::numeric_limits<size_t>::max())
.utf8());
}
TEST_P(ParameterizedWebFrameTest, RemoveSpellingMarkers) {
registerMockedHttpURLLoad("spell.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "spell.html");
SpellCheckClient spellcheck;
webViewHelper.webView()->setSpellCheckClient(&spellcheck);
WebLocalFrameImpl* frame = webViewHelper.webView()->mainFrameImpl();
Document* document = frame->frame()->document();
Element* element = document->getElementById("data");
webViewHelper.webView()->settings()->setEditingBehavior(
WebSettings::EditingBehaviorWin);
element->focus();
NonThrowableExceptionState exceptionState;
document->execCommand("InsertText", false, "_wellcome_.", exceptionState);
EXPECT_FALSE(exceptionState.hadException());
frame->removeSpellingMarkers();
const int allTextBeginOffset = 0;
const int allTextLength = 11;
frame->selectRange(WebRange(allTextBeginOffset, allTextLength));
EphemeralRange selectionRange =
frame->frame()->selection().selection().toNormalizedEphemeralRange();
EXPECT_EQ(0U, document->markers()
.markersInRange(selectionRange, DocumentMarker::Spelling)
.size());
}
TEST_P(ParameterizedWebFrameTest, RemoveSpellingMarkersUnderWords) {
registerMockedHttpURLLoad("spell.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "spell.html");
SpellCheckClient spellcheck;
webViewHelper.webView()->setSpellCheckClient(&spellcheck);
LocalFrame* frame = webViewHelper.webView()->mainFrameImpl()->frame();
Document* document = frame->document();
Element* element = document->getElementById("data");
webViewHelper.webView()->settings()->setEditingBehavior(
WebSettings::EditingBehaviorWin);
element->focus();
NonThrowableExceptionState exceptionState;
document->execCommand("InsertText", false, " wellcome ", exceptionState);
EXPECT_FALSE(exceptionState.hadException());
WebVector<uint32_t> documentMarkers1;
webViewHelper.webView()->spellingMarkers(&documentMarkers1);
EXPECT_EQ(1U, documentMarkers1.size());
Vector<String> words;
words.append("wellcome");
frame->removeSpellingMarkersUnderWords(words);
WebVector<uint32_t> documentMarkers2;
webViewHelper.webView()->spellingMarkers(&documentMarkers2);
EXPECT_EQ(0U, documentMarkers2.size());
}
TEST_P(ParameterizedWebFrameTest, MarkerHashIdentifiers) {
registerMockedHttpURLLoad("spell.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "spell.html");
static const uint32_t kHash = 42;
SpellCheckClient spellcheck(kHash);
webViewHelper.webView()->setSpellCheckClient(&spellcheck);
WebLocalFrameImpl* frame = webViewHelper.webView()->mainFrameImpl();
Document* document = frame->frame()->document();
Element* element = document->getElementById("data");
webViewHelper.webView()->settings()->setEditingBehavior(
WebSettings::EditingBehaviorWin);
element->focus();
NonThrowableExceptionState exceptionState;
document->execCommand("InsertText", false, "wellcome.", exceptionState);
EXPECT_FALSE(exceptionState.hadException());
WebVector<uint32_t> documentMarkers;
webViewHelper.webView()->spellingMarkers(&documentMarkers);
EXPECT_EQ(1U, documentMarkers.size());
EXPECT_EQ(kHash, documentMarkers[0]);
}
class StubbornSpellCheckClient : public WebSpellCheckClient {
public:
StubbornSpellCheckClient() : m_completion(0) {}
virtual ~StubbornSpellCheckClient() {}
virtual void requestCheckingOfText(
const WebString&,
const WebVector<uint32_t>&,
const WebVector<unsigned>&,
WebTextCheckingCompletion* completion) override {
m_completion = completion;
}
void cancelAllPendingRequests() override {
if (!m_completion)
return;
m_completion->didCancelCheckingText();
m_completion = nullptr;
}
void kickNoResults() { kick(-1, -1, WebTextDecorationTypeSpelling); }
void kick() { kick(1, 8, WebTextDecorationTypeSpelling); }
void kickGrammar() { kick(1, 8, WebTextDecorationTypeGrammar); }
void kickInvisibleSpellcheck() {
kick(1, 8, WebTextDecorationTypeInvisibleSpellcheck);
}
private:
void kick(int misspellingStartOffset,
int misspellingLength,
WebTextDecorationType type) {
if (!m_completion)
return;
Vector<WebTextCheckingResult> results;
if (misspellingStartOffset >= 0 && misspellingLength > 0)
results.append(WebTextCheckingResult(type, misspellingStartOffset,
misspellingLength));
m_completion->didFinishCheckingText(results);
m_completion = 0;
}
WebTextCheckingCompletion* m_completion;
};
TEST_P(ParameterizedWebFrameTest, SlowSpellcheckMarkerPosition) {
registerMockedHttpURLLoad("spell.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "spell.html");
StubbornSpellCheckClient spellcheck;
webViewHelper.webView()->setSpellCheckClient(&spellcheck);
WebLocalFrameImpl* frame = webViewHelper.webView()->mainFrameImpl();
Document* document = frame->frame()->document();
Element* element = document->getElementById("data");
webViewHelper.webView()->settings()->setEditingBehavior(
WebSettings::EditingBehaviorWin);
element->focus();
NonThrowableExceptionState exceptionState;
document->execCommand("InsertText", false, "wellcome ", exceptionState);
EXPECT_FALSE(exceptionState.hadException());
document->execCommand("InsertText", false, "he", exceptionState);
EXPECT_FALSE(exceptionState.hadException());
spellcheck.kick();
WebVector<uint32_t> documentMarkers;
webViewHelper.webView()->spellingMarkers(&documentMarkers);
EXPECT_EQ(0U, documentMarkers.size());
}
// This test verifies that cancelling spelling request does not cause a
// write-after-free when there's no spellcheck client set.
TEST_P(ParameterizedWebFrameTest, CancelSpellingRequestCrash) {
registerMockedHttpURLLoad("spell.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "spell.html");
webViewHelper.webView()->setSpellCheckClient(0);
WebLocalFrameImpl* frame = webViewHelper.webView()->mainFrameImpl();
Document* document = frame->frame()->document();
Element* element = document->getElementById("data");
webViewHelper.webView()->settings()->setEditingBehavior(
WebSettings::EditingBehaviorWin);
element->focus();
frame->frame()->editor().replaceSelectionWithText("A", false, false);
frame->frame()->spellChecker().cancelCheck();
}
TEST_P(ParameterizedWebFrameTest, SpellcheckResultErasesMarkers) {
registerMockedHttpURLLoad("spell.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "spell.html");
StubbornSpellCheckClient spellcheck;
webViewHelper.webView()->setSpellCheckClient(&spellcheck);
WebLocalFrameImpl* frame = webViewHelper.webView()->mainFrameImpl();
Document* document = frame->frame()->document();
Element* element = document->getElementById("data");
webViewHelper.webView()->settings()->setEditingBehavior(
WebSettings::EditingBehaviorWin);
element->focus();
NonThrowableExceptionState exceptionState;
document->execCommand("InsertText", false, "welcome ", exceptionState);
document->updateStyleAndLayout();
EXPECT_FALSE(exceptionState.hadException());
auto range = EphemeralRange::rangeOfContents(*element);
document->markers().addMarker(range.startPosition(), range.endPosition(),
DocumentMarker::Spelling);
document->markers().addMarker(range.startPosition(), range.endPosition(),
DocumentMarker::Grammar);
document->markers().addMarker(range.startPosition(), range.endPosition(),
DocumentMarker::InvisibleSpellcheck);
EXPECT_EQ(3U, document->markers().markers().size());
spellcheck.kickNoResults();
EXPECT_EQ(0U, document->markers().markers().size());
}
TEST_P(ParameterizedWebFrameTest, SpellcheckResultsSavedInDocument) {
registerMockedHttpURLLoad("spell.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "spell.html");
StubbornSpellCheckClient spellcheck;
webViewHelper.webView()->setSpellCheckClient(&spellcheck);
WebLocalFrameImpl* frame = webViewHelper.webView()->mainFrameImpl();
Document* document = frame->frame()->document();
Element* element = document->getElementById("data");
webViewHelper.webView()->settings()->setEditingBehavior(
WebSettings::EditingBehaviorWin);
element->focus();
NonThrowableExceptionState exceptionState;
document->execCommand("InsertText", false, "wellcome ", exceptionState);
EXPECT_FALSE(exceptionState.hadException());
spellcheck.kick();
ASSERT_EQ(1U, document->markers().markers().size());
ASSERT_NE(static_cast<DocumentMarker*>(0), document->markers().markers()[0]);
EXPECT_EQ(DocumentMarker::Spelling, document->markers().markers()[0]->type());
document->execCommand("InsertText", false, "wellcome ", exceptionState);
EXPECT_FALSE(exceptionState.hadException());
spellcheck.kickGrammar();
ASSERT_EQ(1U, document->markers().markers().size());
ASSERT_NE(static_cast<DocumentMarker*>(0), document->markers().markers()[0]);
EXPECT_EQ(DocumentMarker::Grammar, document->markers().markers()[0]->type());
document->execCommand("InsertText", false, "wellcome ", exceptionState);
EXPECT_FALSE(exceptionState.hadException());
spellcheck.kickInvisibleSpellcheck();
ASSERT_EQ(1U, document->markers().markers().size());
ASSERT_NE(static_cast<DocumentMarker*>(0), document->markers().markers()[0]);
EXPECT_EQ(DocumentMarker::InvisibleSpellcheck,
document->markers().markers()[0]->type());
}
class TestAccessInitialDocumentWebFrameClient
: public FrameTestHelpers::TestWebFrameClient {
public:
TestAccessInitialDocumentWebFrameClient()
: m_didAccessInitialDocument(false) {}
virtual void didAccessInitialDocument() { m_didAccessInitialDocument = true; }
bool m_didAccessInitialDocument;
};
TEST_P(ParameterizedWebFrameTest, DidAccessInitialDocumentBody) {
// FIXME: Why is this local webViewClient needed instead of the default
// WebViewHelper one? With out it there's some mysterious crash in the
// WebViewHelper destructor.
FrameTestHelpers::TestWebViewClient webViewClient;
TestAccessInitialDocumentWebFrameClient webFrameClient;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initialize(true, &webFrameClient, &webViewClient);
runPendingTasks();
EXPECT_FALSE(webFrameClient.m_didAccessInitialDocument);
// Create another window that will try to access it.
FrameTestHelpers::WebViewHelper newWebViewHelper;
WebView* newView = newWebViewHelper.initializeWithOpener(
webViewHelper.webView()->mainFrame(), true);
runPendingTasks();
EXPECT_FALSE(webFrameClient.m_didAccessInitialDocument);
// Access the initial document by modifying the body.
newView->mainFrame()->executeScript(
WebScriptSource("window.opener.document.body.innerHTML += 'Modified';"));
runPendingTasks();
EXPECT_TRUE(webFrameClient.m_didAccessInitialDocument);
// Access the initial document again, to ensure we don't notify twice.
newView->mainFrame()->executeScript(
WebScriptSource("window.opener.document.body.innerHTML += 'Modified';"));
runPendingTasks();
EXPECT_TRUE(webFrameClient.m_didAccessInitialDocument);
}
TEST_P(ParameterizedWebFrameTest, DidAccessInitialDocumentNavigator) {
// FIXME: Why is this local webViewClient needed instead of the default
// WebViewHelper one? With out it there's some mysterious crash in the
// WebViewHelper destructor.
FrameTestHelpers::TestWebViewClient webViewClient;
TestAccessInitialDocumentWebFrameClient webFrameClient;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initialize(true, &webFrameClient, &webViewClient);
runPendingTasks();
EXPECT_FALSE(webFrameClient.m_didAccessInitialDocument);
// Create another window that will try to access it.
FrameTestHelpers::WebViewHelper newWebViewHelper;
WebView* newView = newWebViewHelper.initializeWithOpener(
webViewHelper.webView()->mainFrame(), true);
runPendingTasks();
EXPECT_FALSE(webFrameClient.m_didAccessInitialDocument);
// Access the initial document to get to the navigator object.
newView->mainFrame()->executeScript(
WebScriptSource("console.log(window.opener.navigator);"));
runPendingTasks();
EXPECT_TRUE(webFrameClient.m_didAccessInitialDocument);
}
TEST_P(ParameterizedWebFrameTest, DidAccessInitialDocumentViaJavascriptUrl) {
TestAccessInitialDocumentWebFrameClient webFrameClient;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initialize(true, &webFrameClient);
runPendingTasks();
EXPECT_FALSE(webFrameClient.m_didAccessInitialDocument);
// Access the initial document from a javascript: URL.
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(),
"javascript:document.body.appendChild(document."
"createTextNode('Modified'))");
EXPECT_TRUE(webFrameClient.m_didAccessInitialDocument);
}
// Fails on the WebKit XP (deps) bot. http://crbug.com/312192
#if OS(WIN)
TEST_P(ParameterizedWebFrameTest,
DISABLED_DidAccessInitialDocumentBodyBeforeModalDialog)
#else
TEST_P(ParameterizedWebFrameTest, DidAccessInitialDocumentBodyBeforeModalDialog)
#endif
{
// FIXME: Why is this local webViewClient needed instead of the default
// WebViewHelper one? With out it there's some mysterious crash in the
// WebViewHelper destructor.
FrameTestHelpers::TestWebViewClient webViewClient;
TestAccessInitialDocumentWebFrameClient webFrameClient;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initialize(true, &webFrameClient, &webViewClient);
runPendingTasks();
EXPECT_FALSE(webFrameClient.m_didAccessInitialDocument);
// Create another window that will try to access it.
FrameTestHelpers::WebViewHelper newWebViewHelper;
WebView* newView = newWebViewHelper.initializeWithOpener(
webViewHelper.webView()->mainFrame(), true);
runPendingTasks();
EXPECT_FALSE(webFrameClient.m_didAccessInitialDocument);
// Access the initial document by modifying the body.
newView->mainFrame()->executeScript(
WebScriptSource("window.opener.document.body.innerHTML += 'Modified';"));
EXPECT_TRUE(webFrameClient.m_didAccessInitialDocument);
// Run a modal dialog, which used to run a nested message loop and require
// a special case for notifying about the access.
newView->mainFrame()->executeScript(
WebScriptSource("window.opener.confirm('Modal');"));
EXPECT_TRUE(webFrameClient.m_didAccessInitialDocument);
// Ensure that we don't notify again later.
runPendingTasks();
EXPECT_TRUE(webFrameClient.m_didAccessInitialDocument);
}
// Fails on the WebKit XP (deps) bot. http://crbug.com/312192
#if OS(WIN)
TEST_P(ParameterizedWebFrameTest,
DISABLED_DidWriteToInitialDocumentBeforeModalDialog)
#else
TEST_P(ParameterizedWebFrameTest, DidWriteToInitialDocumentBeforeModalDialog)
#endif
{
// FIXME: Why is this local webViewClient needed instead of the default
// WebViewHelper one? With out it there's some mysterious crash in the
// WebViewHelper destructor.
FrameTestHelpers::TestWebViewClient webViewClient;
TestAccessInitialDocumentWebFrameClient webFrameClient;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initialize(true, &webFrameClient, &webViewClient);
runPendingTasks();
EXPECT_FALSE(webFrameClient.m_didAccessInitialDocument);
// Create another window that will try to access it.
FrameTestHelpers::WebViewHelper newWebViewHelper;
WebView* newView = newWebViewHelper.initializeWithOpener(
webViewHelper.webView()->mainFrame(), true);
runPendingTasks();
EXPECT_FALSE(webFrameClient.m_didAccessInitialDocument);
// Access the initial document with document.write, which moves us past the
// initial empty document state of the state machine.
newView->mainFrame()->executeScript(
WebScriptSource("window.opener.document.write('Modified'); "
"window.opener.document.close();"));
EXPECT_TRUE(webFrameClient.m_didAccessInitialDocument);
// Run a modal dialog, which used to run a nested message loop and require
// a special case for notifying about the access.
newView->mainFrame()->executeScript(
WebScriptSource("window.opener.confirm('Modal');"));
EXPECT_TRUE(webFrameClient.m_didAccessInitialDocument);
// Ensure that we don't notify again later.
runPendingTasks();
EXPECT_TRUE(webFrameClient.m_didAccessInitialDocument);
}
class TestScrolledFrameClient : public FrameTestHelpers::TestWebFrameClient {
public:
TestScrolledFrameClient() { reset(); }
void reset() { m_didScrollFrame = false; }
bool wasFrameScrolled() const { return m_didScrollFrame; }
// WebFrameClient:
void didChangeScrollOffset(WebLocalFrame* frame) override {
if (frame->parent())
return;
EXPECT_FALSE(m_didScrollFrame);
FrameView* view = toWebLocalFrameImpl(frame)->frameView();
// FrameView can be scrolled in FrameView::setFixedVisibleContentRect which
// is called from LocalFrame::createView (before the frame is associated
// with the the view).
if (view)
m_didScrollFrame = true;
}
private:
bool m_didScrollFrame;
};
TEST_F(WebFrameTest, CompositorScrollIsUserScrollLongPage) {
registerMockedHttpURLLoad("long_scroll.html");
TestScrolledFrameClient client;
// Make sure we initialize to minimum scale, even if the window size
// only becomes available after the load begins.
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "long_scroll.html", true,
&client);
webViewHelper.resize(WebSize(1000, 1000));
WebLocalFrameImpl* frameImpl = webViewHelper.webView()->mainFrameImpl();
DocumentLoader::InitialScrollState& initialScrollState =
frameImpl->frame()->loader().documentLoader()->initialScrollState();
GraphicsLayer* frameViewLayer = frameImpl->frameView()->layerForScrolling();
EXPECT_FALSE(client.wasFrameScrolled());
EXPECT_FALSE(initialScrollState.wasScrolledByUser);
// Do a compositor scroll, verify that this is counted as a user scroll.
frameViewLayer->platformLayer()->setScrollPositionDouble(
WebDoublePoint(0, 1));
frameViewLayer->didScroll();
webViewHelper.webView()->applyViewportDeltas(WebFloatSize(), WebFloatSize(),
WebFloatSize(), 1.7f, 0);
EXPECT_TRUE(client.wasFrameScrolled());
EXPECT_TRUE(initialScrollState.wasScrolledByUser);
client.reset();
initialScrollState.wasScrolledByUser = false;
// The page scale 1.0f and scroll.
frameViewLayer->platformLayer()->setScrollPositionDouble(
WebDoublePoint(0, 2));
frameViewLayer->didScroll();
webViewHelper.webView()->applyViewportDeltas(WebFloatSize(), WebFloatSize(),
WebFloatSize(), 1.0f, 0);
EXPECT_TRUE(client.wasFrameScrolled());
EXPECT_TRUE(initialScrollState.wasScrolledByUser);
client.reset();
initialScrollState.wasScrolledByUser = false;
// No scroll event if there is no scroll delta.
frameViewLayer->didScroll();
webViewHelper.webView()->applyViewportDeltas(WebFloatSize(), WebFloatSize(),
WebFloatSize(), 1.0f, 0);
EXPECT_FALSE(client.wasFrameScrolled());
EXPECT_FALSE(initialScrollState.wasScrolledByUser);
client.reset();
// Non zero page scale and scroll.
frameViewLayer->platformLayer()->setScrollPositionDouble(
WebDoublePoint(9, 15));
frameViewLayer->didScroll();
webViewHelper.webView()->applyViewportDeltas(WebFloatSize(), WebFloatSize(),
WebFloatSize(), 0.6f, 0);
EXPECT_TRUE(client.wasFrameScrolled());
EXPECT_TRUE(initialScrollState.wasScrolledByUser);
client.reset();
initialScrollState.wasScrolledByUser = false;
// Programmatic scroll.
frameImpl->executeScript(WebScriptSource("window.scrollTo(0, 20);"));
EXPECT_TRUE(client.wasFrameScrolled());
EXPECT_FALSE(initialScrollState.wasScrolledByUser);
client.reset();
// Programmatic scroll to same offset. No scroll event should be generated.
frameImpl->executeScript(WebScriptSource("window.scrollTo(0, 20);"));
EXPECT_FALSE(client.wasFrameScrolled());
EXPECT_FALSE(initialScrollState.wasScrolledByUser);
client.reset();
}
TEST_P(ParameterizedWebFrameTest, FirstPartyForCookiesForRedirect) {
String filePath = testing::blinkRootDir();
filePath.append("/Source/web/tests/data/first_party.html");
WebURL testURL(toKURL("http://internal.test/first_party_redirect.html"));
char redirect[] = "http://internal.test/first_party.html";
WebURL redirectURL(toKURL(redirect));
WebURLResponse redirectResponse;
redirectResponse.setMIMEType("text/html");
redirectResponse.setHTTPStatusCode(302);
redirectResponse.setHTTPHeaderField("Location", redirect);
Platform::current()->getURLLoaderMockFactory()->registerURL(
testURL, redirectResponse, filePath);
WebURLResponse finalResponse;
finalResponse.setMIMEType("text/html");
Platform::current()->getURLLoaderMockFactory()->registerURL(
redirectURL, finalResponse, filePath);
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "first_party_redirect.html",
true);
EXPECT_TRUE(
webViewHelper.webView()->mainFrame()->document().firstPartyForCookies() ==
redirectURL);
}
class TestNavigationPolicyWebFrameClient
: public FrameTestHelpers::TestWebFrameClient {
public:
void didNavigateWithinPage(WebLocalFrame*,
const WebHistoryItem&,
WebHistoryCommitType,
bool) override {
EXPECT_TRUE(false);
}
};
TEST_P(ParameterizedWebFrameTest, SimulateFragmentAnchorMiddleClick) {
registerMockedHttpURLLoad("fragment_middle_click.html");
TestNavigationPolicyWebFrameClient client;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "fragment_middle_click.html",
true, &client);
Document* document =
toLocalFrame(webViewHelper.webView()->page()->mainFrame())->document();
KURL destination = document->url();
destination.setFragmentIdentifier("test");
Event* event = MouseEvent::create(
EventTypeNames::click, false, false, document->domWindow(), 0, 0, 0, 0, 0,
0, 0, PlatformEvent::NoModifiers, 1, 0, nullptr, 0,
PlatformMouseEvent::RealOrIndistinguishable, String(), nullptr);
FrameLoadRequest frameRequest(document, ResourceRequest(destination));
frameRequest.setTriggeringEvent(event);
toLocalFrame(webViewHelper.webView()->page()->mainFrame())
->loader()
.load(frameRequest);
}
class TestNewWindowWebViewClient : public FrameTestHelpers::TestWebViewClient {
public:
virtual WebView* createView(WebLocalFrame*,
const WebURLRequest&,
const WebWindowFeatures&,
const WebString&,
WebNavigationPolicy,
bool) override {
EXPECT_TRUE(false);
return 0;
}
};
class TestNewWindowWebFrameClient
: public FrameTestHelpers::TestWebFrameClient {
public:
TestNewWindowWebFrameClient() : m_decidePolicyCallCount(0) {}
WebNavigationPolicy decidePolicyForNavigation(
const NavigationPolicyInfo& info) override {
m_decidePolicyCallCount++;
return info.defaultPolicy;
}
int decidePolicyCallCount() const { return m_decidePolicyCallCount; }
private:
int m_decidePolicyCallCount;
};
TEST_P(ParameterizedWebFrameTest, ModifiedClickNewWindow) {
registerMockedHttpURLLoad("ctrl_click.html");
registerMockedHttpURLLoad("hello_world.html");
TestNewWindowWebViewClient webViewClient;
TestNewWindowWebFrameClient webFrameClient;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "ctrl_click.html", true,
&webFrameClient, &webViewClient);
Document* document =
toLocalFrame(webViewHelper.webView()->page()->mainFrame())->document();
KURL destination = toKURL(m_baseURL + "hello_world.html");
// ctrl+click event
Event* event = MouseEvent::create(
EventTypeNames::click, false, false, document->domWindow(), 0, 0, 0, 0, 0,
0, 0, PlatformEvent::CtrlKey, 0, 0, nullptr, 0,
PlatformMouseEvent::RealOrIndistinguishable, String(), nullptr);
FrameLoadRequest frameRequest(document, ResourceRequest(destination));
frameRequest.setTriggeringEvent(event);
UserGestureIndicator gesture(DefinitelyProcessingUserGesture);
toLocalFrame(webViewHelper.webView()->page()->mainFrame())
->loader()
.load(frameRequest);
FrameTestHelpers::pumpPendingRequestsForFrameToLoad(
webViewHelper.webView()->mainFrame());
// decidePolicyForNavigation should be called both for the original request
// and the ctrl+click.
EXPECT_EQ(2, webFrameClient.decidePolicyCallCount());
}
TEST_P(ParameterizedWebFrameTest, BackToReload) {
registerMockedHttpURLLoad("fragment_middle_click.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "fragment_middle_click.html",
true);
WebFrame* frame = webViewHelper.webView()->mainFrame();
const FrameLoader& mainFrameLoader =
webViewHelper.webView()->mainFrameImpl()->frame()->loader();
Persistent<HistoryItem> firstItem = mainFrameLoader.currentItem();
EXPECT_TRUE(firstItem);
registerMockedHttpURLLoad("white-1x1.png");
FrameTestHelpers::loadFrame(frame, m_baseURL + "white-1x1.png");
EXPECT_NE(firstItem.get(), mainFrameLoader.currentItem());
FrameTestHelpers::loadHistoryItem(frame, WebHistoryItem(firstItem.get()),
WebHistoryDifferentDocumentLoad,
WebCachePolicy::UseProtocolCachePolicy);
EXPECT_EQ(firstItem.get(), mainFrameLoader.currentItem());
FrameTestHelpers::reloadFrame(frame);
EXPECT_EQ(WebCachePolicy::ValidatingCacheData,
frame->dataSource()->request().getCachePolicy());
}
TEST_P(ParameterizedWebFrameTest, BackDuringChildFrameReload) {
registerMockedHttpURLLoad("page_with_blank_iframe.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "page_with_blank_iframe.html",
true);
WebLocalFrame* mainFrame = webViewHelper.webView()->mainFrameImpl();
const FrameLoader& mainFrameLoader =
webViewHelper.webView()->mainFrameImpl()->frame()->loader();
WebFrame* childFrame = mainFrame->firstChild();
ASSERT_TRUE(childFrame);
// Start a history navigation, then have a different frame commit a
// navigation. In this case, reload an about:blank frame, which will commit
// synchronously. After the history navigation completes, both the
// appropriate document url and the current history item should reflect the
// history navigation.
registerMockedHttpURLLoad("white-1x1.png");
WebHistoryItem item;
item.initialize();
WebURL historyURL(toKURL(m_baseURL + "white-1x1.png"));
item.setURLString(historyURL.string());
WebURLRequest request = mainFrame->requestFromHistoryItem(
item, WebCachePolicy::UseProtocolCachePolicy);
mainFrame->load(request, WebFrameLoadType::BackForward, item);
FrameTestHelpers::reloadFrame(childFrame);
EXPECT_EQ(item.urlString(), mainFrame->document().url().string());
EXPECT_EQ(item.urlString(),
WebString(mainFrameLoader.currentItem()->urlString()));
}
TEST_P(ParameterizedWebFrameTest, ReloadPost) {
registerMockedHttpURLLoad("reload_post.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "reload_post.html", true);
WebFrame* frame = webViewHelper.webView()->mainFrame();
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(),
"javascript:document.forms[0].submit()");
// Pump requests one more time after the javascript URL has executed to
// trigger the actual POST load request.
FrameTestHelpers::pumpPendingRequestsForFrameToLoad(
webViewHelper.webView()->mainFrame());
EXPECT_EQ(WebString::fromUTF8("POST"),
frame->dataSource()->request().httpMethod());
FrameTestHelpers::reloadFrame(frame);
EXPECT_EQ(WebCachePolicy::ValidatingCacheData,
frame->dataSource()->request().getCachePolicy());
EXPECT_EQ(WebNavigationTypeFormResubmitted,
frame->dataSource()->navigationType());
}
TEST_P(ParameterizedWebFrameTest, LoadHistoryItemReload) {
registerMockedHttpURLLoad("fragment_middle_click.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "fragment_middle_click.html",
true);
WebFrame* frame = webViewHelper.webView()->mainFrame();
const FrameLoader& mainFrameLoader =
webViewHelper.webView()->mainFrameImpl()->frame()->loader();
Persistent<HistoryItem> firstItem = mainFrameLoader.currentItem();
EXPECT_TRUE(firstItem);
registerMockedHttpURLLoad("white-1x1.png");
FrameTestHelpers::loadFrame(frame, m_baseURL + "white-1x1.png");
EXPECT_NE(firstItem.get(), mainFrameLoader.currentItem());
// Cache policy overrides should take.
FrameTestHelpers::loadHistoryItem(frame, WebHistoryItem(firstItem),
WebHistoryDifferentDocumentLoad,
WebCachePolicy::ValidatingCacheData);
EXPECT_EQ(firstItem.get(), mainFrameLoader.currentItem());
EXPECT_EQ(WebCachePolicy::ValidatingCacheData,
frame->dataSource()->request().getCachePolicy());
}
class TestCachePolicyWebFrameClient
: public FrameTestHelpers::TestWebFrameClient {
public:
explicit TestCachePolicyWebFrameClient(
TestCachePolicyWebFrameClient* parentClient)
: m_parentClient(parentClient),
m_policy(WebCachePolicy::UseProtocolCachePolicy),
m_childClient(0),
m_willSendRequestCallCount(0),
m_childFrameCreationCount(0) {}
void setChildWebFrameClient(TestCachePolicyWebFrameClient* client) {
m_childClient = client;
}
WebCachePolicy getCachePolicy() const { return m_policy; }
int willSendRequestCallCount() const { return m_willSendRequestCallCount; }
int childFrameCreationCount() const { return m_childFrameCreationCount; }
virtual WebLocalFrame* createChildFrame(
WebLocalFrame* parent,
WebTreeScopeType scope,
const WebString&,
const WebString&,
WebSandboxFlags,
const WebFrameOwnerProperties& frameOwnerProperties) {
DCHECK(m_childClient);
m_childFrameCreationCount++;
WebLocalFrame* frame = WebLocalFrame::create(scope, m_childClient);
parent->appendChild(frame);
return frame;
}
virtual void didStartLoading(bool toDifferentDocument) {
if (m_parentClient) {
m_parentClient->didStartLoading(toDifferentDocument);
return;
}
TestWebFrameClient::didStartLoading(toDifferentDocument);
}
virtual void didStopLoading() {
if (m_parentClient) {
m_parentClient->didStopLoading();
return;
}
TestWebFrameClient::didStopLoading();
}
void willSendRequest(WebLocalFrame*, WebURLRequest& request) override {
m_policy = request.getCachePolicy();
m_willSendRequestCallCount++;
}
private:
TestCachePolicyWebFrameClient* m_parentClient;
WebCachePolicy m_policy;
TestCachePolicyWebFrameClient* m_childClient;
int m_willSendRequestCallCount;
int m_childFrameCreationCount;
};
TEST_P(ParameterizedWebFrameTest, ReloadIframe) {
registerMockedHttpURLLoad("iframe_reload.html");
registerMockedHttpURLLoad("visible_iframe.html");
TestCachePolicyWebFrameClient mainClient(0);
TestCachePolicyWebFrameClient childClient(&mainClient);
mainClient.setChildWebFrameClient(&childClient);
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "iframe_reload.html", true,
&mainClient);
WebLocalFrameImpl* mainFrame = webViewHelper.webView()->mainFrameImpl();
WebLocalFrameImpl* childFrame = toWebLocalFrameImpl(mainFrame->firstChild());
ASSERT_EQ(childFrame->client(), &childClient);
EXPECT_EQ(mainClient.childFrameCreationCount(), 1);
EXPECT_EQ(childClient.willSendRequestCallCount(), 1);
EXPECT_EQ(childClient.getCachePolicy(),
WebCachePolicy::UseProtocolCachePolicy);
FrameTestHelpers::reloadFrame(mainFrame);
// A new WebFrame should have been created, but the child WebFrameClient
// should be reused.
ASSERT_NE(childFrame, toWebLocalFrameImpl(mainFrame->firstChild()));
ASSERT_EQ(toWebLocalFrameImpl(mainFrame->firstChild())->client(),
&childClient);
EXPECT_EQ(mainClient.childFrameCreationCount(), 2);
EXPECT_EQ(childClient.willSendRequestCallCount(), 2);
EXPECT_EQ(childClient.getCachePolicy(), WebCachePolicy::ValidatingCacheData);
}
class TestSameDocumentWebFrameClient
: public FrameTestHelpers::TestWebFrameClient {
public:
TestSameDocumentWebFrameClient()
: m_frameLoadTypeReloadMainResourceSeen(false) {}
virtual void willSendRequest(WebLocalFrame* frame, WebURLRequest&) {
if (toWebLocalFrameImpl(frame)->frame()->loader().loadType() ==
FrameLoadTypeReloadMainResource)
m_frameLoadTypeReloadMainResourceSeen = true;
}
bool frameLoadTypeReloadMainResourceSeen() const {
return m_frameLoadTypeReloadMainResourceSeen;
}
private:
bool m_frameLoadTypeReloadMainResourceSeen;
};
TEST_P(ParameterizedWebFrameTest, NavigateToSame) {
registerMockedHttpURLLoad("navigate_to_same.html");
TestSameDocumentWebFrameClient client;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "navigate_to_same.html", true,
&client);
EXPECT_FALSE(client.frameLoadTypeReloadMainResourceSeen());
FrameLoadRequest frameRequest(
0,
ResourceRequest(toLocalFrame(webViewHelper.webView()->page()->mainFrame())
->document()
->url()));
toLocalFrame(webViewHelper.webView()->page()->mainFrame())
->loader()
.load(frameRequest);
FrameTestHelpers::pumpPendingRequestsForFrameToLoad(
webViewHelper.webView()->mainFrame());
EXPECT_TRUE(client.frameLoadTypeReloadMainResourceSeen());
}
class TestSameDocumentWithImageWebFrameClient
: public FrameTestHelpers::TestWebFrameClient {
public:
TestSameDocumentWithImageWebFrameClient() : m_numOfImageRequests(0) {}
virtual void willSendRequest(WebLocalFrame*, WebURLRequest& request) {
if (request.getRequestContext() == WebURLRequest::RequestContextImage) {
m_numOfImageRequests++;
EXPECT_EQ(WebCachePolicy::UseProtocolCachePolicy,
request.getCachePolicy());
}
}
int numOfImageRequests() const { return m_numOfImageRequests; }
private:
int m_numOfImageRequests;
};
TEST_P(ParameterizedWebFrameTest,
NavigateToSameNoConditionalRequestForSubresource) {
registerMockedHttpURLLoad("foo_with_image.html");
registerMockedHttpURLLoad("white-1x1.png");
TestSameDocumentWithImageWebFrameClient client;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "foo_with_image.html", true,
&client, nullptr, nullptr,
&configureLoadsImagesAutomatically);
WebCache::clear();
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(),
m_baseURL + "foo_with_image.html");
// 2 images are requested, and each triggers 2 willSendRequest() calls,
// once for preloading and once for the real request.
EXPECT_EQ(client.numOfImageRequests(), 4);
}
TEST_P(ParameterizedWebFrameTest, WebNodeImageContents) {
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad("about:blank", true);
WebFrame* frame = webViewHelper.webView()->mainFrame();
static const char bluePNG[] =
"<img "
"src=\"data:image/"
"png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+"
"9AAAAGElEQVQYV2NkYPj/n4EIwDiqEF8oUT94AFIQE/cCn90IAAAAAElFTkSuQmCC\">";
// Load up the image and test that we can extract the contents.
KURL testURL = toKURL("about:blank");
FrameTestHelpers::loadHTMLString(frame, bluePNG, testURL);
WebNode node = frame->document().body().firstChild();
EXPECT_TRUE(node.isElementNode());
WebElement element = node.to<WebElement>();
WebImage image = element.imageContents();
ASSERT_FALSE(image.isNull());
EXPECT_EQ(image.size().width, 10);
EXPECT_EQ(image.size().height, 10);
// FIXME: The rest of this test is disabled since the ImageDecodeCache state
// may be inconsistent when this test runs, crbug.com/266088
// SkBitmap bitmap = image.getSkBitmap();
// SkAutoLockPixels locker(bitmap);
// EXPECT_EQ(bitmap.getColor(0, 0), SK_ColorBLUE);
}
class TestStartStopCallbackWebFrameClient
: public FrameTestHelpers::TestWebFrameClient {
public:
TestStartStopCallbackWebFrameClient()
: m_startLoadingCount(0),
m_stopLoadingCount(0),
m_differentDocumentStartCount(0) {}
void didStartLoading(bool toDifferentDocument) override {
TestWebFrameClient::didStartLoading(toDifferentDocument);
m_startLoadingCount++;
if (toDifferentDocument)
m_differentDocumentStartCount++;
}
void didStopLoading() override {
TestWebFrameClient::didStopLoading();
m_stopLoadingCount++;
}
int startLoadingCount() const { return m_startLoadingCount; }
int stopLoadingCount() const { return m_stopLoadingCount; }
int differentDocumentStartCount() const {
return m_differentDocumentStartCount;
}
private:
int m_startLoadingCount;
int m_stopLoadingCount;
int m_differentDocumentStartCount;
};
TEST_P(ParameterizedWebFrameTest, PushStateStartsAndStops) {
registerMockedHttpURLLoad("push_state.html");
TestStartStopCallbackWebFrameClient client;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "push_state.html", true, &client);
EXPECT_EQ(client.startLoadingCount(), 2);
EXPECT_EQ(client.stopLoadingCount(), 2);
EXPECT_EQ(client.differentDocumentStartCount(), 1);
}
class TestDidNavigateCommitTypeWebFrameClient
: public FrameTestHelpers::TestWebFrameClient {
public:
TestDidNavigateCommitTypeWebFrameClient()
: m_lastCommitType(WebHistoryInertCommit) {}
void didNavigateWithinPage(WebLocalFrame*,
const WebHistoryItem&,
WebHistoryCommitType type,
bool) override {
m_lastCommitType = type;
}
WebHistoryCommitType lastCommitType() const { return m_lastCommitType; }
private:
WebHistoryCommitType m_lastCommitType;
};
TEST_P(ParameterizedWebFrameTest, SameDocumentHistoryNavigationCommitType) {
registerMockedHttpURLLoad("push_state.html");
TestDidNavigateCommitTypeWebFrameClient client;
FrameTestHelpers::WebViewHelper webViewHelper;
WebViewImpl* webViewImpl = webViewHelper.initializeAndLoad(
m_baseURL + "push_state.html", true, &client);
Persistent<HistoryItem> item =
toLocalFrame(webViewImpl->page()->mainFrame())->loader().currentItem();
runPendingTasks();
toLocalFrame(webViewImpl->page()->mainFrame())
->loader()
.load(
FrameLoadRequest(
nullptr, FrameLoader::resourceRequestFromHistoryItem(
item.get(), WebCachePolicy::UseProtocolCachePolicy)),
FrameLoadTypeBackForward, item.get(), HistorySameDocumentLoad);
EXPECT_EQ(WebBackForwardCommit, client.lastCommitType());
}
class TestHistoryWebFrameClient : public FrameTestHelpers::TestWebFrameClient {
public:
TestHistoryWebFrameClient() {
m_replacesCurrentHistoryItem = false;
m_frame = nullptr;
}
void didStartProvisionalLoad(WebLocalFrame* frame, double) {
WebDataSource* ds = frame->provisionalDataSource();
m_replacesCurrentHistoryItem = ds->replacesCurrentHistoryItem();
m_frame = frame;
}
bool replacesCurrentHistoryItem() { return m_replacesCurrentHistoryItem; }
WebFrame* frame() { return m_frame; }
private:
bool m_replacesCurrentHistoryItem;
WebFrame* m_frame;
};
// Tests that the first navigation in an initially blank subframe will result in
// a history entry being replaced and not a new one being added.
TEST_P(ParameterizedWebFrameTest, FirstBlankSubframeNavigation) {
registerMockedHttpURLLoad("history.html");
registerMockedHttpURLLoad("find.html");
FrameTestHelpers::WebViewHelper webViewHelper;
TestHistoryWebFrameClient client;
webViewHelper.initializeAndLoad("about:blank", true, &client);
WebFrame* frame = webViewHelper.webView()->mainFrame();
frame->executeScript(WebScriptSource(WebString::fromUTF8(
"document.body.appendChild(document.createElement('iframe'))")));
WebFrame* iframe = frame->firstChild();
ASSERT_EQ(&client, toWebLocalFrameImpl(iframe)->client());
EXPECT_EQ(iframe, client.frame());
std::string url1 = m_baseURL + "history.html";
FrameTestHelpers::loadFrame(iframe, url1);
EXPECT_EQ(iframe, client.frame());
EXPECT_EQ(url1, iframe->document().url().string().utf8());
EXPECT_TRUE(client.replacesCurrentHistoryItem());
std::string url2 = m_baseURL + "find.html";
FrameTestHelpers::loadFrame(iframe, url2);
EXPECT_EQ(iframe, client.frame());
EXPECT_EQ(url2, iframe->document().url().string().utf8());
EXPECT_FALSE(client.replacesCurrentHistoryItem());
}
// Tests that a navigation in a frame with a non-blank initial URL will create
// a new history item, unlike the case above.
TEST_P(ParameterizedWebFrameTest, FirstNonBlankSubframeNavigation) {
registerMockedHttpURLLoad("history.html");
registerMockedHttpURLLoad("find.html");
FrameTestHelpers::WebViewHelper webViewHelper;
TestHistoryWebFrameClient client;
webViewHelper.initializeAndLoad("about:blank", true, &client);
WebFrame* frame = webViewHelper.webView()->mainFrame();
std::string url1 = m_baseURL + "history.html";
FrameTestHelpers::loadFrame(
frame,
"javascript:var f = document.createElement('iframe'); "
"f.src = '" +
url1 +
"';"
"document.body.appendChild(f)");
WebFrame* iframe = frame->firstChild();
EXPECT_EQ(iframe, client.frame());
EXPECT_EQ(url1, iframe->document().url().string().utf8());
std::string url2 = m_baseURL + "find.html";
FrameTestHelpers::loadFrame(iframe, url2);
EXPECT_EQ(iframe, client.frame());
EXPECT_EQ(url2, iframe->document().url().string().utf8());
EXPECT_FALSE(client.replacesCurrentHistoryItem());
}
// Test verifies that layout will change a layer's scrollable attibutes
TEST_F(WebFrameTest, overflowHiddenRewrite) {
registerMockedHttpURLLoad("non-scrollable.html");
std::unique_ptr<FakeCompositingWebViewClient> fakeCompositingWebViewClient =
wrapUnique(new FakeCompositingWebViewClient());
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initialize(true, nullptr, fakeCompositingWebViewClient.get(),
nullptr, &configueCompositingWebView);
webViewHelper.resize(WebSize(100, 100));
FrameTestHelpers::loadFrame(webViewHelper.webView()->mainFrame(),
m_baseURL + "non-scrollable.html");
PaintLayerCompositor* compositor = webViewHelper.webView()->compositor();
ASSERT_TRUE(compositor->scrollLayer());
// Verify that the WebLayer is not scrollable initially.
GraphicsLayer* scrollLayer = compositor->scrollLayer();
WebLayer* webScrollLayer = scrollLayer->platformLayer();
ASSERT_FALSE(webScrollLayer->userScrollableHorizontal());
ASSERT_FALSE(webScrollLayer->userScrollableVertical());
// Call javascript to make the layer scrollable, and verify it.
WebLocalFrameImpl* frame =
(WebLocalFrameImpl*)webViewHelper.webView()->mainFrame();
frame->executeScript(WebScriptSource("allowScroll();"));
webViewHelper.webView()->updateAllLifecyclePhases();
ASSERT_TRUE(webScrollLayer->userScrollableHorizontal());
ASSERT_TRUE(webScrollLayer->userScrollableVertical());
}
// Test that currentHistoryItem reflects the current page, not the provisional
// load.
TEST_P(ParameterizedWebFrameTest, CurrentHistoryItem) {
registerMockedHttpURLLoad("fixed_layout.html");
std::string url = m_baseURL + "fixed_layout.html";
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initialize();
WebFrame* frame = webViewHelper.webView()->mainFrame();
const FrameLoader& mainFrameLoader =
webViewHelper.webView()->mainFrameImpl()->frame()->loader();
WebURLRequest request;
request.setURL(toKURL(url));
request.setRequestorOrigin(WebSecurityOrigin::createUnique());
frame->loadRequest(request);
// Before commit, there is no history item.
EXPECT_FALSE(mainFrameLoader.currentItem());
FrameTestHelpers::pumpPendingRequestsForFrameToLoad(frame);
// After commit, there is.
HistoryItem* item = mainFrameLoader.currentItem();
ASSERT_TRUE(item);
EXPECT_EQ(WTF::String(url.data()), item->urlString());
}
class FailCreateChildFrame : public FrameTestHelpers::TestWebFrameClient {
public:
FailCreateChildFrame() : m_callCount(0) {}
WebLocalFrame* createChildFrame(
WebLocalFrame* parent,
WebTreeScopeType scope,
const WebString& frameName,
const WebString& frameUniqueName,
WebSandboxFlags sandboxFlags,
const WebFrameOwnerProperties& frameOwnerProperties) override {
++m_callCount;
return nullptr;
}
int callCount() const { return m_callCount; }
private:
int m_callCount;
};
// Test that we don't crash if WebFrameClient::createChildFrame() fails.
TEST_P(ParameterizedWebFrameTest, CreateChildFrameFailure) {
registerMockedHttpURLLoad("create_child_frame_fail.html");
FailCreateChildFrame client;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "create_child_frame_fail.html",
true, &client);
EXPECT_EQ(1, client.callCount());
}
TEST_P(ParameterizedWebFrameTest, fixedPositionInFixedViewport) {
registerMockedHttpURLLoad("fixed-position-in-fixed-viewport.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(
m_baseURL + "fixed-position-in-fixed-viewport.html", true, nullptr,
nullptr, nullptr, enableViewportSettings);
WebViewImpl* webView = webViewHelper.webView();
webViewHelper.resize(WebSize(100, 100));
Document* document = webView->mainFrameImpl()->frame()->document();
Element* bottomFixed = document->getElementById("bottom-fixed");
Element* topBottomFixed = document->getElementById("top-bottom-fixed");
Element* rightFixed = document->getElementById("right-fixed");
Element* leftRightFixed = document->getElementById("left-right-fixed");
// The layout viewport will hit the min-scale limit of 0.25, so it'll be
// 400x800.
webViewHelper.resize(WebSize(100, 200));
EXPECT_EQ(800, bottomFixed->offsetTop() + bottomFixed->offsetHeight());
EXPECT_EQ(800, topBottomFixed->offsetHeight());
// Now the layout viewport hits the content width limit of 500px so it'll be
// 500x500.
webViewHelper.resize(WebSize(200, 200));
EXPECT_EQ(500, rightFixed->offsetLeft() + rightFixed->offsetWidth());
EXPECT_EQ(500, leftRightFixed->offsetWidth());
}
TEST_P(ParameterizedWebFrameTest, FrameViewMoveWithSetFrameRect) {
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad("about:blank");
webViewHelper.resize(WebSize(200, 200));
webViewHelper.webView()->updateAllLifecyclePhases();
FrameView* frameView = webViewHelper.webView()->mainFrameImpl()->frameView();
EXPECT_RECT_EQ(IntRect(0, 0, 200, 200), frameView->frameRect());
frameView->setFrameRect(IntRect(100, 100, 200, 200));
EXPECT_RECT_EQ(IntRect(100, 100, 200, 200), frameView->frameRect());
}
TEST_F(WebFrameTest, FrameViewScrollAccountsForTopControls) {
FakeCompositingWebViewClient client;
registerMockedHttpURLLoad("long_scroll.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "long_scroll.html", true, nullptr,
&client, nullptr, configureAndroid);
WebViewImpl* webView = webViewHelper.webView();
FrameView* frameView = webViewHelper.webView()->mainFrameImpl()->frameView();
float topControlsHeight = 40;
webView->resizeWithTopControls(WebSize(100, 100), topControlsHeight, false);
webView->setPageScaleFactor(2.0f);
webView->updateAllLifecyclePhases();
webView->mainFrame()->setScrollOffset(WebSize(0, 2000));
EXPECT_POINT_EQ(IntPoint(0, 1900), IntPoint(frameView->scrollOffset()));
// Simulate the top controls showing by 20px, thus shrinking the viewport
// and allowing it to scroll an additional 20px.
webView->applyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
1.0f, 20.0f / topControlsHeight);
EXPECT_POINT_EQ(IntPoint(0, 1920), frameView->maximumScrollPosition());
// Show more, make sure the scroll actually gets clamped.
webView->applyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
1.0f, 20.0f / topControlsHeight);
webView->mainFrame()->setScrollOffset(WebSize(0, 2000));
EXPECT_POINT_EQ(IntPoint(0, 1940), IntPoint(frameView->scrollOffset()));
// Hide until there's 10px showing.
webView->applyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
1.0f, -30.0f / topControlsHeight);
EXPECT_POINT_EQ(IntPoint(0, 1910), frameView->maximumScrollPosition());
// Simulate a LayoutPart::resize. The frame is resized to accomodate
// the top controls and Blink's view of the top controls matches that of
// the CC
webView->applyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
1.0f, 30.0f / topControlsHeight);
webView->resizeWithTopControls(WebSize(100, 60), 40.0f, true);
webView->updateAllLifecyclePhases();
EXPECT_POINT_EQ(IntPoint(0, 1940), frameView->maximumScrollPosition());
// Now simulate hiding.
webView->applyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
1.0f, -10.0f / topControlsHeight);
EXPECT_POINT_EQ(IntPoint(0, 1930), frameView->maximumScrollPosition());
// Reset to original state: 100px widget height, top controls fully hidden.
webView->applyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
1.0f, -30.0f / topControlsHeight);
webView->resizeWithTopControls(WebSize(100, 100), topControlsHeight, false);
webView->updateAllLifecyclePhases();
EXPECT_POINT_EQ(IntPoint(0, 1900), frameView->maximumScrollPosition());
// Show the top controls by just 1px, since we're zoomed in to 2X, that
// should allow an extra 0.5px of scrolling in the visual viewport. Make
// sure we're not losing any pixels when applying the adjustment on the
// main frame.
webView->applyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
1.0f, 1.0f / topControlsHeight);
EXPECT_POINT_EQ(IntPoint(0, 1901), frameView->maximumScrollPosition());
webView->applyViewportDeltas(WebFloatSize(), WebFloatSize(), WebFloatSize(),
1.0f, 2.0f / topControlsHeight);
EXPECT_POINT_EQ(IntPoint(0, 1903), frameView->maximumScrollPosition());
}
TEST_F(WebFrameTest, MaximumScrollPositionCanBeNegative) {
registerMockedHttpURLLoad("rtl-overview-mode.html");
FixedLayoutTestWebViewClient client;
client.m_screenInfo.deviceScaleFactor = 1;
int viewportWidth = 640;
int viewportHeight = 480;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "rtl-overview-mode.html", true,
nullptr, &client, nullptr,
enableViewportSettings);
webViewHelper.webView()->setInitialPageScaleOverride(-1);
webViewHelper.webView()->settings()->setWideViewportQuirkEnabled(true);
webViewHelper.webView()->settings()->setLoadWithOverviewMode(true);
webViewHelper.webView()->settings()->setUseWideViewport(true);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
webViewHelper.webView()->updateAllLifecyclePhases();
FrameView* frameView = webViewHelper.webView()->mainFrameImpl()->frameView();
EXPECT_LT(frameView->maximumScrollPosition().x(), 0);
}
TEST_P(ParameterizedWebFrameTest, FullscreenLayerSize) {
FakeCompositingWebViewClient client;
registerMockedHttpURLLoad("fullscreen_div.html");
FrameTestHelpers::WebViewHelper webViewHelper;
int viewportWidth = 640;
int viewportHeight = 480;
client.m_screenInfo.rect.width = viewportWidth;
client.m_screenInfo.rect.height = viewportHeight;
WebViewImpl* webViewImpl = webViewHelper.initializeAndLoad(
m_baseURL + "fullscreen_div.html", true, nullptr, &client, nullptr,
configureAndroid);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
webViewImpl->updateAllLifecyclePhases();
Document* document = webViewImpl->mainFrameImpl()->frame()->document();
UserGestureIndicator gesture(DefinitelyProcessingUserGesture);
Element* divFullscreen = document->getElementById("div1");
Fullscreen::requestFullscreen(*divFullscreen, Fullscreen::PrefixedRequest);
webViewImpl->didEnterFullscreen();
webViewImpl->updateAllLifecyclePhases();
EXPECT_EQ(Fullscreen::currentFullScreenElementFrom(*document), divFullscreen);
// Verify that the element is sized to the viewport.
LayoutFullScreen* fullscreenLayoutObject =
Fullscreen::from(*document).fullScreenLayoutObject();
EXPECT_EQ(viewportWidth, fullscreenLayoutObject->logicalWidth().toInt());
EXPECT_EQ(viewportHeight, fullscreenLayoutObject->logicalHeight().toInt());
// Verify it's updated after a device rotation.
client.m_screenInfo.rect.width = viewportHeight;
client.m_screenInfo.rect.height = viewportWidth;
webViewHelper.resize(WebSize(viewportHeight, viewportWidth));
webViewImpl->updateAllLifecyclePhases();
EXPECT_EQ(viewportHeight, fullscreenLayoutObject->logicalWidth().toInt());
EXPECT_EQ(viewportWidth, fullscreenLayoutObject->logicalHeight().toInt());
}
TEST_F(WebFrameTest, FullscreenLayerNonScrollable) {
FakeCompositingWebViewClient client;
registerMockedHttpURLLoad("fullscreen_div.html");
FrameTestHelpers::WebViewHelper webViewHelper;
int viewportWidth = 640;
int viewportHeight = 480;
WebViewImpl* webViewImpl = webViewHelper.initializeAndLoad(
m_baseURL + "fullscreen_div.html", true, nullptr, &client, nullptr,
configureAndroid);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
webViewImpl->updateAllLifecyclePhases();
Document* document = webViewImpl->mainFrameImpl()->frame()->document();
UserGestureIndicator gesture(DefinitelyProcessingUserGesture);
Element* divFullscreen = document->getElementById("div1");
Fullscreen::requestFullscreen(*divFullscreen, Fullscreen::PrefixedRequest);
webViewImpl->didEnterFullscreen();
webViewImpl->updateAllLifecyclePhases();
// Verify that the viewports are nonscrollable.
EXPECT_EQ(Fullscreen::currentFullScreenElementFrom(*document), divFullscreen);
FrameView* frameView = webViewHelper.webView()->mainFrameImpl()->frameView();
WebLayer* layoutViewportScrollLayer =
webViewImpl->compositor()->scrollLayer()->platformLayer();
WebLayer* visualViewportScrollLayer = frameView->page()
->frameHost()
.visualViewport()
.scrollLayer()
->platformLayer();
ASSERT_FALSE(layoutViewportScrollLayer->userScrollableHorizontal());
ASSERT_FALSE(layoutViewportScrollLayer->userScrollableVertical());
ASSERT_FALSE(visualViewportScrollLayer->userScrollableHorizontal());
ASSERT_FALSE(visualViewportScrollLayer->userScrollableVertical());
// Verify that the viewports are scrollable upon exiting fullscreen.
webViewImpl->didExitFullscreen();
webViewImpl->updateAllLifecyclePhases();
EXPECT_EQ(Fullscreen::currentFullScreenElementFrom(*document), nullptr);
ASSERT_TRUE(layoutViewportScrollLayer->userScrollableHorizontal());
ASSERT_TRUE(layoutViewportScrollLayer->userScrollableVertical());
ASSERT_TRUE(visualViewportScrollLayer->userScrollableHorizontal());
ASSERT_TRUE(visualViewportScrollLayer->userScrollableVertical());
}
TEST_P(ParameterizedWebFrameTest, FullscreenMainFrame) {
FakeCompositingWebViewClient client;
registerMockedHttpURLLoad("fullscreen_div.html");
FrameTestHelpers::WebViewHelper webViewHelper;
int viewportWidth = 640;
int viewportHeight = 480;
WebViewImpl* webViewImpl = webViewHelper.initializeAndLoad(
m_baseURL + "fullscreen_div.html", true, nullptr, &client, nullptr,
configureAndroid);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
webViewImpl->updateAllLifecyclePhases();
Document* document = webViewImpl->mainFrameImpl()->frame()->document();
UserGestureIndicator gesture(DefinitelyProcessingUserGesture);
Fullscreen::requestFullscreen(*document->documentElement(),
Fullscreen::PrefixedRequest);
webViewImpl->didEnterFullscreen();
webViewImpl->updateAllLifecyclePhases();
// Verify that the main frame is still scrollable.
EXPECT_EQ(Fullscreen::currentFullScreenElementFrom(*document),
document->documentElement());
WebLayer* webScrollLayer =
webViewImpl->compositor()->scrollLayer()->platformLayer();
ASSERT_TRUE(webScrollLayer->scrollable());
ASSERT_TRUE(webScrollLayer->userScrollableHorizontal());
ASSERT_TRUE(webScrollLayer->userScrollableVertical());
// Verify the main frame still behaves correctly after a resize.
webViewHelper.resize(WebSize(viewportHeight, viewportWidth));
ASSERT_TRUE(webScrollLayer->scrollable());
ASSERT_TRUE(webScrollLayer->userScrollableHorizontal());
ASSERT_TRUE(webScrollLayer->userScrollableVertical());
}
TEST_P(ParameterizedWebFrameTest, FullscreenSubframe) {
FakeCompositingWebViewClient client;
registerMockedHttpURLLoad("fullscreen_iframe.html");
registerMockedHttpURLLoad("fullscreen_div.html");
FrameTestHelpers::WebViewHelper webViewHelper;
WebViewImpl* webViewImpl = webViewHelper.initializeAndLoad(
m_baseURL + "fullscreen_iframe.html", true, nullptr, &client, nullptr,
configureAndroid);
int viewportWidth = 640;
int viewportHeight = 480;
client.m_screenInfo.rect.width = viewportWidth;
client.m_screenInfo.rect.height = viewportHeight;
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
webViewImpl->updateAllLifecyclePhases();
Document* document =
toWebLocalFrameImpl(webViewHelper.webView()->mainFrame()->firstChild())
->frame()
->document();
UserGestureIndicator gesture(DefinitelyProcessingUserGesture);
Element* divFullscreen = document->getElementById("div1");
Fullscreen::requestFullscreen(*divFullscreen, Fullscreen::PrefixedRequest);
webViewImpl->didEnterFullscreen();
webViewImpl->updateAllLifecyclePhases();
// Verify that the element is sized to the viewport.
LayoutFullScreen* fullscreenLayoutObject =
Fullscreen::from(*document).fullScreenLayoutObject();
EXPECT_EQ(viewportWidth, fullscreenLayoutObject->logicalWidth().toInt());
EXPECT_EQ(viewportHeight, fullscreenLayoutObject->logicalHeight().toInt());
// Verify it's updated after a device rotation.
client.m_screenInfo.rect.width = viewportHeight;
client.m_screenInfo.rect.height = viewportWidth;
webViewHelper.resize(WebSize(viewportHeight, viewportWidth));
webViewImpl->updateAllLifecyclePhases();
EXPECT_EQ(viewportHeight, fullscreenLayoutObject->logicalWidth().toInt());
EXPECT_EQ(viewportWidth, fullscreenLayoutObject->logicalHeight().toInt());
}
TEST_P(ParameterizedWebFrameTest, FullscreenWithTinyViewport) {
FakeCompositingWebViewClient client;
registerMockedHttpURLLoad("viewport-tiny.html");
FrameTestHelpers::WebViewHelper webViewHelper;
WebViewImpl* webViewImpl = webViewHelper.initializeAndLoad(
m_baseURL + "viewport-tiny.html", true, nullptr, &client, nullptr,
configureAndroid);
int viewportWidth = 384;
int viewportHeight = 640;
client.m_screenInfo.rect.width = viewportWidth;
client.m_screenInfo.rect.height = viewportHeight;
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
webViewImpl->updateAllLifecyclePhases();
LayoutViewItem layoutViewItem =
webViewHelper.webView()->mainFrameImpl()->frameView()->layoutViewItem();
EXPECT_EQ(320, layoutViewItem.logicalWidth().floor());
EXPECT_EQ(533, layoutViewItem.logicalHeight().floor());
EXPECT_FLOAT_EQ(1.2, webViewImpl->pageScaleFactor());
EXPECT_FLOAT_EQ(1.2, webViewImpl->minimumPageScaleFactor());
EXPECT_FLOAT_EQ(5.0, webViewImpl->maximumPageScaleFactor());
Document* document = webViewImpl->mainFrameImpl()->frame()->document();
UserGestureIndicator gesture(DefinitelyProcessingUserGesture);
Fullscreen::requestFullscreen(*document->documentElement(),
Fullscreen::PrefixedRequest);
webViewImpl->didEnterFullscreen();
webViewImpl->updateAllLifecyclePhases();
EXPECT_EQ(384, layoutViewItem.logicalWidth().floor());
EXPECT_EQ(640, layoutViewItem.logicalHeight().floor());
EXPECT_FLOAT_EQ(1.0, webViewImpl->pageScaleFactor());
EXPECT_FLOAT_EQ(1.0, webViewImpl->minimumPageScaleFactor());
EXPECT_FLOAT_EQ(1.0, webViewImpl->maximumPageScaleFactor());
webViewImpl->didExitFullscreen();
webViewImpl->updateAllLifecyclePhases();
EXPECT_EQ(320, layoutViewItem.logicalWidth().floor());
EXPECT_EQ(533, layoutViewItem.logicalHeight().floor());
EXPECT_FLOAT_EQ(1.2, webViewImpl->pageScaleFactor());
EXPECT_FLOAT_EQ(1.2, webViewImpl->minimumPageScaleFactor());
EXPECT_FLOAT_EQ(5.0, webViewImpl->maximumPageScaleFactor());
}
TEST_P(ParameterizedWebFrameTest, FullscreenResizeWithTinyViewport) {
FakeCompositingWebViewClient client;
registerMockedHttpURLLoad("viewport-tiny.html");
FrameTestHelpers::WebViewHelper webViewHelper;
WebViewImpl* webViewImpl = webViewHelper.initializeAndLoad(
m_baseURL + "viewport-tiny.html", true, nullptr, &client, nullptr,
configureAndroid);
int viewportWidth = 384;
int viewportHeight = 640;
client.m_screenInfo.rect.width = viewportWidth;
client.m_screenInfo.rect.height = viewportHeight;
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
webViewImpl->updateAllLifecyclePhases();
LayoutViewItem layoutViewItem =
webViewHelper.webView()->mainFrameImpl()->frameView()->layoutViewItem();
Document* document = webViewImpl->mainFrameImpl()->frame()->document();
UserGestureIndicator gesture(DefinitelyProcessingUserGesture);
Fullscreen::requestFullscreen(*document->documentElement(),
Fullscreen::PrefixedRequest);
webViewImpl->didEnterFullscreen();
webViewImpl->updateAllLifecyclePhases();
EXPECT_EQ(384, layoutViewItem.logicalWidth().floor());
EXPECT_EQ(640, layoutViewItem.logicalHeight().floor());
EXPECT_FLOAT_EQ(1.0, webViewImpl->pageScaleFactor());
EXPECT_FLOAT_EQ(1.0, webViewImpl->minimumPageScaleFactor());
EXPECT_FLOAT_EQ(1.0, webViewImpl->maximumPageScaleFactor());
viewportWidth = 640;
viewportHeight = 384;
client.m_screenInfo.rect.width = viewportWidth;
client.m_screenInfo.rect.height = viewportHeight;
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
webViewImpl->updateAllLifecyclePhases();
EXPECT_EQ(640, layoutViewItem.logicalWidth().floor());
EXPECT_EQ(384, layoutViewItem.logicalHeight().floor());
EXPECT_FLOAT_EQ(1.0, webViewImpl->pageScaleFactor());
EXPECT_FLOAT_EQ(1.0, webViewImpl->minimumPageScaleFactor());
EXPECT_FLOAT_EQ(1.0, webViewImpl->maximumPageScaleFactor());
webViewImpl->didExitFullscreen();
webViewImpl->updateAllLifecyclePhases();
EXPECT_EQ(320, layoutViewItem.logicalWidth().floor());
EXPECT_EQ(192, layoutViewItem.logicalHeight().floor());
EXPECT_FLOAT_EQ(2, webViewImpl->pageScaleFactor());
EXPECT_FLOAT_EQ(2, webViewImpl->minimumPageScaleFactor());
EXPECT_FLOAT_EQ(5.0, webViewImpl->maximumPageScaleFactor());
}
TEST_P(ParameterizedWebFrameTest, FullscreenRestoreScaleFactorUponExiting) {
// The purpose of this test is to more precisely simulate the sequence of
// resize and switching fullscreen state operations on WebView, with the
// interference from Android status bars like a real device does.
// This verifies we handle the transition and restore states correctly.
WebSize screenSizeMinusStatusBarsMinusUrlBar(598, 303);
WebSize screenSizeMinusStatusBars(598, 359);
WebSize screenSize(640, 384);
FakeCompositingWebViewClient client;
registerMockedHttpURLLoad("fullscreen_restore_scale_factor.html");
FrameTestHelpers::WebViewHelper webViewHelper;
WebViewImpl* webViewImpl = webViewHelper.initializeAndLoad(
m_baseURL + "fullscreen_restore_scale_factor.html", true, nullptr,
&client, nullptr, &configureAndroid);
client.m_screenInfo.rect.width = screenSizeMinusStatusBarsMinusUrlBar.width;
client.m_screenInfo.rect.height = screenSizeMinusStatusBarsMinusUrlBar.height;
webViewHelper.resize(screenSizeMinusStatusBarsMinusUrlBar);
LayoutViewItem layoutViewItem =
webViewHelper.webView()->mainFrameImpl()->frameView()->layoutViewItem();
EXPECT_EQ(screenSizeMinusStatusBarsMinusUrlBar.width,
layoutViewItem.logicalWidth().floor());
EXPECT_EQ(screenSizeMinusStatusBarsMinusUrlBar.height,
layoutViewItem.logicalHeight().floor());
EXPECT_FLOAT_EQ(1.0, webViewImpl->pageScaleFactor());
EXPECT_FLOAT_EQ(1.0, webViewImpl->minimumPageScaleFactor());
EXPECT_FLOAT_EQ(5.0, webViewImpl->maximumPageScaleFactor());
{
Document* document = webViewImpl->mainFrameImpl()->frame()->document();
UserGestureIndicator gesture(DefinitelyProcessingUserGesture);
Fullscreen::requestFullscreen(*document->body(),
Fullscreen::PrefixedRequest);
}
webViewImpl->didEnterFullscreen();
webViewImpl->updateAllLifecyclePhases();
client.m_screenInfo.rect.width = screenSizeMinusStatusBars.width;
client.m_screenInfo.rect.height = screenSizeMinusStatusBars.height;
webViewHelper.resize(screenSizeMinusStatusBars);
client.m_screenInfo.rect.width = screenSize.width;
client.m_screenInfo.rect.height = screenSize.height;
webViewHelper.resize(screenSize);
EXPECT_EQ(screenSize.width, layoutViewItem.logicalWidth().floor());
EXPECT_EQ(screenSize.height, layoutViewItem.logicalHeight().floor());
EXPECT_FLOAT_EQ(1.0, webViewImpl->pageScaleFactor());
EXPECT_FLOAT_EQ(1.0, webViewImpl->minimumPageScaleFactor());
EXPECT_FLOAT_EQ(1.0, webViewImpl->maximumPageScaleFactor());
webViewImpl->didExitFullscreen();
webViewImpl->updateAllLifecyclePhases();
client.m_screenInfo.rect.width = screenSizeMinusStatusBars.width;
client.m_screenInfo.rect.height = screenSizeMinusStatusBars.height;
webViewHelper.resize(screenSizeMinusStatusBars);
client.m_screenInfo.rect.width = screenSizeMinusStatusBarsMinusUrlBar.width;
client.m_screenInfo.rect.height = screenSizeMinusStatusBarsMinusUrlBar.height;
webViewHelper.resize(screenSizeMinusStatusBarsMinusUrlBar);
EXPECT_EQ(screenSizeMinusStatusBarsMinusUrlBar.width,
layoutViewItem.logicalWidth().floor());
EXPECT_EQ(screenSizeMinusStatusBarsMinusUrlBar.height,
layoutViewItem.logicalHeight().floor());
EXPECT_FLOAT_EQ(1.0, webViewImpl->pageScaleFactor());
EXPECT_FLOAT_EQ(1.0, webViewImpl->minimumPageScaleFactor());
EXPECT_FLOAT_EQ(5.0, webViewImpl->maximumPageScaleFactor());
}
// Tests that leaving fullscreen by navigating to a new page resets the
// fullscreen page scale constraints.
TEST_P(ParameterizedWebFrameTest, ClearFullscreenConstraintsOnNavigation) {
registerMockedHttpURLLoad("viewport-tiny.html");
FrameTestHelpers::WebViewHelper webViewHelper;
int viewportWidth = 100;
int viewportHeight = 200;
WebViewImpl* webViewImpl = webViewHelper.initializeAndLoad(
m_baseURL + "viewport-tiny.html", true, nullptr, nullptr, nullptr,
configureAndroid);
webViewHelper.resize(WebSize(viewportWidth, viewportHeight));
webViewImpl->updateAllLifecyclePhases();
// viewport-tiny.html specifies a 320px layout width.
LayoutViewItem layoutViewItem =
webViewImpl->mainFrameImpl()->frameView()->layoutViewItem();
EXPECT_EQ(320, layoutViewItem.logicalWidth().floor());
EXPECT_EQ(640, layoutViewItem.logicalHeight().floor());
EXPECT_FLOAT_EQ(0.3125, webViewImpl->pageScaleFactor());
EXPECT_FLOAT_EQ(0.3125, webViewImpl->minimumPageScaleFactor());
EXPECT_FLOAT_EQ(5.0, webViewImpl->maximumPageScaleFactor());
Document* document = webViewImpl->mainFrameImpl()->frame()->document();
UserGestureIndicator gesture(DefinitelyProcessingUserGesture);
Fullscreen::requestFullscreen(*document->documentElement(),
Fullscreen::PrefixedRequest);
webViewImpl->didEnterFullscreen();
webViewImpl->updateAllLifecyclePhases();
// Entering fullscreen causes layout size and page scale limits to be
// overridden.
EXPECT_EQ(100, layoutViewItem.logicalWidth().floor());
EXPECT_EQ(200, layoutViewItem.logicalHeight().floor());
EXPECT_FLOAT_EQ(1.0, webViewImpl->pageScaleFactor());
EXPECT_FLOAT_EQ(1.0, webViewImpl->minimumPageScaleFactor());
EXPECT_FLOAT_EQ(1.0, webViewImpl->maximumPageScaleFactor());
const char source[] = "<meta name=\"viewport\" content=\"width=200\">";
// Load a new page before exiting fullscreen.
KURL testURL = toKURL("about:blank");
WebFrame* frame = webViewHelper.webView()->mainFrame();
FrameTestHelpers::loadHTMLString(frame, source, testURL);
webViewImpl->didExitFullscreen();
webViewImpl->updateAllLifecyclePhases();
// Make sure the new page's layout size and scale factor limits aren't
// overridden.
layoutViewItem = webViewImpl->mainFrameImpl()->frameView()->layoutViewItem();
EXPECT_EQ(200, layoutViewItem.logicalWidth().floor());
EXPECT_EQ(400, layoutViewItem.logicalHeight().floor());
EXPECT_FLOAT_EQ(0.5, webViewImpl->minimumPageScaleFactor());
EXPECT_FLOAT_EQ(5.0, webViewImpl->maximumPageScaleFactor());
}
TEST_P(ParameterizedWebFrameTest, LayoutBlockPercentHeightDescendants) {
registerMockedHttpURLLoad("percent-height-descendants.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL +
"percent-height-descendants.html");
WebViewImpl* webView = webViewHelper.webView();
webViewHelper.resize(WebSize(800, 800));
webView->updateAllLifecyclePhases();
Document* document = webView->mainFrameImpl()->frame()->document();
LayoutBlock* container =
toLayoutBlock(document->getElementById("container")->layoutObject());
LayoutBox* percentHeightInAnonymous = toLayoutBox(
document->getElementById("percent-height-in-anonymous")->layoutObject());
LayoutBox* percentHeightDirectChild = toLayoutBox(
document->getElementById("percent-height-direct-child")->layoutObject());
EXPECT_TRUE(container->hasPercentHeightDescendant(percentHeightInAnonymous));
EXPECT_TRUE(container->hasPercentHeightDescendant(percentHeightDirectChild));
ASSERT_TRUE(container->percentHeightDescendants());
ASSERT_TRUE(container->hasPercentHeightDescendants());
EXPECT_EQ(2U, container->percentHeightDescendants()->size());
EXPECT_TRUE(container->percentHeightDescendants()->contains(
percentHeightInAnonymous));
EXPECT_TRUE(container->percentHeightDescendants()->contains(
percentHeightDirectChild));
LayoutBlock* anonymousBlock = percentHeightInAnonymous->containingBlock();
EXPECT_TRUE(anonymousBlock->isAnonymous());
EXPECT_FALSE(anonymousBlock->hasPercentHeightDescendants());
}
TEST_P(ParameterizedWebFrameTest, HasVisibleContentOnVisibleFrames) {
registerMockedHttpURLLoad("visible_frames.html");
FrameTestHelpers::WebViewHelper webViewHelper;
WebViewImpl* webViewImpl =
webViewHelper.initializeAndLoad(m_baseURL + "visible_frames.html");
for (WebFrame* frame = webViewImpl->mainFrameImpl()->traverseNext(false);
frame; frame = frame->traverseNext(false)) {
EXPECT_TRUE(frame->hasVisibleContent());
}
}
TEST_P(ParameterizedWebFrameTest, HasVisibleContentOnHiddenFrames) {
registerMockedHttpURLLoad("hidden_frames.html");
FrameTestHelpers::WebViewHelper webViewHelper;
WebViewImpl* webViewImpl =
webViewHelper.initializeAndLoad(m_baseURL + "hidden_frames.html");
for (WebFrame* frame = webViewImpl->mainFrameImpl()->traverseNext(false);
frame; frame = frame->traverseNext(false)) {
EXPECT_FALSE(frame->hasVisibleContent());
}
}
class ManifestChangeWebFrameClient
: public FrameTestHelpers::TestWebFrameClient {
public:
ManifestChangeWebFrameClient() : m_manifestChangeCount(0) {}
void didChangeManifest() override { ++m_manifestChangeCount; }
int manifestChangeCount() { return m_manifestChangeCount; }
private:
int m_manifestChangeCount;
};
TEST_P(ParameterizedWebFrameTest, NotifyManifestChange) {
registerMockedHttpURLLoad("link-manifest-change.html");
ManifestChangeWebFrameClient webFrameClient;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "link-manifest-change.html", true,
&webFrameClient);
EXPECT_EQ(14, webFrameClient.manifestChangeCount());
}
static Resource* fetchManifest(Document* document, const KURL& url) {
FetchRequest fetchRequest =
FetchRequest(ResourceRequest(url), FetchInitiatorInfo());
fetchRequest.mutableResourceRequest().setRequestContext(
WebURLRequest::RequestContextManifest);
return RawResource::fetchSynchronously(fetchRequest, document->fetcher());
}
TEST_P(ParameterizedWebFrameTest, ManifestFetch) {
registerMockedHttpURLLoad("foo.html");
registerMockedHttpURLLoad("link-manifest-fetch.json");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "foo.html");
Document* document =
webViewHelper.webView()->mainFrameImpl()->frame()->document();
Resource* resource =
fetchManifest(document, toKURL(m_baseURL + "link-manifest-fetch.json"));
EXPECT_TRUE(resource->isLoaded());
}
TEST_P(ParameterizedWebFrameTest, ManifestCSPFetchAllow) {
URLTestHelpers::registerMockedURLLoad(
toKURL(m_notBaseURL + "link-manifest-fetch.json"),
"link-manifest-fetch.json");
registerMockedHttpURLLoadWithCSP("foo.html", "manifest-src *");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "foo.html");
Document* document =
webViewHelper.webView()->mainFrameImpl()->frame()->document();
Resource* resource = fetchManifest(
document, toKURL(m_notBaseURL + "link-manifest-fetch.json"));
EXPECT_TRUE(resource->isLoaded());
}
TEST_P(ParameterizedWebFrameTest, ManifestCSPFetchSelf) {
URLTestHelpers::registerMockedURLLoad(
toKURL(m_notBaseURL + "link-manifest-fetch.json"),
"link-manifest-fetch.json");
registerMockedHttpURLLoadWithCSP("foo.html", "manifest-src 'self'");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "foo.html");
Document* document =
webViewHelper.webView()->mainFrameImpl()->frame()->document();
Resource* resource = fetchManifest(
document, toKURL(m_notBaseURL + "link-manifest-fetch.json"));
EXPECT_EQ(0, resource); // Fetching resource wasn't allowed.
}
TEST_P(ParameterizedWebFrameTest, ManifestCSPFetchSelfReportOnly) {
URLTestHelpers::registerMockedURLLoad(
toKURL(m_notBaseURL + "link-manifest-fetch.json"),
"link-manifest-fetch.json");
registerMockedHttpURLLoadWithCSP("foo.html", "manifest-src 'self'",
/* report only */ true);
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "foo.html");
Document* document =
webViewHelper.webView()->mainFrameImpl()->frame()->document();
Resource* resource = fetchManifest(
document, toKURL(m_notBaseURL + "link-manifest-fetch.json"));
EXPECT_TRUE(resource->isLoaded());
}
TEST_P(ParameterizedWebFrameTest, ReloadBypassingCache) {
// Check that a reload ignoring cache on a frame will result in the cache
// policy of the request being set to ReloadBypassingCache.
registerMockedHttpURLLoad("foo.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "foo.html", true);
WebFrame* frame = webViewHelper.webView()->mainFrame();
FrameTestHelpers::reloadFrameIgnoringCache(frame);
EXPECT_EQ(WebCachePolicy::BypassingCache,
frame->dataSource()->request().getCachePolicy());
}
static void nodeImageTestValidation(const IntSize& referenceBitmapSize,
DragImage* dragImage) {
// Prepare the reference bitmap.
SkBitmap bitmap;
bitmap.allocN32Pixels(referenceBitmapSize.width(),
referenceBitmapSize.height());
SkCanvas canvas(bitmap);
canvas.drawColor(SK_ColorGREEN);
EXPECT_EQ(referenceBitmapSize.width(), dragImage->size().width());
EXPECT_EQ(referenceBitmapSize.height(), dragImage->size().height());
const SkBitmap& dragBitmap = dragImage->bitmap();
SkAutoLockPixels lockPixel(dragBitmap);
EXPECT_EQ(
0, memcmp(bitmap.getPixels(), dragBitmap.getPixels(), bitmap.getSize()));
}
TEST_P(ParameterizedWebFrameTest, NodeImageTestCSSTransformDescendant) {
FrameTestHelpers::WebViewHelper webViewHelper;
std::unique_ptr<DragImage> dragImage = nodeImageTestSetup(
&webViewHelper, std::string("case-css-3dtransform-descendant"));
EXPECT_TRUE(dragImage);
nodeImageTestValidation(IntSize(40, 40), dragImage.get());
}
TEST_P(ParameterizedWebFrameTest, NodeImageTestCSSTransform) {
FrameTestHelpers::WebViewHelper webViewHelper;
std::unique_ptr<DragImage> dragImage =
nodeImageTestSetup(&webViewHelper, std::string("case-css-transform"));
EXPECT_TRUE(dragImage);
nodeImageTestValidation(IntSize(40, 40), dragImage.get());
}
TEST_P(ParameterizedWebFrameTest, NodeImageTestCSS3DTransform) {
FrameTestHelpers::WebViewHelper webViewHelper;
std::unique_ptr<DragImage> dragImage =
nodeImageTestSetup(&webViewHelper, std::string("case-css-3dtransform"));
EXPECT_TRUE(dragImage);
nodeImageTestValidation(IntSize(40, 40), dragImage.get());
}
TEST_P(ParameterizedWebFrameTest, NodeImageTestInlineBlock) {
FrameTestHelpers::WebViewHelper webViewHelper;
std::unique_ptr<DragImage> dragImage =
nodeImageTestSetup(&webViewHelper, std::string("case-inlineblock"));
EXPECT_TRUE(dragImage);
nodeImageTestValidation(IntSize(40, 40), dragImage.get());
}
TEST_P(ParameterizedWebFrameTest, NodeImageTestFloatLeft) {
FrameTestHelpers::WebViewHelper webViewHelper;
std::unique_ptr<DragImage> dragImage = nodeImageTestSetup(
&webViewHelper, std::string("case-float-left-overflow-hidden"));
EXPECT_TRUE(dragImage);
nodeImageTestValidation(IntSize(40, 40), dragImage.get());
}
// Crashes on Android: http://crbug.com/403804
#if OS(ANDROID)
TEST_P(ParameterizedWebFrameTest, DISABLED_PrintingBasic)
#else
TEST_P(ParameterizedWebFrameTest, PrintingBasic)
#endif
{
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad("data:text/html,Hello, world.");
WebFrame* frame = webViewHelper.webView()->mainFrame();
WebPrintParams printParams;
printParams.printContentArea.width = 500;
printParams.printContentArea.height = 500;
int pageCount = frame->printBegin(printParams);
EXPECT_EQ(1, pageCount);
frame->printEnd();
}
class ThemeColorTestWebFrameClient
: public FrameTestHelpers::TestWebFrameClient {
public:
ThemeColorTestWebFrameClient() : m_didNotify(false) {}
void reset() { m_didNotify = false; }
bool didNotify() const { return m_didNotify; }
private:
virtual void didChangeThemeColor() { m_didNotify = true; }
bool m_didNotify;
};
TEST_P(ParameterizedWebFrameTest, ThemeColor) {
registerMockedHttpURLLoad("theme_color_test.html");
FrameTestHelpers::WebViewHelper webViewHelper;
ThemeColorTestWebFrameClient client;
webViewHelper.initializeAndLoad(m_baseURL + "theme_color_test.html", true,
&client);
EXPECT_TRUE(client.didNotify());
WebLocalFrameImpl* frame = webViewHelper.webView()->mainFrameImpl();
EXPECT_EQ(0xff0000ff, frame->document().themeColor());
// Change color by rgb.
client.reset();
frame->executeScript(
WebScriptSource("document.getElementById('tc1').setAttribute('content', "
"'rgb(0, 0, 0)');"));
EXPECT_TRUE(client.didNotify());
EXPECT_EQ(0xff000000, frame->document().themeColor());
// Change color by hsl.
client.reset();
frame->executeScript(
WebScriptSource("document.getElementById('tc1').setAttribute('content', "
"'hsl(240,100%, 50%)');"));
EXPECT_TRUE(client.didNotify());
EXPECT_EQ(0xff0000ff, frame->document().themeColor());
// Change of second theme-color meta tag will not change frame's theme
// color.
client.reset();
frame->executeScript(WebScriptSource(
"document.getElementById('tc2').setAttribute('content', '#00FF00');"));
EXPECT_TRUE(client.didNotify());
EXPECT_EQ(0xff0000ff, frame->document().themeColor());
}
// Make sure that an embedder-triggered detach with a remote frame parent
// doesn't leave behind dangling pointers.
TEST_P(ParameterizedWebFrameTest, EmbedderTriggeredDetachWithRemoteMainFrame) {
// FIXME: Refactor some of this logic into WebViewHelper to make it easier to
// write tests with a top-level remote frame.
FrameTestHelpers::TestWebViewClient viewClient;
FrameTestHelpers::TestWebRemoteFrameClient remoteClient;
WebView* view = WebView::create(&viewClient, WebPageVisibilityStateVisible);
view->setMainFrame(remoteClient.frame());
WebLocalFrame* childFrame =
FrameTestHelpers::createLocalChild(view->mainFrame()->toWebRemoteFrame());
// Purposely keep the LocalFrame alive so it's the last thing to be destroyed.
Persistent<Frame> childCoreFrame = childFrame->toImplBase()->frame();
view->close();
childCoreFrame.clear();
}
class WebFrameSwapTest : public WebFrameTest {
protected:
WebFrameSwapTest() {
registerMockedHttpURLLoad("frame-a-b-c.html");
registerMockedHttpURLLoad("subframe-a.html");
registerMockedHttpURLLoad("subframe-b.html");
registerMockedHttpURLLoad("subframe-c.html");
registerMockedHttpURLLoad("subframe-hello.html");
m_webViewHelper.initializeAndLoad(m_baseURL + "frame-a-b-c.html", true);
}
void reset() { m_webViewHelper.reset(); }
WebFrame* mainFrame() const { return m_webViewHelper.webView()->mainFrame(); }
WebView* webView() const { return m_webViewHelper.webView(); }
private:
FrameTestHelpers::WebViewHelper m_webViewHelper;
};
TEST_F(WebFrameSwapTest, SwapMainFrame) {
WebRemoteFrame* remoteFrame =
WebRemoteFrame::create(WebTreeScopeType::Document, nullptr);
mainFrame()->swap(remoteFrame);
FrameTestHelpers::TestWebFrameClient client;
WebLocalFrame* localFrame = WebLocalFrame::createProvisional(
&client, remoteFrame, WebSandboxFlags::None);
FrameTestHelpers::TestWebWidgetClient webWidgetClient;
WebFrameWidget::create(&webWidgetClient, localFrame);
remoteFrame->swap(localFrame);
// Finally, make sure an embedder triggered load in the local frame swapped
// back in works.
FrameTestHelpers::loadFrame(localFrame, m_baseURL + "subframe-hello.html");
std::string content =
WebFrameContentDumper::dumpWebViewAsText(webView(), 1024).utf8();
EXPECT_EQ("hello", content);
// Manually reset to break WebViewHelper's dependency on the stack allocated
// TestWebFrameClient.
reset();
remoteFrame->close();
}
TEST_F(WebFrameSwapTest, ValidateSizeOnRemoteToLocalMainFrameSwap) {
WebSize size(111, 222);
WebRemoteFrame* remoteFrame =
WebRemoteFrame::create(WebTreeScopeType::Document, nullptr);
mainFrame()->swap(remoteFrame);
remoteFrame->view()->resize(size);
FrameTestHelpers::TestWebFrameClient client;
WebLocalFrame* localFrame = WebLocalFrame::createProvisional(
&client, remoteFrame, WebSandboxFlags::None);
remoteFrame->swap(localFrame);
// Verify that the size that was set with a remote main frame is correct
// after swapping to a local frame.
FrameHost* host =
toWebViewImpl(localFrame->view())->page()->mainFrame()->host();
EXPECT_EQ(size.width, host->visualViewport().size().width());
EXPECT_EQ(size.height, host->visualViewport().size().height());
// Manually reset to break WebViewHelper's dependency on the stack allocated
// TestWebFrameClient.
reset();
remoteFrame->close();
}
namespace {
class SwapMainFrameWhenTitleChangesWebFrameClient
: public FrameTestHelpers::TestWebFrameClient {
public:
SwapMainFrameWhenTitleChangesWebFrameClient() : m_remoteFrame(nullptr) {}
~SwapMainFrameWhenTitleChangesWebFrameClient() override {
if (m_remoteFrame)
m_remoteFrame->close();
}
void didReceiveTitle(WebLocalFrame* frame,
const WebString&,
WebTextDirection) override {
if (!frame->parent()) {
m_remoteFrame =
WebRemoteFrame::create(WebTreeScopeType::Document, nullptr);
frame->swap(m_remoteFrame);
}
}
private:
WebRemoteFrame* m_remoteFrame;
};
} // anonymous namespace
TEST_F(WebFrameTest, SwapMainFrameWhileLoading) {
SwapMainFrameWhenTitleChangesWebFrameClient frameClient;
FrameTestHelpers::WebViewHelper webViewHelper;
registerMockedHttpURLLoad("frame-a-b-c.html");
registerMockedHttpURLLoad("subframe-a.html");
registerMockedHttpURLLoad("subframe-b.html");
registerMockedHttpURLLoad("subframe-c.html");
registerMockedHttpURLLoad("subframe-hello.html");
webViewHelper.initializeAndLoad(m_baseURL + "frame-a-b-c.html", true,
&frameClient);
}
void swapAndVerifyFirstChildConsistency(const char* const message,
WebFrame* parent,
WebFrame* newChild) {
SCOPED_TRACE(message);
parent->firstChild()->swap(newChild);
EXPECT_EQ(newChild, parent->firstChild());
EXPECT_EQ(newChild->parent(), parent);
EXPECT_EQ(newChild,
parent->lastChild()->previousSibling()->previousSibling());
EXPECT_EQ(newChild->nextSibling(), parent->lastChild()->previousSibling());
}
TEST_F(WebFrameSwapTest, SwapFirstChild) {
FrameTestHelpers::TestWebRemoteFrameClient remoteFrameClient;
WebRemoteFrame* remoteFrame =
WebRemoteFrame::create(WebTreeScopeType::Document, &remoteFrameClient);
swapAndVerifyFirstChildConsistency("local->remote", mainFrame(), remoteFrame);
FrameTestHelpers::TestWebFrameClient client;
WebLocalFrame* localFrame = WebLocalFrame::createProvisional(
&client, remoteFrame, WebSandboxFlags::None);
swapAndVerifyFirstChildConsistency("remote->local", mainFrame(), localFrame);
// FIXME: This almost certainly fires more load events on the iframe element
// than it should.
// Finally, make sure an embedder triggered load in the local frame swapped
// back in works.
FrameTestHelpers::loadFrame(localFrame, m_baseURL + "subframe-hello.html");
std::string content =
WebFrameContentDumper::dumpWebViewAsText(webView(), 1024).utf8();
EXPECT_EQ(" \n\nhello\n\nb \n\na\n\nc", content);
// Manually reset to break WebViewHelper's dependency on the stack allocated
// TestWebFrameClient.
reset();
remoteFrame->close();
}
void swapAndVerifyMiddleChildConsistency(const char* const message,
WebFrame* parent,
WebFrame* newChild) {
SCOPED_TRACE(message);
parent->firstChild()->nextSibling()->swap(newChild);
EXPECT_EQ(newChild, parent->firstChild()->nextSibling());
EXPECT_EQ(newChild, parent->lastChild()->previousSibling());
EXPECT_EQ(newChild->parent(), parent);
EXPECT_EQ(newChild, parent->firstChild()->nextSibling());
EXPECT_EQ(newChild->previousSibling(), parent->firstChild());
EXPECT_EQ(newChild, parent->lastChild()->previousSibling());
EXPECT_EQ(newChild->nextSibling(), parent->lastChild());
}
TEST_F(WebFrameSwapTest, SwapMiddleChild) {
FrameTestHelpers::TestWebRemoteFrameClient remoteFrameClient;
WebRemoteFrame* remoteFrame =
WebRemoteFrame::create(WebTreeScopeType::Document, &remoteFrameClient);
swapAndVerifyMiddleChildConsistency("local->remote", mainFrame(),
remoteFrame);
FrameTestHelpers::TestWebFrameClient client;
WebLocalFrame* localFrame = WebLocalFrame::createProvisional(
&client, remoteFrame, WebSandboxFlags::None);
swapAndVerifyMiddleChildConsistency("remote->local", mainFrame(), localFrame);
// FIXME: This almost certainly fires more load events on the iframe element
// than it should.
// Finally, make sure an embedder triggered load in the local frame swapped
// back in works.
FrameTestHelpers::loadFrame(localFrame, m_baseURL + "subframe-hello.html");
std::string content =
WebFrameContentDumper::dumpWebViewAsText(webView(), 1024).utf8();
EXPECT_EQ(" \n\na\n\nhello\n\nc", content);
// Manually reset to break WebViewHelper's dependency on the stack allocated
// TestWebFrameClient.
reset();
remoteFrame->close();
}
void swapAndVerifyLastChildConsistency(const char* const message,
WebFrame* parent,
WebFrame* newChild) {
SCOPED_TRACE(message);
parent->lastChild()->swap(newChild);
EXPECT_EQ(newChild, parent->lastChild());
EXPECT_EQ(newChild->parent(), parent);
EXPECT_EQ(newChild, parent->firstChild()->nextSibling()->nextSibling());
EXPECT_EQ(newChild->previousSibling(), parent->firstChild()->nextSibling());
}
TEST_F(WebFrameSwapTest, SwapLastChild) {
FrameTestHelpers::TestWebRemoteFrameClient remoteFrameClient;
WebRemoteFrame* remoteFrame =
WebRemoteFrame::create(WebTreeScopeType::Document, &remoteFrameClient);
swapAndVerifyLastChildConsistency("local->remote", mainFrame(), remoteFrame);
FrameTestHelpers::TestWebFrameClient client;
WebLocalFrame* localFrame = WebLocalFrame::createProvisional(
&client, remoteFrame, WebSandboxFlags::None);
swapAndVerifyLastChildConsistency("remote->local", mainFrame(), localFrame);
// FIXME: This almost certainly fires more load events on the iframe element
// than it should.
// Finally, make sure an embedder triggered load in the local frame swapped
// back in works.
FrameTestHelpers::loadFrame(localFrame, m_baseURL + "subframe-hello.html");
std::string content =
WebFrameContentDumper::dumpWebViewAsText(webView(), 1024).utf8();
EXPECT_EQ(" \n\na\n\nb \n\na\n\nhello", content);
// Manually reset to break WebViewHelper's dependency on the stack allocated
// TestWebFrameClient.
reset();
remoteFrame->close();
}
void swapAndVerifySubframeConsistency(const char* const message,
WebFrame* oldFrame,
WebFrame* newFrame) {
SCOPED_TRACE(message);
EXPECT_TRUE(oldFrame->firstChild());
oldFrame->swap(newFrame);
EXPECT_FALSE(newFrame->firstChild());
EXPECT_FALSE(newFrame->lastChild());
}
TEST_F(WebFrameSwapTest, SwapParentShouldDetachChildren) {
FrameTestHelpers::TestWebRemoteFrameClient remoteFrameClient1;
WebRemoteFrame* remoteFrame =
WebRemoteFrame::create(WebTreeScopeType::Document, &remoteFrameClient1);
WebFrame* targetFrame = mainFrame()->firstChild()->nextSibling();
EXPECT_TRUE(targetFrame);
swapAndVerifySubframeConsistency("local->remote", targetFrame, remoteFrame);
targetFrame = mainFrame()->firstChild()->nextSibling();
EXPECT_TRUE(targetFrame);
// Create child frames in the target frame before testing the swap.
FrameTestHelpers::TestWebRemoteFrameClient remoteFrameClient2;
WebRemoteFrame* childRemoteFrame =
FrameTestHelpers::createRemoteChild(remoteFrame, &remoteFrameClient2);
FrameTestHelpers::TestWebFrameClient client;
WebLocalFrame* localFrame = WebLocalFrame::createProvisional(
&client, remoteFrame, WebSandboxFlags::None);
swapAndVerifySubframeConsistency("remote->local", targetFrame, localFrame);
// FIXME: This almost certainly fires more load events on the iframe element
// than it should.
// Finally, make sure an embedder triggered load in the local frame swapped
// back in works.
FrameTestHelpers::loadFrame(localFrame, m_baseURL + "subframe-hello.html");
std::string content =
WebFrameContentDumper::dumpWebViewAsText(webView(), 1024).utf8();
EXPECT_EQ(" \n\na\n\nhello\n\nc", content);
// Manually reset to break WebViewHelper's dependency on the stack allocated
// TestWebFrameClient.
reset();
remoteFrame->close();
childRemoteFrame->close();
}
TEST_F(WebFrameSwapTest, SwapPreservesGlobalContext) {
v8::HandleScope scope(v8::Isolate::GetCurrent());
v8::Local<v8::Value> windowTop =
mainFrame()->executeScriptAndReturnValue(WebScriptSource("window"));
ASSERT_TRUE(windowTop->IsObject());
v8::Local<v8::Value> originalWindow =
mainFrame()->executeScriptAndReturnValue(
WebScriptSource("document.querySelector('#frame2').contentWindow;"));
ASSERT_TRUE(originalWindow->IsObject());
// Make sure window reference stays the same when swapping to a remote frame.
FrameTestHelpers::TestWebRemoteFrameClient remoteClient;
WebRemoteFrame* remoteFrame = remoteClient.frame();
WebFrame* targetFrame = mainFrame()->firstChild()->nextSibling();
targetFrame->swap(remoteFrame);
remoteFrame->setReplicatedOrigin(SecurityOrigin::createUnique());
v8::Local<v8::Value> remoteWindow = mainFrame()->executeScriptAndReturnValue(
WebScriptSource("document.querySelector('#frame2').contentWindow;"));
EXPECT_TRUE(originalWindow->StrictEquals(remoteWindow));
// Check that its view is consistent with the world.
v8::Local<v8::Value> remoteWindowTop =
mainFrame()->executeScriptAndReturnValue(WebScriptSource(
"document.querySelector('#frame2').contentWindow.top;"));
EXPECT_TRUE(windowTop->StrictEquals(remoteWindowTop));
// Now check that remote -> local works too, since it goes through a different
// code path.
FrameTestHelpers::TestWebFrameClient client;
WebLocalFrame* localFrame = WebLocalFrame::createProvisional(
&client, remoteFrame, WebSandboxFlags::None);
remoteFrame->swap(localFrame);
v8::Local<v8::Value> localWindow = mainFrame()->executeScriptAndReturnValue(
WebScriptSource("document.querySelector('#frame2').contentWindow;"));
EXPECT_TRUE(originalWindow->StrictEquals(localWindow));
v8::Local<v8::Value> localWindowTop =
mainFrame()->executeScriptAndReturnValue(WebScriptSource(
"document.querySelector('#frame2').contentWindow.top;"));
EXPECT_TRUE(windowTop->StrictEquals(localWindowTop));
// Manually reset to break WebViewHelper's dependency on the stack allocated
// TestWebFrameClient.
reset();
}
TEST_F(WebFrameSwapTest, SwapInitializesGlobal) {
v8::HandleScope scope(v8::Isolate::GetCurrent());
v8::Local<v8::Value> windowTop =
mainFrame()->executeScriptAndReturnValue(WebScriptSource("window"));
ASSERT_TRUE(windowTop->IsObject());
v8::Local<v8::Value> lastChild = mainFrame()->executeScriptAndReturnValue(
WebScriptSource("saved = window[2]"));
ASSERT_TRUE(lastChild->IsObject());
FrameTestHelpers::TestWebRemoteFrameClient remoteClient;
WebRemoteFrame* remoteFrame = remoteClient.frame();
mainFrame()->lastChild()->swap(remoteFrame);
remoteFrame->setReplicatedOrigin(SecurityOrigin::createUnique());
v8::Local<v8::Value> remoteWindowTop =
mainFrame()->executeScriptAndReturnValue(WebScriptSource("saved.top"));
EXPECT_TRUE(remoteWindowTop->IsObject());
EXPECT_TRUE(windowTop->StrictEquals(remoteWindowTop));
FrameTestHelpers::TestWebFrameClient client;
WebLocalFrame* localFrame = WebLocalFrame::createProvisional(
&client, remoteFrame, WebSandboxFlags::None);
remoteFrame->swap(localFrame);
v8::Local<v8::Value> localWindowTop =
mainFrame()->executeScriptAndReturnValue(WebScriptSource("saved.top"));
EXPECT_TRUE(localWindowTop->IsObject());
EXPECT_TRUE(windowTop->StrictEquals(localWindowTop));
reset();
}
TEST_F(WebFrameSwapTest, RemoteFramesAreIndexable) {
v8::HandleScope scope(v8::Isolate::GetCurrent());
FrameTestHelpers::TestWebRemoteFrameClient remoteClient;
WebRemoteFrame* remoteFrame = remoteClient.frame();
mainFrame()->lastChild()->swap(remoteFrame);
remoteFrame->setReplicatedOrigin(SecurityOrigin::createUnique());
v8::Local<v8::Value> remoteWindow =
mainFrame()->executeScriptAndReturnValue(WebScriptSource("window[2]"));
EXPECT_TRUE(remoteWindow->IsObject());
v8::Local<v8::Value> windowLength = mainFrame()->executeScriptAndReturnValue(
WebScriptSource("window.length"));
ASSERT_TRUE(windowLength->IsInt32());
EXPECT_EQ(3, windowLength.As<v8::Int32>()->Value());
reset();
}
TEST_F(WebFrameSwapTest, RemoteFrameLengthAccess) {
v8::HandleScope scope(v8::Isolate::GetCurrent());
FrameTestHelpers::TestWebRemoteFrameClient remoteClient;
WebRemoteFrame* remoteFrame = remoteClient.frame();
mainFrame()->lastChild()->swap(remoteFrame);
remoteFrame->setReplicatedOrigin(SecurityOrigin::createUnique());
v8::Local<v8::Value> remoteWindowLength =
mainFrame()->executeScriptAndReturnValue(
WebScriptSource("window[2].length"));
ASSERT_TRUE(remoteWindowLength->IsInt32());
EXPECT_EQ(0, remoteWindowLength.As<v8::Int32>()->Value());
reset();
}
TEST_F(WebFrameSwapTest, RemoteWindowNamedAccess) {
v8::HandleScope scope(v8::Isolate::GetCurrent());
// FIXME: Once OOPIF unit test infrastructure is in place, test that named
// window access on a remote window works. For now, just test that accessing
// a named property doesn't crash.
FrameTestHelpers::TestWebRemoteFrameClient remoteClient;
WebRemoteFrame* remoteFrame = remoteClient.frame();
mainFrame()->lastChild()->swap(remoteFrame);
remoteFrame->setReplicatedOrigin(SecurityOrigin::createUnique());
v8::Local<v8::Value> remoteWindowProperty =
mainFrame()->executeScriptAndReturnValue(
WebScriptSource("window[2].foo"));
EXPECT_TRUE(remoteWindowProperty.IsEmpty());
reset();
}
// TODO(alexmos, dcheng): This test and some other OOPIF tests use
// very little of the test fixture support in WebFrameSwapTest. We should
// clean these tests up.
TEST_F(WebFrameSwapTest, FramesOfRemoteParentAreIndexable) {
v8::HandleScope scope(v8::Isolate::GetCurrent());
FrameTestHelpers::TestWebRemoteFrameClient remoteClient;
WebRemoteFrame* remoteParentFrame = remoteClient.frame();
mainFrame()->swap(remoteParentFrame);
remoteParentFrame->setReplicatedOrigin(SecurityOrigin::createUnique());
WebLocalFrame* childFrame =
FrameTestHelpers::createLocalChild(remoteParentFrame);
FrameTestHelpers::loadFrame(childFrame, m_baseURL + "subframe-hello.html");
v8::Local<v8::Value> window =
childFrame->executeScriptAndReturnValue(WebScriptSource("window"));
v8::Local<v8::Value> childOfRemoteParent =
childFrame->executeScriptAndReturnValue(
WebScriptSource("parent.frames[0]"));
EXPECT_TRUE(childOfRemoteParent->IsObject());
EXPECT_TRUE(window->StrictEquals(childOfRemoteParent));
v8::Local<v8::Value> windowLength = childFrame->executeScriptAndReturnValue(
WebScriptSource("parent.frames.length"));
ASSERT_TRUE(windowLength->IsInt32());
EXPECT_EQ(1, windowLength.As<v8::Int32>()->Value());
// Manually reset to break WebViewHelper's dependency on the stack allocated
// clients.
reset();
}
// Check that frames with a remote parent don't crash while accessing
// window.frameElement.
TEST_F(WebFrameSwapTest, FrameElementInFramesWithRemoteParent) {
v8::HandleScope scope(v8::Isolate::GetCurrent());
FrameTestHelpers::TestWebRemoteFrameClient remoteClient;
WebRemoteFrame* remoteParentFrame = remoteClient.frame();
mainFrame()->swap(remoteParentFrame);
remoteParentFrame->setReplicatedOrigin(SecurityOrigin::createUnique());
WebLocalFrame* childFrame =
FrameTestHelpers::createLocalChild(remoteParentFrame);
FrameTestHelpers::loadFrame(childFrame, m_baseURL + "subframe-hello.html");
v8::Local<v8::Value> frameElement = childFrame->executeScriptAndReturnValue(
WebScriptSource("window.frameElement"));
// frameElement should be null if cross-origin.
ASSERT_FALSE(frameElement.IsEmpty());
EXPECT_TRUE(frameElement->IsNull());
// Manually reset to break WebViewHelper's dependency on the stack allocated
// clients.
reset();
}
class RemoteToLocalSwapWebFrameClient
: public FrameTestHelpers::TestWebFrameClient {
public:
explicit RemoteToLocalSwapWebFrameClient(WebRemoteFrame* remoteFrame)
: m_historyCommitType(WebHistoryInertCommit),
m_remoteFrame(remoteFrame) {}
void didCommitProvisionalLoad(
WebLocalFrame* frame,
const WebHistoryItem&,
WebHistoryCommitType historyCommitType) override {
m_historyCommitType = historyCommitType;
m_remoteFrame->swap(frame);
}
WebHistoryCommitType historyCommitType() const { return m_historyCommitType; }
WebHistoryCommitType m_historyCommitType;
WebRemoteFrame* m_remoteFrame;
};
// The commit type should be Initial if we are swapping a RemoteFrame to a
// LocalFrame as it is first being created. This happens when another frame
// exists in the same process, such that we create the RemoteFrame before the
// first navigation occurs.
TEST_F(WebFrameSwapTest, HistoryCommitTypeAfterNewRemoteToLocalSwap) {
FrameTestHelpers::TestWebRemoteFrameClient remoteFrameClient;
WebRemoteFrame* remoteFrame =
WebRemoteFrame::create(WebTreeScopeType::Document, &remoteFrameClient);
WebFrame* targetFrame = mainFrame()->firstChild();
ASSERT_TRUE(targetFrame);
targetFrame->swap(remoteFrame);
ASSERT_TRUE(mainFrame()->firstChild());
ASSERT_EQ(mainFrame()->firstChild(), remoteFrame);
RemoteToLocalSwapWebFrameClient client(remoteFrame);
WebLocalFrame* localFrame = WebLocalFrame::createProvisional(
&client, remoteFrame, WebSandboxFlags::None);
FrameTestHelpers::loadFrame(localFrame, m_baseURL + "subframe-hello.html");
EXPECT_EQ(WebInitialCommitInChildFrame, client.historyCommitType());
// Manually reset to break WebViewHelper's dependency on the stack allocated
// TestWebFrameClient.
reset();
remoteFrame->close();
}
// The commit type should be Standard if we are swapping a RemoteFrame to a
// LocalFrame after commits have already happened in the frame. The browser
// process will inform us via setCommittedFirstRealLoad.
TEST_F(WebFrameSwapTest, HistoryCommitTypeAfterExistingRemoteToLocalSwap) {
FrameTestHelpers::TestWebRemoteFrameClient remoteFrameClient;
WebRemoteFrame* remoteFrame =
WebRemoteFrame::create(WebTreeScopeType::Document, &remoteFrameClient);
WebFrame* targetFrame = mainFrame()->firstChild();
ASSERT_TRUE(targetFrame);
targetFrame->swap(remoteFrame);
ASSERT_TRUE(mainFrame()->firstChild());
ASSERT_EQ(mainFrame()->firstChild(), remoteFrame);
RemoteToLocalSwapWebFrameClient client(remoteFrame);
WebLocalFrame* localFrame = WebLocalFrame::createProvisional(
&client, remoteFrame, WebSandboxFlags::None);
localFrame->setCommittedFirstRealLoad();
FrameTestHelpers::loadFrame(localFrame, m_baseURL + "subframe-hello.html");
EXPECT_EQ(WebStandardCommit, client.historyCommitType());
// Manually reset to break WebViewHelper's dependency on the stack allocated
// TestWebFrameClient.
reset();
remoteFrame->close();
}
// The uniqueName should be preserved when swapping to a RemoteFrame and back,
// whether the frame has a name or not.
TEST_F(WebFrameSwapTest, UniqueNameAfterRemoteToLocalSwap) {
// Start with a named frame.
WebFrame* targetFrame = mainFrame()->firstChild();
ASSERT_TRUE(targetFrame);
WebString uniqueName = targetFrame->uniqueName();
EXPECT_EQ("frame1", uniqueName.utf8());
// Swap to a RemoteFrame.
FrameTestHelpers::TestWebRemoteFrameClient remoteFrameClient;
WebRemoteFrameImpl* remoteFrame = WebRemoteFrameImpl::create(
WebTreeScopeType::Document, &remoteFrameClient);
targetFrame->swap(remoteFrame);
ASSERT_TRUE(mainFrame()->firstChild());
ASSERT_EQ(mainFrame()->firstChild(), remoteFrame);
EXPECT_EQ(uniqueName.utf8(),
WebString(remoteFrame->frame()->tree().uniqueName()).utf8());
// Swap back to a LocalFrame.
RemoteToLocalSwapWebFrameClient client(remoteFrame);
WebLocalFrame* localFrame = WebLocalFrame::createProvisional(
&client, remoteFrame, WebSandboxFlags::None);
FrameTestHelpers::loadFrame(localFrame, m_baseURL + "subframe-hello.html");
EXPECT_EQ(uniqueName.utf8(), localFrame->uniqueName().utf8());
EXPECT_EQ(uniqueName.utf8(), WebString(toWebLocalFrameImpl(localFrame)
->frame()
->loader()
.currentItem()
->target())
.utf8());
// Repeat with no name on the frame.
// (note that uniqueName is immutable after first real commit).
localFrame->setName("");
WebString uniqueName2 = localFrame->uniqueName();
EXPECT_EQ("frame1", uniqueName2.utf8());
FrameTestHelpers::TestWebRemoteFrameClient remoteFrameClient2;
WebRemoteFrameImpl* remoteFrame2 = WebRemoteFrameImpl::create(
WebTreeScopeType::Document, &remoteFrameClient2);
localFrame->swap(remoteFrame2);
ASSERT_TRUE(mainFrame()->firstChild());
ASSERT_EQ(mainFrame()->firstChild(), remoteFrame2);
EXPECT_EQ(uniqueName2.utf8(),
WebString(remoteFrame2->frame()->tree().uniqueName()).utf8());
RemoteToLocalSwapWebFrameClient client2(remoteFrame2);
WebLocalFrame* localFrame2 = WebLocalFrame::createProvisional(
&client2, remoteFrame2, WebSandboxFlags::None);
FrameTestHelpers::loadFrame(localFrame2, m_baseURL + "subframe-hello.html");
EXPECT_EQ(uniqueName2.utf8(), localFrame2->uniqueName().utf8());
EXPECT_EQ(uniqueName2.utf8(), WebString(toWebLocalFrameImpl(localFrame2)
->frame()
->loader()
.currentItem()
->target())
.utf8());
// Manually reset to break WebViewHelper's dependency on the stack allocated
// TestWebFrameClient.
reset();
remoteFrame->close();
remoteFrame2->close();
}
class RemoteNavigationClient
: public FrameTestHelpers::TestWebRemoteFrameClient {
public:
void navigate(const WebURLRequest& request,
bool shouldReplaceCurrentEntry) override {
m_lastRequest = request;
}
const WebURLRequest& lastRequest() const { return m_lastRequest; }
private:
WebURLRequest m_lastRequest;
};
TEST_F(WebFrameSwapTest, NavigateRemoteFrameViaLocation) {
RemoteNavigationClient client;
WebRemoteFrame* remoteFrame = client.frame();
WebFrame* targetFrame = mainFrame()->firstChild();
ASSERT_TRUE(targetFrame);
targetFrame->swap(remoteFrame);
ASSERT_TRUE(mainFrame()->firstChild());
ASSERT_EQ(mainFrame()->firstChild(), remoteFrame);
remoteFrame->setReplicatedOrigin(
WebSecurityOrigin::createFromString("http://127.0.0.1"));
mainFrame()->executeScript(
WebScriptSource("document.getElementsByTagName('iframe')[0]."
"contentWindow.location = 'data:text/html,hi'"));
ASSERT_FALSE(client.lastRequest().isNull());
EXPECT_EQ(WebURL(toKURL("data:text/html,hi")), client.lastRequest().url());
// Manually reset to break WebViewHelper's dependency on the stack allocated
// TestWebFrameClient.
reset();
}
TEST_F(WebFrameSwapTest, WindowOpenOnRemoteFrame) {
RemoteNavigationClient remoteClient;
WebRemoteFrame* remoteFrame = remoteClient.frame();
mainFrame()->firstChild()->swap(remoteFrame);
remoteFrame->setReplicatedOrigin(
WebSecurityOrigin::createFromString("http://127.0.0.1"));
ASSERT_TRUE(mainFrame()->isWebLocalFrame());
ASSERT_TRUE(mainFrame()->firstChild()->isWebRemoteFrame());
LocalDOMWindow* mainWindow =
toWebLocalFrameImpl(mainFrame())->frame()->localDOMWindow();
KURL destination = toKURL("data:text/html:destination");
mainWindow->open(destination.getString(), "frame1", "", mainWindow,
mainWindow);
ASSERT_FALSE(remoteClient.lastRequest().isNull());
EXPECT_EQ(remoteClient.lastRequest().url(), WebURL(destination));
// Pointing a named frame to an empty URL should just return a reference to
// the frame's window without navigating it.
DOMWindow* result =
mainWindow->open("", "frame1", "", mainWindow, mainWindow);
EXPECT_EQ(remoteClient.lastRequest().url(), WebURL(destination));
EXPECT_EQ(result, remoteFrame->toImplBase()->frame()->domWindow());
reset();
}
class RemoteWindowCloseClient : public FrameTestHelpers::TestWebViewClient {
public:
RemoteWindowCloseClient() : m_closed(false) {}
void closeWidgetSoon() override { m_closed = true; }
bool closed() const { return m_closed; }
private:
bool m_closed;
};
TEST_F(WebFrameTest, WindowOpenRemoteClose) {
FrameTestHelpers::WebViewHelper mainWebView;
mainWebView.initialize(true);
// Create a remote window that will be closed later in the test.
RemoteWindowCloseClient viewClient;
FrameTestHelpers::TestWebRemoteFrameClient frameClient;
WebRemoteFrameImpl* webRemoteFrame = frameClient.frame();
WebView* view = WebView::create(&viewClient, WebPageVisibilityStateVisible);
view->setMainFrame(webRemoteFrame);
view->mainFrame()->setOpener(mainWebView.webView()->mainFrame());
webRemoteFrame->setReplicatedOrigin(
WebSecurityOrigin::createFromString("http://127.0.0.1"));
LocalFrame* localFrame =
toLocalFrame(mainWebView.webView()->mainFrame()->toImplBase()->frame());
RemoteFrame* remoteFrame = webRemoteFrame->frame();
// Attempt to close the window, which should fail as it isn't opened
// by a script.
remoteFrame->domWindow()->close(localFrame->document());
EXPECT_FALSE(viewClient.closed());
// Marking it as opened by a script should now allow it to be closed.
remoteFrame->page()->setOpenedByDOM();
remoteFrame->domWindow()->close(localFrame->document());
EXPECT_TRUE(viewClient.closed());
view->close();
}
TEST_F(WebFrameTest, NavigateRemoteToLocalWithOpener) {
FrameTestHelpers::WebViewHelper mainWebView;
mainWebView.initialize(true);
WebFrame* mainFrame = mainWebView.webView()->mainFrame();
// Create a popup with a remote frame and set its opener to the main frame.
FrameTestHelpers::TestWebViewClient popupViewClient;
WebView* popupView =
WebView::create(&popupViewClient, WebPageVisibilityStateVisible);
FrameTestHelpers::TestWebRemoteFrameClient popupRemoteClient;
WebRemoteFrame* popupRemoteFrame = popupRemoteClient.frame();
popupView->setMainFrame(popupRemoteFrame);
popupRemoteFrame->setOpener(mainFrame);
popupRemoteFrame->setReplicatedOrigin(
WebSecurityOrigin::createFromString("http://foo.com"));
EXPECT_FALSE(mainFrame->getSecurityOrigin().canAccess(
popupView->mainFrame()->getSecurityOrigin()));
// Do a remote-to-local swap in the popup.
FrameTestHelpers::TestWebFrameClient popupLocalClient;
WebLocalFrame* popupLocalFrame = WebLocalFrame::createProvisional(
&popupLocalClient, popupRemoteFrame, WebSandboxFlags::None);
popupRemoteFrame->swap(popupLocalFrame);
// The initial document created during the remote-to-local swap should have
// inherited its opener's SecurityOrigin.
EXPECT_TRUE(mainFrame->getSecurityOrigin().canAccess(
popupView->mainFrame()->getSecurityOrigin()));
popupView->close();
}
TEST_F(WebFrameTest, SwapWithOpenerCycle) {
// First, create a remote main frame with itself as the opener.
FrameTestHelpers::TestWebViewClient viewClient;
WebView* view = WebView::create(&viewClient, WebPageVisibilityStateVisible);
FrameTestHelpers::TestWebRemoteFrameClient remoteClient;
WebRemoteFrame* remoteFrame = remoteClient.frame();
view->setMainFrame(remoteFrame);
remoteFrame->setOpener(remoteFrame);
// Now swap in a local frame. It shouldn't crash.
FrameTestHelpers::TestWebFrameClient localClient;
WebLocalFrame* localFrame = WebLocalFrame::createProvisional(
&localClient, remoteFrame, WebSandboxFlags::None);
remoteFrame->swap(localFrame);
// And the opener cycle should still be preserved.
EXPECT_EQ(localFrame, localFrame->opener());
view->close();
}
class CommitTypeWebFrameClient : public FrameTestHelpers::TestWebFrameClient {
public:
explicit CommitTypeWebFrameClient()
: m_historyCommitType(WebHistoryInertCommit) {}
void didCommitProvisionalLoad(
WebLocalFrame*,
const WebHistoryItem&,
WebHistoryCommitType historyCommitType) override {
m_historyCommitType = historyCommitType;
}
WebHistoryCommitType historyCommitType() const { return m_historyCommitType; }
private:
WebHistoryCommitType m_historyCommitType;
};
TEST_P(ParameterizedWebFrameTest, RemoteFrameInitialCommitType) {
FrameTestHelpers::TestWebViewClient viewClient;
FrameTestHelpers::TestWebRemoteFrameClient remoteClient;
WebView* view = WebView::create(&viewClient, WebPageVisibilityStateVisible);
view->setMainFrame(remoteClient.frame());
remoteClient.frame()->setReplicatedOrigin(
WebSecurityOrigin::createFromString(WebString::fromUTF8(m_baseURL)));
// If an iframe has a remote main frame, ensure the inital commit is correctly
// identified as WebInitialCommitInChildFrame.
CommitTypeWebFrameClient childFrameClient;
WebLocalFrame* childFrame = FrameTestHelpers::createLocalChild(
view->mainFrame()->toWebRemoteFrame(), "frameName", &childFrameClient);
registerMockedHttpURLLoad("foo.html");
FrameTestHelpers::loadFrame(childFrame, m_baseURL + "foo.html");
EXPECT_EQ(WebInitialCommitInChildFrame, childFrameClient.historyCommitType());
view->close();
}
class GestureEventTestWebWidgetClient
: public FrameTestHelpers::TestWebWidgetClient {
public:
GestureEventTestWebWidgetClient() : m_didHandleGestureEvent(false) {}
void didHandleGestureEvent(const WebGestureEvent& event,
bool eventCancelled) override {
m_didHandleGestureEvent = true;
}
bool didHandleGestureEvent() const { return m_didHandleGestureEvent; }
private:
bool m_didHandleGestureEvent;
};
TEST_P(ParameterizedWebFrameTest, FrameWidgetTest) {
FrameTestHelpers::TestWebViewClient viewClient;
WebView* view = WebView::create(&viewClient, WebPageVisibilityStateVisible);
FrameTestHelpers::TestWebRemoteFrameClient remoteClient;
view->setMainFrame(remoteClient.frame());
GestureEventTestWebWidgetClient childWidgetClient;
WebLocalFrame* childFrame = FrameTestHelpers::createLocalChild(
view->mainFrame()->toWebRemoteFrame(), WebString(), nullptr,
&childWidgetClient);
view->resize(WebSize(1000, 1000));
childFrame->frameWidget()->handleInputEvent(fatTap(20, 20));
EXPECT_TRUE(childWidgetClient.didHandleGestureEvent());
view->close();
}
class MockDocumentThreadableLoaderClient
: public DocumentThreadableLoaderClient {
public:
MockDocumentThreadableLoaderClient() : m_failed(false) {}
void didFail(const ResourceError&) override { m_failed = true; }
void reset() { m_failed = false; }
bool failed() { return m_failed; }
bool m_failed;
};
// FIXME: This would be better as a unittest on DocumentThreadableLoader but it
// requires spin-up of a frame. It may be possible to remove that requirement
// and convert it to a unittest.
TEST_P(ParameterizedWebFrameTest, LoaderOriginAccess) {
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad("about:blank");
SchemeRegistry::registerURLSchemeAsDisplayIsolated("chrome");
// Cross-origin request.
KURL resourceUrl(ParsedURLString, "chrome://test.pdf");
ResourceRequest request(resourceUrl);
request.setRequestContext(WebURLRequest::RequestContextObject);
registerMockedChromeURLLoad("test.pdf");
LocalFrame* frame(toLocalFrame(webViewHelper.webView()->page()->mainFrame()));
MockDocumentThreadableLoaderClient client;
ThreadableLoaderOptions options;
// First try to load the request with regular access. Should fail.
options.crossOriginRequestPolicy = UseAccessControl;
ResourceLoaderOptions resourceLoaderOptions;
DocumentThreadableLoader::loadResourceSynchronously(
*frame->document(), request, client, options, resourceLoaderOptions);
EXPECT_TRUE(client.failed());
client.reset();
// Try to load the request with cross origin access. Should succeed.
options.crossOriginRequestPolicy = AllowCrossOriginRequests;
DocumentThreadableLoader::loadResourceSynchronously(
*frame->document(), request, client, options, resourceLoaderOptions);
EXPECT_FALSE(client.failed());
}
TEST_P(ParameterizedWebFrameTest, DetachRemoteFrame) {
FrameTestHelpers::TestWebViewClient viewClient;
FrameTestHelpers::TestWebRemoteFrameClient remoteClient;
WebView* view = WebView::create(&viewClient, WebPageVisibilityStateVisible);
view->setMainFrame(remoteClient.frame());
FrameTestHelpers::TestWebRemoteFrameClient childFrameClient;
WebRemoteFrame* childFrame = FrameTestHelpers::createRemoteChild(
view->mainFrame()->toWebRemoteFrame(), &childFrameClient);
childFrame->detach();
view->close();
childFrame->close();
}
class TestConsoleMessageWebFrameClient
: public FrameTestHelpers::TestWebFrameClient {
public:
virtual void didAddMessageToConsole(const WebConsoleMessage& message,
const WebString& sourceName,
unsigned sourceLine,
const WebString& stackTrace) {
messages.append(message);
}
Vector<WebConsoleMessage> messages;
};
TEST_P(ParameterizedWebFrameTest, CrossDomainAccessErrorsUseCallingWindow) {
registerMockedHttpURLLoad("hidden_frames.html");
registerMockedChromeURLLoad("hello_world.html");
FrameTestHelpers::WebViewHelper webViewHelper;
TestConsoleMessageWebFrameClient webFrameClient;
FrameTestHelpers::TestWebViewClient webViewClient;
webViewHelper.initializeAndLoad(m_baseURL + "hidden_frames.html", true,
&webFrameClient, &webViewClient);
// Create another window with a cross-origin page, and point its opener to
// first window.
FrameTestHelpers::WebViewHelper popupWebViewHelper;
TestConsoleMessageWebFrameClient popupWebFrameClient;
WebView* popupView = popupWebViewHelper.initializeAndLoad(
m_chromeURL + "hello_world.html", true, &popupWebFrameClient);
popupView->mainFrame()->setOpener(webViewHelper.webView()->mainFrame());
// Attempt a blocked navigation of an opener's subframe, and ensure that
// the error shows up on the popup (calling) window's console, rather than
// the target window.
popupView->mainFrame()->executeScript(WebScriptSource(
"try { opener.frames[1].location.href='data:text/html,foo'; } catch (e) "
"{}"));
EXPECT_TRUE(webFrameClient.messages.isEmpty());
ASSERT_EQ(1u, popupWebFrameClient.messages.size());
EXPECT_TRUE(std::string::npos !=
popupWebFrameClient.messages[0].text.utf8().find(
"Unsafe JavaScript attempt to initiate navigation"));
// Try setting a cross-origin iframe element's source to a javascript: URL,
// and check that this error is also printed on the calling window.
popupView->mainFrame()->executeScript(
WebScriptSource("opener.document.querySelectorAll('iframe')[1].src='"
"javascript:alert()'"));
EXPECT_TRUE(webFrameClient.messages.isEmpty());
ASSERT_EQ(2u, popupWebFrameClient.messages.size());
EXPECT_TRUE(
std::string::npos !=
popupWebFrameClient.messages[1].text.utf8().find("Blocked a frame"));
// Manually reset to break WebViewHelpers' dependencies on the stack
// allocated WebFrameClients.
webViewHelper.reset();
popupWebViewHelper.reset();
}
TEST_P(ParameterizedWebFrameTest, ResizeInvalidatesDeviceMediaQueries) {
registerMockedHttpURLLoad("device_media_queries.html");
FixedLayoutTestWebViewClient client;
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "device_media_queries.html", true,
nullptr, &client, nullptr, configureAndroid);
LocalFrame* frame =
toLocalFrame(webViewHelper.webView()->page()->mainFrame());
Element* element = frame->document()->getElementById("test");
ASSERT_TRUE(element);
client.m_screenInfo.rect = WebRect(0, 0, 700, 500);
client.m_screenInfo.availableRect = client.m_screenInfo.rect;
webViewHelper.resize(WebSize(700, 500));
EXPECT_EQ(300, element->offsetWidth());
EXPECT_EQ(300, element->offsetHeight());
client.m_screenInfo.rect = WebRect(0, 0, 710, 500);
client.m_screenInfo.availableRect = client.m_screenInfo.rect;
webViewHelper.resize(WebSize(710, 500));
EXPECT_EQ(400, element->offsetWidth());
EXPECT_EQ(300, element->offsetHeight());
client.m_screenInfo.rect = WebRect(0, 0, 690, 500);
client.m_screenInfo.availableRect = client.m_screenInfo.rect;
webViewHelper.resize(WebSize(690, 500));
EXPECT_EQ(200, element->offsetWidth());
EXPECT_EQ(300, element->offsetHeight());
client.m_screenInfo.rect = WebRect(0, 0, 700, 510);
client.m_screenInfo.availableRect = client.m_screenInfo.rect;
webViewHelper.resize(WebSize(700, 510));
EXPECT_EQ(300, element->offsetWidth());
EXPECT_EQ(400, element->offsetHeight());
client.m_screenInfo.rect = WebRect(0, 0, 700, 490);
client.m_screenInfo.availableRect = client.m_screenInfo.rect;
webViewHelper.resize(WebSize(700, 490));
EXPECT_EQ(300, element->offsetWidth());
EXPECT_EQ(200, element->offsetHeight());
client.m_screenInfo.rect = WebRect(0, 0, 690, 510);
client.m_screenInfo.availableRect = client.m_screenInfo.rect;
webViewHelper.resize(WebSize(690, 510));
EXPECT_EQ(200, element->offsetWidth());
EXPECT_EQ(400, element->offsetHeight());
}
class DeviceEmulationTest : public ParameterizedWebFrameTest {
protected:
DeviceEmulationTest() {
registerMockedHttpURLLoad("device_emulation.html");
m_client.m_screenInfo.deviceScaleFactor = 1;
m_webViewHelper.initializeAndLoad(m_baseURL + "device_emulation.html", true,
0, &m_client);
}
void testResize(const WebSize size, const String& expectedSize) {
m_client.m_screenInfo.rect = WebRect(0, 0, size.width, size.height);
m_client.m_screenInfo.availableRect = m_client.m_screenInfo.rect;
m_webViewHelper.resize(size);
EXPECT_EQ(expectedSize, dumpSize("test"));
}
String dumpSize(const String& id) {
String code = "dumpSize('" + id + "')";
v8::HandleScope scope(v8::Isolate::GetCurrent());
ScriptExecutionCallbackHelper callbackHelper(
m_webViewHelper.webView()->mainFrame()->mainWorldScriptContext());
m_webViewHelper.webView()
->mainFrameImpl()
->requestExecuteScriptAndReturnValue(WebScriptSource(WebString(code)),
false, &callbackHelper);
runPendingTasks();
EXPECT_TRUE(callbackHelper.didComplete());
return callbackHelper.stringValue();
}
FixedLayoutTestWebViewClient m_client;
FrameTestHelpers::WebViewHelper m_webViewHelper;
};
INSTANTIATE_TEST_CASE_P(All, DeviceEmulationTest, ::testing::Bool());
TEST_P(DeviceEmulationTest, DeviceSizeInvalidatedOnResize) {
WebDeviceEmulationParams params;
params.screenPosition = WebDeviceEmulationParams::Mobile;
m_webViewHelper.webView()->enableDeviceEmulation(params);
testResize(WebSize(700, 500), "300x300");
testResize(WebSize(710, 500), "400x300");
testResize(WebSize(690, 500), "200x300");
testResize(WebSize(700, 510), "300x400");
testResize(WebSize(700, 490), "300x200");
testResize(WebSize(710, 510), "400x400");
testResize(WebSize(690, 490), "200x200");
testResize(WebSize(800, 600), "400x400");
m_webViewHelper.webView()->disableDeviceEmulation();
}
TEST_P(DeviceEmulationTest, PointerAndHoverTypes) {
WebDeviceEmulationParams params;
params.screenPosition = WebDeviceEmulationParams::Mobile;
m_webViewHelper.webView()->enableDeviceEmulation(params);
EXPECT_EQ("20x20", dumpSize("pointer"));
m_webViewHelper.webView()->disableDeviceEmulation();
}
TEST_P(ParameterizedWebFrameTest, CreateLocalChildWithPreviousSibling) {
FrameTestHelpers::TestWebViewClient viewClient;
FrameTestHelpers::TestWebRemoteFrameClient remoteClient;
WebView* view = WebView::create(&viewClient, WebPageVisibilityStateVisible);
view->setMainFrame(remoteClient.frame());
WebRemoteFrame* parent = view->mainFrame()->toWebRemoteFrame();
WebLocalFrame* secondFrame(
FrameTestHelpers::createLocalChild(parent, "name2"));
WebLocalFrame* fourthFrame(FrameTestHelpers::createLocalChild(
parent, "name4", nullptr, nullptr, secondFrame));
WebLocalFrame* thirdFrame(FrameTestHelpers::createLocalChild(
parent, "name3", nullptr, nullptr, secondFrame));
WebLocalFrame* firstFrame(
FrameTestHelpers::createLocalChild(parent, "name1"));
EXPECT_EQ(firstFrame, parent->firstChild());
EXPECT_EQ(nullptr, firstFrame->previousSibling());
EXPECT_EQ(secondFrame, firstFrame->nextSibling());
EXPECT_EQ(firstFrame, secondFrame->previousSibling());
EXPECT_EQ(thirdFrame, secondFrame->nextSibling());
EXPECT_EQ(secondFrame, thirdFrame->previousSibling());
EXPECT_EQ(fourthFrame, thirdFrame->nextSibling());
EXPECT_EQ(thirdFrame, fourthFrame->previousSibling());
EXPECT_EQ(nullptr, fourthFrame->nextSibling());
EXPECT_EQ(fourthFrame, parent->lastChild());
EXPECT_EQ(parent, firstFrame->parent());
EXPECT_EQ(parent, secondFrame->parent());
EXPECT_EQ(parent, thirdFrame->parent());
EXPECT_EQ(parent, fourthFrame->parent());
view->close();
}
TEST_P(ParameterizedWebFrameTest, SendBeaconFromChildWithRemoteMainFrame) {
FrameTestHelpers::TestWebViewClient viewClient;
FrameTestHelpers::TestWebRemoteFrameClient remoteClient;
WebView* view = WebView::create(&viewClient, WebPageVisibilityStateVisible);
view->settings()->setJavaScriptEnabled(true);
view->setMainFrame(remoteClient.frame());
WebRemoteFrame* root = view->mainFrame()->toWebRemoteFrame();
root->setReplicatedOrigin(SecurityOrigin::createUnique());
WebLocalFrame* localFrame = FrameTestHelpers::createLocalChild(root);
// Finally, make sure an embedder triggered load in the local frame swapped
// back in works.
registerMockedHttpURLLoad("send_beacon.html");
registerMockedHttpURLLoad("reload_post.html"); // url param to sendBeacon()
FrameTestHelpers::loadFrame(localFrame, m_baseURL + "send_beacon.html");
view->close();
}
TEST_P(ParameterizedWebFrameTest,
FirstPartyForCookiesFromChildWithRemoteMainFrame) {
FrameTestHelpers::TestWebViewClient viewClient;
FrameTestHelpers::TestWebRemoteFrameClient remoteClient;
WebView* view = WebView::create(&viewClient, WebPageVisibilityStateVisible);
view->setMainFrame(remoteClient.frame());
WebRemoteFrame* root = view->mainFrame()->toWebRemoteFrame();
root->setReplicatedOrigin(SecurityOrigin::create(toKURL(m_notBaseURL)));
WebLocalFrame* localFrame = FrameTestHelpers::createLocalChild(root);
registerMockedHttpURLLoad("foo.html");
FrameTestHelpers::loadFrame(localFrame, m_baseURL + "foo.html");
EXPECT_EQ(WebURL(SecurityOrigin::urlWithUniqueSecurityOrigin()),
localFrame->document().firstPartyForCookies());
SchemeRegistry::registerURLSchemeAsFirstPartyWhenTopLevel("http");
EXPECT_EQ(WebURL(toKURL(m_notBaseURL)),
localFrame->document().firstPartyForCookies());
SchemeRegistry::removeURLSchemeAsFirstPartyWhenTopLevel("http");
view->close();
}
// See https://crbug.com/525285.
TEST_P(ParameterizedWebFrameTest,
RemoteToLocalSwapOnMainFrameInitializesCoreFrame) {
FrameTestHelpers::TestWebViewClient viewClient;
FrameTestHelpers::TestWebRemoteFrameClient remoteClient;
WebView* view = WebView::create(&viewClient, WebPageVisibilityStateVisible);
view->setMainFrame(remoteClient.frame());
WebRemoteFrame* remoteRoot = view->mainFrame()->toWebRemoteFrame();
remoteRoot->setReplicatedOrigin(SecurityOrigin::createUnique());
FrameTestHelpers::createLocalChild(remoteRoot);
// Do a remote-to-local swap of the top frame.
FrameTestHelpers::TestWebFrameClient localClient;
WebLocalFrame* localRoot = WebLocalFrame::createProvisional(
&localClient, remoteRoot, WebSandboxFlags::None);
FrameTestHelpers::TestWebWidgetClient webWidgetClient;
WebFrameWidget::create(&webWidgetClient, localRoot);
remoteRoot->swap(localRoot);
// Load a page with a child frame in the new root to make sure this doesn't
// crash when the child frame invokes setCoreFrame.
registerMockedHttpURLLoad("single_iframe.html");
registerMockedHttpURLLoad("visible_iframe.html");
FrameTestHelpers::loadFrame(localRoot, m_baseURL + "single_iframe.html");
view->close();
}
// See https://crbug.com/628942.
TEST_P(ParameterizedWebFrameTest, DeferredPageLoadWithRemoteMainFrame) {
// Prepare a page with a remote main frame.
FrameTestHelpers::TestWebViewClient viewClient;
FrameTestHelpers::TestWebRemoteFrameClient remoteClient;
WebView* view = WebView::create(&viewClient, WebPageVisibilityStateVisible);
view->setMainFrame(remoteClient.frame());
WebRemoteFrame* remoteRoot = view->mainFrame()->toWebRemoteFrame();
remoteRoot->setReplicatedOrigin(SecurityOrigin::createUnique());
// Check that ScopedPageLoadDeferrer properly triggers deferred loading for
// the current Page.
Page* page = remoteRoot->toImplBase()->frame()->page();
EXPECT_FALSE(page->defersLoading());
{
ScopedPageLoadDeferrer deferrer;
EXPECT_TRUE(page->defersLoading());
}
EXPECT_FALSE(page->defersLoading());
// Repeat this for a page with a local child frame, and ensure that the
// child frame's loads are also deferred.
WebLocalFrame* webLocalChild = FrameTestHelpers::createLocalChild(remoteRoot);
registerMockedHttpURLLoad("foo.html");
FrameTestHelpers::loadFrame(webLocalChild, m_baseURL + "foo.html");
LocalFrame* localChild = toWebLocalFrameImpl(webLocalChild)->frame();
EXPECT_FALSE(page->defersLoading());
EXPECT_FALSE(localChild->document()->fetcher()->defersLoading());
{
ScopedPageLoadDeferrer deferrer;
EXPECT_TRUE(page->defersLoading());
EXPECT_TRUE(localChild->document()->fetcher()->defersLoading());
}
EXPECT_FALSE(page->defersLoading());
EXPECT_FALSE(localChild->document()->fetcher()->defersLoading());
view->close();
}
class OverscrollWebViewClient : public FrameTestHelpers::TestWebViewClient {
public:
MOCK_METHOD4(didOverscroll,
void(const WebFloatSize&,
const WebFloatSize&,
const WebFloatPoint&,
const WebFloatSize&));
};
class WebFrameOverscrollTest
: public WebFrameTest,
public ::testing::WithParamInterface<blink::WebGestureDevice> {
protected:
WebGestureEvent generateEvent(WebInputEvent::Type type,
float deltaX = 0.0,
float deltaY = 0.0) {
WebGestureEvent event;
event.type = type;
// TODO(wjmaclean): Make sure that touchpad device is only ever used for
// gesture scrolling event types.
event.sourceDevice = GetParam();
event.x = 100;
event.y = 100;
if (type == WebInputEvent::GestureScrollUpdate) {
event.data.scrollUpdate.deltaX = deltaX;
event.data.scrollUpdate.deltaY = deltaY;
}
return event;
}
void ScrollBegin(FrameTestHelpers::WebViewHelper* webViewHelper) {
webViewHelper->webView()->handleInputEvent(
generateEvent(WebInputEvent::GestureScrollBegin));
}
void ScrollUpdate(FrameTestHelpers::WebViewHelper* webViewHelper,
float deltaX,
float deltaY) {
webViewHelper->webView()->handleInputEvent(
generateEvent(WebInputEvent::GestureScrollUpdate, deltaX, deltaY));
}
void ScrollEnd(FrameTestHelpers::WebViewHelper* webViewHelper) {
webViewHelper->webView()->handleInputEvent(
generateEvent(WebInputEvent::GestureScrollEnd));
}
};
INSTANTIATE_TEST_CASE_P(All,
WebFrameOverscrollTest,
::testing::Values(WebGestureDeviceTouchpad,
WebGestureDeviceTouchscreen));
TEST_P(WebFrameOverscrollTest,
AccumulatedRootOverscrollAndUnsedDeltaValuesOnOverscroll) {
OverscrollWebViewClient client;
registerMockedHttpURLLoad("overscroll/overscroll.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "overscroll/overscroll.html",
true, nullptr, &client, nullptr,
configureAndroid);
webViewHelper.resize(WebSize(200, 200));
// Calculation of accumulatedRootOverscroll and unusedDelta on multiple
// scrollUpdate.
ScrollBegin(&webViewHelper);
EXPECT_CALL(client, didOverscroll(WebFloatSize(8, 16), WebFloatSize(8, 16),
WebFloatPoint(100, 100), WebFloatSize()));
ScrollUpdate(&webViewHelper, -308, -316);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client, didOverscroll(WebFloatSize(0, 13), WebFloatSize(8, 29),
WebFloatPoint(100, 100), WebFloatSize()));
ScrollUpdate(&webViewHelper, 0, -13);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client, didOverscroll(WebFloatSize(20, 13), WebFloatSize(28, 42),
WebFloatPoint(100, 100), WebFloatSize()));
ScrollUpdate(&webViewHelper, -20, -13);
Mock::VerifyAndClearExpectations(&client);
// Overscroll is not reported.
EXPECT_CALL(client, didOverscroll(_, _, _, _)).Times(0);
ScrollUpdate(&webViewHelper, 0, 1);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client, didOverscroll(_, _, _, _)).Times(0);
ScrollUpdate(&webViewHelper, 1, 0);
Mock::VerifyAndClearExpectations(&client);
// Overscroll is reported.
EXPECT_CALL(client,
didOverscroll(WebFloatSize(0, -701), WebFloatSize(0, -701),
WebFloatPoint(100, 100), WebFloatSize()));
ScrollUpdate(&webViewHelper, 0, 1000);
Mock::VerifyAndClearExpectations(&client);
// Overscroll is not reported.
EXPECT_CALL(client, didOverscroll(_, _, _, _)).Times(0);
ScrollEnd(&webViewHelper);
Mock::VerifyAndClearExpectations(&client);
}
TEST_P(WebFrameOverscrollTest,
AccumulatedOverscrollAndUnusedDeltaValuesOnDifferentAxesOverscroll) {
OverscrollWebViewClient client;
registerMockedHttpURLLoad("overscroll/div-overscroll.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "overscroll/div-overscroll.html",
true, nullptr, &client, nullptr,
configureAndroid);
webViewHelper.resize(WebSize(200, 200));
ScrollBegin(&webViewHelper);
// Scroll the Div to the end.
EXPECT_CALL(client, didOverscroll(_, _, _, _)).Times(0);
ScrollUpdate(&webViewHelper, 0, -316);
Mock::VerifyAndClearExpectations(&client);
ScrollEnd(&webViewHelper);
ScrollBegin(&webViewHelper);
// Now On Scrolling DIV, scroll is bubbled and root layer is over-scrolled.
EXPECT_CALL(client, didOverscroll(WebFloatSize(0, 100), WebFloatSize(0, 100),
WebFloatPoint(100, 100), WebFloatSize()));
ScrollUpdate(&webViewHelper, 0, -100);
ScrollUpdate(&webViewHelper, 0, -100);
Mock::VerifyAndClearExpectations(&client);
// TODO(bokan): This has never worked but by the accident that this test was
// being run in a WebView without a size. This test should be fixed along with
// the bug, crbug.com/589320.
// Page scrolls vertically, but over-scrolls horizontally.
// EXPECT_CALL(client, didOverscroll(WebFloatSize(-100, 0), WebFloatSize(-100,
// 0), WebFloatPoint(100, 100), WebFloatSize()));
// ScrollUpdate(&webViewHelper, 100, 50);
// Mock::VerifyAndClearExpectations(&client);
// Scrolling up, Overscroll is not reported.
// EXPECT_CALL(client, didOverscroll(_, _, _, _)).Times(0);
// ScrollUpdate(&webViewHelper, 0, -50);
// Mock::VerifyAndClearExpectations(&client);
// Page scrolls horizontally, but over-scrolls vertically.
// EXPECT_CALL(client, didOverscroll(WebFloatSize(0, 100), WebFloatSize(0,
// 100), WebFloatPoint(100, 100), WebFloatSize()));
// ScrollUpdate(&webViewHelper, -100, -100);
// Mock::VerifyAndClearExpectations(&client);
}
TEST_P(WebFrameOverscrollTest, RootLayerOverscrolledOnInnerDivOverScroll) {
OverscrollWebViewClient client;
registerMockedHttpURLLoad("overscroll/div-overscroll.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "overscroll/div-overscroll.html",
true, nullptr, &client, nullptr,
configureAndroid);
webViewHelper.resize(WebSize(200, 200));
ScrollBegin(&webViewHelper);
// Scroll the Div to the end.
EXPECT_CALL(client, didOverscroll(_, _, _, _)).Times(0);
ScrollUpdate(&webViewHelper, 0, -316);
Mock::VerifyAndClearExpectations(&client);
ScrollEnd(&webViewHelper);
ScrollBegin(&webViewHelper);
// Now On Scrolling DIV, scroll is bubbled and root layer is over-scrolled.
EXPECT_CALL(client, didOverscroll(WebFloatSize(0, 50), WebFloatSize(0, 50),
WebFloatPoint(100, 100), WebFloatSize()));
ScrollUpdate(&webViewHelper, 0, -150);
Mock::VerifyAndClearExpectations(&client);
}
TEST_P(WebFrameOverscrollTest, RootLayerOverscrolledOnInnerIFrameOverScroll) {
OverscrollWebViewClient client;
registerMockedHttpURLLoad("overscroll/iframe-overscroll.html");
registerMockedHttpURLLoad("overscroll/scrollable-iframe.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(
m_baseURL + "overscroll/iframe-overscroll.html", true, nullptr, &client,
nullptr, configureAndroid);
webViewHelper.resize(WebSize(200, 200));
ScrollBegin(&webViewHelper);
// Scroll the IFrame to the end.
EXPECT_CALL(client, didOverscroll(_, _, _, _)).Times(0);
// This scroll will fully scroll the iframe but will be consumed before being
// counted as overscroll.
ScrollUpdate(&webViewHelper, 0, -320);
// This scroll will again target the iframe but wont bubble further up. Make
// sure that the unused scroll isn't handled as overscroll.
ScrollUpdate(&webViewHelper, 0, -50);
Mock::VerifyAndClearExpectations(&client);
ScrollEnd(&webViewHelper);
ScrollBegin(&webViewHelper);
// Now On Scrolling IFrame, scroll is bubbled and root layer is over-scrolled.
EXPECT_CALL(client, didOverscroll(WebFloatSize(0, 50), WebFloatSize(0, 50),
WebFloatPoint(100, 100), WebFloatSize()));
ScrollUpdate(&webViewHelper, 0, -150);
Mock::VerifyAndClearExpectations(&client);
ScrollEnd(&webViewHelper);
}
TEST_P(WebFrameOverscrollTest, ScaledPageRootLayerOverscrolled) {
OverscrollWebViewClient client;
registerMockedHttpURLLoad("overscroll/overscroll.html");
FrameTestHelpers::WebViewHelper webViewHelper;
WebViewImpl* webViewImpl = webViewHelper.initializeAndLoad(
m_baseURL + "overscroll/overscroll.html", true, nullptr, &client, nullptr,
configureAndroid);
webViewHelper.resize(WebSize(200, 200));
webViewImpl->setPageScaleFactor(3.0);
// Calculation of accumulatedRootOverscroll and unusedDelta on scaled page.
// The point is (99, 99) because we clamp in the division by 3 to 33 so when
// we go back to viewport coordinates it becomes (99, 99).
ScrollBegin(&webViewHelper);
EXPECT_CALL(client, didOverscroll(WebFloatSize(0, -30), WebFloatSize(0, -30),
WebFloatPoint(99, 99), WebFloatSize()));
ScrollUpdate(&webViewHelper, 0, 30);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client, didOverscroll(WebFloatSize(0, -30), WebFloatSize(0, -60),
WebFloatPoint(99, 99), WebFloatSize()));
ScrollUpdate(&webViewHelper, 0, 30);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client,
didOverscroll(WebFloatSize(-30, -30), WebFloatSize(-30, -90),
WebFloatPoint(99, 99), WebFloatSize()));
ScrollUpdate(&webViewHelper, 30, 30);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client,
didOverscroll(WebFloatSize(-30, 0), WebFloatSize(-60, -90),
WebFloatPoint(99, 99), WebFloatSize()));
ScrollUpdate(&webViewHelper, 30, 0);
Mock::VerifyAndClearExpectations(&client);
// Overscroll is not reported.
EXPECT_CALL(client, didOverscroll(_, _, _, _)).Times(0);
ScrollEnd(&webViewHelper);
Mock::VerifyAndClearExpectations(&client);
}
TEST_P(WebFrameOverscrollTest, NoOverscrollForSmallvalues) {
OverscrollWebViewClient client;
registerMockedHttpURLLoad("overscroll/overscroll.html");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "overscroll/overscroll.html",
true, nullptr, &client, nullptr,
configureAndroid);
webViewHelper.resize(WebSize(200, 200));
ScrollBegin(&webViewHelper);
EXPECT_CALL(client,
didOverscroll(WebFloatSize(-10, -10), WebFloatSize(-10, -10),
WebFloatPoint(100, 100), WebFloatSize()));
ScrollUpdate(&webViewHelper, 10, 10);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client,
didOverscroll(WebFloatSize(0, -0.10), WebFloatSize(-10, -10.10),
WebFloatPoint(100, 100), WebFloatSize()));
ScrollUpdate(&webViewHelper, 0, 0.10);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client, didOverscroll(WebFloatSize(-0.10, 0),
WebFloatSize(-10.10, -10.10),
WebFloatPoint(100, 100), WebFloatSize()));
ScrollUpdate(&webViewHelper, 0.10, 0);
Mock::VerifyAndClearExpectations(&client);
// For residual values overscrollDelta should be reset and didOverscroll
// shouldn't be called.
EXPECT_CALL(client, didOverscroll(_, _, _, _)).Times(0);
ScrollUpdate(&webViewHelper, 0, 0.09);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client, didOverscroll(_, _, _, _)).Times(0);
ScrollUpdate(&webViewHelper, 0.09, 0.09);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client, didOverscroll(_, _, _, _)).Times(0);
ScrollUpdate(&webViewHelper, 0.09, 0);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client, didOverscroll(_, _, _, _)).Times(0);
ScrollUpdate(&webViewHelper, 0, -0.09);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client, didOverscroll(_, _, _, _)).Times(0);
ScrollUpdate(&webViewHelper, -0.09, -0.09);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client, didOverscroll(_, _, _, _)).Times(0);
ScrollUpdate(&webViewHelper, -0.09, 0);
Mock::VerifyAndClearExpectations(&client);
EXPECT_CALL(client, didOverscroll(_, _, _, _)).Times(0);
ScrollEnd(&webViewHelper);
Mock::VerifyAndClearExpectations(&client);
}
TEST_F(WebFrameTest, OrientationFrameDetach) {
RuntimeEnabledFeatures::setOrientationEventEnabled(true);
registerMockedHttpURLLoad("orientation-frame-detach.html");
FrameTestHelpers::WebViewHelper webViewHelper;
WebViewImpl* webViewImpl = webViewHelper.initializeAndLoad(
m_baseURL + "orientation-frame-detach.html", true);
webViewImpl->mainFrameImpl()->sendOrientationChangeEvent();
}
TEST_F(WebFrameTest, MaxFramesDetach) {
registerMockedHttpURLLoad("max-frames-detach.html");
FrameTestHelpers::WebViewHelper webViewHelper;
WebViewImpl* webViewImpl = webViewHelper.initializeAndLoad(
m_baseURL + "max-frames-detach.html", true);
webViewImpl->mainFrameImpl()->collectGarbage();
}
TEST_F(WebFrameTest, ImageDocumentLoadFinishTime) {
// Loading an image resource directly generates an ImageDocument with
// the document loader feeding image data into the resource of a generated
// img tag. We expect the load finish time to be the same for the document
// and the image resource.
registerMockedHttpURLLoadWithMimeType("white-1x1.png", "image/png");
FrameTestHelpers::WebViewHelper webViewHelper;
webViewHelper.initializeAndLoad(m_baseURL + "white-1x1.png");
WebViewImpl* webView = webViewHelper.webView();
Document* document = webView->mainFrameImpl()->frame()->document();
EXPECT_TRUE(document);
EXPECT_TRUE(document->isImageDocument());
ImageDocument* imgDocument = toImageDocument(document);
ImageResource* resource = imgDocument->cachedImage();
EXPECT_TRUE(resource);
EXPECT_NE(0, resource->loadFinishTime());
DocumentLoader* loader = document->loader();
EXPECT_TRUE(loader);
EXPECT_EQ(loader->timing().responseEnd(), resource->loadFinishTime());
}
class CallbackOrderingWebFrameClient
: public FrameTestHelpers::TestWebFrameClient {
public:
CallbackOrderingWebFrameClient() : m_callbackCount(0) {}
void didStartLoading(bool toDifferentDocument) override {
EXPECT_EQ(0, m_callbackCount++);
FrameTestHelpers::TestWebFrameClient::didStartLoading(toDifferentDocument);
}
void didStartProvisionalLoad(WebLocalFrame*, double) override {
EXPECT_EQ(1, m_callbackCount++);
}
void didCommitProvisionalLoad(WebLocalFrame*,
const WebHistoryItem&,
WebHistoryCommitType) override {
EXPECT_EQ(2, m_callbackCount++);
}
void didFinishDocumentLoad(WebLocalFrame*) override {
EXPECT_EQ(3, m_callbackCount++);
}
void didHandleOnloadEvents(WebLocalFrame*) override {
EXPECT_EQ(4, m_callbackCount++);
}
void didFinishLoad(WebLocalFrame*) override {
EXPECT_EQ(5, m_callbackCount++);
}
void didStopLoading() override {
EXPECT_EQ(6, m_callbackCount++);
FrameTestHelpers::TestWebFrameClient::didStopLoading();
}
private:
int m_callbackCount;
};
TEST_F(WebFrameTest, CallbackOrdering) {
registerMockedHttpURLLoad("foo.html");
FrameTestHelpers::WebViewHelper webViewHelper;
CallbackOrderingWebFrameClient client;
webViewHelper.initializeAndLoad(m_baseURL + "foo.html", true, &client);
}
class TestWebRemoteFrameClientForVisibility
: public FrameTestHelpers::TestWebRemoteFrameClient {
public:
TestWebRemoteFrameClientForVisibility() : m_visible(true) {}
void visibilityChanged(bool visible) override { m_visible = visible; }
bool isVisible() const { return m_visible; }
private:
bool m_visible;
};
class WebFrameVisibilityChangeTest : public WebFrameTest {
public:
WebFrameVisibilityChangeTest() {
registerMockedHttpURLLoad("visible_iframe.html");
registerMockedHttpURLLoad("single_iframe.html");
m_frame = m_webViewHelper
.initializeAndLoad(m_baseURL + "single_iframe.html", true)
->mainFrame();
m_webRemoteFrame = remoteFrameClient()->frame();
}
~WebFrameVisibilityChangeTest() {}
void executeScriptOnMainFrame(const WebScriptSource& script) {
mainFrame()->executeScript(script);
mainFrame()->view()->updateAllLifecyclePhases();
runPendingTasks();
}
void swapLocalFrameToRemoteFrame() {
mainFrame()->lastChild()->swap(remoteFrame());
remoteFrame()->setReplicatedOrigin(SecurityOrigin::createUnique());
}
WebFrame* mainFrame() { return m_frame; }
WebRemoteFrameImpl* remoteFrame() { return m_webRemoteFrame; }
TestWebRemoteFrameClientForVisibility* remoteFrameClient() {
return &m_remoteFrameClient;
}
private:
TestWebRemoteFrameClientForVisibility m_remoteFrameClient;
FrameTestHelpers::WebViewHelper m_webViewHelper;
WebFrame* m_frame;
Persistent<WebRemoteFrameImpl> m_webRemoteFrame;
};
TEST_F(WebFrameVisibilityChangeTest, RemoteFrameVisibilityChange) {
swapLocalFrameToRemoteFrame();
executeScriptOnMainFrame(WebScriptSource(
"document.querySelector('iframe').style.display = 'none';"));
EXPECT_FALSE(remoteFrameClient()->isVisible());
executeScriptOnMainFrame(WebScriptSource(
"document.querySelector('iframe').style.display = 'block';"));
EXPECT_TRUE(remoteFrameClient()->isVisible());
}
TEST_F(WebFrameVisibilityChangeTest, RemoteFrameParentVisibilityChange) {
swapLocalFrameToRemoteFrame();
executeScriptOnMainFrame(
WebScriptSource("document.querySelector('iframe').parentElement.style."
"display = 'none';"));
EXPECT_FALSE(remoteFrameClient()->isVisible());
}
static void enableGlobalReuseForUnownedMainFrames(WebSettings* settings) {
settings->setShouldReuseGlobalForUnownedMainFrame(true);
}
// A main frame with no opener should have a unique security origin. Thus, the
// global should never be reused on the initial navigation.
TEST(WebFrameGlobalReuseTest, MainFrameWithNoOpener) {
FrameTestHelpers::WebViewHelper helper;
helper.initialize(true);
WebLocalFrame* mainFrame = helper.webView()->mainFrameImpl();
v8::HandleScope scope(v8::Isolate::GetCurrent());
mainFrame->executeScript(WebScriptSource("hello = 'world';"));
FrameTestHelpers::loadFrame(mainFrame, "data:text/html,new page");
v8::Local<v8::Value> result =
mainFrame->executeScriptAndReturnValue(WebScriptSource("hello"));
EXPECT_TRUE(result.IsEmpty());
}
// Child frames should never reuse the global on a cross-origin navigation, even
// if the setting is enabled. It's not safe to since the parent could have
// injected script before the initial navigation.
TEST(WebFrameGlobalReuseTest, ChildFrame) {
FrameTestHelpers::WebViewHelper helper;
helper.initialize(true, nullptr, nullptr, nullptr,
enableGlobalReuseForUnownedMainFrames);
WebLocalFrame* mainFrame = helper.webView()->mainFrameImpl();
FrameTestHelpers::loadFrame(mainFrame, "data:text/html,<iframe></iframe>");
WebLocalFrame* childFrame = mainFrame->firstChild()->toWebLocalFrame();
v8::HandleScope scope(v8::Isolate::GetCurrent());
childFrame->executeScript(WebScriptSource("hello = 'world';"));
FrameTestHelpers::loadFrame(childFrame, "data:text/html,new page");
v8::Local<v8::Value> result =
childFrame->executeScriptAndReturnValue(WebScriptSource("hello"));
EXPECT_TRUE(result.IsEmpty());
}
// A main frame with an opener should never reuse the global on a cross-origin
// navigation, even if the setting is enabled. It's not safe to since the opener
// could have injected script.
TEST(WebFrameGlobalReuseTest, MainFrameWithOpener) {
FrameTestHelpers::TestWebViewClient openerWebViewClient;
FrameTestHelpers::WebViewHelper openerHelper;
openerHelper.initialize(false, nullptr, &openerWebViewClient, nullptr);
FrameTestHelpers::WebViewHelper helper;
helper.initializeWithOpener(openerHelper.webView()->mainFrame(), true,
nullptr, nullptr, nullptr,
enableGlobalReuseForUnownedMainFrames);
WebLocalFrame* mainFrame = helper.webView()->mainFrameImpl();
v8::HandleScope scope(v8::Isolate::GetCurrent());
mainFrame->executeScript(WebScriptSource("hello = 'world';"));
FrameTestHelpers::loadFrame(mainFrame, "data:text/html,new page");
v8::Local<v8::Value> result =
mainFrame->executeScriptAndReturnValue(WebScriptSource("hello"));
EXPECT_TRUE(result.IsEmpty());
}
// A main frame that is unrelated to any other frame /can/ reuse the global if
// the setting is enabled. In this case, it's impossible for any other frames to
// have touched the global. Only the embedder could have injected script, and
// the embedder enabling this setting is a signal that the injected script needs
// to persist on the first navigation away from the initial empty document.
TEST(WebFrameGlobalReuseTest, ReuseForMainFrameIfEnabled) {
FrameTestHelpers::WebViewHelper helper;
helper.initialize(true, nullptr, nullptr, nullptr,
enableGlobalReuseForUnownedMainFrames);
WebLocalFrame* mainFrame = helper.webView()->mainFrameImpl();
v8::HandleScope scope(v8::Isolate::GetCurrent());
mainFrame->executeScript(WebScriptSource("hello = 'world';"));
FrameTestHelpers::loadFrame(mainFrame, "data:text/html,new page");
v8::Local<v8::Value> result =
mainFrame->executeScriptAndReturnValue(WebScriptSource("hello"));
ASSERT_TRUE(result->IsString());
EXPECT_EQ("world",
toCoreString(result->ToString(mainFrame->mainWorldScriptContext())
.ToLocalChecked()));
}
class SaveImageFromDataURLWebFrameClient
: public FrameTestHelpers::TestWebFrameClient {
public:
// WebFrameClient methods
void saveImageFromDataURL(const WebString& dataURL) override {
m_dataURL = dataURL;
}
// Local methods
const WebString& result() const { return m_dataURL; }
void reset() { m_dataURL = WebString(); }
private:
WebString m_dataURL;
};
TEST_F(WebFrameTest, SaveImageAt) {
std::string url = m_baseURL + "image-with-data-url.html";
URLTestHelpers::registerMockedURLLoad(toKURL(url),
"image-with-data-url.html");
URLTestHelpers::registerMockedURLLoad(toKURL("http://test"), "white-1x1.png");
FrameTestHelpers::WebViewHelper helper;
SaveImageFromDataURLWebFrameClient client;
WebViewImpl* webView = helper.initializeAndLoad(url, true, &client);
webView->resize(WebSize(400, 400));
webView->updateAllLifecyclePhases();
WebLocalFrame* localFrame = webView->mainFrameImpl();
client.reset();
localFrame->saveImageAt(WebPoint(1, 1));
EXPECT_EQ(
WebString::fromUTF8("data:image/gif;base64"
",R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="),
client.result());
client.reset();
localFrame->saveImageAt(WebPoint(1, 2));
EXPECT_EQ(WebString(), client.result());
webView->setPageScaleFactor(4);
webView->setVisualViewportOffset(WebFloatPoint(1, 1));
client.reset();
localFrame->saveImageAt(WebPoint(3, 3));
EXPECT_EQ(
WebString::fromUTF8("data:image/gif;base64"
",R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="),
client.result());
// Explicitly reset to break dependency on locally scoped client.
helper.reset();
}
TEST_F(WebFrameTest, SaveImageWithImageMap) {
std::string url = m_baseURL + "image-map.html";
URLTestHelpers::registerMockedURLLoad(toKURL(url), "image-map.html");
FrameTestHelpers::WebViewHelper helper;
SaveImageFromDataURLWebFrameClient client;
WebViewImpl* webView = helper.initializeAndLoad(url, true, &client);
webView->resize(WebSize(400, 400));
WebLocalFrame* localFrame = webView->mainFrameImpl();
client.reset();
localFrame->saveImageAt(WebPoint(25, 25));
EXPECT_EQ(
WebString::fromUTF8("data:image/gif;base64"
",R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="),
client.result());
client.reset();
localFrame->saveImageAt(WebPoint(75, 25));
EXPECT_EQ(
WebString::fromUTF8("data:image/gif;base64"
",R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="),
client.result());
client.reset();
localFrame->saveImageAt(WebPoint(125, 25));
EXPECT_EQ(WebString(), client.result());
// Explicitly reset to break dependency on locally scoped client.
helper.reset();
}
TEST_F(WebFrameTest, CopyImageAt) {
std::string url = m_baseURL + "canvas-copy-image.html";
URLTestHelpers::registerMockedURLLoad(toKURL(url), "canvas-copy-image.html");
FrameTestHelpers::WebViewHelper helper;
WebViewImpl* webView = helper.initializeAndLoad(url, true, 0);
webView->resize(WebSize(400, 400));
uint64_t sequence = Platform::current()->clipboard()->sequenceNumber(
WebClipboard::BufferStandard);
WebLocalFrame* localFrame = webView->mainFrameImpl();
localFrame->copyImageAt(WebPoint(50, 50));
EXPECT_NE(sequence, Platform::current()->clipboard()->sequenceNumber(
WebClipboard::BufferStandard));
WebImage image =
static_cast<WebMockClipboard*>(Platform::current()->clipboard())
->readRawImage(WebClipboard::Buffer());
SkAutoLockPixels autoLock(image.getSkBitmap());
EXPECT_EQ(SkColorSetARGB(255, 255, 0, 0), image.getSkBitmap().getColor(0, 0));
};
TEST_F(WebFrameTest, CopyImageAtWithPinchZoom) {
std::string url = m_baseURL + "canvas-copy-image.html";
URLTestHelpers::registerMockedURLLoad(toKURL(url), "canvas-copy-image.html");
FrameTestHelpers::WebViewHelper helper;
WebViewImpl* webView = helper.initializeAndLoad(url, true, 0);
webView->resize(WebSize(400, 400));
webView->updateAllLifecyclePhases();
webView->setPageScaleFactor(2);
webView->setVisualViewportOffset(WebFloatPoint(200, 200));
uint64_t sequence = Platform::current()->clipboard()->sequenceNumber(
WebClipboard::BufferStandard);
WebLocalFrame* localFrame = webView->mainFrameImpl();
localFrame->copyImageAt(WebPoint(0, 0));
EXPECT_NE(sequence, Platform::current()->clipboard()->sequenceNumber(
WebClipboard::BufferStandard));
WebImage image =
static_cast<WebMockClipboard*>(Platform::current()->clipboard())
->readRawImage(WebClipboard::Buffer());
SkAutoLockPixels autoLock(image.getSkBitmap());
EXPECT_EQ(SkColorSetARGB(255, 255, 0, 0), image.getSkBitmap().getColor(0, 0));
};
TEST_F(WebFrameTest, CopyImageWithImageMap) {
SaveImageFromDataURLWebFrameClient client;
std::string url = m_baseURL + "image-map.html";
URLTestHelpers::registerMockedURLLoad(toKURL(url), "image-map.html");
FrameTestHelpers::WebViewHelper helper;
WebViewImpl* webView = helper.initializeAndLoad(url, true, &client);
webView->resize(WebSize(400, 400));
client.reset();
WebLocalFrame* localFrame = webView->mainFrameImpl();
localFrame->saveImageAt(WebPoint(25, 25));
EXPECT_EQ(
WebString::fromUTF8("data:image/gif;base64"
",R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="),
client.result());
client.reset();
localFrame->saveImageAt(WebPoint(75, 25));
EXPECT_EQ(
WebString::fromUTF8("data:image/gif;base64"
",R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="),
client.result());
client.reset();
localFrame->saveImageAt(WebPoint(125, 25));
EXPECT_EQ(WebString(), client.result());
// Explicitly reset to break dependency on locally scoped client.
helper.reset();
}
TEST_F(WebFrameTest, LoadJavascriptURLInNewFrame) {
FrameTestHelpers::WebViewHelper helper;
helper.initialize(true);
WebURLRequest request;
std::string redirectURL = m_baseURL + "foo.html";
URLTestHelpers::registerMockedURLLoad(toKURL(redirectURL), "foo.html");
request.setURL(toKURL("javascript:location='" + redirectURL + "'"));
request.setRequestorOrigin(WebSecurityOrigin::createUnique());
helper.webView()->mainFrameImpl()->loadRequest(request);
// Normally, the result of the JS url replaces the existing contents on the
// Document. However, if the JS triggers a navigation, the contents should
// not be replaced.
EXPECT_EQ("", toLocalFrame(helper.webView()->page()->mainFrame())
->document()
->documentElement()
->innerText());
}
class TestResourcePriorityWebFrameClient
: public FrameTestHelpers::TestWebFrameClient {
public:
class ExpectedRequest {
public:
ExpectedRequest(const KURL& url, WebURLRequest::Priority priority)
: url(url), priority(priority), seen(false) {}
KURL url;
WebURLRequest::Priority priority;
bool seen;
};
TestResourcePriorityWebFrameClient() {}
void willSendRequest(WebLocalFrame*, WebURLRequest& request) override {
ExpectedRequest* expectedRequest = m_expectedRequests.get(request.url());
DCHECK(expectedRequest);
EXPECT_EQ(expectedRequest->priority, request.getPriority());
expectedRequest->seen = true;
}
void addExpectedRequest(const KURL& url, WebURLRequest::Priority priority) {
m_expectedRequests.add(url, wrapUnique(new ExpectedRequest(url, priority)));
}
void verifyAllRequests() {
for (const auto& request : m_expectedRequests)
EXPECT_TRUE(request.value->seen);
}
private:
HashMap<KURL, std::unique_ptr<ExpectedRequest>> m_expectedRequests;
};
TEST_F(WebFrameTest, ChangeResourcePriority) {
TestResourcePriorityWebFrameClient client;
registerMockedHttpURLLoad("promote_img_in_viewport_priority.html");
registerMockedHttpURLLoad("image_slow.pl");
registerMockedHttpURLLoad("image_slow_out_of_viewport.pl");
client.addExpectedRequest(
toKURL("http://internal.test/promote_img_in_viewport_priority.html"),
WebURLRequest::PriorityVeryHigh);
client.addExpectedRequest(toKURL("http://internal.test/image_slow.pl"),
WebURLRequest::PriorityLow);
client.addExpectedRequest(
toKURL("http://internal.test/image_slow_out_of_viewport.pl"),
WebURLRequest::PriorityLow);
FrameTestHelpers::WebViewHelper helper;
helper.initialize(true, &client);
helper.resize(WebSize(640, 480));
FrameTestHelpers::loadFrame(
helper.webView()->mainFrame(),
m_baseURL + "promote_img_in_viewport_priority.html");
// Ensure the image in the viewport got promoted after the request was sent.
Resource* image = toWebLocalFrameImpl(helper.webView()->mainFrame())
->frame()
->document()
->fetcher()
->allResources()
.get(toKURL("http://internal.test/image_slow.pl"));
DCHECK(image);
EXPECT_EQ(ResourceLoadPriorityHigh, image->resourceRequest().priority());
client.verifyAllRequests();
}
TEST_F(WebFrameTest, ScriptPriority) {
TestResourcePriorityWebFrameClient client;
registerMockedHttpURLLoad("script_priority.html");
registerMockedHttpURLLoad("priorities/defer.js");
registerMockedHttpURLLoad("priorities/async.js");
registerMockedHttpURLLoad("priorities/head.js");
registerMockedHttpURLLoad("priorities/document-write.js");
registerMockedHttpURLLoad("priorities/injected.js");
registerMockedHttpURLLoad("priorities/injected-async.js");
registerMockedHttpURLLoad("priorities/body.js");
client.addExpectedRequest(toKURL("http://internal.test/script_priority.html"),
WebURLRequest::PriorityVeryHigh);
client.addExpectedRequest(toKURL("http://internal.test/priorities/defer.js"),
WebURLRequest::PriorityLow);
client.addExpectedRequest(toKURL("http://internal.test/priorities/async.js"),
WebURLRequest::PriorityLow);
client.addExpectedRequest(toKURL("http://internal.test/priorities/head.js"),
WebURLRequest::PriorityHigh);
client.addExpectedRequest(
toKURL("http://internal.test/priorities/document-write.js"),
WebURLRequest::PriorityHigh);
client.addExpectedRequest(
toKURL("http://internal.test/priorities/injected.js"),
WebURLRequest::PriorityLow);
client.addExpectedRequest(
toKURL("http://internal.test/priorities/injected-async.js"),
WebURLRequest::PriorityLow);
client.addExpectedRequest(toKURL("http://internal.test/priorities/body.js"),
WebURLRequest::PriorityHigh);
FrameTestHelpers::WebViewHelper helper;
helper.initializeAndLoad(m_baseURL + "script_priority.html", true, &client);
client.verifyAllRequests();
}
class MultipleDataChunkDelegate : public WebURLLoaderTestDelegate {
public:
void didReceiveData(WebURLLoaderClient* originalClient,
WebURLLoader* loader,
const char* data,
int dataLength,
int encodedDataLength) override {
EXPECT_GT(dataLength, 16);
originalClient->didReceiveData(loader, data, 16, 16, 16);
// This didReceiveData call shouldn't crash due to a failed assertion.
originalClient->didReceiveData(loader, data + 16, dataLength - 16,
encodedDataLength - 16, dataLength - 16);
}
};
TEST_F(WebFrameTest, ImageDocumentDecodeError) {
std::string url = m_baseURL + "not_an_image.ico";
URLTestHelpers::registerMockedURLLoad(toKURL(url), "not_an_image.ico",
"image/x-icon");
MultipleDataChunkDelegate delegate;
Platform::current()->getURLLoaderMockFactory()->setLoaderDelegate(&delegate);
FrameTestHelpers::WebViewHelper helper;
helper.initializeAndLoad(url, true);
Platform::current()->getURLLoaderMockFactory()->setLoaderDelegate(nullptr);
Document* document =
toLocalFrame(helper.webView()->page()->mainFrame())->document();
EXPECT_TRUE(document->isImageDocument());
EXPECT_EQ(Resource::DecodeError,
toImageDocument(document)->cachedImage()->getStatus());
}
} // namespace blink