Adam Barth 77264d3e94 Teach sky.Paragraph how to actually compute layout
Previously layout was exiting early because the frame view
was null. Now we actually compute some layout and paint the
text. This patch makes paragraph_builder_test pass.
2015-09-17 09:37:30 -07:00

387 lines
13 KiB
C++

/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "sky/engine/core/rendering/RenderView.h"
#include "gen/sky/platform/RuntimeEnabledFeatures.h"
#include "sky/engine/core/dom/Document.h"
#include "sky/engine/core/dom/Element.h"
#include "sky/engine/core/frame/LocalFrame.h"
#include "sky/engine/core/page/Page.h"
#include "sky/engine/core/rendering/HitTestResult.h"
#include "sky/engine/core/rendering/RenderGeometryMap.h"
#include "sky/engine/core/rendering/RenderLayer.h"
#include "sky/engine/platform/TraceEvent.h"
#include "sky/engine/platform/geometry/FloatQuad.h"
#include "sky/engine/platform/geometry/TransformState.h"
#include "sky/engine/platform/graphics/GraphicsContext.h"
namespace blink {
RenderView::RenderView(Document* document)
: RenderFlexibleBox(document)
, m_frameView(document ? document->view() : nullptr)
, m_selectionStart(nullptr)
, m_selectionEnd(nullptr)
, m_selectionStartPos(-1)
, m_selectionEndPos(-1)
, m_renderCounterCount(0)
, m_hitTestCount(0)
{
// init RenderObject attributes
setInline(false);
m_minPreferredLogicalWidth = 0;
m_maxPreferredLogicalWidth = 0;
setPreferredLogicalWidthsDirty(MarkOnlyThis);
setPositionState(AbsolutePosition); // to 0,0 :)
}
RenderView::~RenderView()
{
}
bool RenderView::hitTest(const HitTestRequest& request, HitTestResult& result)
{
return hitTest(request, result.hitTestLocation(), result);
}
bool RenderView::hitTest(const HitTestRequest& request, const HitTestLocation& location, HitTestResult& result)
{
TRACE_EVENT0("blink", "RenderView::hitTest");
m_hitTestCount++;
if (!m_frameView->visibleContentRect().contains(location.roundedPoint()))
return false;
// We have to recursively update layout/style here because otherwise, when the hit test recurses
// into a child document, it could trigger a layout on the parent document, which can destroy RenderLayers
// that are higher up in the call stack, leading to crashes.
// Note that Document::updateLayout calls its parent's updateLayout.
// FIXME: It should be the caller's responsibility to ensure an up-to-date layout.
frameView()->updateLayoutAndStyleIfNeededRecursive();
// RenderView should make sure to update layout before entering hit testing
ASSERT(!frame()->view()->layoutPending());
ASSERT(!document().renderView()->needsLayout());
// TODO(ojan): Does any of this intersection stuff make sense for Sky?
LayoutRect hitTestArea = view()->documentRect();
if (!request.ignoreClipping())
hitTestArea.intersect(frame()->view()->visibleContentRect());
bool insideLayer = hitTestLayer(layer(), 0, request, result, hitTestArea, location);
if (!insideLayer) {
// TODO(ojan): Is this code needed for Sky?
// We didn't hit any layer. If we are the root layer and the mouse is -- or just was -- down,
// return ourselves. We do this so mouse events continue getting delivered after a drag has
// exited the WebView, and so hit testing over a scrollbar hits the content document.
if (request.active() || request.release()) {
updateHitTestResult(result, location.point());
insideLayer = true;
}
}
return insideLayer;
}
void RenderView::computeLogicalHeight(LayoutUnit logicalHeight, LayoutUnit, LogicalExtentComputedValues& computedValues) const
{
computedValues.m_extent = logicalHeight;
}
void RenderView::updateLogicalWidth()
{
setLogicalWidth(viewLogicalWidth());
}
bool RenderView::isChildAllowed(RenderObject* child, RenderStyle*) const
{
return child->isBox();
}
void RenderView::layout()
{
SubtreeLayoutScope layoutScope(*this);
bool relayoutChildren = width() != viewWidth() || height() != viewHeight();
if (relayoutChildren) {
layoutScope.setChildNeedsLayout(this);
for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {
if ((child->isBox() && toRenderBox(child)->hasRelativeLogicalHeight())
|| child->style()->logicalHeight().isPercent()
|| child->style()->logicalMinHeight().isPercent()
|| child->style()->logicalMaxHeight().isPercent())
layoutScope.setChildNeedsLayout(child);
}
}
if (!needsLayout())
return;
RenderFlexibleBox::layout();
clearNeedsLayout();
}
void RenderView::mapLocalToContainer(const RenderBox* paintInvalidationContainer, TransformState& transformState, MapCoordinatesFlags mode) const
{
if (!paintInvalidationContainer && mode & UseTransforms && shouldUseTransformFromContainer(0)) {
TransformationMatrix t;
getTransformFromContainer(0, LayoutSize(), t);
transformState.applyTransform(t);
}
}
const RenderObject* RenderView::pushMappingToContainer(const RenderBox* ancestorToStopAt, RenderGeometryMap& geometryMap) const
{
LayoutSize offset;
RenderObject* container = 0;
// If a container was specified, and was not 0 or the RenderView, then we
// should have found it by now unless we're traversing to a parent document.
ASSERT_ARG(ancestorToStopAt, !ancestorToStopAt || ancestorToStopAt == this || container);
if ((!ancestorToStopAt || container) && shouldUseTransformFromContainer(container)) {
TransformationMatrix t;
getTransformFromContainer(container, LayoutSize(), t);
geometryMap.push(this, t, false, false, true);
} else {
geometryMap.push(this, offset, false, false, false);
}
return container;
}
void RenderView::mapAbsoluteToLocalPoint(MapCoordinatesFlags mode, TransformState& transformState) const
{
if (mode & UseTransforms && shouldUseTransformFromContainer(0)) {
TransformationMatrix t;
getTransformFromContainer(0, LayoutSize(), t);
transformState.applyTransform(t);
}
}
void RenderView::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, Vector<RenderBox*>& layers)
{
// If we ever require layout but receive a paint anyway, something has gone horribly wrong.
ASSERT(!needsLayout());
// RenderViews should never be called to paint with an offset not on device pixels.
ASSERT(LayoutPoint(IntPoint(paintOffset.x(), paintOffset.y())) == paintOffset);
// This avoids painting garbage between columns if there is a column gap.
if (m_frameView && style()->isOverflowPaged())
paintInfo.context->fillRect(paintInfo.rect, m_frameView->baseBackgroundColor());
paintObject(paintInfo, paintOffset, layers);
}
void RenderView::paintBoxDecorationBackground(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
if (!view())
return;
// This code typically only executes if the root element's visibility has been set to hidden,
// if there is a transform on the <html>, or if there is a page scale factor less than 1.
// Only fill with the base background color (typically white) if we're the root document,
// since iframes/frames with no background in the child document should show the parent's background.
if (!frameView()->isTransparent()) {
Color baseColor = frameView()->baseBackgroundColor();
if (baseColor.alpha()) {
CompositeOperator previousOperator = paintInfo.context->compositeOperation();
paintInfo.context->setCompositeOperation(CompositeCopy);
paintInfo.context->fillRect(paintInfo.rect, baseColor);
paintInfo.context->setCompositeOperation(previousOperator);
} else {
paintInfo.context->clearRect(paintInfo.rect);
}
}
paintCustomPainting(paintInfo, paintOffset);
}
void RenderView::absoluteQuads(Vector<FloatQuad>& quads) const
{
quads.append(FloatRect(FloatPoint(), layer()->size()));
}
static RenderObject* rendererAfterPosition(RenderObject* object, unsigned offset)
{
if (!object)
return 0;
RenderObject* child = object->childAt(offset);
return child ? child : object->nextInPreOrderAfterChildren();
}
// When exploring the RenderTree looking for the nodes involved in the Selection, sometimes it's
// required to change the traversing direction because the "start" position is below the "end" one.
static inline RenderObject* getNextOrPrevRenderObjectBasedOnDirection(const RenderObject* o, const RenderObject* stop, bool& continueExploring, bool& exploringBackwards)
{
RenderObject* next;
if (exploringBackwards) {
next = o->previousInPreOrder();
continueExploring = next && !(next)->isRenderView();
} else {
next = o->nextInPreOrder();
continueExploring = next && next != stop;
exploringBackwards = !next && (next != stop);
if (exploringBackwards) {
next = stop->previousInPreOrder();
continueExploring = next && !next->isRenderView();
}
}
return next;
}
void RenderView::setSelection(RenderObject* start, int startPos, RenderObject* end, int endPos)
{
// This code makes no assumptions as to if the rendering tree is up to date or not
// and will not try to update it. Currently clearSelection calls this
// (intentionally) without updating the rendering tree as it doesn't care.
// Other callers may want to force recalc style before calling this.
// Make sure both our start and end objects are defined.
// Check www.msnbc.com and try clicking around to find the case where this happened.
if ((start && !end) || (end && !start))
return;
// Just return if the selection hasn't changed.
if (m_selectionStart == start && m_selectionStartPos == startPos &&
m_selectionEnd == end && m_selectionEndPos == endPos)
return;
RenderObject* os = m_selectionStart;
RenderObject* stop = rendererAfterPosition(m_selectionEnd, m_selectionEndPos);
bool exploringBackwards = false;
bool continueExploring = os && (os != stop);
while (continueExploring) {
if ((os->canBeSelectionLeaf() || os == m_selectionStart || os == m_selectionEnd) && os->selectionState() != SelectionNone) {
os->setSelectionStateIfNeeded(SelectionNone);
}
os = getNextOrPrevRenderObjectBasedOnDirection(os, stop, continueExploring, exploringBackwards);
}
// set selection start and end
m_selectionStart = start;
m_selectionStartPos = startPos;
m_selectionEnd = end;
m_selectionEndPos = endPos;
// Update the selection status of all objects between m_selectionStart and m_selectionEnd
if (start && start == end) {
start->setSelectionStateIfNeeded(SelectionBoth);
} else {
if (start)
start->setSelectionStateIfNeeded(SelectionStart);
if (end)
end->setSelectionStateIfNeeded(SelectionEnd);
}
RenderObject* o = start;
stop = rendererAfterPosition(end, endPos);
while (o && o != stop) {
if (o != start && o != end && o->canBeSelectionLeaf())
o->setSelectionStateIfNeeded(SelectionInside);
o = o->nextInPreOrder();
}
}
void RenderView::getSelection(RenderObject*& startRenderer, int& startOffset, RenderObject*& endRenderer, int& endOffset) const
{
startRenderer = m_selectionStart;
startOffset = m_selectionStartPos;
endRenderer = m_selectionEnd;
endOffset = m_selectionEndPos;
}
void RenderView::clearSelection()
{
setSelection(0, -1, 0, -1);
}
void RenderView::selectionStartEnd(int& startPos, int& endPos) const
{
startPos = m_selectionStartPos;
endPos = m_selectionEndPos;
}
LayoutRect RenderView::viewRect() const
{
if (m_frameView)
return m_frameView->visibleContentRect();
return LayoutRect();
}
IntRect RenderView::unscaledDocumentRect() const
{
return pixelSnappedIntRect(layoutOverflowRect());
}
LayoutRect RenderView::backgroundRect(RenderBox* backgroundRenderer) const
{
return unscaledDocumentRect();
}
IntRect RenderView::documentRect() const
{
FloatRect overflowRect(unscaledDocumentRect());
if (hasTransform())
overflowRect = transform()->mapRect(overflowRect);
return IntRect(overflowRect);
}
int RenderView::viewHeight() const
{
return m_frameViewSize.height();
}
int RenderView::viewWidth() const
{
return m_frameViewSize.width();
}
int RenderView::viewLogicalHeight() const
{
return viewHeight();
}
LayoutUnit RenderView::viewLogicalHeightForPercentages() const
{
return viewLogicalHeight();
}
// FIXME(sky): remove
double RenderView::layoutViewportWidth() const
{
return viewWidth();
}
// FIXME(sky): remove
double RenderView::layoutViewportHeight() const
{
return viewHeight();
}
} // namespace blink