| /* |
| * Copyright (C) 2012 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS IN..0TERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "RenderNamedFlowThread.h" |
| |
| #include "FlowThreadController.h" |
| #include "RenderRegion.h" |
| #include "RenderView.h" |
| #include "WebKitNamedFlow.h" |
| |
| namespace WebCore { |
| |
| RenderNamedFlowThread::RenderNamedFlowThread(Node* node, PassRefPtr<WebKitNamedFlow> namedFlow) |
| : RenderFlowThread(node) |
| , m_namedFlow(namedFlow) |
| , m_regionLayoutUpdateEventTimer(this, &RenderNamedFlowThread::regionLayoutUpdateEventTimerFired) |
| { |
| m_namedFlow->setRenderer(this); |
| } |
| |
| RenderNamedFlowThread::~RenderNamedFlowThread() |
| { |
| m_namedFlow->setRenderer(0); |
| } |
| |
| const char* RenderNamedFlowThread::renderName() const |
| { |
| return "RenderNamedFlowThread"; |
| } |
| |
| RenderObject* RenderNamedFlowThread::nextRendererForNode(Node* node) const |
| { |
| FlowThreadChildList::const_iterator it = m_flowThreadChildList.begin(); |
| FlowThreadChildList::const_iterator end = m_flowThreadChildList.end(); |
| |
| for (; it != end; ++it) { |
| RenderObject* child = *it; |
| ASSERT(child->node()); |
| unsigned short position = node->compareDocumentPosition(child->node()); |
| if (position & Node::DOCUMENT_POSITION_FOLLOWING) |
| return child; |
| } |
| |
| return 0; |
| } |
| |
| RenderObject* RenderNamedFlowThread::previousRendererForNode(Node* node) const |
| { |
| if (m_flowThreadChildList.isEmpty()) |
| return 0; |
| |
| FlowThreadChildList::const_iterator begin = m_flowThreadChildList.begin(); |
| FlowThreadChildList::const_iterator end = m_flowThreadChildList.end(); |
| FlowThreadChildList::const_iterator it = end; |
| |
| do { |
| --it; |
| RenderObject* child = *it; |
| ASSERT(child->node()); |
| unsigned short position = node->compareDocumentPosition(child->node()); |
| if (position & Node::DOCUMENT_POSITION_PRECEDING) |
| return child; |
| } while (it != begin); |
| |
| return 0; |
| } |
| |
| void RenderNamedFlowThread::addFlowChild(RenderObject* newChild, RenderObject* beforeChild) |
| { |
| // The child list is used to sort the flow thread's children render objects |
| // based on their corresponding nodes DOM order. The list is needed to avoid searching the whole DOM. |
| |
| // Do not add anonymous objects. |
| if (!newChild->node()) |
| return; |
| |
| if (beforeChild) |
| m_flowThreadChildList.insertBefore(beforeChild, newChild); |
| else |
| m_flowThreadChildList.add(newChild); |
| } |
| |
| void RenderNamedFlowThread::removeFlowChild(RenderObject* child) |
| { |
| m_flowThreadChildList.remove(child); |
| } |
| |
| bool RenderNamedFlowThread::dependsOn(RenderNamedFlowThread* otherRenderFlowThread) const |
| { |
| if (m_layoutBeforeThreadsSet.contains(otherRenderFlowThread)) |
| return true; |
| |
| // Recursively traverse the m_layoutBeforeThreadsSet. |
| RenderNamedFlowThreadCountedSet::const_iterator iterator = m_layoutBeforeThreadsSet.begin(); |
| RenderNamedFlowThreadCountedSet::const_iterator end = m_layoutBeforeThreadsSet.end(); |
| for (; iterator != end; ++iterator) { |
| const RenderNamedFlowThread* beforeFlowThread = (*iterator).first; |
| if (beforeFlowThread->dependsOn(otherRenderFlowThread)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // Compare two regions to determine in which one the content should flow first. |
| // The function returns true if the first passed region is "less" than the second passed region. |
| // If the first region appears before second region in DOM, |
| // the first region is "less" than the second region. |
| // If the first region is "less" than the second region, the first region receives content before second region. |
| static bool compareRenderRegions(const RenderRegion* firstRegion, const RenderRegion* secondRegion) |
| { |
| ASSERT(firstRegion); |
| ASSERT(secondRegion); |
| |
| // If the regions have the same region-index, compare their position in dom. |
| ASSERT(firstRegion->node()); |
| ASSERT(secondRegion->node()); |
| |
| unsigned short position = firstRegion->node()->compareDocumentPosition(secondRegion->node()); |
| return (position & Node::DOCUMENT_POSITION_FOLLOWING); |
| } |
| |
| void RenderNamedFlowThread::addRegionToThread(RenderRegion* renderRegion) |
| { |
| ASSERT(renderRegion); |
| if (m_regionList.isEmpty()) |
| m_regionList.add(renderRegion); |
| else { |
| // Find the first region "greater" than renderRegion. |
| RenderRegionList::iterator it = m_regionList.begin(); |
| while (it != m_regionList.end() && !compareRenderRegions(renderRegion, *it)) |
| ++it; |
| m_regionList.insertBefore(it, renderRegion); |
| } |
| |
| ASSERT(!renderRegion->isValid()); |
| if (renderRegion->parentNamedFlowThread()) { |
| if (renderRegion->parentNamedFlowThread()->dependsOn(this)) { |
| // Register ourself to get a notification when the state changes. |
| renderRegion->parentNamedFlowThread()->m_observerThreadsSet.add(this); |
| return; |
| } |
| |
| addDependencyOnFlowThread(renderRegion->parentNamedFlowThread()); |
| } |
| |
| renderRegion->setIsValid(true); |
| |
| invalidateRegions(); |
| } |
| |
| void RenderNamedFlowThread::removeRegionFromThread(RenderRegion* renderRegion) |
| { |
| ASSERT(renderRegion); |
| m_regionRangeMap.clear(); |
| m_regionList.remove(renderRegion); |
| |
| if (renderRegion->parentNamedFlowThread()) { |
| if (!renderRegion->isValid()) { |
| renderRegion->parentNamedFlowThread()->m_observerThreadsSet.remove(this); |
| // No need to invalidate the regions rectangles. The removed region |
| // was not taken into account. Just return here. |
| return; |
| } |
| removeDependencyOnFlowThread(renderRegion->parentNamedFlowThread()); |
| } |
| |
| if (canBeDestroyed()) { |
| destroy(); |
| return; |
| } |
| |
| // After removing all the regions in the flow the following layout needs to dispatch the regionLayoutUpdate event |
| if (m_regionList.isEmpty()) |
| setDispatchRegionLayoutUpdateEvent(true); |
| |
| invalidateRegions(); |
| } |
| |
| void RenderNamedFlowThread::checkInvalidRegions() |
| { |
| for (RenderRegionList::iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) { |
| RenderRegion* region = *iter; |
| // The only reason a region would be invalid is because it has a parent flow thread. |
| ASSERT(region->isValid() || region->parentNamedFlowThread()); |
| if (region->isValid() || region->parentNamedFlowThread()->dependsOn(this)) |
| continue; |
| |
| region->parentNamedFlowThread()->m_observerThreadsSet.remove(this); |
| addDependencyOnFlowThread(region->parentNamedFlowThread()); |
| region->setIsValid(true); |
| invalidateRegions(); |
| } |
| |
| if (m_observerThreadsSet.isEmpty()) |
| return; |
| |
| // Notify all the flow threads that were dependent on this flow. |
| |
| // Create a copy of the list first. That's because observers might change the list when calling checkInvalidRegions. |
| Vector<RenderNamedFlowThread*> observers; |
| copyToVector(m_observerThreadsSet, observers); |
| |
| for (size_t i = 0; i < observers.size(); ++i) { |
| RenderNamedFlowThread* flowThread = observers.at(i); |
| flowThread->checkInvalidRegions(); |
| } |
| } |
| |
| void RenderNamedFlowThread::addDependencyOnFlowThread(RenderNamedFlowThread* otherFlowThread) |
| { |
| RenderNamedFlowThreadCountedSet::AddResult result = m_layoutBeforeThreadsSet.add(otherFlowThread); |
| if (result.isNewEntry) { |
| // This is the first time we see this dependency. Make sure we recalculate all the dependencies. |
| view()->flowThreadController()->setIsRenderNamedFlowThreadOrderDirty(true); |
| } |
| } |
| |
| void RenderNamedFlowThread::removeDependencyOnFlowThread(RenderNamedFlowThread* otherFlowThread) |
| { |
| bool removed = m_layoutBeforeThreadsSet.remove(otherFlowThread); |
| if (removed) { |
| checkInvalidRegions(); |
| view()->flowThreadController()->setIsRenderNamedFlowThreadOrderDirty(true); |
| } |
| } |
| |
| void RenderNamedFlowThread::pushDependencies(RenderNamedFlowThreadList& list) |
| { |
| for (RenderNamedFlowThreadCountedSet::iterator iter = m_layoutBeforeThreadsSet.begin(); iter != m_layoutBeforeThreadsSet.end(); ++iter) { |
| RenderNamedFlowThread* flowThread = (*iter).first; |
| if (list.contains(flowThread)) |
| continue; |
| flowThread->pushDependencies(list); |
| list.add(flowThread); |
| } |
| } |
| |
| // The content nodes list contains those nodes with -webkit-flow-into: flow. |
| // An element with display:none should also be listed among those nodes. |
| // The list of nodes is ordered. |
| void RenderNamedFlowThread::registerNamedFlowContentNode(Node* contentNode) |
| { |
| ASSERT(contentNode && contentNode->isElementNode()); |
| |
| contentNode->setInNamedFlow(); |
| |
| // Find the first content node following the new content node. |
| for (NamedFlowContentNodes::iterator it = m_contentNodes.begin(); it != m_contentNodes.end(); ++it) { |
| Node* node = *it; |
| unsigned short position = contentNode->compareDocumentPosition(node); |
| if (position & Node::DOCUMENT_POSITION_FOLLOWING) { |
| m_contentNodes.insertBefore(node, contentNode); |
| return; |
| } |
| } |
| m_contentNodes.add(contentNode); |
| } |
| |
| void RenderNamedFlowThread::unregisterNamedFlowContentNode(Node* contentNode) |
| { |
| ASSERT(contentNode && contentNode->isElementNode()); |
| ASSERT(m_contentNodes.contains(contentNode)); |
| ASSERT(contentNode->inNamedFlow()); |
| |
| contentNode->clearInNamedFlow(); |
| m_contentNodes.remove(contentNode); |
| |
| if (canBeDestroyed()) |
| destroy(); |
| } |
| |
| const AtomicString& RenderNamedFlowThread::flowThreadName() const |
| { |
| return m_namedFlow->name(); |
| } |
| |
| void RenderNamedFlowThread::willBeDestroyed() |
| { |
| if (!documentBeingDestroyed()) |
| view()->flowThreadController()->removeFlowThread(this); |
| |
| RenderFlowThread::willBeDestroyed(); |
| } |
| |
| void RenderNamedFlowThread::dispatchRegionLayoutUpdateEvent() |
| { |
| RenderFlowThread::dispatchRegionLayoutUpdateEvent(); |
| |
| if (!m_regionLayoutUpdateEventTimer.isActive() && m_namedFlow->hasEventListeners()) |
| m_regionLayoutUpdateEventTimer.startOneShot(0); |
| } |
| |
| void RenderNamedFlowThread::regionLayoutUpdateEventTimerFired(Timer<RenderNamedFlowThread>*) |
| { |
| ASSERT(m_namedFlow); |
| |
| m_namedFlow->dispatchRegionLayoutUpdateEvent(); |
| } |
| |
| } |