mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
This removes the bulk of core/editing/*. The following files remain, because they might be useful yet: EditingBoundary.h FindOptions.h htmlediting.cpp htmlediting.h PlainTextRange.cpp PlainTextRange.h PositionWithAffinity.cpp PositionWithAffinity.h RenderedPosition.cpp RenderedPosition.h TextAffinity.h TextGranularity.h TextIterator.cpp TextIterator.h VisiblePosition.cpp VisiblePosition.h VisibleSelection.cpp VisibleSelection.h VisibleUnits.cpp VisibleUnits.h In addition to remove obviously editing-related stuff like "ApplyBlockElementCommand.cpp" and "InsertLineBreakCommand.cpp", this also removes the DOM side of selection, all the caret management and painting code, composition support (IME) including the relevant events, spelling checker support, and the undo stack. Outside the core/editing/* directory, I also deleted the EditorClient, SpellCheckerClient, and EmptyClients classes. The other changes outside of editing/ are mostly just about removing mentions of the selection or carets. I tried to leave the code for _painting_ selections and composition runs, though that code is mostly disconnected now.
1880 lines
66 KiB
C++
1880 lines
66 KiB
C++
/*
|
|
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
|
|
* (C) 1999 Antti Koivisto (koivisto@kde.org)
|
|
* (C) 2000 Dirk Mueller (mueller@kde.org)
|
|
* (C) 2004 Allan Sandfeld Jensen (kde@carewolf.com)
|
|
* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2011 Apple Inc. All rights reserved.
|
|
* Copyright (C) 2009 Google Inc. All rights reserved.
|
|
* Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
|
|
*
|
|
* 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/RenderObject.h"
|
|
|
|
#include <algorithm>
|
|
#include "gen/sky/platform/RuntimeEnabledFeatures.h"
|
|
#include "sky/engine/core/css/resolver/StyleResolver.h"
|
|
#include "sky/engine/core/dom/ElementTraversal.h"
|
|
#include "sky/engine/core/dom/Range.h"
|
|
#include "sky/engine/core/dom/StyleEngine.h"
|
|
#include "sky/engine/core/dom/shadow/ShadowRoot.h"
|
|
#include "sky/engine/core/editing/EditingBoundary.h"
|
|
#include "sky/engine/core/editing/htmlediting.h"
|
|
#include "sky/engine/core/frame/FrameView.h"
|
|
#include "sky/engine/core/frame/LocalFrame.h"
|
|
#include "sky/engine/core/frame/Settings.h"
|
|
#include "sky/engine/core/html/HTMLElement.h"
|
|
#include "sky/engine/core/page/Page.h"
|
|
#include "sky/engine/core/rendering/HitTestResult.h"
|
|
#include "sky/engine/core/rendering/RenderFlexibleBox.h"
|
|
#include "sky/engine/core/rendering/RenderGeometryMap.h"
|
|
#include "sky/engine/core/rendering/RenderInline.h"
|
|
#include "sky/engine/core/rendering/RenderLayer.h"
|
|
#include "sky/engine/core/rendering/RenderObjectInlines.h"
|
|
#include "sky/engine/core/rendering/RenderParagraph.h"
|
|
#include "sky/engine/core/rendering/RenderText.h"
|
|
#include "sky/engine/core/rendering/RenderTheme.h"
|
|
#include "sky/engine/core/rendering/RenderView.h"
|
|
#include "sky/engine/core/rendering/style/ShadowList.h"
|
|
#include "sky/engine/platform/JSONValues.h"
|
|
#include "sky/engine/platform/Partitions.h"
|
|
#include "sky/engine/platform/TraceEvent.h"
|
|
#include "sky/engine/platform/geometry/TransformState.h"
|
|
#include "sky/engine/platform/graphics/GraphicsContext.h"
|
|
#include "sky/engine/wtf/RefCountedLeakCounter.h"
|
|
#include "sky/engine/wtf/text/StringBuilder.h"
|
|
#include "sky/engine/wtf/text/WTFString.h"
|
|
#ifndef NDEBUG
|
|
#include <stdio.h>
|
|
#endif
|
|
|
|
namespace blink {
|
|
|
|
#if ENABLE(ASSERT)
|
|
|
|
RenderObject::SetLayoutNeededForbiddenScope::SetLayoutNeededForbiddenScope(RenderObject& renderObject)
|
|
: m_renderObject(renderObject)
|
|
, m_preexistingForbidden(m_renderObject.isSetNeedsLayoutForbidden())
|
|
{
|
|
m_renderObject.setNeedsLayoutIsForbidden(true);
|
|
}
|
|
|
|
RenderObject::SetLayoutNeededForbiddenScope::~SetLayoutNeededForbiddenScope()
|
|
{
|
|
m_renderObject.setNeedsLayoutIsForbidden(m_preexistingForbidden);
|
|
}
|
|
#endif
|
|
|
|
struct SameSizeAsRenderObject {
|
|
virtual ~SameSizeAsRenderObject() { } // Allocate vtable pointer.
|
|
void* pointers[5];
|
|
#if ENABLE(ASSERT)
|
|
unsigned m_debugBitfields : 2;
|
|
#if ENABLE(OILPAN)
|
|
unsigned m_oilpanBitfields : 1;
|
|
#endif
|
|
#endif
|
|
unsigned m_bitfields;
|
|
};
|
|
|
|
COMPILE_ASSERT(sizeof(RenderObject) == sizeof(SameSizeAsRenderObject), RenderObject_should_stay_small);
|
|
|
|
bool RenderObject::s_affectsParentBlock = false;
|
|
|
|
#if !ENABLE(OILPAN)
|
|
void* RenderObject::operator new(size_t sz)
|
|
{
|
|
ASSERT(isMainThread());
|
|
return partitionAlloc(Partitions::getRenderingPartition(), sz);
|
|
}
|
|
|
|
void RenderObject::operator delete(void* ptr)
|
|
{
|
|
ASSERT(isMainThread());
|
|
partitionFree(ptr);
|
|
}
|
|
#endif
|
|
|
|
RenderObject* RenderObject::createObject(Element* element, RenderStyle* style)
|
|
{
|
|
ASSERT(isAllowedToModifyRenderTreeStructure(element->document()));
|
|
|
|
switch (style->display()) {
|
|
case NONE:
|
|
return 0;
|
|
case INLINE:
|
|
return new RenderInline(element);
|
|
case PARAGRAPH:
|
|
return new RenderParagraph(element);
|
|
case FLEX:
|
|
case INLINE_FLEX:
|
|
return new RenderFlexibleBox(element);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, renderObjectCounter, ("RenderObject"));
|
|
unsigned RenderObject::s_instanceCount = 0;
|
|
|
|
RenderObject::RenderObject(Node* node)
|
|
: m_style(nullptr)
|
|
, m_node(node)
|
|
, m_parent(nullptr)
|
|
, m_previous(nullptr)
|
|
, m_next(nullptr)
|
|
#if ENABLE(ASSERT)
|
|
, m_setNeedsLayoutForbidden(false)
|
|
#if ENABLE(OILPAN)
|
|
, m_didCallDestroy(false)
|
|
#endif
|
|
#endif
|
|
, m_bitfields(node)
|
|
{
|
|
#ifndef NDEBUG
|
|
renderObjectCounter.increment();
|
|
#endif
|
|
++s_instanceCount;
|
|
}
|
|
|
|
RenderObject::~RenderObject()
|
|
{
|
|
#if ENABLE(OILPAN)
|
|
ASSERT(m_didCallDestroy);
|
|
#endif
|
|
#ifndef NDEBUG
|
|
renderObjectCounter.decrement();
|
|
#endif
|
|
--s_instanceCount;
|
|
}
|
|
|
|
String RenderObject::debugName() const
|
|
{
|
|
StringBuilder name;
|
|
name.append(renderName());
|
|
|
|
if (Node* node = this->node()) {
|
|
name.append(' ');
|
|
name.append(node->debugName());
|
|
}
|
|
|
|
return name.toString();
|
|
}
|
|
|
|
bool RenderObject::isDescendantOf(const RenderObject* obj) const
|
|
{
|
|
for (const RenderObject* r = this; r; r = r->m_parent) {
|
|
if (r == obj)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void RenderObject::addChild(RenderObject* newChild, RenderObject* beforeChild)
|
|
{
|
|
ASSERT(isAllowedToModifyRenderTreeStructure(document()));
|
|
|
|
RenderObjectChildList* children = virtualChildren();
|
|
ASSERT(children);
|
|
children->insertChildNode(this, newChild, beforeChild);
|
|
}
|
|
|
|
void RenderObject::removeChild(RenderObject* oldChild)
|
|
{
|
|
ASSERT(isAllowedToModifyRenderTreeStructure(document()));
|
|
|
|
RenderObjectChildList* children = virtualChildren();
|
|
ASSERT(children);
|
|
if (!children)
|
|
return;
|
|
|
|
children->removeChildNode(this, oldChild);
|
|
}
|
|
|
|
RenderObject* RenderObject::nextInPreOrder() const
|
|
{
|
|
if (RenderObject* o = slowFirstChild())
|
|
return o;
|
|
|
|
return nextInPreOrderAfterChildren();
|
|
}
|
|
|
|
RenderObject* RenderObject::nextInPreOrderAfterChildren() const
|
|
{
|
|
RenderObject* o = nextSibling();
|
|
if (!o) {
|
|
o = parent();
|
|
while (o && !o->nextSibling())
|
|
o = o->parent();
|
|
if (o)
|
|
o = o->nextSibling();
|
|
}
|
|
|
|
return o;
|
|
}
|
|
|
|
RenderObject* RenderObject::nextInPreOrder(const RenderObject* stayWithin) const
|
|
{
|
|
if (RenderObject* o = slowFirstChild())
|
|
return o;
|
|
|
|
return nextInPreOrderAfterChildren(stayWithin);
|
|
}
|
|
|
|
RenderObject* RenderObject::nextInPreOrderAfterChildren(const RenderObject* stayWithin) const
|
|
{
|
|
if (this == stayWithin)
|
|
return 0;
|
|
|
|
const RenderObject* current = this;
|
|
RenderObject* next = current->nextSibling();
|
|
for (; !next; next = current->nextSibling()) {
|
|
current = current->parent();
|
|
if (!current || current == stayWithin)
|
|
return 0;
|
|
}
|
|
return next;
|
|
}
|
|
|
|
RenderObject* RenderObject::previousInPreOrder() const
|
|
{
|
|
if (RenderObject* o = previousSibling()) {
|
|
while (RenderObject* lastChild = o->slowLastChild())
|
|
o = lastChild;
|
|
return o;
|
|
}
|
|
|
|
return parent();
|
|
}
|
|
|
|
RenderObject* RenderObject::previousInPreOrder(const RenderObject* stayWithin) const
|
|
{
|
|
if (this == stayWithin)
|
|
return 0;
|
|
|
|
return previousInPreOrder();
|
|
}
|
|
|
|
RenderObject* RenderObject::childAt(unsigned index) const
|
|
{
|
|
RenderObject* child = slowFirstChild();
|
|
for (unsigned i = 0; child && i < index; i++)
|
|
child = child->nextSibling();
|
|
return child;
|
|
}
|
|
|
|
RenderObject* RenderObject::lastLeafChild() const
|
|
{
|
|
RenderObject* r = slowLastChild();
|
|
while (r) {
|
|
RenderObject* n = 0;
|
|
n = r->slowLastChild();
|
|
if (!n)
|
|
break;
|
|
r = n;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static void addLayers(RenderObject* obj, RenderLayer* parentLayer, RenderObject*& newObject,
|
|
RenderLayer*& beforeChild)
|
|
{
|
|
if (obj->hasLayer()) {
|
|
if (!beforeChild && newObject) {
|
|
// We need to figure out the layer that follows newObject. We only do
|
|
// this the first time we find a child layer, and then we update the
|
|
// pointer values for newObject and beforeChild used by everyone else.
|
|
beforeChild = newObject->parent()->findNextLayer(parentLayer, newObject);
|
|
newObject = 0;
|
|
}
|
|
parentLayer->addChild(toRenderBox(obj)->layer(), beforeChild);
|
|
return;
|
|
}
|
|
|
|
for (RenderObject* curr = obj->slowFirstChild(); curr; curr = curr->nextSibling())
|
|
addLayers(curr, parentLayer, newObject, beforeChild);
|
|
}
|
|
|
|
void RenderObject::addLayers(RenderLayer* parentLayer)
|
|
{
|
|
if (!parentLayer)
|
|
return;
|
|
|
|
RenderObject* object = this;
|
|
RenderLayer* beforeChild = 0;
|
|
blink::addLayers(this, parentLayer, object, beforeChild);
|
|
}
|
|
|
|
void RenderObject::removeLayers(RenderLayer* parentLayer)
|
|
{
|
|
if (!parentLayer)
|
|
return;
|
|
|
|
if (hasLayer()) {
|
|
parentLayer->removeChild(toRenderBox(this)->layer());
|
|
return;
|
|
}
|
|
|
|
for (RenderObject* curr = slowFirstChild(); curr; curr = curr->nextSibling())
|
|
curr->removeLayers(parentLayer);
|
|
}
|
|
|
|
void RenderObject::moveLayers(RenderLayer* oldParent, RenderLayer* newParent)
|
|
{
|
|
if (!newParent)
|
|
return;
|
|
|
|
if (hasLayer()) {
|
|
RenderLayer* layer = toRenderBox(this)->layer();
|
|
ASSERT(oldParent == layer->parent());
|
|
if (oldParent)
|
|
oldParent->removeChild(layer);
|
|
newParent->addChild(layer);
|
|
return;
|
|
}
|
|
|
|
for (RenderObject* curr = slowFirstChild(); curr; curr = curr->nextSibling())
|
|
curr->moveLayers(oldParent, newParent);
|
|
}
|
|
|
|
RenderLayer* RenderObject::findNextLayer(RenderLayer* parentLayer, RenderObject* startPoint,
|
|
bool checkParent)
|
|
{
|
|
// Error check the parent layer passed in. If it's null, we can't find anything.
|
|
if (!parentLayer)
|
|
return 0;
|
|
|
|
// Step 1: If our layer is a child of the desired parent, then return our layer.
|
|
RenderLayer* ourLayer = hasLayer() ? toRenderBox(this)->layer() : 0;
|
|
if (ourLayer && ourLayer->parent() == parentLayer)
|
|
return ourLayer;
|
|
|
|
// Step 2: If we don't have a layer, or our layer is the desired parent, then descend
|
|
// into our siblings trying to find the next layer whose parent is the desired parent.
|
|
if (!ourLayer || ourLayer == parentLayer) {
|
|
for (RenderObject* curr = startPoint ? startPoint->nextSibling() : slowFirstChild();
|
|
curr; curr = curr->nextSibling()) {
|
|
RenderLayer* nextLayer = curr->findNextLayer(parentLayer, 0, false);
|
|
if (nextLayer)
|
|
return nextLayer;
|
|
}
|
|
}
|
|
|
|
// Step 3: If our layer is the desired parent layer, then we're finished. We didn't
|
|
// find anything.
|
|
if (parentLayer == ourLayer)
|
|
return 0;
|
|
|
|
// Step 4: If |checkParent| is set, climb up to our parent and check its siblings that
|
|
// follow us to see if we can locate a layer.
|
|
if (checkParent && parent())
|
|
return parent()->findNextLayer(parentLayer, this, true);
|
|
|
|
return 0;
|
|
}
|
|
|
|
RenderLayer* RenderObject::enclosingLayer() const
|
|
{
|
|
for (const RenderObject* current = this; current; current = current->parent()) {
|
|
if (current->hasLayer())
|
|
return toRenderBox(current)->layer();
|
|
}
|
|
// FIXME: We should remove the one caller that triggers this case and make
|
|
// this function return a reference.
|
|
ASSERT(!m_parent && !isRenderView());
|
|
return 0;
|
|
}
|
|
|
|
RenderBox* RenderObject::enclosingBox() const
|
|
{
|
|
RenderObject* curr = const_cast<RenderObject*>(this);
|
|
while (curr) {
|
|
if (curr->isBox())
|
|
return toRenderBox(curr);
|
|
curr = curr->parent();
|
|
}
|
|
|
|
ASSERT_NOT_REACHED();
|
|
return 0;
|
|
}
|
|
|
|
RenderBoxModelObject* RenderObject::enclosingBoxModelObject() const
|
|
{
|
|
RenderObject* curr = const_cast<RenderObject*>(this);
|
|
while (curr) {
|
|
if (curr->isBoxModelObject())
|
|
return toRenderBoxModelObject(curr);
|
|
curr = curr->parent();
|
|
}
|
|
|
|
ASSERT_NOT_REACHED();
|
|
return 0;
|
|
}
|
|
|
|
bool RenderObject::skipInvalidationWhenLaidOutChildren() const
|
|
{
|
|
if (!neededLayoutBecauseOfChildren())
|
|
return false;
|
|
|
|
// SVG renderers need to be invalidated when their children are laid out.
|
|
// RenderBlocks with line boxes are responsible to invalidate them so we can't ignore them.
|
|
if (isRenderParagraph() && toRenderParagraph(this)->firstLineBox())
|
|
return false;
|
|
|
|
return rendererHasNoBoxEffect();
|
|
}
|
|
|
|
RenderBlock* RenderObject::firstLineBlock() const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline bool objectIsRelayoutBoundary(const RenderObject* object)
|
|
{
|
|
if (!object->hasOverflowClip())
|
|
return false;
|
|
|
|
if (object->style()->width().isIntrinsicOrAuto() || object->style()->height().isIntrinsicOrAuto() || object->style()->height().isPercent())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void RenderObject::markContainingBlocksForLayout(bool scheduleRelayout, RenderObject* newRoot, SubtreeLayoutScope* layouter)
|
|
{
|
|
ASSERT(!scheduleRelayout || !newRoot);
|
|
ASSERT(!isSetNeedsLayoutForbidden());
|
|
ASSERT(!layouter || this != layouter->root());
|
|
|
|
RenderObject* object = container();
|
|
RenderObject* last = this;
|
|
|
|
bool simplifiedNormalFlowLayout = needsSimplifiedNormalFlowLayout() && !selfNeedsLayout() && !normalChildNeedsLayout();
|
|
|
|
while (object) {
|
|
if (object->selfNeedsLayout())
|
|
return;
|
|
|
|
// Don't mark the outermost object of an unrooted subtree. That object will be
|
|
// marked when the subtree is added to the document.
|
|
RenderObject* container = object->container();
|
|
if (!container && !object->isRenderView())
|
|
return;
|
|
if (!last->isText() && last->style()->hasOutOfFlowPosition()) {
|
|
bool willSkipRelativelyPositionedInlines = !object->isRenderBlock();
|
|
// Skip relatively positioned inlines and anonymous blocks to get to the enclosing RenderBlock.
|
|
while (object && !object->isRenderBlock())
|
|
object = object->container();
|
|
if (!object || object->posChildNeedsLayout())
|
|
return;
|
|
if (willSkipRelativelyPositionedInlines)
|
|
container = object->container();
|
|
object->setPosChildNeedsLayout(true);
|
|
simplifiedNormalFlowLayout = true;
|
|
ASSERT(!object->isSetNeedsLayoutForbidden());
|
|
} else if (simplifiedNormalFlowLayout) {
|
|
if (object->needsSimplifiedNormalFlowLayout())
|
|
return;
|
|
object->setNeedsSimplifiedNormalFlowLayout(true);
|
|
ASSERT(!object->isSetNeedsLayoutForbidden());
|
|
} else {
|
|
if (object->normalChildNeedsLayout())
|
|
return;
|
|
object->setNormalChildNeedsLayout(true);
|
|
ASSERT(!object->isSetNeedsLayoutForbidden());
|
|
}
|
|
|
|
if (layouter) {
|
|
layouter->addRendererToLayout(object);
|
|
if (object == layouter->root())
|
|
return;
|
|
}
|
|
|
|
if (object == newRoot)
|
|
return;
|
|
|
|
last = object;
|
|
if (scheduleRelayout && objectIsRelayoutBoundary(last))
|
|
break;
|
|
object = container;
|
|
}
|
|
|
|
if (scheduleRelayout)
|
|
last->scheduleRelayout();
|
|
}
|
|
|
|
#if ENABLE(ASSERT)
|
|
void RenderObject::checkBlockPositionedObjectsNeedLayout()
|
|
{
|
|
ASSERT(!needsLayout());
|
|
|
|
if (isRenderBlock())
|
|
toRenderBlock(this)->checkPositionedObjectsNeedLayout();
|
|
}
|
|
#endif
|
|
|
|
void RenderObject::setPreferredLogicalWidthsDirty(MarkingBehavior markParents)
|
|
{
|
|
m_bitfields.setPreferredLogicalWidthsDirty(true);
|
|
if (markParents == MarkContainingBlockChain && (isText() || !style()->hasOutOfFlowPosition()))
|
|
invalidateContainerPreferredLogicalWidths();
|
|
}
|
|
|
|
void RenderObject::clearPreferredLogicalWidthsDirty()
|
|
{
|
|
m_bitfields.setPreferredLogicalWidthsDirty(false);
|
|
}
|
|
|
|
void RenderObject::invalidateContainerPreferredLogicalWidths()
|
|
{
|
|
// In order to avoid pathological behavior when inlines are deeply nested, we do include them
|
|
// in the chain that we mark dirty (even though they're kind of irrelevant).
|
|
RenderObject* o = container();
|
|
while (o && !o->preferredLogicalWidthsDirty()) {
|
|
// Don't invalidate the outermost object of an unrooted subtree. That object will be
|
|
// invalidated when the subtree is added to the document.
|
|
RenderObject* container = o->container();
|
|
if (!container && !o->isRenderView())
|
|
break;
|
|
|
|
o->m_bitfields.setPreferredLogicalWidthsDirty(true);
|
|
if (o->style()->hasOutOfFlowPosition())
|
|
// A positioned object has no effect on the min/max width of its containing block ever.
|
|
// We can optimize this case and not go up any further.
|
|
break;
|
|
o = container;
|
|
}
|
|
}
|
|
|
|
RenderBlock* RenderObject::containingBlock() const
|
|
{
|
|
RenderObject* o = parent();
|
|
if (!isText() && m_style->position() == AbsolutePosition) {
|
|
while (o) {
|
|
// For relpositioned inlines, we return the nearest non-anonymous enclosing block. We don't try
|
|
// to return the inline itself. This allows us to avoid having a positioned objects
|
|
// list in all RenderInlines and lets us return a strongly-typed RenderBlock* result
|
|
// from this method. The container() method can actually be used to obtain the
|
|
// inline directly.
|
|
if (o->style()->position() != StaticPosition && (!o->isInline() || o->isReplaced()))
|
|
break;
|
|
|
|
if (o->canContainAbsolutePositionObjects())
|
|
break;
|
|
|
|
if (o->style()->hasInFlowPosition() && o->isInline() && !o->isReplaced()) {
|
|
o = o->containingBlock();
|
|
break;
|
|
}
|
|
|
|
o = o->parent();
|
|
}
|
|
|
|
if (o && !o->isRenderBlock())
|
|
o = o->containingBlock();
|
|
} else {
|
|
while (o && ((o->isInline() && !o->isReplaced()) || !o->isRenderBlock()))
|
|
o = o->parent();
|
|
}
|
|
|
|
if (!o || !o->isRenderBlock())
|
|
return 0; // This can still happen in case of an orphaned tree
|
|
|
|
return toRenderBlock(o);
|
|
}
|
|
|
|
void RenderObject::drawLineForBoxSide(GraphicsContext* graphicsContext, int x1, int y1, int x2, int y2,
|
|
BoxSide side, Color color, EBorderStyle style,
|
|
int adjacentWidth1, int adjacentWidth2, bool antialias)
|
|
{
|
|
int thickness;
|
|
int length;
|
|
if (side == BSTop || side == BSBottom) {
|
|
thickness = y2 - y1;
|
|
length = x2 - x1;
|
|
} else {
|
|
thickness = x2 - x1;
|
|
length = y2 - y1;
|
|
}
|
|
|
|
// FIXME: We really would like this check to be an ASSERT as we don't want to draw empty borders. However
|
|
// nothing guarantees that the following recursive calls to drawLineForBoxSide will have non-null dimensions.
|
|
if (!thickness || !length)
|
|
return;
|
|
|
|
if (style == DOUBLE && thickness < 3)
|
|
style = SOLID;
|
|
|
|
switch (style) {
|
|
case BNONE:
|
|
case BHIDDEN:
|
|
return;
|
|
case DOTTED:
|
|
case DASHED:
|
|
drawDashedOrDottedBoxSide(graphicsContext, x1, y1, x2, y2, side,
|
|
color, thickness, style, antialias);
|
|
break;
|
|
case DOUBLE:
|
|
drawDoubleBoxSide(graphicsContext, x1, y1, x2, y2, length, side, color,
|
|
thickness, adjacentWidth1, adjacentWidth2, antialias);
|
|
break;
|
|
case RIDGE:
|
|
case GROOVE:
|
|
drawRidgeOrGrooveBoxSide(graphicsContext, x1, y1, x2, y2, side, color,
|
|
style, adjacentWidth1, adjacentWidth2, antialias);
|
|
break;
|
|
case INSET:
|
|
// FIXME: Maybe we should lighten the colors on one side like Firefox.
|
|
// https://bugs.webkit.org/show_bug.cgi?id=58608
|
|
if (side == BSTop || side == BSLeft)
|
|
color = color.dark();
|
|
// fall through
|
|
case OUTSET:
|
|
if (style == OUTSET && (side == BSBottom || side == BSRight))
|
|
color = color.dark();
|
|
// fall through
|
|
case SOLID:
|
|
drawSolidBoxSide(graphicsContext, x1, y1, x2, y2, side, color, adjacentWidth1, adjacentWidth2, antialias);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void RenderObject::drawDashedOrDottedBoxSide(GraphicsContext* graphicsContext, int x1, int y1, int x2, int y2,
|
|
BoxSide side, Color color, int thickness, EBorderStyle style, bool antialias)
|
|
{
|
|
if (thickness <= 0)
|
|
return;
|
|
|
|
bool wasAntialiased = graphicsContext->shouldAntialias();
|
|
StrokeStyle oldStrokeStyle = graphicsContext->strokeStyle();
|
|
graphicsContext->setShouldAntialias(antialias);
|
|
graphicsContext->setStrokeColor(color);
|
|
graphicsContext->setStrokeThickness(thickness);
|
|
graphicsContext->setStrokeStyle(style == DASHED ? DashedStroke : DottedStroke);
|
|
|
|
switch (side) {
|
|
case BSBottom:
|
|
case BSTop:
|
|
graphicsContext->drawLine(IntPoint(x1, (y1 + y2) / 2), IntPoint(x2, (y1 + y2) / 2));
|
|
break;
|
|
case BSRight:
|
|
case BSLeft:
|
|
graphicsContext->drawLine(IntPoint((x1 + x2) / 2, y1), IntPoint((x1 + x2) / 2, y2));
|
|
break;
|
|
}
|
|
graphicsContext->setShouldAntialias(wasAntialiased);
|
|
graphicsContext->setStrokeStyle(oldStrokeStyle);
|
|
}
|
|
|
|
void RenderObject::drawDoubleBoxSide(GraphicsContext* graphicsContext, int x1, int y1, int x2, int y2,
|
|
int length, BoxSide side, Color color, int thickness, int adjacentWidth1, int adjacentWidth2, bool antialias)
|
|
{
|
|
int thirdOfThickness = (thickness + 1) / 3;
|
|
ASSERT(thirdOfThickness);
|
|
|
|
if (!adjacentWidth1 && !adjacentWidth2) {
|
|
StrokeStyle oldStrokeStyle = graphicsContext->strokeStyle();
|
|
graphicsContext->setStrokeStyle(NoStroke);
|
|
graphicsContext->setFillColor(color);
|
|
|
|
bool wasAntialiased = graphicsContext->shouldAntialias();
|
|
graphicsContext->setShouldAntialias(antialias);
|
|
|
|
switch (side) {
|
|
case BSTop:
|
|
case BSBottom:
|
|
graphicsContext->drawRect(IntRect(x1, y1, length, thirdOfThickness));
|
|
graphicsContext->drawRect(IntRect(x1, y2 - thirdOfThickness, length, thirdOfThickness));
|
|
break;
|
|
case BSLeft:
|
|
case BSRight:
|
|
// FIXME: Why do we offset the border by 1 in this case but not the other one?
|
|
if (length > 1) {
|
|
graphicsContext->drawRect(IntRect(x1, y1 + 1, thirdOfThickness, length - 1));
|
|
graphicsContext->drawRect(IntRect(x2 - thirdOfThickness, y1 + 1, thirdOfThickness, length - 1));
|
|
}
|
|
break;
|
|
}
|
|
|
|
graphicsContext->setShouldAntialias(wasAntialiased);
|
|
graphicsContext->setStrokeStyle(oldStrokeStyle);
|
|
return;
|
|
}
|
|
|
|
int adjacent1BigThird = ((adjacentWidth1 > 0) ? adjacentWidth1 + 1 : adjacentWidth1 - 1) / 3;
|
|
int adjacent2BigThird = ((adjacentWidth2 > 0) ? adjacentWidth2 + 1 : adjacentWidth2 - 1) / 3;
|
|
|
|
switch (side) {
|
|
case BSTop:
|
|
drawLineForBoxSide(graphicsContext, x1 + std::max((-adjacentWidth1 * 2 + 1) / 3, 0),
|
|
y1, x2 - std::max((-adjacentWidth2 * 2 + 1) / 3, 0), y1 + thirdOfThickness,
|
|
side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias);
|
|
drawLineForBoxSide(graphicsContext, x1 + std::max((adjacentWidth1 * 2 + 1) / 3, 0),
|
|
y2 - thirdOfThickness, x2 - std::max((adjacentWidth2 * 2 + 1) / 3, 0), y2,
|
|
side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias);
|
|
break;
|
|
case BSLeft:
|
|
drawLineForBoxSide(graphicsContext, x1, y1 + std::max((-adjacentWidth1 * 2 + 1) / 3, 0),
|
|
x1 + thirdOfThickness, y2 - std::max((-adjacentWidth2 * 2 + 1) / 3, 0),
|
|
side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias);
|
|
drawLineForBoxSide(graphicsContext, x2 - thirdOfThickness, y1 + std::max((adjacentWidth1 * 2 + 1) / 3, 0),
|
|
x2, y2 - std::max((adjacentWidth2 * 2 + 1) / 3, 0),
|
|
side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias);
|
|
break;
|
|
case BSBottom:
|
|
drawLineForBoxSide(graphicsContext, x1 + std::max((adjacentWidth1 * 2 + 1) / 3, 0),
|
|
y1, x2 - std::max((adjacentWidth2 * 2 + 1) / 3, 0), y1 + thirdOfThickness,
|
|
side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias);
|
|
drawLineForBoxSide(graphicsContext, x1 + std::max((-adjacentWidth1 * 2 + 1) / 3, 0),
|
|
y2 - thirdOfThickness, x2 - std::max((-adjacentWidth2 * 2 + 1) / 3, 0), y2,
|
|
side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias);
|
|
break;
|
|
case BSRight:
|
|
drawLineForBoxSide(graphicsContext, x1, y1 + std::max((adjacentWidth1 * 2 + 1) / 3, 0),
|
|
x1 + thirdOfThickness, y2 - std::max((adjacentWidth2 * 2 + 1) / 3, 0),
|
|
side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias);
|
|
drawLineForBoxSide(graphicsContext, x2 - thirdOfThickness, y1 + std::max((-adjacentWidth1 * 2 + 1) / 3, 0),
|
|
x2, y2 - std::max((-adjacentWidth2 * 2 + 1) / 3, 0),
|
|
side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void RenderObject::drawRidgeOrGrooveBoxSide(GraphicsContext* graphicsContext, int x1, int y1, int x2, int y2,
|
|
BoxSide side, Color color, EBorderStyle style, int adjacentWidth1, int adjacentWidth2, bool antialias)
|
|
{
|
|
EBorderStyle s1;
|
|
EBorderStyle s2;
|
|
if (style == GROOVE) {
|
|
s1 = INSET;
|
|
s2 = OUTSET;
|
|
} else {
|
|
s1 = OUTSET;
|
|
s2 = INSET;
|
|
}
|
|
|
|
int adjacent1BigHalf = ((adjacentWidth1 > 0) ? adjacentWidth1 + 1 : adjacentWidth1 - 1) / 2;
|
|
int adjacent2BigHalf = ((adjacentWidth2 > 0) ? adjacentWidth2 + 1 : adjacentWidth2 - 1) / 2;
|
|
|
|
switch (side) {
|
|
case BSTop:
|
|
drawLineForBoxSide(graphicsContext, x1 + std::max(-adjacentWidth1, 0) / 2, y1, x2 - std::max(-adjacentWidth2, 0) / 2, (y1 + y2 + 1) / 2,
|
|
side, color, s1, adjacent1BigHalf, adjacent2BigHalf, antialias);
|
|
drawLineForBoxSide(graphicsContext, x1 + std::max(adjacentWidth1 + 1, 0) / 2, (y1 + y2 + 1) / 2, x2 - std::max(adjacentWidth2 + 1, 0) / 2, y2,
|
|
side, color, s2, adjacentWidth1 / 2, adjacentWidth2 / 2, antialias);
|
|
break;
|
|
case BSLeft:
|
|
drawLineForBoxSide(graphicsContext, x1, y1 + std::max(-adjacentWidth1, 0) / 2, (x1 + x2 + 1) / 2, y2 - std::max(-adjacentWidth2, 0) / 2,
|
|
side, color, s1, adjacent1BigHalf, adjacent2BigHalf, antialias);
|
|
drawLineForBoxSide(graphicsContext, (x1 + x2 + 1) / 2, y1 + std::max(adjacentWidth1 + 1, 0) / 2, x2, y2 - std::max(adjacentWidth2 + 1, 0) / 2,
|
|
side, color, s2, adjacentWidth1 / 2, adjacentWidth2 / 2, antialias);
|
|
break;
|
|
case BSBottom:
|
|
drawLineForBoxSide(graphicsContext, x1 + std::max(adjacentWidth1, 0) / 2, y1, x2 - std::max(adjacentWidth2, 0) / 2, (y1 + y2 + 1) / 2,
|
|
side, color, s2, adjacent1BigHalf, adjacent2BigHalf, antialias);
|
|
drawLineForBoxSide(graphicsContext, x1 + std::max(-adjacentWidth1 + 1, 0) / 2, (y1 + y2 + 1) / 2, x2 - std::max(-adjacentWidth2 + 1, 0) / 2, y2,
|
|
side, color, s1, adjacentWidth1 / 2, adjacentWidth2 / 2, antialias);
|
|
break;
|
|
case BSRight:
|
|
drawLineForBoxSide(graphicsContext, x1, y1 + std::max(adjacentWidth1, 0) / 2, (x1 + x2 + 1) / 2, y2 - std::max(adjacentWidth2, 0) / 2,
|
|
side, color, s2, adjacent1BigHalf, adjacent2BigHalf, antialias);
|
|
drawLineForBoxSide(graphicsContext, (x1 + x2 + 1) / 2, y1 + std::max(-adjacentWidth1 + 1, 0) / 2, x2, y2 - std::max(-adjacentWidth2 + 1, 0) / 2,
|
|
side, color, s1, adjacentWidth1 / 2, adjacentWidth2 / 2, antialias);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void RenderObject::drawSolidBoxSide(GraphicsContext* graphicsContext, int x1, int y1, int x2, int y2,
|
|
BoxSide side, Color color, int adjacentWidth1, int adjacentWidth2, bool antialias)
|
|
{
|
|
StrokeStyle oldStrokeStyle = graphicsContext->strokeStyle();
|
|
graphicsContext->setStrokeStyle(NoStroke);
|
|
graphicsContext->setFillColor(color);
|
|
ASSERT(x2 >= x1);
|
|
ASSERT(y2 >= y1);
|
|
if (!adjacentWidth1 && !adjacentWidth2) {
|
|
// Turn off antialiasing to match the behavior of drawConvexPolygon();
|
|
// this matters for rects in transformed contexts.
|
|
bool wasAntialiased = graphicsContext->shouldAntialias();
|
|
graphicsContext->setShouldAntialias(antialias);
|
|
graphicsContext->drawRect(IntRect(x1, y1, x2 - x1, y2 - y1));
|
|
graphicsContext->setShouldAntialias(wasAntialiased);
|
|
graphicsContext->setStrokeStyle(oldStrokeStyle);
|
|
return;
|
|
}
|
|
FloatPoint quad[4];
|
|
switch (side) {
|
|
case BSTop:
|
|
quad[0] = FloatPoint(x1 + std::max(-adjacentWidth1, 0), y1);
|
|
quad[1] = FloatPoint(x1 + std::max(adjacentWidth1, 0), y2);
|
|
quad[2] = FloatPoint(x2 - std::max(adjacentWidth2, 0), y2);
|
|
quad[3] = FloatPoint(x2 - std::max(-adjacentWidth2, 0), y1);
|
|
break;
|
|
case BSBottom:
|
|
quad[0] = FloatPoint(x1 + std::max(adjacentWidth1, 0), y1);
|
|
quad[1] = FloatPoint(x1 + std::max(-adjacentWidth1, 0), y2);
|
|
quad[2] = FloatPoint(x2 - std::max(-adjacentWidth2, 0), y2);
|
|
quad[3] = FloatPoint(x2 - std::max(adjacentWidth2, 0), y1);
|
|
break;
|
|
case BSLeft:
|
|
quad[0] = FloatPoint(x1, y1 + std::max(-adjacentWidth1, 0));
|
|
quad[1] = FloatPoint(x1, y2 - std::max(-adjacentWidth2, 0));
|
|
quad[2] = FloatPoint(x2, y2 - std::max(adjacentWidth2, 0));
|
|
quad[3] = FloatPoint(x2, y1 + std::max(adjacentWidth1, 0));
|
|
break;
|
|
case BSRight:
|
|
quad[0] = FloatPoint(x1, y1 + std::max(adjacentWidth1, 0));
|
|
quad[1] = FloatPoint(x1, y2 - std::max(adjacentWidth2, 0));
|
|
quad[2] = FloatPoint(x2, y2 - std::max(-adjacentWidth2, 0));
|
|
quad[3] = FloatPoint(x2, y1 + std::max(-adjacentWidth1, 0));
|
|
break;
|
|
}
|
|
|
|
graphicsContext->drawConvexPolygon(4, quad, antialias);
|
|
graphicsContext->setStrokeStyle(oldStrokeStyle);
|
|
}
|
|
|
|
void RenderObject::paintFocusRing(PaintInfo& paintInfo, const LayoutPoint& paintOffset, RenderStyle* style)
|
|
{
|
|
Vector<IntRect> focusRingRects;
|
|
addFocusRingRects(focusRingRects, paintOffset, paintInfo.paintContainer());
|
|
ASSERT(style->outlineStyleIsAuto());
|
|
paintInfo.context->drawFocusRing(focusRingRects, style->outlineWidth(), style->outlineOffset(), resolveColor(style, CSSPropertyOutlineColor));
|
|
}
|
|
|
|
void RenderObject::paintOutline(PaintInfo& paintInfo, const LayoutRect& paintRect)
|
|
{
|
|
RenderStyle* styleToUse = style();
|
|
if (!styleToUse->hasOutline())
|
|
return;
|
|
|
|
LayoutUnit outlineWidth = styleToUse->outlineWidth();
|
|
|
|
int outlineOffset = styleToUse->outlineOffset();
|
|
|
|
if (styleToUse->outlineStyleIsAuto())
|
|
return;
|
|
|
|
if (styleToUse->outlineStyle() == BNONE)
|
|
return;
|
|
|
|
IntRect inner = pixelSnappedIntRect(paintRect);
|
|
inner.inflate(outlineOffset);
|
|
|
|
IntRect outer = pixelSnappedIntRect(inner);
|
|
outer.inflate(outlineWidth);
|
|
|
|
// FIXME: This prevents outlines from painting inside the object. See bug 12042
|
|
if (outer.isEmpty())
|
|
return;
|
|
|
|
EBorderStyle outlineStyle = styleToUse->outlineStyle();
|
|
Color outlineColor = resolveColor(styleToUse, CSSPropertyOutlineColor);
|
|
|
|
GraphicsContext* graphicsContext = paintInfo.context;
|
|
bool useTransparencyLayer = outlineColor.hasAlpha();
|
|
if (useTransparencyLayer) {
|
|
if (outlineStyle == SOLID) {
|
|
Path path;
|
|
path.addRect(outer);
|
|
path.addRect(inner);
|
|
graphicsContext->setFillRule(RULE_EVENODD);
|
|
graphicsContext->setFillColor(outlineColor);
|
|
graphicsContext->fillPath(path);
|
|
return;
|
|
}
|
|
graphicsContext->beginTransparencyLayer(static_cast<float>(outlineColor.alpha()) / 255);
|
|
outlineColor = Color(outlineColor.red(), outlineColor.green(), outlineColor.blue());
|
|
}
|
|
|
|
int leftOuter = outer.x();
|
|
int leftInner = inner.x();
|
|
int rightOuter = outer.maxX();
|
|
int rightInner = inner.maxX();
|
|
int topOuter = outer.y();
|
|
int topInner = inner.y();
|
|
int bottomOuter = outer.maxY();
|
|
int bottomInner = inner.maxY();
|
|
|
|
drawLineForBoxSide(graphicsContext, leftOuter, topOuter, leftInner, bottomOuter, BSLeft, outlineColor, outlineStyle, outlineWidth, outlineWidth);
|
|
drawLineForBoxSide(graphicsContext, leftOuter, topOuter, rightOuter, topInner, BSTop, outlineColor, outlineStyle, outlineWidth, outlineWidth);
|
|
drawLineForBoxSide(graphicsContext, rightInner, topOuter, rightOuter, bottomOuter, BSRight, outlineColor, outlineStyle, outlineWidth, outlineWidth);
|
|
drawLineForBoxSide(graphicsContext, leftOuter, bottomInner, rightOuter, bottomOuter, BSBottom, outlineColor, outlineStyle, outlineWidth, outlineWidth);
|
|
|
|
if (useTransparencyLayer)
|
|
graphicsContext->endLayer();
|
|
}
|
|
|
|
void RenderObject::addChildFocusRingRects(Vector<IntRect>& rects, const LayoutPoint& additionalOffset, const RenderBox* paintContainer) const
|
|
{
|
|
for (RenderObject* current = slowFirstChild(); current; current = current->nextSibling()) {
|
|
if (current->isText())
|
|
continue;
|
|
|
|
if (current->isBox()) {
|
|
RenderBox* box = toRenderBox(current);
|
|
if (box->hasLayer()) {
|
|
Vector<IntRect> layerFocusRingRects;
|
|
box->addFocusRingRects(layerFocusRingRects, LayoutPoint(), box);
|
|
for (size_t i = 0; i < layerFocusRingRects.size(); ++i) {
|
|
FloatQuad quadInBox = box->localToContainerQuad(FloatRect(layerFocusRingRects[i]), paintContainer);
|
|
FloatRect rect = quadInBox.boundingBox();
|
|
// Floor the location instead of using pixelSnappedIntRect to match the !hasLayer() path.
|
|
// FIXME: roundedIntSize matches pixelSnappedIntRect in other places of addFocusRingRects
|
|
// because we always floor the offset.
|
|
// This assumption is fragile and should be replaced by better solution.
|
|
rects.append(IntRect(flooredIntPoint(rect.location()), roundedIntSize(rect.size())));
|
|
}
|
|
} else {
|
|
FloatPoint pos(additionalOffset);
|
|
pos.move(box->locationOffset());
|
|
box->addFocusRingRects(rects, flooredIntPoint(pos), paintContainer);
|
|
}
|
|
} else {
|
|
current->addFocusRingRects(rects, additionalOffset, paintContainer);
|
|
}
|
|
}
|
|
}
|
|
|
|
IntRect RenderObject::absoluteBoundingBoxRect() const
|
|
{
|
|
Vector<FloatQuad> quads;
|
|
absoluteQuads(quads);
|
|
|
|
size_t n = quads.size();
|
|
if (!n)
|
|
return IntRect();
|
|
|
|
IntRect result = quads[0].enclosingBoundingBox();
|
|
for (size_t i = 1; i < n; ++i)
|
|
result.unite(quads[i].enclosingBoundingBox());
|
|
return result;
|
|
}
|
|
|
|
void RenderObject::absoluteFocusRingQuads(Vector<FloatQuad>& quads)
|
|
{
|
|
Vector<IntRect> rects;
|
|
const RenderBox* container = containerForPaintInvalidation();
|
|
addFocusRingRects(rects, LayoutPoint(localToContainerPoint(FloatPoint(), container)), container);
|
|
size_t count = rects.size();
|
|
for (size_t i = 0; i < count; ++i)
|
|
quads.append(container->localToAbsoluteQuad(FloatQuad(rects[i])));
|
|
}
|
|
|
|
FloatRect RenderObject::absoluteBoundingBoxRectForRange(const Range* range)
|
|
{
|
|
if (!range || !range->startContainer())
|
|
return FloatRect();
|
|
|
|
range->ownerDocument().updateLayout();
|
|
|
|
Vector<FloatQuad> quads;
|
|
range->textQuads(quads);
|
|
|
|
FloatRect result;
|
|
for (size_t i = 0; i < quads.size(); ++i)
|
|
result.unite(quads[i].boundingBox());
|
|
|
|
return result;
|
|
}
|
|
|
|
void RenderObject::addAbsoluteRectForLayer(LayoutRect& result)
|
|
{
|
|
if (hasLayer())
|
|
result.unite(absoluteBoundingBoxRect());
|
|
for (RenderObject* current = slowFirstChild(); current; current = current->nextSibling())
|
|
current->addAbsoluteRectForLayer(result);
|
|
}
|
|
|
|
void RenderObject::paint(PaintInfo&, const LayoutPoint&, Vector<RenderBox*>& layers)
|
|
{
|
|
}
|
|
|
|
const RenderView* RenderObject::containerForPaintInvalidation() const
|
|
{
|
|
return isRooted() ? view() : 0;
|
|
}
|
|
|
|
const RenderBox* RenderObject::adjustCompositedContainerForSpecialAncestors(const RenderBox* paintInvalidationContainer) const
|
|
{
|
|
// FIXME(sky): We shouldn't have any special ancestors and we don't have composited containers
|
|
if (paintInvalidationContainer)
|
|
return paintInvalidationContainer;
|
|
return view();
|
|
}
|
|
|
|
void RenderObject::dirtyLinesFromChangedChild(RenderObject*)
|
|
{
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
|
|
void RenderObject::showTreeForThis() const
|
|
{
|
|
if (node())
|
|
node()->showTreeForThis();
|
|
}
|
|
|
|
void RenderObject::showRenderTreeForThis() const
|
|
{
|
|
showRenderTree(this, 0);
|
|
}
|
|
|
|
void RenderObject::showLineTreeForThis() const
|
|
{
|
|
if (containingBlock())
|
|
containingBlock()->showLineTreeAndMark(0, 0, 0, 0, this);
|
|
}
|
|
|
|
void RenderObject::showRenderObject() const
|
|
{
|
|
showRenderObject(0);
|
|
}
|
|
|
|
void RenderObject::showRenderObject(int printedCharacters) const
|
|
{
|
|
printedCharacters += fprintf(stderr, "%s %p", renderName(), this);
|
|
|
|
if (node()) {
|
|
if (printedCharacters)
|
|
for (; printedCharacters < showTreeCharacterOffset; printedCharacters++)
|
|
fputc(' ', stderr);
|
|
fputc('\t', stderr);
|
|
node()->showNode();
|
|
} else
|
|
fputc('\n', stderr);
|
|
}
|
|
|
|
void RenderObject::showRenderTreeAndMark(const RenderObject* markedObject1, const char* markedLabel1, const RenderObject* markedObject2, const char* markedLabel2, int depth) const
|
|
{
|
|
int printedCharacters = 0;
|
|
if (markedObject1 == this && markedLabel1)
|
|
printedCharacters += fprintf(stderr, "%s", markedLabel1);
|
|
if (markedObject2 == this && markedLabel2)
|
|
printedCharacters += fprintf(stderr, "%s", markedLabel2);
|
|
for (; printedCharacters < depth * 2; printedCharacters++)
|
|
fputc(' ', stderr);
|
|
|
|
showRenderObject(printedCharacters);
|
|
|
|
for (const RenderObject* child = slowFirstChild(); child; child = child->nextSibling())
|
|
child->showRenderTreeAndMark(markedObject1, markedLabel1, markedObject2, markedLabel2, depth + 1);
|
|
}
|
|
|
|
#endif // NDEBUG
|
|
|
|
bool RenderObject::isSelectable() const
|
|
{
|
|
return !(style()->userSelect() == SELECT_NONE && style()->userModify() == READ_ONLY);
|
|
}
|
|
|
|
Color RenderObject::selectionBackgroundColor() const
|
|
{
|
|
ASSERT_NOT_REACHED();
|
|
// TODO(ianh): if we expose selection painting, we should expose a way to set the background colour
|
|
// TODO(ianh): need to be able to configure whether to consider the selection focused and active or not
|
|
if (!isSelectable())
|
|
return Color::transparent;
|
|
bool isFocusedAndActive = true;
|
|
return isFocusedAndActive ?
|
|
RenderTheme::theme().activeSelectionBackgroundColor() :
|
|
RenderTheme::theme().inactiveSelectionBackgroundColor();
|
|
}
|
|
|
|
Color RenderObject::selectionColor(int colorProperty) const
|
|
{
|
|
ASSERT_NOT_REACHED();
|
|
// TODO(ianh): if we expose selection painting, we should expose a way to set the text colour
|
|
// TODO(ianh): need to be able to configure whether to consider the selection focused and active or not
|
|
if (!isSelectable())
|
|
return resolveColor(colorProperty);
|
|
if (!RenderTheme::theme().supportsSelectionForegroundColors())
|
|
return resolveColor(colorProperty);
|
|
bool isFocusedAndActive = true;
|
|
return isFocusedAndActive ?
|
|
RenderTheme::theme().activeSelectionForegroundColor() :
|
|
RenderTheme::theme().inactiveSelectionForegroundColor();
|
|
}
|
|
|
|
Color RenderObject::selectionForegroundColor() const
|
|
{
|
|
return selectionColor(CSSPropertyWebkitTextFillColor);
|
|
}
|
|
|
|
Color RenderObject::selectionEmphasisMarkColor() const
|
|
{
|
|
return selectionColor(CSSPropertyWebkitTextEmphasisColor);
|
|
}
|
|
|
|
void RenderObject::selectionStartEnd(int& spos, int& epos) const
|
|
{
|
|
view()->selectionStartEnd(spos, epos);
|
|
}
|
|
|
|
StyleDifference RenderObject::adjustStyleDifference(StyleDifference diff) const
|
|
{
|
|
// The answer to layerTypeRequired() for plugins, iframes, and canvas can change without the actual
|
|
// style changing, since it depends on whether we decide to composite these elements. When the
|
|
// layer status of one of these elements changes, we need to force a layout.
|
|
if (!diff.needsFullLayout() && style() && isBox()) {
|
|
bool requiresLayer = toRenderBox(this)->layerTypeRequired() != NoLayer;
|
|
if (hasLayer() != requiresLayer)
|
|
diff.setNeedsFullLayout();
|
|
}
|
|
|
|
return diff;
|
|
}
|
|
|
|
inline bool RenderObject::hasImmediateNonWhitespaceTextChildOrPropertiesDependentOnColor() const
|
|
{
|
|
if (style()->hasBorder() || style()->hasOutline())
|
|
return true;
|
|
for (const RenderObject* r = slowFirstChild(); r; r = r->nextSibling()) {
|
|
if (r->isText() && !toRenderText(r)->isAllCollapsibleWhitespace())
|
|
return true;
|
|
if (r->style()->hasOutline() || r->style()->hasBorder())
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void RenderObject::markContainingBlocksForOverflowRecalc()
|
|
{
|
|
for (RenderBlock* container = containingBlock(); container && !container->childNeedsOverflowRecalcAfterStyleChange(); container = container->containingBlock())
|
|
container->setChildNeedsOverflowRecalcAfterStyleChange(true);
|
|
}
|
|
|
|
void RenderObject::setNeedsOverflowRecalcAfterStyleChange()
|
|
{
|
|
bool neededRecalc = needsOverflowRecalcAfterStyleChange();
|
|
setSelfNeedsOverflowRecalcAfterStyleChange(true);
|
|
if (!neededRecalc)
|
|
markContainingBlocksForOverflowRecalc();
|
|
}
|
|
|
|
void RenderObject::setStyle(PassRefPtr<RenderStyle> style)
|
|
{
|
|
ASSERT(style);
|
|
StyleDifference diff;
|
|
if (m_style)
|
|
diff = m_style->visualInvalidationDiff(*style);
|
|
|
|
diff = adjustStyleDifference(diff);
|
|
|
|
styleWillChange(diff, *style);
|
|
|
|
RefPtr<RenderStyle> oldStyle = m_style.release();
|
|
setStyleInternal(style);
|
|
|
|
updateFillImages(oldStyle ? &oldStyle->backgroundLayers() : 0, m_style->backgroundLayers());
|
|
|
|
bool doesNotNeedLayout = !m_parent || isText();
|
|
|
|
styleDidChange(diff, oldStyle.get());
|
|
|
|
// FIXME: |this| might be destroyed here. This can currently happen for a RenderTextFragment when
|
|
// its first-letter block gets an update in RenderTextFragment::styleDidChange. For RenderTextFragment(s),
|
|
// we will safely bail out with the doesNotNeedLayout flag. We might want to broaden this condition
|
|
// in the future as we move renderer changes out of layout and into style changes.
|
|
// FIXME(sky): Remove this.
|
|
if (doesNotNeedLayout)
|
|
return;
|
|
|
|
// Now that the layer (if any) has been updated, we need to adjust the diff again,
|
|
// check whether we should layout now, and decide if we need to invalidate paints.
|
|
StyleDifference updatedDiff = adjustStyleDifference(diff);
|
|
|
|
if (!diff.needsFullLayout()) {
|
|
if (updatedDiff.needsFullLayout())
|
|
setNeedsLayoutAndPrefWidthsRecalc();
|
|
else if (updatedDiff.needsPositionedMovementLayout())
|
|
setNeedsPositionedMovementLayout();
|
|
}
|
|
|
|
if (diff.transformChanged() && !needsLayout()) {
|
|
if (RenderBlock* container = containingBlock())
|
|
container->setNeedsOverflowRecalcAfterStyleChange();
|
|
}
|
|
}
|
|
|
|
void RenderObject::styleWillChange(StyleDifference diff, const RenderStyle& newStyle)
|
|
{
|
|
if (m_style) {
|
|
if (isOutOfFlowPositioned() && (m_style->position() != newStyle.position()))
|
|
// For changes in positioning styles, we need to conceivably remove ourselves
|
|
// from the positioned objects list.
|
|
toRenderBox(this)->removeFloatingOrPositionedChildFromBlockLists();
|
|
|
|
s_affectsParentBlock = isFloatingOrOutOfFlowPositioned()
|
|
&& !newStyle.hasOutOfFlowPosition()
|
|
&& parent() && (parent()->isRenderParagraph() || parent()->isRenderInline());
|
|
|
|
// Clearing these bits is required to avoid leaving stale renderers.
|
|
// FIXME: We shouldn't need that hack if our logic was totally correct.
|
|
if (diff.needsLayout()) {
|
|
clearPositionedState();
|
|
}
|
|
} else {
|
|
s_affectsParentBlock = false;
|
|
}
|
|
}
|
|
|
|
void RenderObject::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
|
|
{
|
|
if (s_affectsParentBlock) {
|
|
// An object that was floating or positioned became a normal flow object again. We have to make sure the
|
|
// render tree updates as needed to accommodate the new normal flow object.
|
|
setInline(style()->isDisplayInlineType());
|
|
ASSERT(isInline() == parent()->isRenderParagraph());
|
|
}
|
|
|
|
if (!m_parent)
|
|
return;
|
|
|
|
if (diff.needsFullLayout()) {
|
|
// If the object already needs layout, then setNeedsLayout won't do
|
|
// any work. But if the containing block has changed, then we may need
|
|
// to mark the new containing blocks for layout. The change that can
|
|
// directly affect the containing block of this object is a change to
|
|
// the position style.
|
|
if (needsLayout() && oldStyle->position() != m_style->position())
|
|
markContainingBlocksForLayout();
|
|
|
|
// Ditto.
|
|
if (needsOverflowRecalcAfterStyleChange() && oldStyle->position() != m_style->position())
|
|
markContainingBlocksForOverflowRecalc();
|
|
|
|
if (diff.needsFullLayout())
|
|
setNeedsLayoutAndPrefWidthsRecalc();
|
|
} else if (diff.needsPositionedMovementLayout())
|
|
setNeedsPositionedMovementLayout();
|
|
|
|
// Don't check for paint invalidation here; we need to wait until the layer has been
|
|
// updated by subclasses before we know if we have to invalidate paints (in setStyle()).
|
|
}
|
|
|
|
void RenderObject::updateFillImages(const FillLayer* oldLayers, const FillLayer& newLayers)
|
|
{
|
|
// Optimize the common case
|
|
if (oldLayers && !oldLayers->next() && !newLayers.next() && (oldLayers->image() == newLayers.image()))
|
|
return;
|
|
|
|
// Go through the new layers and addClients first, to avoid removing all clients of an image.
|
|
for (const FillLayer* currNew = &newLayers; currNew; currNew = currNew->next()) {
|
|
if (currNew->image())
|
|
currNew->image()->addClient(this);
|
|
}
|
|
|
|
for (const FillLayer* currOld = oldLayers; currOld; currOld = currOld->next()) {
|
|
if (currOld->image())
|
|
currOld->image()->removeClient(this);
|
|
}
|
|
}
|
|
|
|
void RenderObject::updateImage(StyleImage* oldImage, StyleImage* newImage)
|
|
{
|
|
if (oldImage != newImage) {
|
|
if (oldImage)
|
|
oldImage->removeClient(this);
|
|
if (newImage)
|
|
newImage->addClient(this);
|
|
}
|
|
}
|
|
|
|
LayoutRect RenderObject::viewRect() const
|
|
{
|
|
return view()->viewRect();
|
|
}
|
|
|
|
FloatPoint RenderObject::localToAbsolute(const FloatPoint& localPoint, MapCoordinatesFlags mode) const
|
|
{
|
|
TransformState transformState(TransformState::ApplyTransformDirection, localPoint);
|
|
mapLocalToContainer(0, transformState, mode | ApplyContainerFlip);
|
|
transformState.flatten();
|
|
|
|
return transformState.lastPlanarPoint();
|
|
}
|
|
|
|
FloatPoint RenderObject::absoluteToLocal(const FloatPoint& containerPoint, MapCoordinatesFlags mode) const
|
|
{
|
|
TransformState transformState(TransformState::UnapplyInverseTransformDirection, containerPoint);
|
|
mapAbsoluteToLocalPoint(mode, transformState);
|
|
transformState.flatten();
|
|
|
|
return transformState.lastPlanarPoint();
|
|
}
|
|
|
|
FloatQuad RenderObject::absoluteToLocalQuad(const FloatQuad& quad, MapCoordinatesFlags mode) const
|
|
{
|
|
TransformState transformState(TransformState::UnapplyInverseTransformDirection, quad.boundingBox().center(), quad);
|
|
mapAbsoluteToLocalPoint(mode, transformState);
|
|
transformState.flatten();
|
|
return transformState.lastPlanarQuad();
|
|
}
|
|
|
|
void RenderObject::mapLocalToContainer(const RenderBox* paintInvalidationContainer, TransformState& transformState, MapCoordinatesFlags mode) const
|
|
{
|
|
if (paintInvalidationContainer == this)
|
|
return;
|
|
|
|
RenderObject* o = parent();
|
|
if (!o)
|
|
return;
|
|
|
|
// FIXME: this should call offsetFromContainer to share code, but I'm not sure it's ever called.
|
|
if (mode & ApplyContainerFlip && o->isBox())
|
|
mode &= ~ApplyContainerFlip;
|
|
|
|
o->mapLocalToContainer(paintInvalidationContainer, transformState, mode);
|
|
}
|
|
|
|
const RenderObject* RenderObject::pushMappingToContainer(const RenderBox* ancestorToStopAt, RenderGeometryMap& geometryMap) const
|
|
{
|
|
ASSERT_UNUSED(ancestorToStopAt, ancestorToStopAt != this);
|
|
|
|
RenderObject* container = parent();
|
|
if (!container)
|
|
return 0;
|
|
// FIXME(sky): Do we need to make this call?
|
|
geometryMap.push(this, LayoutSize(), false);
|
|
return container;
|
|
}
|
|
|
|
void RenderObject::mapAbsoluteToLocalPoint(MapCoordinatesFlags mode, TransformState& transformState) const
|
|
{
|
|
RenderObject* o = parent();
|
|
if (o)
|
|
o->mapAbsoluteToLocalPoint(mode, transformState);
|
|
}
|
|
|
|
bool RenderObject::shouldUseTransformFromContainer(const RenderObject* containerObject) const
|
|
{
|
|
// hasTransform() indicates whether the object has transform, transform-style or perspective. We just care about transform,
|
|
// so check the layer's transform directly.
|
|
return (isBox() && toRenderBox(this)->transform()) || (containerObject && containerObject->style()->hasPerspective());
|
|
}
|
|
|
|
void RenderObject::getTransformFromContainer(const RenderObject* containerObject, const LayoutSize& offsetInContainer, TransformationMatrix& transform) const
|
|
{
|
|
transform.makeIdentity();
|
|
transform.translate(offsetInContainer.width().toFloat(), offsetInContainer.height().toFloat());
|
|
TransformationMatrix* localTransform = isBox() ? toRenderBox(this)->transform() : 0;
|
|
if (localTransform)
|
|
transform.multiply(*localTransform);
|
|
|
|
if (containerObject && containerObject->hasLayer() && containerObject->style()->hasPerspective()) {
|
|
// Perpsective on the container affects us, so we have to factor it in here.
|
|
ASSERT(containerObject->hasLayer());
|
|
FloatPoint perspectiveOrigin = toRenderBox(containerObject)->perspectiveOrigin();
|
|
|
|
TransformationMatrix perspectiveMatrix;
|
|
perspectiveMatrix.applyPerspective(containerObject->style()->perspective());
|
|
|
|
transform.translateRight3d(-perspectiveOrigin.x(), -perspectiveOrigin.y(), 0);
|
|
transform = perspectiveMatrix * transform;
|
|
transform.translateRight3d(perspectiveOrigin.x(), perspectiveOrigin.y(), 0);
|
|
}
|
|
}
|
|
|
|
FloatQuad RenderObject::localToContainerQuad(const FloatQuad& localQuad, const RenderBox* paintInvalidationContainer, MapCoordinatesFlags mode) const
|
|
{
|
|
// Track the point at the center of the quad's bounding box. As mapLocalToContainer() calls offsetFromContainer(),
|
|
// it will use that point as the reference point to decide which column's transform to apply in multiple-column blocks.
|
|
TransformState transformState(TransformState::ApplyTransformDirection, localQuad.boundingBox().center(), localQuad);
|
|
mapLocalToContainer(paintInvalidationContainer, transformState, mode | ApplyContainerFlip | UseTransforms);
|
|
transformState.flatten();
|
|
|
|
return transformState.lastPlanarQuad();
|
|
}
|
|
|
|
FloatPoint RenderObject::localToContainerPoint(const FloatPoint& localPoint, const RenderBox* paintInvalidationContainer, MapCoordinatesFlags mode) const
|
|
{
|
|
TransformState transformState(TransformState::ApplyTransformDirection, localPoint);
|
|
mapLocalToContainer(paintInvalidationContainer, transformState, mode | ApplyContainerFlip | UseTransforms);
|
|
transformState.flatten();
|
|
|
|
return transformState.lastPlanarPoint();
|
|
}
|
|
|
|
LayoutSize RenderObject::offsetFromContainer(const RenderObject* o, const LayoutPoint& point, bool* offsetDependsOnPoint) const
|
|
{
|
|
ASSERT(o == container());
|
|
LayoutSize offset;
|
|
if (offsetDependsOnPoint)
|
|
*offsetDependsOnPoint = false;
|
|
return offset;
|
|
}
|
|
|
|
LayoutSize RenderObject::offsetFromAncestorContainer(const RenderObject* container) const
|
|
{
|
|
LayoutSize offset;
|
|
LayoutPoint referencePoint;
|
|
const RenderObject* currContainer = this;
|
|
do {
|
|
const RenderObject* nextContainer = currContainer->container();
|
|
ASSERT(nextContainer); // This means we reached the top without finding container.
|
|
if (!nextContainer)
|
|
break;
|
|
ASSERT(!currContainer->hasTransform());
|
|
LayoutSize currentOffset = currContainer->offsetFromContainer(nextContainer, referencePoint);
|
|
offset += currentOffset;
|
|
referencePoint.move(currentOffset);
|
|
currContainer = nextContainer;
|
|
} while (currContainer != container);
|
|
|
|
return offset;
|
|
}
|
|
|
|
LayoutRect RenderObject::localCaretRect(InlineBox*, int, LayoutUnit* extraWidthToEndOfLine)
|
|
{
|
|
if (extraWidthToEndOfLine)
|
|
*extraWidthToEndOfLine = 0;
|
|
|
|
return LayoutRect();
|
|
}
|
|
|
|
bool RenderObject::isRooted() const
|
|
{
|
|
const RenderObject* object = this;
|
|
while (object->parent() && !object->hasLayer())
|
|
object = object->parent();
|
|
if (object->hasLayer())
|
|
return toRenderBox(object)->layer()->root()->isRootLayer();
|
|
return false;
|
|
}
|
|
|
|
RespectImageOrientationEnum RenderObject::shouldRespectImageOrientation() const
|
|
{
|
|
return DoNotRespectImageOrientation;
|
|
}
|
|
|
|
bool RenderObject::hasEntirelyFixedBackground() const
|
|
{
|
|
return m_style->hasEntirelyFixedBackground();
|
|
}
|
|
|
|
RenderObject* RenderObject::container(const RenderBox* paintInvalidationContainer, bool* paintInvalidationContainerSkipped) const
|
|
{
|
|
if (paintInvalidationContainerSkipped)
|
|
*paintInvalidationContainerSkipped = false;
|
|
|
|
// This method is extremely similar to containingBlock(), but with a few notable
|
|
// exceptions.
|
|
// (1) It can be used on orphaned subtrees, i.e., it can be called safely even when
|
|
// the object is not part of the primary document subtree yet.
|
|
// (2) For normal flow elements, it just returns the parent.
|
|
// (3) For absolute positioned elements, it will return a relative positioned inline.
|
|
// containingBlock() simply skips relpositioned inlines and lets an enclosing block handle
|
|
// the layout of the positioned object. This does mean that computePositionedLogicalWidth and
|
|
// computePositionedLogicalHeight have to use container().
|
|
RenderObject* o = parent();
|
|
|
|
if (isText())
|
|
return o;
|
|
|
|
EPosition pos = m_style->position();
|
|
if (pos == AbsolutePosition) {
|
|
// We technically just want our containing block, but
|
|
// we may not have one if we're part of an uninstalled
|
|
// subtree. We'll climb as high as we can though.
|
|
while (o) {
|
|
if (o->style()->position() != StaticPosition)
|
|
break;
|
|
|
|
if (o->canContainAbsolutePositionObjects())
|
|
break;
|
|
|
|
if (paintInvalidationContainerSkipped && o == paintInvalidationContainer)
|
|
*paintInvalidationContainerSkipped = true;
|
|
|
|
o = o->parent();
|
|
}
|
|
}
|
|
|
|
return o;
|
|
}
|
|
|
|
bool RenderObject::isSelectionBorder() const
|
|
{
|
|
SelectionState st = selectionState();
|
|
return st == SelectionStart || st == SelectionEnd || st == SelectionBoth;
|
|
}
|
|
|
|
inline void RenderObject::clearLayoutRootIfNeeded() const
|
|
{
|
|
if (frame()) {
|
|
if (FrameView* view = frame()->view()) {
|
|
if (view->layoutRoot() == this) {
|
|
if (!documentBeingDestroyed())
|
|
ASSERT_NOT_REACHED();
|
|
// This indicates a failure to layout the child, which is why
|
|
// the layout root is still set to |this|. Make sure to clear it
|
|
// since we are getting destroyed.
|
|
view->clearLayoutSubtreeRoot();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RenderObject::willBeDestroyed()
|
|
{
|
|
// Destroy any leftover anonymous children.
|
|
RenderObjectChildList* children = virtualChildren();
|
|
if (children)
|
|
children->destroyLeftoverChildren();
|
|
|
|
remove();
|
|
setAncestorLineBoxDirty(false);
|
|
clearLayoutRootIfNeeded();
|
|
}
|
|
|
|
void RenderObject::insertedIntoTree()
|
|
{
|
|
// FIXME: We should ASSERT(isRooted()) here but generated content makes some out-of-order insertion.
|
|
|
|
// Keep our layer hierarchy updated. Optimize for the common case where we don't have any children
|
|
// and don't have a layer attached to ourselves.
|
|
RenderLayer* layer = 0;
|
|
if (slowFirstChild() || hasLayer()) {
|
|
layer = parent()->enclosingLayer();
|
|
addLayers(layer);
|
|
}
|
|
|
|
if (parent()->isRenderParagraph())
|
|
parent()->dirtyLinesFromChangedChild(this);
|
|
}
|
|
|
|
void RenderObject::willBeRemovedFromTree()
|
|
{
|
|
// FIXME: We should ASSERT(isRooted()) but we have some out-of-order removals which would need to be fixed first.
|
|
|
|
// Keep our layer hierarchy updated.
|
|
if (slowFirstChild() || hasLayer())
|
|
removeLayers(parent()->enclosingLayer());
|
|
|
|
if (isOutOfFlowPositioned() && parent()->isRenderParagraph())
|
|
parent()->dirtyLinesFromChangedChild(this);
|
|
}
|
|
|
|
void RenderObject::destroy()
|
|
{
|
|
#if ENABLE(ASSERT) && ENABLE(OILPAN)
|
|
ASSERT(!m_didCallDestroy);
|
|
m_didCallDestroy = true;
|
|
#endif
|
|
willBeDestroyed();
|
|
postDestroy();
|
|
}
|
|
|
|
void RenderObject::postDestroy()
|
|
{
|
|
// It seems ugly that this is not in willBeDestroyed().
|
|
if (m_style) {
|
|
for (const FillLayer* bgLayer = &m_style->backgroundLayers(); bgLayer; bgLayer = bgLayer->next()) {
|
|
if (StyleImage* backgroundImage = bgLayer->image())
|
|
backgroundImage->removeClient(this);
|
|
}
|
|
}
|
|
delete this;
|
|
}
|
|
|
|
PositionWithAffinity RenderObject::positionForPoint(const LayoutPoint&)
|
|
{
|
|
return createPositionWithAffinity(caretMinOffset(), DOWNSTREAM);
|
|
}
|
|
|
|
// FIXME(sky): Change the callers to use nodeAtPoint direclty and remove this function.
|
|
// Or, rename nodeAtPoint to hitTest?
|
|
bool RenderObject::hitTest(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset)
|
|
{
|
|
return nodeAtPoint(request, result, locationInContainer, accumulatedOffset);
|
|
}
|
|
|
|
void RenderObject::updateHitTestResult(HitTestResult& result, const LayoutPoint& point)
|
|
{
|
|
if (result.innerNode())
|
|
return;
|
|
|
|
Node* node = this->node();
|
|
|
|
if (node) {
|
|
result.setInnerNode(node);
|
|
if (!result.innerNonSharedNode())
|
|
result.setInnerNonSharedNode(node);
|
|
result.setLocalPoint(point);
|
|
}
|
|
}
|
|
|
|
bool RenderObject::nodeAtPoint(const HitTestRequest&, HitTestResult&, const HitTestLocation& /*locationInContainer*/, const LayoutPoint& /*accumulatedOffset*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void RenderObject::scheduleRelayout()
|
|
{
|
|
if (isRenderView()) {
|
|
FrameView* view = toRenderView(this)->frameView();
|
|
if (view)
|
|
view->scheduleRelayout();
|
|
} else {
|
|
if (isRooted()) {
|
|
if (RenderView* renderView = view()) {
|
|
if (FrameView* frameView = renderView->frameView())
|
|
frameView->scheduleRelayoutOfSubtree(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RenderObject::forceLayout()
|
|
{
|
|
setSelfNeedsLayout(true);
|
|
layout();
|
|
}
|
|
|
|
// FIXME: Does this do anything different than forceLayout given that we don't walk
|
|
// the containing block chain. If not, we should change all callers to use forceLayout.
|
|
void RenderObject::forceChildLayout()
|
|
{
|
|
setNormalChildNeedsLayout(true);
|
|
layout();
|
|
}
|
|
|
|
void RenderObject::getTextDecorations(unsigned decorations, AppliedTextDecoration& underline, AppliedTextDecoration& overline, AppliedTextDecoration& linethrough, bool quirksMode, bool firstlineStyle)
|
|
{
|
|
RenderObject* curr = this;
|
|
RenderStyle* styleToUse = 0;
|
|
unsigned currDecs = TextDecorationNone;
|
|
Color resultColor;
|
|
TextDecorationStyle resultStyle;
|
|
do {
|
|
styleToUse = curr->style(firstlineStyle);
|
|
currDecs = styleToUse->textDecoration();
|
|
currDecs &= decorations;
|
|
resultColor = styleToUse->decorationColor();
|
|
resultStyle = styleToUse->textDecorationStyle();
|
|
// Parameter 'decorations' is cast as an int to enable the bitwise operations below.
|
|
if (currDecs) {
|
|
if (currDecs & TextDecorationUnderline) {
|
|
decorations &= ~TextDecorationUnderline;
|
|
underline.color = resultColor;
|
|
underline.style = resultStyle;
|
|
}
|
|
if (currDecs & TextDecorationOverline) {
|
|
decorations &= ~TextDecorationOverline;
|
|
overline.color = resultColor;
|
|
overline.style = resultStyle;
|
|
}
|
|
if (currDecs & TextDecorationLineThrough) {
|
|
decorations &= ~TextDecorationLineThrough;
|
|
linethrough.color = resultColor;
|
|
linethrough.style = resultStyle;
|
|
}
|
|
}
|
|
curr = curr->parent();
|
|
} while (curr && decorations);
|
|
|
|
// If we bailed out, use the element we bailed out at (typically a <font> or <a> element).
|
|
if (decorations && curr) {
|
|
styleToUse = curr->style(firstlineStyle);
|
|
resultColor = styleToUse->decorationColor();
|
|
if (decorations & TextDecorationUnderline) {
|
|
underline.color = resultColor;
|
|
underline.style = resultStyle;
|
|
}
|
|
if (decorations & TextDecorationOverline) {
|
|
overline.color = resultColor;
|
|
overline.style = resultStyle;
|
|
}
|
|
if (decorations & TextDecorationLineThrough) {
|
|
linethrough.color = resultColor;
|
|
linethrough.style = resultStyle;
|
|
}
|
|
}
|
|
}
|
|
|
|
int RenderObject::caretMinOffset() const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int RenderObject::caretMaxOffset() const
|
|
{
|
|
if (isReplaced())
|
|
return node() ? std::max(1U, node()->countChildren()) : 1;
|
|
return 0;
|
|
}
|
|
|
|
int RenderObject::previousOffset(int current) const
|
|
{
|
|
return current - 1;
|
|
}
|
|
|
|
int RenderObject::previousOffsetForBackwardDeletion(int current) const
|
|
{
|
|
return current - 1;
|
|
}
|
|
|
|
int RenderObject::nextOffset(int current) const
|
|
{
|
|
return current + 1;
|
|
}
|
|
|
|
// touch-action applies to all elements with both width AND height properties.
|
|
// According to the CSS Box Model Spec (http://dev.w3.org/csswg/css-box/#the-width-and-height-properties)
|
|
// width applies to all elements but non-replaced inline elements, table rows, and row groups and
|
|
// height applies to all elements but non-replaced inline elements, table columns, and column groups.
|
|
bool RenderObject::supportsTouchAction() const
|
|
{
|
|
if (isInline() && !isReplaced())
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
Element* RenderObject::offsetParent() const
|
|
{
|
|
Node* node = 0;
|
|
for (RenderObject* ancestor = parent(); ancestor; ancestor = ancestor->parent()) {
|
|
// Spec: http://www.w3.org/TR/cssom-view/#offset-attributes
|
|
node = ancestor->node();
|
|
if (!node)
|
|
continue;
|
|
|
|
if (ancestor->isPositioned())
|
|
break;
|
|
}
|
|
|
|
return node && node->isElementNode() ? toElement(node) : 0;
|
|
}
|
|
|
|
PositionWithAffinity RenderObject::createPositionWithAffinity(int offset, EAffinity affinity)
|
|
{
|
|
// If this is a non-anonymous renderer in an editable area, then it's simple.
|
|
if (Node* node = this->node()) {
|
|
if (!node->hasEditableStyle()) {
|
|
// If it can be found, we prefer a visually equivalent position that is editable.
|
|
Position position = createLegacyEditingPosition(node, offset);
|
|
Position candidate = position.downstream(CanCrossEditingBoundary);
|
|
if (candidate.deprecatedNode()->hasEditableStyle())
|
|
return PositionWithAffinity(candidate, affinity);
|
|
candidate = position.upstream(CanCrossEditingBoundary);
|
|
if (candidate.deprecatedNode()->hasEditableStyle())
|
|
return PositionWithAffinity(candidate, affinity);
|
|
}
|
|
// FIXME: Eliminate legacy editing positions
|
|
return PositionWithAffinity(createLegacyEditingPosition(node, offset), affinity);
|
|
}
|
|
|
|
// We don't want to cross the boundary between editable and non-editable
|
|
// regions of the document, but that is either impossible or at least
|
|
// extremely unlikely in any normal case because we stop as soon as we
|
|
// find a single non-anonymous renderer.
|
|
|
|
// Find a nearby non-anonymous renderer.
|
|
RenderObject* child = this;
|
|
while (RenderObject* parent = child->parent()) {
|
|
// Find non-anonymous content after.
|
|
for (RenderObject* renderer = child->nextInPreOrder(parent); renderer; renderer = renderer->nextInPreOrder(parent)) {
|
|
if (Node* node = renderer->node())
|
|
return PositionWithAffinity(firstPositionInOrBeforeNode(node), DOWNSTREAM);
|
|
}
|
|
|
|
// Find non-anonymous content before.
|
|
for (RenderObject* renderer = child->previousInPreOrder(); renderer; renderer = renderer->previousInPreOrder()) {
|
|
if (renderer == parent)
|
|
break;
|
|
if (Node* node = renderer->node())
|
|
return PositionWithAffinity(lastPositionInOrAfterNode(node), DOWNSTREAM);
|
|
}
|
|
|
|
// Use the parent itself unless it too is anonymous.
|
|
if (Node* node = parent->node())
|
|
return PositionWithAffinity(firstPositionInOrBeforeNode(node), DOWNSTREAM);
|
|
|
|
// Repeat at the next level up.
|
|
child = parent;
|
|
}
|
|
|
|
// Everything was anonymous. Give up.
|
|
return PositionWithAffinity();
|
|
}
|
|
|
|
PositionWithAffinity RenderObject::createPositionWithAffinity(const Position& position)
|
|
{
|
|
if (position.isNotNull())
|
|
return PositionWithAffinity(position);
|
|
|
|
ASSERT(!node());
|
|
return createPositionWithAffinity(0, DOWNSTREAM);
|
|
}
|
|
|
|
bool RenderObject::canUpdateSelectionOnRootLineBoxes()
|
|
{
|
|
if (needsLayout())
|
|
return false;
|
|
|
|
RenderBlock* containingBlock = this->containingBlock();
|
|
return containingBlock ? !containingBlock->needsLayout() : false;
|
|
}
|
|
|
|
bool RenderObject::nodeAtFloatPoint(const HitTestRequest&, HitTestResult&, const FloatPoint&)
|
|
{
|
|
ASSERT_NOT_REACHED();
|
|
return false;
|
|
}
|
|
|
|
bool RenderObject::isAllowedToModifyRenderTreeStructure(Document& document)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
} // namespace blink
|
|
|
|
#ifndef NDEBUG
|
|
|
|
void showTree(const blink::RenderObject* object)
|
|
{
|
|
if (object)
|
|
object->showTreeForThis();
|
|
}
|
|
|
|
void showLineTree(const blink::RenderObject* object)
|
|
{
|
|
if (object)
|
|
object->showLineTreeForThis();
|
|
}
|
|
|
|
void showRenderTree(const blink::RenderObject* object1)
|
|
{
|
|
showRenderTree(object1, 0);
|
|
}
|
|
|
|
void showRenderTree(const blink::RenderObject* object1, const blink::RenderObject* object2)
|
|
{
|
|
if (object1) {
|
|
const blink::RenderObject* root = object1;
|
|
while (root->parent())
|
|
root = root->parent();
|
|
root->showRenderTreeAndMark(object1, "*", object2, "-", 0);
|
|
}
|
|
}
|
|
|
|
#endif
|