mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
The primary goal of this change was to remove EventTarget from the
sky_engine C++ code. Since EventTarget is so core to the entire event
system that sky_engine was based on, this is a rather invasive change.
As such, it had some knock-on effects. I deleted some of the files
that were affected, and cauterised the remainder.
In many cases, a file would depend on another file that it didn't
include directly, but instead included indirectly via another file
that I deleted. When this happened, if the features that this broke
were obsolete, I sometimes just removed the features instead.
Specifically:
- removed EventTarget
- removed EventQueue, since without a target, what's a queue going to
do?
- same with EventDispatch*
- removed ExecutionContext, since it had an EventQueue and nothing
else it did was relevant to Sky anymore
- removed ActiveDOMObject, which was all about ExecutionContexts
- removed ContextLifecycleNotifier since it dependend on
ExecutionContext and ActiveDOMObject
- removed the other Lifecycle classes for consistency, and replaced
them with four booleans in the Document class
- removed some of the attributes that are no longer relevant from
IDLExtendedAttributes (ConstructorCallWith and
CallWith=ExecutionContext)
- removed the Document member on DOMDartState since we never set it to
anything but null.
- removed BuiltinSky::InstallWindow since it relied on the Document
member of DOMDartState
- removed EventHandler, EventListener, and mentions of those in
various binding scripts
- removed NewEventHandler, since we're not using that either
- removed the following interfaces from the Sky Dart API:
- EventTarget
- EventListener (since without a target, there's no way to listen)
- FocusEvent (since it's only member was an EventTarget)
- HashChangeEvent (mostly by accident, but it's defunct anyway)
- FontFace (it used ConstructorCallWith=ExecutionContext)
- changed the following interfaces of the Sky DART API:
- MediaQueryList is no longer an EventTarget
- Node is no longer an EventTarget
- Document no longer has defaultView (depended on
DOMDartState's document)
- DocumentFragment, Element, Range, and Text no longer have a
constructor (they all depended on DOMDartState's document, which
is now gone)
- Event lost its EventTarget members and path.
- Window lost its WindowTimers partial interface (it used
EventTarget and ExecutionContext a lot)
- removed numerous hacks in the bindings around features that are now
gone, like addEventListener
- removed a bunch of console logging code, since that relied on
ExecutionContext
- cauterised the wound in FontFace.cpp by removing constructors and
methods that called now-removed features
- same with MediaQuery and friends
- same with some editor features and focus-related features
- same with Document
- removed DOMTimer classes since they use ExecutionContexts
1888 lines
66 KiB
C++
1888 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/StyleEngine.h"
|
|
#include "sky/engine/core/dom/shadow/ShadowRoot.h"
|
|
#include "sky/engine/core/editing/EditingBoundary.h"
|
|
#include "sky/engine/core/editing/FrameSelection.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);
|
|
}
|
|
|
|
bool RenderObject::canRenderBorderImage() const
|
|
{
|
|
ASSERT(style()->hasBorder());
|
|
|
|
StyleImage* borderImage = style()->borderImage().image();
|
|
return borderImage && borderImage->canRender(*this) && borderImage->isLoaded();
|
|
}
|
|
|
|
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
|
|
{
|
|
if (!isSelectable())
|
|
return Color::transparent;
|
|
|
|
return frame()->selection().isFocusedAndActive() ?
|
|
RenderTheme::theme().activeSelectionBackgroundColor() :
|
|
RenderTheme::theme().inactiveSelectionBackgroundColor();
|
|
}
|
|
|
|
Color RenderObject::selectionColor(int colorProperty) const
|
|
{
|
|
// If the element is unselectable, don't override the foreground
|
|
// color with the selection foreground color.
|
|
if (!isSelectable())
|
|
return resolveColor(colorProperty);
|
|
|
|
if (!RenderTheme::theme().supportsSelectionForegroundColors())
|
|
return resolveColor(colorProperty);
|
|
return frame()->selection().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());
|
|
updateImage(oldStyle ? oldStyle->borderImage().image() : 0, m_style->borderImage().image());
|
|
|
|
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);
|
|
}
|
|
|
|
if (StyleImage* borderImage = m_style->borderImage().image())
|
|
borderImage->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
|