/* * Copyright (C) 1998, 1999 Torben Weis * 1999 Lars Knoll * 1999 Antti Koivisto * 2000 Dirk Mueller * 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/core/frame/FrameView.h" #include "gen/sky/platform/RuntimeEnabledFeatures.h" #include "sky/engine/core/css/resolver/StyleResolver.h" #include "sky/engine/core/dom/DocumentMarkerController.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/loader/FrameLoaderClient.h" #include "sky/engine/core/page/ChromeClient.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/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_hasSoftwareFilters(false) , m_visibleContentScaleFactor(1) , m_inputEventsScaleFactorForEmulation(1) , m_layoutSizeFixedToFrameSize(true) { ASSERT(m_frame); init(); } PassRefPtr FrameView::create(LocalFrame* frame) { RefPtr view = adoptRef(new FrameView(frame)); return view.release(); } PassRefPtr FrameView::create(LocalFrame* frame, const IntSize& initialSize) { RefPtr 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_layoutSchedulingEnabled = true; m_inPerformLayout = false; m_inSynchronousPostLayout = false; m_layoutCount = 0; m_nestedLayoutCount = 0; m_postLayoutTasksTimer.stop(); m_firstLayout = true; m_lastViewportSize = IntSize(); 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::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(); } RenderObject* FrameView::layoutRoot(bool onlyDuringLayout) const { return onlyDuringLayout && layoutPending() ? 0 : m_layoutSubtreeRoot; } void FrameView::performPreLayoutTasks() { TRACE_EVENT0("blink", "FrameView::performPreLayoutTasks"); // Don't schedule more layouts, we're in one. TemporaryChange 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(); document->mediaQueryAffectingValueChanged(); // TODO(esprehn): This is way too much work, it rebuilds the entire sheet list // and does a full document recalc. document->styleResolverChanged(); } document->updateRenderTreeIfNeeded(); } void FrameView::performLayout(RenderObject* rootForThisLayout, bool inSubtreeLayout) { TRACE_EVENT0("blink", "FrameView::performLayout"); ScriptForbiddenScope forbidScript; ASSERT(!isInPerformLayout()); TemporaryChange 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. rootForThisLayout->layout(); } 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); 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 protector(this); m_hasPendingLayout = false; RELEASE_ASSERT(!isPainting()); 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 changeSchedulingEnabled(m_layoutSchedulingEnabled, false); m_nestedLayoutCount++; if (!inSubtreeLayout) { if (m_firstLayout) { m_firstLayout = false; m_lastViewportSize = layoutSize(); } m_size = LayoutSize(layoutSize()); } layer = rootForThisLayout->enclosingLayer(); performLayout(rootForThisLayout, inSubtreeLayout); m_layoutSubtreeRoot = 0; } // Reset m_layoutSchedulingEnabled to its previous value. layer->updateLayerPositionsAfterLayout(); m_layoutCount++; ASSERT(!rootForThisLayout->needsLayout()); scheduleOrPerformPostLayoutTasks(); 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 } 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; } // FIXME(sky): remove IntSize FrameView::layoutSize() const { return m_layoutSize; } void FrameView::setLayoutSize(const IntSize& size) { ASSERT(!layoutSizeFixedToFrameSize()); setLayoutSizeInternal(size); } HostWindow* FrameView::hostWindow() const { return frame().page(); } 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()->isActive()) return; if (m_hasPendingLayout) return; m_hasPendingLayout = true; m_frame->document()->scheduleVisualUpdate(); } 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); 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; m_frame->document()->scheduleVisualUpdate(); } } bool FrameView::layoutPending() const { // FIXME: This should check Document::lifecycle instead. return m_hasPendingLayout; } bool FrameView::isInPerformLayout() const { return m_inPerformLayout; } bool FrameView::needsLayout() const { 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; } 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 protect(this); m_postLayoutTasksTimer.stop(); ASSERT(m_frame->document()); sendResizeEventIfNeeded(); } bool FrameView::wasViewportResized() { return layoutSize() != m_lastViewportSize; } void FrameView::sendResizeEventIfNeeded() { } void FrameView::postLayoutTimerFired(Timer*) { performPostLayoutTasks(); } IntRect FrameView::windowClipRect() const { ASSERT(m_frame->view() == this); if (paintsEntireContents()) return IntRect(IntPoint(), size()); // Set our clip rect to be our contents. IntRect clipRect = contentsToWindow(visibleContentRect()); return clipRect; } bool FrameView::isActive() const { return false; } 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; } void FrameView::paint(GraphicsContext* context, const IntRect& rect) { #ifndef NDEBUG bool fillWithRed; if (isTransparent()) fillWithRed = false; // Transparent, don't fill with red. else fillWithRed = true; if (fillWithRed) context->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()); bool isTopLevelPainter = !s_inPaintContents; s_inPaintContents = true; FontCachePurgePreventer fontCachePurgePreventer; ASSERT(!m_isPainting); m_isPainting = true; #if ENABLE(ASSERT) renderView->assertSubtreeIsLaidOut(); RenderObject::SetLayoutNeededForbiddenScope forbidSetNeedsLayout(*renderView); #endif LayerPaintingInfo paintingInfo(renderView->layer(), pixelSnappedIntRect(renderView->viewRect()), LayoutSize()); renderView->paintLayer(context, paintingInfo); 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::updateLayoutAndStyleForPainting() { // Updating layout can run script, which can tear down the FrameView. RefPtr protector(this); updateLayoutAndStyleIfNeededRecursive(); } 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)); } bool FrameView::isVerticalDocument() const { // FIXME(sky): Remove return true; } bool FrameView::isFlippedDocument() const { // FIXME(sky): Remove return false; } 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