blob: d56ac9de700578600398cbaef65f5bd4066c1038 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 2000 Simon Hausmann <hausmann@kde.org>
* (C) 2000 Stefan Schimanski (1Stein@gmx.de)
* Copyright (C) 2004, 2005, 2006, 2009 Apple Inc. All rights reserved.
* Copyright (C) Research In Motion Limited 2011. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "core/layout/LayoutPart.h"
#include "core/dom/AXObjectCache.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalFrame.h"
#include "core/html/HTMLFrameElementBase.h"
#include "core/layout/HitTestResult.h"
#include "core/layout/LayoutAnalyzer.h"
#include "core/layout/LayoutView.h"
#include "core/layout/api/LayoutAPIShim.h"
#include "core/paint/PartPainter.h"
#include "core/plugins/PluginView.h"
namespace blink {
LayoutPart::LayoutPart(Element* element)
: LayoutReplaced(element)
// Reference counting is used to prevent the part from being destroyed
// while inside the Widget code, which might not be able to handle that.
, m_refCount(1)
{
ASSERT(element);
frameView()->addPart(this);
setInline(false);
}
void LayoutPart::deref()
{
if (--m_refCount <= 0)
delete this;
}
void LayoutPart::willBeDestroyed()
{
frameView()->removePart(this);
if (AXObjectCache* cache = document().existingAXObjectCache()) {
cache->childrenChanged(this->parent());
cache->remove(this);
}
Element* element = toElement(node());
if (element && element->isFrameOwnerElement())
toHTMLFrameOwnerElement(element)->setWidget(nullptr);
LayoutReplaced::willBeDestroyed();
}
void LayoutPart::destroy()
{
willBeDestroyed();
// We call clearNode here because LayoutPart is ref counted. This call to destroy
// may not actually destroy the layout object. We can keep it around because of
// references from the FrameView class. (The actual destruction of the class happens
// in postDestroy() which is called from deref()).
//
// But, we've told the system we've destroyed the layoutObject, which happens when
// the DOM node is destroyed. So there is a good change the DOM node this object
// points too is invalid, so we have to clear the node so we make sure we don't
// access it in the future.
clearNode();
deref();
}
LayoutPart::~LayoutPart()
{
ASSERT(m_refCount <= 0);
}
Widget* LayoutPart::widget() const
{
// Plugin widgets are stored in their DOM node.
Element* element = toElement(node());
if (element && element->isFrameOwnerElement())
return toHTMLFrameOwnerElement(element)->ownedWidget();
return nullptr;
}
PaintLayerType LayoutPart::layerTypeRequired() const
{
PaintLayerType type = LayoutReplaced::layerTypeRequired();
if (type != NoPaintLayer)
return type;
return ForcedPaintLayer;
}
bool LayoutPart::requiresAcceleratedCompositing() const
{
// There are two general cases in which we can return true. First, if this is a plugin
// LayoutObject and the plugin has a layer, then we need a layer. Second, if this is
// a LayoutObject with a contentDocument and that document needs a layer, then we need
// a layer.
if (widget() && widget()->isPluginView() && toPluginView(widget())->platformLayer())
return true;
if (!node() || !node()->isFrameOwnerElement())
return false;
HTMLFrameOwnerElement* element = toHTMLFrameOwnerElement(node());
if (element->contentFrame() && element->contentFrame()->isRemoteFrame())
return true;
if (Document* contentDocument = element->contentDocument()) {
LayoutViewItem viewItem = contentDocument->layoutViewItem();
if (!viewItem.isNull())
return viewItem.usesCompositing();
}
return false;
}
bool LayoutPart::needsPreferredWidthsRecalculation() const
{
if (LayoutReplaced::needsPreferredWidthsRecalculation())
return true;
return embeddedReplacedContent();
}
bool LayoutPart::nodeAtPointOverWidget(HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction action)
{
bool hadResult = result.innerNode();
bool inside = LayoutReplaced::nodeAtPoint(result, locationInContainer, accumulatedOffset, action);
// Check to see if we are really over the widget itself (and not just in the border/padding area).
if ((inside || result.isRectBasedTest()) && !hadResult && result.innerNode() == node())
result.setIsOverWidget(contentBoxRect().contains(result.localPoint()));
return inside;
}
bool LayoutPart::nodeAtPoint(HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction action)
{
if (!widget() || !widget()->isFrameView() || !result.hitTestRequest().allowsChildFrameContent())
return nodeAtPointOverWidget(result, locationInContainer, accumulatedOffset, action);
// A hit test can never hit an off-screen element; only off-screen iframes are throttled;
// therefore, hit tests can skip descending into throttled iframes.
if (toFrameView(widget())->shouldThrottleRendering())
return nodeAtPointOverWidget(result, locationInContainer, accumulatedOffset, action);
ASSERT(document().lifecycle().state() >= DocumentLifecycle::CompositingClean);
if (action == HitTestForeground) {
FrameView* childFrameView = toFrameView(widget());
LayoutViewItem childRootItem = childFrameView->layoutViewItem();
if (visibleToHitTestRequest(result.hitTestRequest()) && !childRootItem.isNull()) {
LayoutPoint adjustedLocation = accumulatedOffset + location();
LayoutPoint contentOffset = LayoutPoint(borderLeft() + paddingLeft(), borderTop() + paddingTop()) - LayoutSize(childFrameView->scrollOffset());
HitTestLocation newHitTestLocation(locationInContainer, -adjustedLocation - contentOffset);
HitTestRequest newHitTestRequest(result.hitTestRequest().type() | HitTestRequest::ChildFrameHitTest);
HitTestResult childFrameResult(newHitTestRequest, newHitTestLocation);
// The frame's layout and style must be up to date if we reach here.
bool isInsideChildFrame = childRootItem.hitTestNoLifecycleUpdate(childFrameResult);
if (result.hitTestRequest().listBased()) {
result.append(childFrameResult);
} else if (isInsideChildFrame) {
// Force the result not to be cacheable because the parent
// frame should not cache this result; as it won't be notified of
// changes in the child.
childFrameResult.setCacheable(false);
result = childFrameResult;
}
// Don't trust |isInsideChildFrame|. For rect-based hit-test, returns
// true only when the hit test rect is totally within the iframe,
// i.e. nodeAtPointOverWidget() also returns true.
// Use a temporary HitTestResult because we don't want to collect the
// iframe element itself if the hit-test rect is totally within the iframe.
if (isInsideChildFrame) {
if (!locationInContainer.isRectBasedTest())
return true;
HitTestResult pointOverWidgetResult = result;
bool pointOverWidget = nodeAtPointOverWidget(pointOverWidgetResult, locationInContainer, accumulatedOffset, action);
if (pointOverWidget)
return true;
result = pointOverWidgetResult;
return false;
}
}
}
return nodeAtPointOverWidget(result, locationInContainer, accumulatedOffset, action);
}
CompositingReasons LayoutPart::additionalCompositingReasons() const
{
if (requiresAcceleratedCompositing())
return CompositingReasonIFrame;
return CompositingReasonNone;
}
void LayoutPart::styleDidChange(StyleDifference diff, const ComputedStyle* oldStyle)
{
LayoutReplaced::styleDidChange(diff, oldStyle);
Widget* widget = this->widget();
if (!widget)
return;
// If the iframe has custom scrollbars, recalculate their style.
if (widget && widget->isFrameView())
toFrameView(widget)->recalculateCustomScrollbarStyle();
if (style()->visibility() != EVisibility::Visible) {
widget->hide();
} else {
widget->show();
}
}
void LayoutPart::layout()
{
ASSERT(needsLayout());
LayoutAnalyzer::Scope analyzer(*this);
clearNeedsLayout();
}
void LayoutPart::paint(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) const
{
PartPainter(*this).paint(paintInfo, paintOffset);
}
void LayoutPart::paintContents(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) const
{
PartPainter(*this).paintContents(paintInfo, paintOffset);
}
CursorDirective LayoutPart::getCursor(const LayoutPoint& point, Cursor& cursor) const
{
if (widget() && widget()->isPluginView()) {
// A plugin is responsible for setting the cursor when the pointer is over it.
return DoNotSetCursor;
}
return LayoutReplaced::getCursor(point, cursor);
}
LayoutRect LayoutPart::replacedContentRect() const
{
// We don't propagate sub-pixel into sub-frame layout, in other words, the rect is snapped
// at the document boundary, and sub-pixel movement could cause the sub-frame to layout
// due to the 1px snap difference. In order to avoid that, the size of sub-frame is rounded
// in advance.
LayoutRect sizeRoundedRect = contentBoxRect();
sizeRoundedRect.setSize(LayoutSize(roundedIntSize(sizeRoundedRect.size())));
return sizeRoundedRect;
}
void LayoutPart::updateOnWidgetChange()
{
Widget* widget = this->widget();
if (!widget)
return;
if (!style())
return;
if (!needsLayout())
updateWidgetGeometryInternal();
if (style()->visibility() != EVisibility::Visible) {
widget->hide();
} else {
widget->show();
// FIXME: Why do we issue a full paint invalidation in this case, but not the other?
setShouldDoFullPaintInvalidation();
}
}
void LayoutPart::updateWidgetGeometry()
{
Widget* widget = this->widget();
if (!widget || !node()) // Check the node in case destroy() has been called.
return;
updateWidgetGeometryInternal();
// If view needs layout, either because bounds have changed or possibly
// indicating content size is wrong, we have to do a layout to set the right
// widget size.
FrameView* frameView = widget->isFrameView() ? toFrameView(widget) : nullptr;
if (frameView && frameView->needsLayout() && frameView->frame().page())
frameView->layout();
widget->widgetGeometryMayHaveChanged();
}
void LayoutPart::updateWidgetGeometryInternal()
{
Widget* widget = this->widget();
ASSERT(widget);
// Ignore transform here, as we only care about the sub-pixel accumulation.
// TODO(trchen): What about multicol? Need a LayoutBox function to query sub-pixel accumulation.
LayoutPoint absoluteLocation(localToAbsolute(FloatPoint()));
LayoutRect absoluteReplacedRect = replacedContentRect();
absoluteReplacedRect.moveBy(absoluteLocation);
IntRect frameRect(IntPoint(), pixelSnappedIntRect(absoluteReplacedRect).size());
// Normally the location of the frame rect is ignored by the painter, but currently it is
// still used by a family of coordinate conversion function in Widget/FrameView. This is
// incorrect because coordinate conversion needs to take transform and into account.
// A few callers still use the family of conversion function, including but not exhaustive:
// FrameView::updateViewportIntersectionIfNeeded()
// RemoteFrameView::frameRectsChanged().
// WebPluginContainerImpl::reportGeometry()
// TODO(trchen): Remove this hack once we fixed all callers.
FloatRect absoluteBoundingBox = localToAbsoluteQuad(FloatRect(replacedContentRect())).boundingBox();
frameRect.setLocation(roundedIntPoint(absoluteBoundingBox.location()));
// Why is the protector needed?
RefPtr<LayoutPart> protector(this);
widget->setFrameRect(frameRect);
}
void LayoutPart::invalidatePaintOfSubtreesIfNeeded(const PaintInvalidationState& paintInvalidationState)
{
if (widget() && widget()->isFrameView() && !isThrottledFrameView()) {
FrameView* childFrameView = toFrameView(widget());
// |childFrameView| is in another document, which could be
// missing its LayoutView. TODO(jchaffraix): Ideally we should
// not need this code.
if (LayoutView* childLayoutView = toLayoutView(LayoutAPIShim::layoutObjectFrom(childFrameView->layoutViewItem()))) {
PaintInvalidationState childViewPaintInvalidationState(paintInvalidationState, *childLayoutView);
childFrameView->invalidateTreeIfNeeded(childViewPaintInvalidationState);
}
}
LayoutReplaced::invalidatePaintOfSubtreesIfNeeded(paintInvalidationState);
}
bool LayoutPart::isThrottledFrameView() const
{
if (!widget() || !widget()->isFrameView())
return false;
const FrameView* frameView = toFrameView(widget());
return frameView->shouldThrottleRendering();
}
} // namespace blink