blob: 8f7fcbcea1e031191b03289189965b89fa26434d [file] [log] [blame]
/*
* Copyright (C) 2014 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 "core/layout/LayoutGeometryMap.h"
#include "core/dom/Document.h"
#include "core/layout/LayoutBox.h"
#include "core/paint/PaintLayer.h"
#include "platform/testing/URLTestHelpers.h"
#include "public/platform/Platform.h"
#include "public/platform/WebURLLoaderMockFactory.h"
#include "public/web/WebCache.h"
#include "public/web/WebFrameClient.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "web/WebLocalFrameImpl.h"
#include "web/tests/FrameTestHelpers.h"
namespace blink {
class LayoutGeometryMapTest : public testing::Test {
public:
LayoutGeometryMapTest() : m_baseURL("http://www.test.com/") {}
void TearDown() override {
Platform::current()->getURLLoaderMockFactory()->unregisterAllURLs();
WebCache::clear();
}
protected:
static LayoutBox* getFrameElement(const char* iframeName,
WebView* webView,
const WTF::AtomicString& elementId) {
WebLocalFrameImpl* iframe = toWebLocalFrameImpl(
webView->findFrameByName(WebString::fromUTF8(iframeName)));
if (!iframe)
return nullptr;
LocalFrame* frame = iframe->frame();
Document* doc = frame->document();
Element* element = doc->getElementById(elementId);
if (!element)
return nullptr;
return element->layoutBox();
}
static LayoutBox* getLayoutBox(WebView* webView,
const WTF::AtomicString& elementId) {
WebViewImpl* webViewImpl = toWebViewImpl(webView);
if (!webViewImpl)
return nullptr;
LocalFrame* frame = webViewImpl->mainFrameImpl()->frame();
Document* doc = frame->document();
Element* element = doc->getElementById(elementId);
if (!element)
return nullptr;
return element->layoutBox();
}
static const LayoutBoxModelObject* getLayoutContainer(
WebView* webView,
const WTF::AtomicString& elementId) {
LayoutBox* rb = getLayoutBox(webView, elementId);
if (!rb)
return nullptr;
PaintLayer* compositingLayer =
rb->enclosingLayer()->enclosingLayerForPaintInvalidation();
if (!compositingLayer)
return nullptr;
return compositingLayer->layoutObject();
}
static const LayoutBoxModelObject* getFrameLayoutContainer(
const char* frameId,
WebView* webView,
const WTF::AtomicString& elementId) {
LayoutBox* rb = getFrameElement(frameId, webView, elementId);
if (!rb)
return nullptr;
PaintLayer* compositingLayer =
rb->enclosingLayer()->enclosingLayerForPaintInvalidation();
if (!compositingLayer)
return nullptr;
return compositingLayer->layoutObject();
}
static const FloatRect rectFromQuad(const FloatQuad& quad) {
FloatRect rect;
rect.setX(std::min(
quad.p1().x(),
std::min(quad.p2().x(), std::min(quad.p3().x(), quad.p4().x()))));
rect.setY(std::min(
quad.p1().y(),
std::min(quad.p2().y(), std::min(quad.p3().y(), quad.p4().y()))));
rect.setWidth(std::max(quad.p1().x(),
std::max(quad.p2().x(),
std::max(quad.p3().x(), quad.p4().x()))) -
rect.x());
rect.setHeight(std::max(quad.p1().y(),
std::max(quad.p2().y(),
std::max(quad.p3().y(), quad.p4().y()))) -
rect.y());
return rect;
}
void registerMockedHttpURLLoad(const std::string& fileName) {
URLTestHelpers::registerMockedURLFromBaseURL(
WebString::fromUTF8(m_baseURL.c_str()),
WebString::fromUTF8(fileName.c_str()));
}
const std::string m_baseURL;
FrameTestHelpers::TestWebFrameClient m_mockWebViewClient;
};
TEST_F(LayoutGeometryMapTest, SimpleGeometryMapTest) {
registerMockedHttpURLLoad("rgm_test.html");
FrameTestHelpers::WebViewHelper webViewHelper;
WebView* webView =
webViewHelper.initializeAndLoad(m_baseURL + "rgm_test.html", true, 0, 0);
webView->resize(WebSize(1000, 1000));
webView->updateAllLifecyclePhases();
// We are going test everything twice. Once with FloatPoints and once with
// FloatRects. This is because LayoutGeometryMap treats both slightly
// differently
LayoutGeometryMap rgm;
rgm.pushMappingsToAncestor(getLayoutBox(webView, "InitialDiv"), 0);
FloatRect rect(0.0f, 0.0f, 1.0f, 2.0f);
EXPECT_EQ(FloatQuad(FloatRect(8.0f, 8.0f, 1.0f, 2.0f)),
rgm.mapToAncestor(rect, nullptr));
rgm.popMappingsToAncestor(static_cast<PaintLayer*>(nullptr));
EXPECT_EQ(FloatQuad(FloatRect(0.0f, 0.0f, 1.0f, 2.0f)),
rgm.mapToAncestor(rect, nullptr));
rgm.pushMappingsToAncestor(getLayoutBox(webView, "InnerDiv"), 0);
EXPECT_EQ(FloatQuad(FloatRect(21.0f, 6.0f, 1.0f, 2.0f)),
rgm.mapToAncestor(rect, getLayoutBox(webView, "CenterDiv")));
rgm.pushMappingsToAncestor(getLayoutBox(webView, "OtherDiv"),
getLayoutBox(webView, "InnerDiv"));
EXPECT_EQ(FloatQuad(FloatRect(22.0f, 12.0f, 1.0f, 2.0f)),
rgm.mapToAncestor(rect, getLayoutBox(webView, "CenterDiv")));
EXPECT_EQ(FloatQuad(FloatRect(1.0f, 6.0f, 1.0f, 2.0f)),
rgm.mapToAncestor(rect, getLayoutBox(webView, "InnerDiv")));
EXPECT_EQ(FloatQuad(FloatRect(50.0f, 44.0f, 1.0f, 2.0f)),
rgm.mapToAncestor(rect, nullptr));
}
// Fails on Windows due to crbug.com/391457. When run through the transform the
// position on windows differs by a pixel
#if OS(WIN)
TEST_F(LayoutGeometryMapTest, DISABLED_TransformedGeometryTest)
#else
TEST_F(LayoutGeometryMapTest, TransformedGeometryTest)
#endif
{
registerMockedHttpURLLoad("rgm_transformed_test.html");
FrameTestHelpers::WebViewHelper webViewHelper;
WebView* webView = webViewHelper.initializeAndLoad(
m_baseURL + "rgm_transformed_test.html", true, 0, 0);
webView->resize(WebSize(1000, 1000));
webView->updateAllLifecyclePhases();
LayoutGeometryMap rgm;
rgm.pushMappingsToAncestor(getLayoutBox(webView, "InitialDiv"), 0);
const float rectWidth = 15.0f;
const float scaleWidth = 2.0f;
const float scaleHeight = 3.0f;
FloatRect rect(0.0f, 0.0f, 15.0f, 25.0f);
EXPECT_EQ(FloatQuad(FloatRect(8.0f, 8.0f, 15.0f, 25.0f)),
rgm.mapToAncestor(rect, nullptr));
rgm.popMappingsToAncestor(static_cast<PaintLayer*>(nullptr));
EXPECT_EQ(FloatQuad(FloatRect(0.0f, 0.0f, 15.0f, 25.0f)).boundingBox(),
rgm.mapToAncestor(rect, nullptr).boundingBox());
rgm.pushMappingsToAncestor(getLayoutBox(webView, "InnerDiv"), 0);
EXPECT_EQ(FloatQuad(FloatRect(523.0f - rectWidth, 6.0f, 15.0f, 25.0f))
.boundingBox(),
rgm.mapToAncestor(rect, getLayoutBox(webView, "CenterDiv"))
.boundingBox());
rgm.pushMappingsToAncestor(getLayoutBox(webView, "OtherDiv"),
getLayoutBox(webView, "InnerDiv"));
EXPECT_EQ(FloatQuad(FloatRect(522.0f - rectWidth, 12.0f, 15.0f, 25.0f))
.boundingBox(),
rgm.mapToAncestor(rect, getLayoutBox(webView, "CenterDiv"))
.boundingBox());
EXPECT_EQ(
FloatQuad(FloatRect(1.0f, 6.0f, 15.0f, 25.0f)).boundingBox(),
rgm.mapToAncestor(rect, getLayoutBox(webView, "InnerDiv")).boundingBox());
EXPECT_EQ(FloatQuad(FloatRect(821.0f - rectWidth * scaleWidth, 31.0f,
15.0f * scaleWidth, 25.0f * scaleHeight))
.boundingBox(),
rgm.mapToAncestor(rect, nullptr).boundingBox());
rect = FloatRect(10.0f, 25.0f, 15.0f, 25.0f);
EXPECT_EQ(FloatQuad(FloatRect(512.0f - rectWidth, 37.0f, 15.0f, 25.0f))
.boundingBox(),
rgm.mapToAncestor(rect, getLayoutBox(webView, "CenterDiv"))
.boundingBox());
EXPECT_EQ(
FloatQuad(FloatRect(11.0f, 31.0f, 15.0f, 25.0f)).boundingBox(),
rgm.mapToAncestor(rect, getLayoutBox(webView, "InnerDiv")).boundingBox());
EXPECT_EQ(FloatQuad(FloatRect(801.0f - rectWidth * scaleWidth, 106.0f,
15.0f * scaleWidth, 25.0f * scaleHeight))
.boundingBox(),
rgm.mapToAncestor(rect, nullptr).boundingBox());
}
TEST_F(LayoutGeometryMapTest, FixedGeometryTest) {
registerMockedHttpURLLoad("rgm_fixed_position_test.html");
FrameTestHelpers::WebViewHelper webViewHelper;
WebView* webView = webViewHelper.initializeAndLoad(
m_baseURL + "rgm_fixed_position_test.html", true, 0, 0);
webView->resize(WebSize(1000, 1000));
webView->updateAllLifecyclePhases();
LayoutGeometryMap rgm;
rgm.pushMappingsToAncestor(getLayoutBox(webView, "InitialDiv"), 0);
FloatRect rect(0.0f, 0.0f, 15.0f, 25.0f);
EXPECT_EQ(FloatQuad(FloatRect(8.0f, 8.0f, 15.0f, 25.0f)),
rgm.mapToAncestor(rect, nullptr));
rgm.popMappingsToAncestor(static_cast<PaintLayer*>(nullptr));
EXPECT_EQ(FloatQuad(FloatRect(0.0f, 0.0f, 15.0f, 25.0f)),
rgm.mapToAncestor(rect, nullptr));
rgm.pushMappingsToAncestor(getLayoutBox(webView, "InnerDiv"), 0);
EXPECT_EQ(FloatQuad(FloatRect(20.0f, 14.0f, 15.0f, 25.0f)),
rgm.mapToAncestor(rect, nullptr));
rgm.pushMappingsToAncestor(getLayoutBox(webView, "OtherDiv"),
getLayoutBox(webView, "InnerDiv"));
EXPECT_EQ(FloatQuad(FloatRect(21.0f, 20.0f, 15.0f, 25.0f)),
rgm.mapToAncestor(rect, getLayoutContainer(webView, "CenterDiv")));
rect = FloatRect(22.0f, 15.2f, 15.3f, 0.0f);
EXPECT_EQ(FloatQuad(FloatRect(43.0f, 35.2f, 15.3f, 0.0f)),
rgm.mapToAncestor(rect, getLayoutContainer(webView, "CenterDiv")));
EXPECT_EQ(FloatQuad(FloatRect(43.0f, 35.2f, 15.3f, 0.0f)),
rgm.mapToAncestor(rect, getLayoutContainer(webView, "InnerDiv")));
EXPECT_EQ(FloatQuad(FloatRect(43.0f, 35.2f, 15.3f, 0.0f)),
rgm.mapToAncestor(rect, nullptr));
}
TEST_F(LayoutGeometryMapTest, ContainsFixedPositionTest) {
registerMockedHttpURLLoad("rgm_contains_fixed_position_test.html");
FrameTestHelpers::WebViewHelper webViewHelper;
WebView* webView = webViewHelper.initializeAndLoad(
m_baseURL + "rgm_contains_fixed_position_test.html", true, 0, 0);
webView->resize(WebSize(1000, 1000));
webView->updateAllLifecyclePhases();
FloatRect rect(0.0f, 0.0f, 100.0f, 100.0f);
LayoutGeometryMap rgm;
// This fixed position element is not contained and so is attached at the top
// of the viewport.
rgm.pushMappingsToAncestor(getLayoutBox(webView, "simple-container"), 0);
EXPECT_EQ(FloatQuad(FloatRect(8.0f, 100.0f, 100.0f, 100.0f)),
rgm.mapToAncestor(rect, nullptr));
rgm.pushMappingsToAncestor(getLayoutBox(webView, "fixed1"),
getLayoutBox(webView, "simple-container"));
EXPECT_EQ(FloatQuad(FloatRect(8.0f, 50.0f, 100.0f, 100.0f)),
rgm.mapToAncestor(rect, nullptr));
rgm.popMappingsToAncestor(static_cast<PaintLayer*>(nullptr));
// Transforms contain fixed position descendants.
rgm.pushMappingsToAncestor(getLayoutBox(webView, "has-transform"), 0);
EXPECT_EQ(FloatQuad(FloatRect(8.0f, 100.0f, 100.0f, 100.0f)),
rgm.mapToAncestor(rect, nullptr));
rgm.pushMappingsToAncestor(getLayoutBox(webView, "fixed2"),
getLayoutBox(webView, "has-transform"));
EXPECT_EQ(FloatQuad(FloatRect(8.0f, 100.0f, 100.0f, 100.0f)),
rgm.mapToAncestor(rect, nullptr));
rgm.popMappingsToAncestor(static_cast<PaintLayer*>(nullptr));
// Paint containment contains fixed position descendants.
rgm.pushMappingsToAncestor(getLayoutBox(webView, "contains-paint"), 0);
EXPECT_EQ(FloatQuad(FloatRect(8.0f, 100.0f, 100.0f, 100.0f)),
rgm.mapToAncestor(rect, nullptr));
rgm.pushMappingsToAncestor(getLayoutBox(webView, "fixed3"),
getLayoutBox(webView, "contains-paint"));
EXPECT_EQ(FloatQuad(FloatRect(8.0f, 100.0f, 100.0f, 100.0f)),
rgm.mapToAncestor(rect, nullptr));
rgm.popMappingsToAncestor(static_cast<PaintLayer*>(nullptr));
}
TEST_F(LayoutGeometryMapTest, IframeTest) {
registerMockedHttpURLLoad("rgm_iframe_test.html");
registerMockedHttpURLLoad("rgm_test.html");
FrameTestHelpers::WebViewHelper webViewHelper;
WebView* webView = webViewHelper.initializeAndLoad(
m_baseURL + "rgm_iframe_test.html", true, 0, 0);
webView->resize(WebSize(1000, 1000));
webView->updateAllLifecyclePhases();
LayoutGeometryMap rgm(TraverseDocumentBoundaries);
LayoutGeometryMap rgmNoFrame;
rgmNoFrame.pushMappingsToAncestor(
getFrameElement("test_frame", webView, "InitialDiv"), 0);
rgm.pushMappingsToAncestor(
getFrameElement("test_frame", webView, "InitialDiv"), 0);
FloatRect rect(0.0f, 0.0f, 1.0f, 2.0f);
EXPECT_EQ(FloatQuad(FloatRect(8.0f, 8.0f, 1.0f, 2.0f)),
rgmNoFrame.mapToAncestor(rect, nullptr));
// Our initial rect looks like: (0, 0, 1, 2)
// p0_____
// | |
// | |
// | |
// |___|
// When we rotate we get a rect of the form:
// p0_
// / -_
// / /
// / /
// -_/
// So it is sensible that the minimum y is the same as for a point at 0, 0.
// The minimum x should be p0.x - 2 * sin(30deg) = p0.x - 1.
// That maximum x should likewise be p0.x + cos(30deg) = p0.x + 0.866.
// And the maximum y should be p0.x + sin(30deg) + 2*cos(30deg)
// = p0.y + 2.232.
EXPECT_NEAR(69.5244f, rectFromQuad(rgm.mapToAncestor(rect, nullptr)).x(),
0.0001f);
EXPECT_NEAR(-44.0237, rectFromQuad(rgm.mapToAncestor(rect, nullptr)).y(),
0.0001f);
EXPECT_NEAR(1.866, rectFromQuad(rgm.mapToAncestor(rect, nullptr)).width(),
0.0001f);
EXPECT_NEAR(2.232, rectFromQuad(rgm.mapToAncestor(rect, nullptr)).height(),
0.0001f);
rgm.popMappingsToAncestor(static_cast<PaintLayer*>(nullptr));
rgmNoFrame.popMappingsToAncestor(static_cast<PaintLayer*>(nullptr));
rgm.pushMappingsToAncestor(getFrameElement("test_frame", webView, "InnerDiv"),
0);
rgmNoFrame.pushMappingsToAncestor(
getFrameElement("test_frame", webView, "InnerDiv"), 0);
EXPECT_EQ(FloatQuad(FloatRect(21.0f, 6.0f, 1.0f, 2.0f)),
rgm.mapToAncestor(rect, getFrameLayoutContainer(
"test_frame", webView, "CenterDiv")));
EXPECT_EQ(
FloatQuad(FloatRect(21.0f, 6.0f, 1.0f, 2.0f)),
rgmNoFrame.mapToAncestor(
rect, getFrameLayoutContainer("test_frame", webView, "CenterDiv")));
rgm.pushMappingsToAncestor(
getFrameElement("test_frame", webView, "OtherDiv"),
getFrameLayoutContainer("test_frame", webView, "InnerDiv"));
rgmNoFrame.pushMappingsToAncestor(
getFrameElement("test_frame", webView, "OtherDiv"),
getFrameLayoutContainer("test_frame", webView, "InnerDiv"));
EXPECT_EQ(FloatQuad(FloatRect(22.0f, 12.0f, 1.0f, 2.0f)),
rgm.mapToAncestor(rect, getFrameLayoutContainer(
"test_frame", webView, "CenterDiv")));
EXPECT_EQ(
FloatQuad(FloatRect(22.0f, 12.0f, 1.0f, 2.0f)),
rgmNoFrame.mapToAncestor(
rect, getFrameLayoutContainer("test_frame", webView, "CenterDiv")));
EXPECT_EQ(FloatQuad(FloatRect(1.0f, 6.0f, 1.0f, 2.0f)),
rgm.mapToAncestor(rect, getFrameLayoutContainer(
"test_frame", webView, "InnerDiv")));
EXPECT_EQ(
FloatQuad(FloatRect(1.0f, 6.0f, 1.0f, 2.0f)),
rgmNoFrame.mapToAncestor(
rect, getFrameLayoutContainer("test_frame", webView, "InnerDiv")));
EXPECT_NEAR(87.8975f, rectFromQuad(rgm.mapToAncestor(rect, nullptr)).x(),
0.0001f);
EXPECT_NEAR(8.1532f, rectFromQuad(rgm.mapToAncestor(rect, nullptr)).y(),
0.0001f);
EXPECT_NEAR(1.866, rectFromQuad(rgm.mapToAncestor(rect, nullptr)).width(),
0.0001f);
EXPECT_NEAR(2.232, rectFromQuad(rgm.mapToAncestor(rect, nullptr)).height(),
0.0001f);
EXPECT_EQ(FloatQuad(FloatRect(50.0f, 44.0f, 1.0f, 2.0f)),
rgmNoFrame.mapToAncestor(rect, nullptr));
}
TEST_F(LayoutGeometryMapTest, ColumnTest) {
registerMockedHttpURLLoad("rgm_column_test.html");
FrameTestHelpers::WebViewHelper webViewHelper;
WebView* webView = webViewHelper.initializeAndLoad(
m_baseURL + "rgm_column_test.html", true, 0, 0);
webView->resize(WebSize(1000, 1000));
webView->updateAllLifecyclePhases();
// The document is 1000f wide (we resized to that size).
// We have a 8px margin on either side of the document.
// Between each column we have a 10px gap, and we have 3 columns.
// The width of a given column is (1000 - 16 - 20)/3.
// The total offset between any column and it's neighbour is width + 10px
// (for the gap).
float offset = (1000.0f - 16.0f - 20.0f) / 3.0f + 10.0f;
LayoutGeometryMap rgm;
rgm.pushMappingsToAncestor(getLayoutBox(webView, "A"), 0);
FloatPoint point;
FloatRect rect(0.0f, 0.0f, 5.0f, 3.0f);
EXPECT_EQ(FloatQuad(FloatRect(8.0f, 8.0f, 5.0f, 3.0f)),
rgm.mapToAncestor(rect, nullptr));
rgm.popMappingsToAncestor(static_cast<PaintLayer*>(nullptr));
EXPECT_EQ(FloatQuad(FloatRect(0.0f, 0.0f, 5.0f, 3.0f)),
rgm.mapToAncestor(rect, nullptr));
rgm.pushMappingsToAncestor(getLayoutBox(webView, "Col1"), 0);
EXPECT_EQ(FloatQuad(FloatRect(8.0f, 8.0f, 5.0f, 3.0f)),
rgm.mapToAncestor(rect, nullptr));
rgm.popMappingsToAncestor(static_cast<PaintLayer*>(nullptr));
rgm.pushMappingsToAncestor(getLayoutBox(webView, "Col2"), nullptr);
EXPECT_NEAR(8.0f + offset, rectFromQuad(rgm.mapToAncestor(rect, nullptr)).x(),
0.1f);
EXPECT_NEAR(8.0f, rectFromQuad(rgm.mapToAncestor(rect, nullptr)).y(), 0.1f);
EXPECT_EQ(5.0f, rectFromQuad(rgm.mapToAncestor(rect, nullptr)).width());
EXPECT_EQ(3.0f, rectFromQuad(rgm.mapToAncestor(rect, nullptr)).height());
rgm.popMappingsToAncestor(static_cast<PaintLayer*>(nullptr));
rgm.pushMappingsToAncestor(getLayoutBox(webView, "Col3"), nullptr);
EXPECT_NEAR(8.0f + offset * 2.0f,
rectFromQuad(rgm.mapToAncestor(rect, nullptr)).x(), 0.1f);
EXPECT_NEAR(8.0f, rectFromQuad(rgm.mapToAncestor(rect, nullptr)).y(), 0.1f);
EXPECT_EQ(5.0f, rectFromQuad(rgm.mapToAncestor(rect, nullptr)).width());
EXPECT_EQ(3.0f, rectFromQuad(rgm.mapToAncestor(rect, nullptr)).height());
}
} // namespace blink