Ojan Vafai ce43f3d0c1 First pass at deleting paint invalidation code.
This is all wasted effort in sky since we invalidate
the whole viewport on every frame. We'll probably eventually
add back in some invalidation, but it likely won't
be rect-based.

R=esprehn@chromium.org

Review URL: https://codereview.chromium.org/840403003
2015-01-09 15:20:35 -08:00

1016 lines
32 KiB
C++

/*
* Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
* 1999 Lars Knoll <knoll@kde.org>
* 1999 Antti Koivisto <koivisto@kde.org>
* 2000 Dirk Mueller <mueller@kde.org>
* Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
* (C) 2006 Graham Dennis (graham.dennis@gmail.com)
* (C) 2006 Alexey Proskuryakov (ap@nypop.com)
* Copyright (C) 2009 Google Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "sky/engine/config.h"
#include "sky/engine/core/frame/FrameView.h"
#include "gen/sky/platform/RuntimeEnabledFeatures.h"
#include "sky/engine/core/animation/DocumentAnimations.h"
#include "sky/engine/core/css/FontFaceSet.h"
#include "sky/engine/core/css/resolver/StyleResolver.h"
#include "sky/engine/core/dom/DocumentMarkerController.h"
#include "sky/engine/core/editing/FrameSelection.h"
#include "sky/engine/core/fetch/ResourceFetcher.h"
#include "sky/engine/core/frame/FrameHost.h"
#include "sky/engine/core/frame/LocalFrame.h"
#include "sky/engine/core/frame/Settings.h"
#include "sky/engine/core/html/parser/TextResourceDecoder.h"
#include "sky/engine/core/inspector/InspectorTraceEvents.h"
#include "sky/engine/core/loader/FrameLoaderClient.h"
#include "sky/engine/core/page/Chrome.h"
#include "sky/engine/core/page/ChromeClient.h"
#include "sky/engine/core/page/EventHandler.h"
#include "sky/engine/core/page/FocusController.h"
#include "sky/engine/core/page/Page.h"
#include "sky/engine/core/rendering/RenderLayer.h"
#include "sky/engine/core/rendering/RenderView.h"
#include "sky/engine/core/rendering/style/RenderStyle.h"
#include "sky/engine/platform/ScriptForbiddenScope.h"
#include "sky/engine/platform/TraceEvent.h"
#include "sky/engine/platform/fonts/FontCache.h"
#include "sky/engine/platform/geometry/FloatRect.h"
#include "sky/engine/platform/graphics/GraphicsContext.h"
#include "sky/engine/platform/scroll/ScrollAnimator.h"
#include "sky/engine/platform/scroll/Scrollbar.h"
#include "sky/engine/platform/text/TextStream.h"
#include "sky/engine/wtf/CurrentTime.h"
#include "sky/engine/wtf/TemporaryChange.h"
namespace blink {
double FrameView::s_currentFrameTimeStamp = 0.0;
bool FrameView::s_inPaintContents = false;
FrameView::FrameView(LocalFrame* frame)
: m_frame(frame)
, m_hasPendingLayout(false)
, m_layoutSubtreeRoot(0)
, m_inSynchronousPostLayout(false)
, m_postLayoutTasksTimer(this, &FrameView::postLayoutTimerFired)
, m_isTransparent(false)
, m_baseBackgroundColor(Color::white)
, m_mediaType("screen")
, m_overflowStatusDirty(true)
, m_viewportRenderer(0)
, m_isTrackingPaintInvalidations(false)
, m_hasSoftwareFilters(false)
, m_visibleContentScaleFactor(1)
, m_inputEventsScaleFactorForEmulation(1)
, m_layoutSizeFixedToFrameSize(true)
{
ASSERT(m_frame);
init();
}
PassRefPtr<FrameView> FrameView::create(LocalFrame* frame)
{
RefPtr<FrameView> view = adoptRef(new FrameView(frame));
return view.release();
}
PassRefPtr<FrameView> FrameView::create(LocalFrame* frame, const IntSize& initialSize)
{
RefPtr<FrameView> view = adoptRef(new FrameView(frame));
view->Widget::setFrameRect(IntRect(view->location(), initialSize));
view->setLayoutSizeInternal(initialSize);
return view.release();
}
FrameView::~FrameView()
{
if (m_postLayoutTasksTimer.isActive())
m_postLayoutTasksTimer.stop();
ASSERT(m_frame);
ASSERT(m_frame->view() != this || !m_frame->contentRenderer());
}
void FrameView::reset()
{
m_hasPendingLayout = false;
m_layoutSubtreeRoot = 0;
m_doFullPaintInvalidation = false;
m_layoutSchedulingEnabled = true;
m_inPerformLayout = false;
m_canInvalidatePaintDuringPerformLayout = false;
m_inSynchronousPostLayout = false;
m_layoutCount = 0;
m_nestedLayoutCount = 0;
m_postLayoutTasksTimer.stop();
m_firstLayout = true;
m_firstLayoutCallbackPending = false;
m_lastViewportSize = IntSize();
m_isTrackingPaintInvalidations = false;
m_trackedPaintInvalidationRects.clear();
m_lastPaintTime = 0;
m_isPainting = false;
}
void FrameView::init()
{
reset();
m_size = LayoutSize();
}
void FrameView::prepareForDetach()
{
// FIXME(sky): Remove
}
void FrameView::clear()
{
reset();
}
bool FrameView::didFirstLayout() const
{
return !m_firstLayout;
}
void FrameView::invalidateRect(const IntRect& rect)
{
// FIXME(sky): Parent is always null in sky?
if (!parent()) {
if (HostWindow* window = hostWindow())
window->invalidateContentsAndRootView(rect);
}
}
void FrameView::setFrameRect(const IntRect& newRect)
{
IntRect oldRect = frameRect();
if (newRect == oldRect)
return;
Widget::setFrameRect(newRect);
}
Page* FrameView::page() const
{
return frame().page();
}
RenderView* FrameView::renderView() const
{
return frame().contentRenderer();
}
IntPoint FrameView::clampOffsetAtScale(const IntPoint& offset, float scale) const
{
FloatSize scaledSize = unscaledVisibleContentSize();
if (scale)
scaledSize.scale(1 / scale);
IntPoint clampedOffset = offset;
clampedOffset = clampedOffset.shrunkTo(
IntPoint(size()) - expandedIntSize(scaledSize));
return clampedOffset;
}
void FrameView::recalcOverflowAfterStyleChange()
{
RenderView* renderView = this->renderView();
RELEASE_ASSERT(renderView);
if (!renderView->needsOverflowRecalcAfterStyleChange())
return;
renderView->recalcOverflowAfterStyleChange();
}
bool FrameView::scheduleAnimation()
{
if (HostWindow* window = hostWindow()) {
window->scheduleAnimation();
return true;
}
return false;
}
bool FrameView::isEnclosedInCompositingLayer() const
{
return false;
}
RenderObject* FrameView::layoutRoot(bool onlyDuringLayout) const
{
return onlyDuringLayout && layoutPending() ? 0 : m_layoutSubtreeRoot;
}
void FrameView::performPreLayoutTasks()
{
TRACE_EVENT0("blink", "FrameView::performPreLayoutTasks");
lifecycle().advanceTo(DocumentLifecycle::InPreLayout);
// Don't schedule more layouts, we're in one.
TemporaryChange<bool> changeSchedulingEnabled(m_layoutSchedulingEnabled, false);
if (!m_nestedLayoutCount && !m_inSynchronousPostLayout && m_postLayoutTasksTimer.isActive()) {
// This is a new top-level layout. If there are any remaining tasks from the previous layout, finish them now.
m_inSynchronousPostLayout = true;
performPostLayoutTasks();
m_inSynchronousPostLayout = false;
}
Document* document = m_frame->document();
if (wasViewportResized())
document->notifyResizeForViewportUnits();
// Viewport-dependent media queries may cause us to need completely different style information.
if (!document->styleResolver() || document->styleResolver()->mediaQueryAffectedByViewportChange()) {
document->styleResolverChanged();
document->mediaQueryAffectingValueChanged();
} else {
document->evaluateMediaQueryList();
}
document->updateRenderTreeIfNeeded();
lifecycle().advanceTo(DocumentLifecycle::StyleClean);
}
void FrameView::performLayout(RenderObject* rootForThisLayout, bool inSubtreeLayout)
{
TRACE_EVENT0("blink", "FrameView::performLayout");
ScriptForbiddenScope forbidScript;
ASSERT(!isInPerformLayout());
lifecycle().advanceTo(DocumentLifecycle::InPerformLayout);
TemporaryChange<bool> changeInPerformLayout(m_inPerformLayout, true);
// performLayout is the actual guts of layout().
// FIXME: The 300 other lines in layout() probably belong in other helper functions
// so that a single human could understand what layout() is actually doing.
LayoutState layoutState(*rootForThisLayout);
// FIXME (crbug.com/256657): Do not do two layouts for text autosizing.
rootForThisLayout->layout();
lifecycle().advanceTo(DocumentLifecycle::AfterPerformLayout);
}
void FrameView::scheduleOrPerformPostLayoutTasks()
{
if (m_postLayoutTasksTimer.isActive())
return;
if (!m_inSynchronousPostLayout) {
m_inSynchronousPostLayout = true;
// Calls resumeScheduledEvents()
performPostLayoutTasks();
m_inSynchronousPostLayout = false;
}
if (!m_postLayoutTasksTimer.isActive() && (needsLayout() || m_inSynchronousPostLayout)) {
// If we need layout or are already in a synchronous call to postLayoutTasks(),
// defer widget updates and event dispatch until after we return. postLayoutTasks()
// can make us need to update again, and we can get stuck in a nasty cycle unless
// we call it through the timer here.
m_postLayoutTasksTimer.startOneShot(0, FROM_HERE);
if (needsLayout())
layout();
}
}
void FrameView::layout(bool allowSubtree)
{
// We should never layout a Document which is not in a LocalFrame.
ASSERT(m_frame);
ASSERT(m_frame->view() == this);
ASSERT(m_frame->page());
ScriptForbiddenScope forbidScript;
if (isInPerformLayout() || !m_frame->document()->isActive())
return;
TRACE_EVENT0("blink", "FrameView::layout");
TRACE_EVENT_SCOPED_SAMPLING_STATE("blink", "Layout");
// Protect the view from being deleted during layout (in recalcStyle)
RefPtr<FrameView> protector(this);
m_hasPendingLayout = false;
DocumentLifecycle::Scope lifecycleScope(lifecycle(), DocumentLifecycle::LayoutClean);
RELEASE_ASSERT(!isPainting());
TRACE_EVENT_BEGIN1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "Layout", "beginData", InspectorLayoutEvent::beginData(this));
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline.stack"), "CallStack", TRACE_EVENT_SCOPE_PROCESS, "stack", InspectorCallStackEvent::currentCallStack());
if (!allowSubtree && isSubtreeLayout()) {
m_layoutSubtreeRoot->markContainingBlocksForLayout(false);
m_layoutSubtreeRoot = 0;
}
performPreLayoutTasks();
// If there is only one ref to this view left, then its going to be destroyed as soon as we exit,
// so there's no point to continuing to layout
if (protector->hasOneRef())
return;
Document* document = m_frame->document();
bool inSubtreeLayout = isSubtreeLayout();
RenderObject* rootForThisLayout = inSubtreeLayout ? m_layoutSubtreeRoot : document->renderView();
if (!rootForThisLayout) {
// FIXME: Do we need to set m_size here?
ASSERT_NOT_REACHED();
return;
}
FontCachePurgePreventer fontCachePurgePreventer;
RenderLayer* layer;
{
TemporaryChange<bool> changeSchedulingEnabled(m_layoutSchedulingEnabled, false);
m_nestedLayoutCount++;
if (!inSubtreeLayout) {
if (m_firstLayout) {
m_doFullPaintInvalidation = true;
m_firstLayout = false;
m_firstLayoutCallbackPending = true;
m_lastViewportSize = layoutSize(IncludeScrollbars);
}
m_size = LayoutSize(layoutSize().width(), layoutSize().height());
// We need to set m_doFullPaintInvalidation before triggering layout as RenderObject::checkForPaintInvalidation
// checks the boolean to disable local paint invalidations.
m_doFullPaintInvalidation |= renderView()->shouldDoFullPaintInvalidationForNextLayout();
}
layer = rootForThisLayout->enclosingLayer();
performLayout(rootForThisLayout, inSubtreeLayout);
m_layoutSubtreeRoot = 0;
} // Reset m_layoutSchedulingEnabled to its previous value.
layer->updateLayerPositionsAfterLayout();
m_layoutCount++;
ASSERT(!rootForThisLayout->needsLayout());
scheduleOrPerformPostLayoutTasks();
TRACE_EVENT_END1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "Layout", "endData", InspectorLayoutEvent::endData(rootForThisLayout));
m_nestedLayoutCount--;
if (m_nestedLayoutCount)
return;
#if ENABLE(ASSERT)
// Post-layout assert that nobody was re-marked as needing layout during layout.
document->renderView()->assertSubtreeIsLaidOut();
#endif
// FIXME: It should be not possible to remove the FrameView from the frame/page during layout
// however m_inPerformLayout is not set for most of this function, so none of our RELEASE_ASSERTS
// in LocalFrame/Page will fire. One of the post-layout tasks is disconnecting the LocalFrame from
// the page in fast/frames/crash-remove-iframe-during-object-beforeload-2.html
// necessitating this check here.
// ASSERT(frame()->page());
if (frame().page())
frame().page()->chrome().client().layoutUpdated(m_frame.get());
}
void FrameView::invalidateTreeIfNeeded()
{
lifecycle().advanceTo(DocumentLifecycle::InPaintInvalidation);
ASSERT(renderView());
RenderView& rootForPaintInvalidation = *renderView();
ASSERT(!rootForPaintInvalidation.needsLayout());
TRACE_EVENT1("blink", "FrameView::invalidateTree", "root", rootForPaintInvalidation.debugName().ascii().data());
PaintInvalidationState rootPaintInvalidationState(rootForPaintInvalidation);
rootForPaintInvalidation.invalidateTreeIfNeeded(rootPaintInvalidationState);
m_doFullPaintInvalidation = false;
#ifndef NDEBUG
renderView()->assertSubtreeClearedPaintInvalidationState();
#endif
if (m_frame->selection().isCaretBoundsDirty())
m_frame->selection().invalidateCaretRect();
lifecycle().advanceTo(DocumentLifecycle::PaintInvalidationClean);
}
DocumentLifecycle& FrameView::lifecycle() const
{
return m_frame->document()->lifecycle();
}
void FrameView::setMediaType(const AtomicString& mediaType)
{
ASSERT(m_frame->document());
m_frame->document()->mediaQueryAffectingValueChanged();
m_mediaType = mediaType;
}
AtomicString FrameView::mediaType() const
{
// See if we have an override type.
String overrideType;
if (!overrideType.isNull())
return AtomicString(overrideType);
return m_mediaType;
}
bool FrameView::shouldSetCursor() const
{
Page* page = frame().page();
return page && page->visibilityState() != PageVisibilityStateHidden && page->focusController().isActive() && page->settings().deviceSupportsMouse();
}
// FIXME(sky): remove
IntSize FrameView::layoutSize(IncludeScrollbarsInRect) const
{
return m_layoutSize;
}
void FrameView::setLayoutSize(const IntSize& size)
{
ASSERT(!layoutSizeFixedToFrameSize());
setLayoutSizeInternal(size);
}
HostWindow* FrameView::hostWindow() const
{
Page* page = frame().page();
if (!page)
return 0;
return &page->chrome();
}
void FrameView::contentRectangleForPaintInvalidation(const IntRect& r)
{
ASSERT(paintInvalidationIsAllowed());
if (m_isTrackingPaintInvalidations) {
m_trackedPaintInvalidationRects.append(r);
// FIXME: http://crbug.com/368518. Eventually, invalidateContentRectangleForPaint
// is going away entirely once all layout tests are FCM. In the short
// term, no code should be tracking non-composited FrameView paint invalidations.
RELEASE_ASSERT_NOT_REACHED();
}
IntRect paintRect = r;
if (clipsPaintInvalidations() && !paintsEntireContents())
paintRect.intersect(visibleContentRect());
if (paintRect.isEmpty())
return;
if (HostWindow* window = hostWindow())
window->invalidateContentsAndRootView(contentsToWindow(paintRect));
}
void FrameView::contentsResized()
{
setNeedsLayout();
}
void FrameView::scheduleRelayout()
{
ASSERT(m_frame->view() == this);
if (isSubtreeLayout()) {
m_layoutSubtreeRoot->markContainingBlocksForLayout(false);
m_layoutSubtreeRoot = 0;
}
if (!m_layoutSchedulingEnabled)
return;
if (!needsLayout())
return;
if (!m_frame->document()->shouldScheduleLayout())
return;
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "InvalidateLayout", TRACE_EVENT_SCOPE_PROCESS, "frame", m_frame.get());
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline.stack"), "CallStack", TRACE_EVENT_SCOPE_PROCESS, "stack", InspectorCallStackEvent::currentCallStack());
if (m_hasPendingLayout)
return;
m_hasPendingLayout = true;
page()->animator().scheduleVisualUpdate();
lifecycle().ensureStateAtMost(DocumentLifecycle::StyleClean);
}
static bool isObjectAncestorContainerOf(RenderObject* ancestor, RenderObject* descendant)
{
for (RenderObject* r = descendant; r; r = r->container()) {
if (r == ancestor)
return true;
}
return false;
}
void FrameView::scheduleRelayoutOfSubtree(RenderObject* relayoutRoot)
{
ASSERT(m_frame->view() == this);
// FIXME: Should this call shouldScheduleLayout instead?
if (!m_frame->document()->isActive())
return;
RenderView* renderView = this->renderView();
if (renderView && renderView->needsLayout()) {
if (relayoutRoot)
relayoutRoot->markContainingBlocksForLayout(false);
return;
}
if (layoutPending() || !m_layoutSchedulingEnabled) {
if (m_layoutSubtreeRoot != relayoutRoot) {
if (isObjectAncestorContainerOf(m_layoutSubtreeRoot, relayoutRoot)) {
// Keep the current root
relayoutRoot->markContainingBlocksForLayout(false, m_layoutSubtreeRoot);
ASSERT(!m_layoutSubtreeRoot->container() || !m_layoutSubtreeRoot->container()->needsLayout());
} else if (isSubtreeLayout() && isObjectAncestorContainerOf(relayoutRoot, m_layoutSubtreeRoot)) {
// Re-root at relayoutRoot
m_layoutSubtreeRoot->markContainingBlocksForLayout(false, relayoutRoot);
m_layoutSubtreeRoot = relayoutRoot;
ASSERT(!m_layoutSubtreeRoot->container() || !m_layoutSubtreeRoot->container()->needsLayout());
} else {
// Just do a full relayout
if (isSubtreeLayout())
m_layoutSubtreeRoot->markContainingBlocksForLayout(false);
m_layoutSubtreeRoot = 0;
relayoutRoot->markContainingBlocksForLayout(false);
}
}
} else if (m_layoutSchedulingEnabled) {
m_layoutSubtreeRoot = relayoutRoot;
ASSERT(!m_layoutSubtreeRoot->container() || !m_layoutSubtreeRoot->container()->needsLayout());
m_hasPendingLayout = true;
page()->animator().scheduleVisualUpdate();
lifecycle().ensureStateAtMost(DocumentLifecycle::StyleClean);
}
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "InvalidateLayout", TRACE_EVENT_SCOPE_PROCESS, "frame", m_frame.get());
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline.stack"), "CallStack", TRACE_EVENT_SCOPE_PROCESS, "stack", InspectorCallStackEvent::currentCallStack());
}
bool FrameView::layoutPending() const
{
// FIXME: This should check Document::lifecycle instead.
return m_hasPendingLayout;
}
bool FrameView::isInPerformLayout() const
{
ASSERT(m_inPerformLayout == (lifecycle().state() == DocumentLifecycle::InPerformLayout));
return m_inPerformLayout;
}
bool FrameView::needsLayout() const
{
// This can return true in cases where the document does not have a body yet.
// Document::shouldScheduleLayout takes care of preventing us from scheduling
// layout in that case.
RenderView* renderView = this->renderView();
return layoutPending()
|| (renderView && renderView->needsLayout())
|| isSubtreeLayout();
}
void FrameView::setNeedsLayout()
{
if (RenderView* renderView = this->renderView())
renderView->setNeedsLayout();
}
bool FrameView::isTransparent() const
{
return m_isTransparent;
}
void FrameView::setTransparent(bool isTransparent)
{
m_isTransparent = isTransparent;
}
bool FrameView::hasOpaqueBackground() const
{
return !m_isTransparent && !m_baseBackgroundColor.hasAlpha();
}
Color FrameView::baseBackgroundColor() const
{
return m_baseBackgroundColor;
}
void FrameView::setBaseBackgroundColor(const Color& backgroundColor)
{
m_baseBackgroundColor = backgroundColor;
}
void FrameView::updateBackgroundRecursively(const Color& backgroundColor, bool transparent)
{
// FIXME(sky): simplify
setTransparent(transparent);
setBaseBackgroundColor(backgroundColor);
}
void FrameView::flushAnyPendingPostLayoutTasks()
{
ASSERT(!isInPerformLayout());
if (m_postLayoutTasksTimer.isActive())
performPostLayoutTasks();
}
void FrameView::performPostLayoutTasks()
{
// FIXME: We can reach here, even when the page is not active!
// http/tests/inspector/elements/html-link-import.html and many other
// tests hit that case.
// We should ASSERT(isActive()); or at least return early if we can!
ASSERT(!isInPerformLayout()); // Always before or after performLayout(), part of the highest-level layout() call.
TRACE_EVENT0("blink", "FrameView::performPostLayoutTasks");
RefPtr<FrameView> protect(this);
m_postLayoutTasksTimer.stop();
m_frame->selection().setCaretRectNeedsUpdate();
m_frame->selection().updateAppearance();
ASSERT(m_frame->document());
if (m_nestedLayoutCount <= 1) {
if (m_firstLayoutCallbackPending)
m_firstLayoutCallbackPending = false;
}
FontFaceSet::didLayout(*m_frame->document());
sendResizeEventIfNeeded();
}
bool FrameView::wasViewportResized()
{
return layoutSize(IncludeScrollbars) != m_lastViewportSize;
}
void FrameView::sendResizeEventIfNeeded()
{
if (!wasViewportResized())
return;
m_lastViewportSize = layoutSize(IncludeScrollbars);
m_frame->document()->enqueueResizeEvent();
}
void FrameView::postLayoutTimerFired(Timer<FrameView>*)
{
performPostLayoutTasks();
}
IntRect FrameView::windowClipRect(IncludeScrollbarsInRect scrollbarInclusion) const
{
ASSERT(m_frame->view() == this);
if (paintsEntireContents())
return IntRect(IntPoint(), size());
// Set our clip rect to be our contents.
IntRect clipRect = contentsToWindow(visibleContentRect(scrollbarInclusion));
return clipRect;
}
bool FrameView::isActive() const
{
Page* page = frame().page();
return page && page->focusController().isActive();
}
void FrameView::setVisibleContentScaleFactor(float visibleContentScaleFactor)
{
if (m_visibleContentScaleFactor == visibleContentScaleFactor)
return;
m_visibleContentScaleFactor = visibleContentScaleFactor;
}
void FrameView::setInputEventsTransformForEmulation(const IntSize& offset, float contentScaleFactor)
{
m_inputEventsOffsetForEmulation = offset;
m_inputEventsScaleFactorForEmulation = contentScaleFactor;
}
IntSize FrameView::inputEventsOffsetForEmulation() const
{
return m_inputEventsOffsetForEmulation;
}
float FrameView::inputEventsScaleFactor() const
{
float pageScale = visibleContentScaleFactor();
return pageScale * m_inputEventsScaleFactorForEmulation;
}
Color FrameView::documentBackgroundColor() const
{
// <https://bugs.webkit.org/show_bug.cgi?id=59540> We blend the background color of
// the document and the body against the base background color of the frame view.
// Background images are unfortunately impractical to include.
Color result = baseBackgroundColor();
if (!frame().document())
return result;
Element* htmlElement = frame().document()->documentElement();
// We take the aggregate of the base background color
// the <html> background color, and the <body>
// background color to find the document color. The
// addition of the base background color is not
// technically part of the document background, but it
// otherwise poses problems when the aggregate is not
// fully opaque.
if (htmlElement && htmlElement->renderer())
result = result.blend(htmlElement->renderer()->style()->colorIncludingFallback(CSSPropertyBackgroundColor));
return result;
}
void FrameView::paint(GraphicsContext* context, const IntRect& rect)
{
paintContents(context, rect);
}
void FrameView::paintContents(GraphicsContext* p, const IntRect& rect)
{
Document* document = m_frame->document();
#ifndef NDEBUG
bool fillWithRed;
if (isTransparent())
fillWithRed = false; // Transparent, don't fill with red.
else if (m_nodeToDraw)
fillWithRed = false; // Element images are transparent, don't fill with red.
else
fillWithRed = true;
if (fillWithRed)
p->fillRect(rect, Color(0xFF, 0, 0));
#endif
RenderView* renderView = this->renderView();
if (!renderView) {
WTF_LOG_ERROR("called FrameView::paint with nil renderer");
return;
}
RELEASE_ASSERT(!needsLayout());
ASSERT(document->lifecycle().state() >= DocumentLifecycle::PaintInvalidationClean);
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "Paint", "data", InspectorPaintEvent::data(renderView, rect));
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline.stack"), "CallStack", TRACE_EVENT_SCOPE_PROCESS, "stack", InspectorCallStackEvent::currentCallStack());
bool isTopLevelPainter = !s_inPaintContents;
s_inPaintContents = true;
FontCachePurgePreventer fontCachePurgePreventer;
document->markers().invalidateRenderedRectsForMarkersInRect(rect);
ASSERT(!m_isPainting);
m_isPainting = true;
// m_nodeToDraw is used to draw only one element (and its descendants)
RenderObject* renderer = m_nodeToDraw ? m_nodeToDraw->renderer() : 0;
RenderLayer* rootLayer = renderView->layer();
#if ENABLE(ASSERT)
renderView->assertSubtreeIsLaidOut();
RenderObject::SetLayoutNeededForbiddenScope forbidSetNeedsLayout(*rootLayer->renderer());
#endif
rootLayer->paint(p, rect, renderer);
if (rootLayer->containsDirtyOverlayScrollbars())
rootLayer->paintOverlayScrollbars(p, rect, renderer);
m_isPainting = false;
m_lastPaintTime = currentTime();
if (isTopLevelPainter) {
// Everything that happens after paintContents completions is considered
// to be part of the next frame.
s_currentFrameTimeStamp = currentTime();
s_inPaintContents = false;
}
}
bool FrameView::isPainting() const
{
return m_isPainting;
}
void FrameView::setNodeToDraw(Node* node)
{
m_nodeToDraw = node;
}
void FrameView::updateLayoutAndStyleForPainting()
{
// Updating layout can run script, which can tear down the FrameView.
RefPtr<FrameView> protector(this);
updateLayoutAndStyleIfNeededRecursive();
if (RenderView* view = renderView()) {
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "InvalidateTreeAndUpdateIframes", TRACE_EVENT_SCOPE_PROCESS, "frame", m_frame.get());
invalidateTreeIfNeeded();
view->updateIFramesAfterLayout();
}
ASSERT(lifecycle().state() == DocumentLifecycle::PaintInvalidationClean);
DocumentAnimations::startPendingAnimations(*m_frame->document());
}
void FrameView::updateLayoutAndStyleIfNeededRecursive()
{
// We have to crawl our entire tree looking for any FrameViews that need
// layout and make sure they are up to date.
// Mac actually tests for intersection with the dirty region and tries not to
// update layout for frames that are outside the dirty region. Not only does this seem
// pointless (since those frames will have set a zero timer to layout anyway), but
// it is also incorrect, since if two frames overlap, the first could be excluded from the dirty
// region but then become included later by the second frame adding rects to the dirty region
// when it lays out.
m_frame->document()->updateRenderTreeIfNeeded();
if (needsLayout())
layout();
// These asserts ensure that parent frames are clean, when child frames finished updating layout and style.
ASSERT(!needsLayout());
#if ENABLE(ASSERT)
m_frame->document()->renderView()->assertRendererLaidOut();
#endif
}
void FrameView::forceLayout(bool allowSubtree)
{
layout(allowSubtree);
}
IntRect FrameView::convertFromRenderer(const RenderObject& renderer, const IntRect& rendererRect) const
{
return pixelSnappedIntRect(enclosingLayoutRect(renderer.localToAbsoluteQuad(FloatRect(rendererRect)).boundingBox()));
}
IntRect FrameView::convertToRenderer(const RenderObject& renderer, const IntRect& viewRect) const
{
IntRect rect = viewRect;
// FIXME: we don't have a way to map an absolute rect down to a local quad, so just
// move the rect for now.
rect.setLocation(roundedIntPoint(renderer.absoluteToLocal(rect.location(), UseTransforms)));
return rect;
}
IntPoint FrameView::convertFromRenderer(const RenderObject& renderer, const IntPoint& rendererPoint) const
{
return roundedIntPoint(renderer.localToAbsolute(rendererPoint, UseTransforms));
}
IntPoint FrameView::convertToRenderer(const RenderObject& renderer, const IntPoint& viewPoint) const
{
return roundedIntPoint(renderer.absoluteToLocal(viewPoint, UseTransforms));
}
void FrameView::setTracksPaintInvalidations(bool trackPaintInvalidations)
{
// FIXME(sky): Does this code work anymore now that we don't have the compositor?
if (trackPaintInvalidations == m_isTrackingPaintInvalidations)
return;
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("blink.invalidation"),
"FrameView::setTracksPaintInvalidations", TRACE_EVENT_SCOPE_PROCESS, "enabled", trackPaintInvalidations);
resetTrackedPaintInvalidations();
m_isTrackingPaintInvalidations = trackPaintInvalidations;
}
void FrameView::resetTrackedPaintInvalidations()
{
m_trackedPaintInvalidationRects.clear();
}
String FrameView::trackedPaintInvalidationRectsAsText() const
{
TextStream ts;
if (!m_trackedPaintInvalidationRects.isEmpty()) {
ts << "(repaint rects\n";
for (size_t i = 0; i < m_trackedPaintInvalidationRects.size(); ++i)
ts << " (rect " << m_trackedPaintInvalidationRects[i].x() << " " << m_trackedPaintInvalidationRects[i].y() << " " << m_trackedPaintInvalidationRects[i].width() << " " << m_trackedPaintInvalidationRects[i].height() << ")\n";
ts << ")\n";
}
return ts.release();
}
void FrameView::addScrollableArea(ScrollableArea* scrollableArea)
{
ASSERT(scrollableArea);
if (!m_scrollableAreas)
m_scrollableAreas = adoptPtr(new ScrollableAreaSet);
m_scrollableAreas->add(scrollableArea);
}
void FrameView::removeScrollableArea(ScrollableArea* scrollableArea)
{
if (!m_scrollableAreas)
return;
m_scrollableAreas->remove(scrollableArea);
}
bool FrameView::wheelEvent(const PlatformWheelEvent& wheelEvent)
{
// FIXME(sky): Remove
return false;
}
bool FrameView::isVerticalDocument() const
{
// FIXME(sky): Remove
return true;
}
bool FrameView::isFlippedDocument() const
{
// FIXME(sky): Remove
return false;
}
void FrameView::setCursor(const Cursor& cursor)
{
Page* page = frame().page();
if (!page || !page->settings().deviceSupportsMouse())
return;
page->chrome().setCursor(cursor);
}
void FrameView::setLayoutSizeInternal(const IntSize& size)
{
if (m_layoutSize == size)
return;
m_layoutSize = size;
contentsResized();
}
void FrameView::countObjectsNeedingLayout(unsigned& needsLayoutObjects, unsigned& totalObjects, bool& isPartial)
{
RenderObject* root = layoutRoot();
isPartial = true;
if (!root) {
isPartial = false;
root = m_frame->contentRenderer();
}
needsLayoutObjects = 0;
totalObjects = 0;
for (RenderObject* o = root; o; o = o->nextInPreOrder(root)) {
++totalObjects;
if (o->needsLayout())
++needsLayoutObjects;
}
}
} // namespace blink